feat(subagent): activation-based continuable subagents (source)
Replace the Task-backed continuation manager with one durable Session plus at
most one process-local Activation — a residency epoch for a reconstructed child
Agent, not a request, result, cancellation, or Task boundary. The manager owns
activation admission, authority, the live ownership graph, cold resume, and
child-first disposal; the Agent inbox is the only turn FIFO.
- startContinuable() is async and returns { childId, messageId } at inbox
acceptance; followup() takes a SubagentAuthority and returns AgentMessageId.
- SubagentProvider.resume?(), SubagentProviderResumeRequest, SubagentRun.steer?(),
SubagentProviderStartRequest and SubagentContinuation are deleted;
prepareContinuable?() is the continuable-creation capability.
- Cold resume calls ctx.agents.resume() from the manager through a private
activation-owner scope, never dispatching through a provider.
- Extract shared child composition, descriptor seeding, depth accounting, and
one-shot run settlement so the manager and one-shot driver keep one home
per fact.
Tests and docs follow in subsequent commits.
This commit is contained in:
@@ -1,9 +1,9 @@
|
||||
/**
|
||||
* The globally named `send_message` tool: a thin model-facing adapter over
|
||||
* `ctx.subagents.followup()`. It performs no lifecycle routing of its
|
||||
* own — steer-or-resume orchestration belongs to the subagent service — and it
|
||||
* lives apart from the provider-bound `@deepseek-ai/dsh-tool-subagent`
|
||||
* instances so multiple delegation tools share one control tool.
|
||||
* `ctx.subagents.followup()`. It performs no lifecycle routing of its own —
|
||||
* residency and cold resume belong to the subagent service — and it lives apart
|
||||
* from the provider-bound `@deepseek-ai/dsh-tool-subagent` instances so multiple
|
||||
* delegation tools share one control tool.
|
||||
* @module @deepseek-ai/dsh-tool-subagent-control
|
||||
*/
|
||||
|
||||
@@ -24,10 +24,10 @@ export function apply(ctx: Context): void {
|
||||
ctx.tools.register(defineTool({
|
||||
name: 'send_message',
|
||||
description:
|
||||
'Send a follow-up message to a background subagent by its subagent id. If it is still working, the '
|
||||
+ 'message joins its current task; if it has finished, this starts a new task that continues the same '
|
||||
+ 'subagent conversation. Either way the response arrives through the returned task id — collect it '
|
||||
+ 'with `task_output`. A failure means the message was NOT delivered.',
|
||||
'Send a message to a background subagent by its subagent id, continuing the same conversation. It '
|
||||
+ 'becomes the subagent\'s next turn: if it is still working, the message waits until its current turn '
|
||||
+ 'finishes, so it cannot redirect work already underway. The subagent does not reply to you — read its '
|
||||
+ 'transcript by its id to see what it did. A failure means the message was NOT delivered.',
|
||||
parameters: {
|
||||
subagent_id: {
|
||||
type: 'string',
|
||||
@@ -45,30 +45,23 @@ export function apply(ctx: Context): void {
|
||||
type: 'object',
|
||||
additionalProperties: false,
|
||||
properties: {
|
||||
route: {
|
||||
type: 'string',
|
||||
required: true,
|
||||
enum: ['steered', 'started'],
|
||||
},
|
||||
taskId: { type: 'string', required: true },
|
||||
messageId: { type: 'string', required: true },
|
||||
},
|
||||
},
|
||||
render: (args, value) => [{
|
||||
render: (args, _value) => [{
|
||||
type: 'text',
|
||||
text: value.route === 'steered'
|
||||
? `message delivered to running task ${value.taskId}`
|
||||
: `message started task ${value.taskId} continuing subagent ${args.subagent_id}`,
|
||||
text: `message queued as the next turn for subagent ${args.subagent_id}`,
|
||||
}],
|
||||
},
|
||||
async execute(args, exec) {
|
||||
const parent = exec.agent
|
||||
if (!parent) {
|
||||
// Non-agent callers have no session to authorize Task access with.
|
||||
// Parent authority requires an exact live calling agent.
|
||||
throw new Error('send_message requires a calling agent (exec.agent was undefined)')
|
||||
}
|
||||
const message: ContentBlock[] = [{ type: 'text', text: args.message }]
|
||||
const result = await ctx.subagents.followup(
|
||||
parent,
|
||||
const messageId = await ctx.subagents.followup(
|
||||
{ kind: 'parent', agent: parent },
|
||||
SessionId(args.subagent_id),
|
||||
message,
|
||||
{
|
||||
@@ -76,7 +69,7 @@ export function apply(ctx: Context): void {
|
||||
signal: exec.signal,
|
||||
},
|
||||
)
|
||||
return result
|
||||
return { messageId }
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user