Files
opencode/specs/v2/plugin-session-tools-plan.md
2026-06-28 19:34:35 +00:00

4.5 KiB

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.

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:

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:

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:

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:

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.