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:
Dudu-0223
2026-07-30 13:26:13 +08:00
committed by Tianyi Cui
parent dabd50e067
commit 26a117f842
15 changed files with 1721 additions and 868 deletions

View File

@@ -12,12 +12,13 @@ import z from 'schemastery'
import type { SessionEvent } from '@deepseek-ai/dsh-session'
import type { Agent } from '@deepseek-ai/dsh-agent'
import type {
ContinuableCreateRequest,
ContinuableCreateSpec,
SubagentCapabilities,
SubagentProvider,
SubagentProviderResumeRequest,
SubagentProviderStartRequest,
SubagentStartRequest,
} from '@deepseek-ai/dsh-subagent'
import { resumeInProcessRun, startInProcessRun } from '@deepseek-ai/dsh-subagent-inprocess'
import { startInProcessRun } from '@deepseek-ai/dsh-subagent-inprocess'
export const name = 'subagent-fork'
// `tools` is deliberately NOT injected — same rationale as subagent-spawn: the
@@ -64,7 +65,7 @@ class ForkProvider implements SubagentProvider {
constructor(readonly name: string) {}
start(request: SubagentProviderStartRequest) {
start(request: SubagentStartRequest) {
const seed = completedTurnPrefix(request.parent)
return startInProcessRun(request, {
// Only pass a seed when there's a completed turn to inherit; an empty seed
@@ -73,11 +74,12 @@ class ForkProvider implements SubagentProvider {
})
}
resume(request: SubagentProviderResumeRequest) {
// Cold resume loads the child's OWN persisted transcript, which already
// contains the completed-turn prefix captured at initial creation; it
// never forks the parent's newer history again.
return resumeInProcessRun(request)
prepareContinuable(request: ContinuableCreateRequest): Promise<ContinuableCreateSpec> {
// The fork prefix is captured ONCE, at creation: it becomes part of the
// child's own durable transcript, so a later cold resume replays that
// prefix instead of re-forking the parent's newer history.
const seed = completedTurnPrefix(request.parent)
return Promise.resolve(seed.length > 0 ? { seed } : {})
}
}