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.
Recommended direction
Keep location-scoped services. Split location-specific session behavior out of the global session data service.
SessionV2.Serviceremains global and owns session data APIs:creategetlistmessagesmessagecontexteventshistory- metadata-only updates such as
rename,switchAgent,switchModel
- Add a location-scoped session runtime service for behavior that touches location services:
promptresumewaitinterruptactiverevert.stagerevert.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
- Add
packages/core/src/session/runtime.tsas a location node. Implemented in this draft. - Move
prompt,resume,wait,interrupt,active, and location-sensitiverevertoperations fromSessionV2.Serviceinto the runtime service. Implemented in this draft for the new runtime path; oldSessionV2entrypoints are left as compatibility stubs and should be removed once callers migrate. - 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.
- Add
ctx.sessiontoPluginHostby composingSessionV2.Serviceand the location session runtime. Implemented in this draft. - Add public plugin
ctx.tool.transformtypes and adapt it to the existing canonical coreTool.makerepresentation. - Convert
ToolRegistryregistration to transform/rebuild semantics. - Port
subagentto a built-in plugin that registers a normal location tool. - Remove
ApplicationToolsonce 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.