mirror of
https://github.com/anomalyco/opencode.git
synced 2026-07-24 04:01:10 -04:00
125 lines
4.5 KiB
Markdown
125 lines
4.5 KiB
Markdown
# Plugin Session and Tool Architecture Prototype
|
|
|
|
## Goal
|
|
|
|
Let V2 plugins access a normal session API and define tools through plugin transforms without introducing application-vs-location tools or a `Session -> LocationServiceMap -> Plugin -> Session` construction cycle.
|
|
|
|
## Recommended direction
|
|
|
|
Keep location-scoped services. Split location-specific session behavior out of the global session data service.
|
|
|
|
- `SessionV2.Service` remains global and owns session data APIs:
|
|
- `create`
|
|
- `get`
|
|
- `list`
|
|
- `messages`
|
|
- `message`
|
|
- `context`
|
|
- `events`
|
|
- `history`
|
|
- metadata-only updates such as `rename`, `switchAgent`, `switchModel`
|
|
- Add a location-scoped session runtime service for behavior that touches location services:
|
|
- `prompt`
|
|
- `resume`
|
|
- `wait`
|
|
- `interrupt`
|
|
- `active`
|
|
- `revert.stage`
|
|
- `revert.clear`
|
|
- any future runner/filesystem/snapshot-coupled session operation
|
|
|
|
This keeps the dependency rule simple:
|
|
|
|
```txt
|
|
global services do not call location services
|
|
location services may call global services
|
|
```
|
|
|
|
## Why
|
|
|
|
The current V2 shape becomes cyclic if `PluginHost` gets full `ctx.session` while `SessionV2.Service` depends on `LocationServiceMap`:
|
|
|
|
```txt
|
|
SessionV2
|
|
-> LocationServiceMap
|
|
-> LocationRuntime
|
|
-> PluginService
|
|
-> PluginHost
|
|
-> SessionV2
|
|
```
|
|
|
|
The prototype in `plugin-session-cycle.prototype.ts` recreates this red case and compares green alternatives.
|
|
|
|
## Ideal plugin call site
|
|
|
|
Plugin authors should not see the split. The host composes global session data plus location session runtime into one `ctx.session` API:
|
|
|
|
```ts
|
|
export const Plugin = define({
|
|
id: "subagent",
|
|
effect: Effect.fn(function* (ctx) {
|
|
yield* ctx.tool.transform((draft) => {
|
|
draft.set("subagent", tool({
|
|
description,
|
|
input: Input,
|
|
output: Output,
|
|
execute: Effect.fn(function* (input, call) {
|
|
const parent = yield* ctx.session.get(call.sessionID)
|
|
const child = yield* ctx.session.create({
|
|
parentID: parent.id,
|
|
title: input.description,
|
|
agent: input.agent,
|
|
})
|
|
|
|
yield* ctx.session.prompt({
|
|
sessionID: child.id,
|
|
prompt: { text: input.prompt },
|
|
resume: false,
|
|
})
|
|
yield* ctx.session.resume(child.id)
|
|
yield* ctx.session.wait(child.id)
|
|
|
|
return {
|
|
sessionID: child.id,
|
|
status: "completed",
|
|
output: yield* ctx.session.finalText(child.id),
|
|
}
|
|
}),
|
|
}))
|
|
})
|
|
}),
|
|
})
|
|
```
|
|
|
|
## Tool model
|
|
|
|
Tools stay location-scoped. There should not be public application tools or global tools.
|
|
|
|
Plugins boot per location, and tools are contributed through transforms:
|
|
|
|
```ts
|
|
yield* ctx.tool.transform((draft) => {
|
|
draft.set("repo_summary", tool({ description, input, output, execute }))
|
|
})
|
|
```
|
|
|
|
The SDK should be implemented as one plugin instance: it receives a plugin host/context internally, and SDK methods call the host methods. Therefore an SDK-registered tool is just a plugin tool transform applied as each location boots.
|
|
|
|
## Concrete implementation slices
|
|
|
|
1. Add `packages/core/src/session/runtime.ts` as a location node. **Implemented in this draft.**
|
|
2. Move `prompt`, `resume`, `wait`, `interrupt`, `active`, and location-sensitive `revert` operations from `SessionV2.Service` into the runtime service. **Implemented in this draft for the new runtime path; old `SessionV2` entrypoints are left as compatibility stubs and should be removed once callers migrate.**
|
|
3. Update server route handlers to route location-sensitive requests at the API boundary by resolving the session location and providing that location runtime. **Implemented in this draft.**
|
|
4. Add `ctx.session` to `PluginHost` by composing `SessionV2.Service` and the location session runtime. **Implemented in this draft.**
|
|
5. Add public plugin `ctx.tool.transform` types and adapt it to the existing canonical core `Tool.make` representation.
|
|
6. Convert `ToolRegistry` registration to transform/rebuild semantics.
|
|
7. Port `subagent` to a built-in plugin that registers a normal location tool.
|
|
8. Remove `ApplicationTools` once no built-in tool requires process-global registration.
|
|
|
|
## Invariants to preserve
|
|
|
|
- A tool materialization snapshots executable tool identity; stale calls fail.
|
|
- Tool output bounding remains centralized in `ToolRegistry.Materialization.settle`.
|
|
- Location session runtime asserts that the target session belongs to the current location before running location-sensitive operations.
|
|
- Public HTTP/SDK API shape does not need to change.
|