DSH / Atlas
2026-08-05implementedfeature

Durable Agent Teams over continuable children

Durable Agent Teams over continuable children

The subagent seam supplies fresh/fork providers, durable child Sessions, FIFO follow-ups, and cold-resumable Activations. Its direct-parent controls do not provide peer communication, a stable named roster, or shared task ownership. A coordinator can create several workers, but workers cannot address one another, durable follow-up intent lives only in target inboxes, and no common compare-and-set board prevents stale

English

Problem

The subagent seam supplies fresh/fork providers, durable child Sessions, FIFO follow-ups, and cold-resumable Activations. Its direct-parent controls do not provide peer communication, a stable named roster, or shared task ownership. A coordinator can create several workers, but workers cannot address one another, durable follow-up intent lives only in target inboxes, and no common compare-and-set board prevents stale assignment updates.

All same-process Agents also share one checkout. Filesystem edit tools can reject an observed stale version, but Bash, formatters, generators, and external writers bypass that fence. Treating a teammate name or task owner as a file lock would hide rather than solve this concurrency boundary.

Agent Teams needs an explicit opt-in composition while its public contracts remain experimental. The default tool catalog and simple-task behavior must remain unchanged, while an explicitly requested Team must survive child Activation settlement and mailbox delivery races long enough for the Lead to aggregate the result before process teardown.

Decision

Every ordinary runtime root is the implicit Lead of a Team identified by that root's SessionId. The Team has no creation event: its Lead pseudo-row exists by identity, while durable state begins with the first member, message, or task event. A roster is flat and contains at most the configured number of immutable lowercase-kebab-case names. Each teammate is a continuable direct child with a reserved Session id; only the Lead creates or interrupts teammates. Ordinary provider-owned subagents outside the roster are not Team members, and an ordinary fork is a new root whose inherited Team records are excluded by their ancestor TeamId.

The implementation is split into @deepseek-ai/dsh-experimental-agent-team, which owns ctx.agentTeams and durable semantics, and @deepseek-ai/dsh-experimental-tool-agent-team, which owns scoped schemas and model guidance. Every Team tool declares its complete result schema and renders that value as compact JSON, so the compiler checks each execute against what the model is promised and no result spends tokens on indentation. Deployments mount both plugins explicitly and may disable legacy continuable controls with the same model-visible names. The shared policy announces Team availability and describes coordination responsibilities. Both packages are public members of packages/experimental/; the experimental package reference owns publication, dependency isolation, and promotion.

The Lead must wait for required work before its final answer. Process teardown remains the final lifecycle owner and drains continuation Activations; a Team task owner is durable state and is not automatically released by idle, interruption, or process exit.

Profile delegation

The Team profile disables subagent and subagent_fork together with the overlapping global controls. Direct model delegation uses spawn_teammate with fresh or fork context, keeping those children in the durable roster. Workflow remains available through the base profile’s fresh spawn provider for scripted orchestration; it cannot inherit a teammate’s conversation identity through the model tool. The Subagent services and providers remain shared infrastructure. Ordinary Session forks retain their history; current identity handling supplies the fork’s own role.

Team identity

Shared system policy and all tool schemas stay uniform across members; execution owns role restrictions. Team tools resolve the caller’s Team and accept member names, so the model needs no Team id. Required runtime-context identity owns identity injection, retention, and fork handling.

Provisioning and recovery

Creation first appends and flushes a team/member provisioning snapshot in the Lead Session, then starts the reserved continuable child through the selected fresh or fork provider. Failure before initial inbox acceptance appends a failed snapshot. Success flushes the child's accepted inbox item before appending active. Recovery recognizes that initial message while it is still pending or after it enters user-message history. Names are reserved by the first provisioning record and never reused, including after failure. Disposal closes admission, aborts and awaits admitted creation and send operations, then stops every live child recorded by the roster; a failed child remains cleanup-owned until its Activation exits, and cleanup rejection fails disposal.

A root recovery reconciles an unterminated provisioning record against the child's independently persisted Session. Matching direct-parent and continuable descriptors plus a recorded initial user message prove successful admission and produce active; absence, corruption, mismatched provider/lineage, or a missing admitted message produces failed. The creator re-reads the terminal phase under the same Lead-log serializer; if recovery marked failed while creation succeeded, it drains the child and reports a provisioning conflict instead of retaining an orphan. This avoids reconstructing an initial prompt that was never retained in the Team log and contains plugin-reload races.

Fresh children have no inherited conversation. Fork children capture the Lead's completed-turn prefix once and retain it as their own durable seed. The current delegation turn remains excluded, matching the existing fork provider contract.

Mailbox and task transactions

The historical Lead-log mailbox remains a readable format: queued and delivered records keep their stable identity and sender, and Team payload validation still applies to them. Team no longer delivers or acknowledges them; new sends follow the direct inbox decision.

Unknown mailbox content stays as decoded JSON during event projection and checkpoint admission. Validation inspects its type without rebuilding its fields, because generic object parsing can omit a valid own __proto__ key. Locally declared content variants undergo structural validation; opaque plugin fields do not gain Team semantics. Cache invalidation is necessary because parser correction alone cannot recover keys omitted from cached state. The Team documentation owns the restore behavior.

send_message uses Steer for running, idle, and cold targets. New sends return inbox acceptance. The Team Steer messaging decision records the single-tool scheduling rationale.

Shared tasks are complete snapshots with Team-local ids and monotonic revisions. Every mutation carries expectedRevision. Any member creates, reads, or claims a ready unowned task; the owner or Lead edits and transitions it, while only the Lead assigns another member. Numeric task ids remain within the safe-integer allocation range, and exhaustion fails without reusing an id. Dependencies must name non-deleted tasks and form a complete DAG. Deleted tasks are retained tombstones. writeScopes are normalized path prefixes that produce overlap diagnostics but never block claim or authorize a write.

wait_agent blocks on one roster, mailbox, task, or live-status edge registered after the call starts instead of encouraging model polling. It does not replay an earlier edge, so callers re-read authoritative state after wakeup or timeout. Lead-only interruption cancels the current turn with inbox preservation and does not alter mailbox or task ownership.

Shared checkout boundary

Members start in the Lead’s current directory and observe shared filesystem writes immediately. Later directory changes stay local to each Session. The policy tells members to partition tasks, record advisory write scopes, order dependent work, and let the Lead inspect the final diff and run tests. A filesystem stale-version rejection requires rereading and rebasing the intended change. No equivalent guarantee is claimed for Bash, formatters, code generation, or direct external writes.

The Team domain does not infer branches, create worktrees, merge changes, or silently change cwd. A deployment or caller can use explicit worktree creation separately. This preserves the existing same-world subagent and sandbox contracts.

Web projection

The Web panel reads the Lead Session's agentTeam wire projection because the existing projection stream keeps browser state current without a separate Team read API. Both Lead and teammate conversations select values from the shared cross-Session store. The panel owns no projection reads: Session loading, cached list values, and control baselines supply the data.

The wire view carries durable roster phases, non-deleted task views with shared readiness and overlap derivation, and failure beside the last valid state. Live activity comes from Session status. Model selection has its own durable projection; the panel displays the shared store's available values without opening member conversations. A missing cached value remains absent until Session loading or a live update supplies it.

Publication requires apply to return a new state object per applied Team event and replace only the touched collection. Mailbox-only events retain the view reference and publish nothing. The existing carrier sends each changed complete view to every connected browser; the package README records that cost.

Alternatives considered

Extend direct-child subagent tools with peer ids. Rejected because parent/child authority and Team peer membership are different domains. Adding peer access to the continuation seam would weaken its exact-parent authorization and still leave roster and tasks without a persistence owner.

Store mail in each target Session before Lead-log enqueue. Rejected because target materialization and acceptance can fail after the Team has committed the send. The always-live Lead Session is the transaction home; target recording is the acknowledgement and de-duplication boundary.

Treat task ownership or write scopes as locks. Rejected because external writers bypass them, crashed owners remain durable, and path-prefix overlap cannot prove semantic independence. False mutual exclusion is more dangerous than an explicit warning.

Create isolated worktrees automatically. Rejected because worktree creation, branch naming, merge policy, ignored files, build artifacts, and cleanup are deployment choices. It also changes the same-world behavior existing subagents and sandboxes expose.

Enable Teams in the default catalog. Rejected because scoped Team controls would shadow same-named legacy globals and unsolicited delegation would add latency and token cost to simple tasks. An opt-in profile bundle inserts Team and disables the legacy controls without adding Team packages to shipped dependency graphs.

Use an in-memory board and mailbox. Roster and task state must survive settlement, HMR, and interruption. Historical mailbox records stay readable; message ownership follows the target inbox.

Return Team tool results as untyped JSON. Rejected because an undeclared result type lets execute drift from the value the model is promised without a compiler error, and it invites indentation that costs tokens on every roster, task, and receipt. Each Team tool therefore declares its complete result schema and one shared helper renders it compactly.

Testing

Package tests cover Team authority, provisioning durability, task transitions, direct inbox acceptance, sender attribution, lifecycle cancellation, and historical mailbox readability. The keyless Team profile snapshot pins the policy, tools, and workflow; the CLI composition test checks persisted Team and child logs.

Model-facing member identities and availability follow the tool projection reference; service residency and durable identity remain distinct.

Consequences

The Lead Session retains whole task/member snapshots and historical mailbox records. This favors inspectable recovery over compact deltas; deleted and delivered history remains append-only until broader Session retention applies.

An active roster member can be non-resident, so inactive is not failure and sends can incur cold-resume latency. Admission failures reject new sends. Historical pending mail is never delivered. Failed provisioning permanently consumes its name and member slot.

Coordination reduces likely checkout conflicts but cannot eliminate writes outside filesystem compare-and-set tools. The final diff and tests remain the Lead's integration boundary.

中文

Problem

subagent seam 已提供 fresh/fork provider、持久 child Session、FIFO follow-up 与可冷恢复 Activation。它的直接 parent control 不提供 peer 通讯、稳定具名 roster 或共享任务 owner。coordinator 可以创建多个 worker,但 worker 无法互相寻址;持久 follow-up 意图只存在于 target inbox;也没有公共 compare-and-set 任务板来阻止陈旧 assignment 更新。

同进程 Agent 还共享一个 checkout。文件系统 edit 工具可以拒绝已观察到的陈旧版本,但 Bash、formatter、generator 与外部 writer 会绕过该屏障。把 teammate name 或 task owner 当作文件锁只会掩盖而不是解决该并发边界。

Agent Teams 的公开约定仍处于实验阶段,因此需要显式启用的组合。默认工具目录与简单任务行为必须保持不变;而显式请求的 Team 必须能跨越 child Activation settlement 与 mailbox 投递竞争,使 Lead 在进程 teardown 前汇总结果。

Decision

每个普通运行时 Root 都是一个隐式 Team 的 Lead,Team id 等于该 Root 的 SessionId。Team 没有 creation event:Lead pseudo-row 由身份直接存在,持久状态从第一条 member、message 或 task event 开始。roster 是扁平结构,最多包含配置数量、不可变且采用小写 kebab-case 的名字。每个 teammate 都是使用预留 Session id 的 continuable 直接 child;只有 Lead 可以创建或 interrupt teammate。roster 外由 provider 管理的普通 subagent 不是 Team member;普通 fork 是新的 Root,继承的 Team 记录会因 ancestor TeamId 被排除。

实现拆分为 @deepseek-ai/dsh-experimental-agent-team 与 @deepseek-ai/dsh-experimental-tool-agent-team:前者负责 ctx.agentTeams 和持久语义,后者负责 scoped schema 与模型指引。每个 Team 工具都声明完整的结果 schema,并把该值渲染为紧凑 JSON,因此编译器会检查每个 execute 是否符合对模型的承诺,也没有结果把 token 花在缩进上。部署显式挂载两个插件,并可禁用具有相同模型可见名称的旧 continuable control。共享策略声明 Team 可用,并说明协调责任。两个包都是 packages/experimental/ 的公开成员;实验性包参考负责发布、依赖隔离与 promotion。

Lead 必须等待所需工作后才能给出最终答案。进程 teardown 仍是最终生命周期 owner,并会 drain continuation Activation;Team task owner 是持久状态,不会因 idle、interrupt 或进程退出自动释放。

Profile delegation

Team profile 禁用 subagent、subagent_fork 以及名称重叠的全局控件。模型直接委派使用支持 fresh 或 fork 上下文的 spawn_teammate,使这些子代理进入持久 roster。Workflow 仍通过 base profile 的 fresh spawn 提供方执行脚本编排;经模型工具创建的 workflow 子代理不会继承 teammate 的对话身份。Subagent 服务和提供方仍是共享基础设施。普通 Session fork 保留历史;当前身份处理提供 fork 自己的角色。

Team identity

共享 system 策略和全部工具 schema 在成员间保持一致;执行时检查角色权限。Team 工具根据调用者确定 Team,并接受成员名字,因此模型不需要 Team id。必需 runtime-context 身份负责身份注入、保留与 fork 处理。

Provisioning and recovery

创建操作先在 Lead Session 中追加并 flush team/member provisioning 快照,再通过选定 fresh 或 fork provider 启动预留的 continuable child。初始 inbox 获准前的失败会追加 failed 快照;成功会先 flush child 中已接受的 inbox 条目,再追加 active。恢复会在初始消息仍处于 pending 或已进入用户消息历史时识别它。名字由第一条 provisioning 记录永久保留,包括失败后也不能复用。dispose 会关闭准入,中止并等待已获准的创建与发送操作,再停止 roster 记录的所有 live child;failed child 在 Activation 退出前仍由 cleanup 拥有,cleanup 拒绝会让 dispose 失败。

Root 恢复时会把未终结 provisioning 记录与独立持久 child Session 对账。直接 parent 与 continuable descriptor 匹配,并且已经记录初始用户消息,才能证明准入成功并转为 active;缺失、损坏、provider/lineage 不匹配或缺少已准入消息都会转为 failed。creator 会在同一 Lead 日志 serializer 内重读终态;如果 recovery 在创建成功时先标记 failed,creator 会 drain child 并报告 provisioning conflict,而不是遗留孤儿。这样既无需重建从未保存在 Team 日志中的初始 prompt,也能约束插件 reload 竞争。

fresh child 不继承对话。fork child 只捕获一次 Lead 已完成 turn 前缀,并保留为自己的持久 seed。当前 delegation turn 保持排除,与既有 fork provider 契约一致。

Mailbox and task transactions

历史 Lead-log mailbox 仍是可读格式:queued 与 delivered 记录保留稳定身份与发送者,Team payload 校验仍适用于它们。Team 不再投递或确认这些记录;新发送遵循直接 inbox 决策。

事件投影和 checkpoint 准入期间,未知 mailbox 内容保持为已解码 JSON。校验只检查其 type,不重建字段,因为通用对象解析可能省略有效的自有 __proto__ 键。本地声明的内容变体接受结构校验;不透明的插件字段不获得 Team 语义。缓存失效是必要的,因为只修正解析器无法恢复缓存状态中省略的键。Team 文档拥有恢复行为的说明。

send_message 对 running、idle 和 cold target 使用 Steer。新发送返回 inbox 接收结果。Team Steer 消息决策记录单一工具调度的理由。

共享 task 是带 Team-local id 与单调 revision 的完整快照。每次变更都携带 expectedRevision。任意 member 可以创建、读取或 claim ready 且无 owner 的任务;Owner 或 Lead 可以编辑和转换;只有 Lead 可以分配给另一个 member。数字 task id 保持在安全整数分配范围内;该范围耗尽时会失败,不会复用 id。依赖必须指向未删除任务,并形成完整 DAG。删除任务保留为 tombstone。writeScopes 是规范化路径前缀,只产生重叠诊断,绝不会阻止 claim 或授予写权限。

wait_agent 等待调用注册后发生的下一条 roster、mailbox、task 或实时 status 边,避免模型轮询。它不会回放更早的边,因此调用方需要在唤醒或超时后重新读取权威状态。仅限 Lead 的 interrupt 使用 inbox preservation 取消当前 turn,不改变 mailbox 或 task owner。

Shared checkout boundary

所有 member 创建时使用 Lead 的当前目录,并立即观察共享文件系统写入。后续目录变更仅影响各自 Session。策略要求 member 切分任务、记录提示性 write scope、为有序工作添加依赖,并由 Lead 检查最终 diff 和运行测试。文件系统 stale-version 拒绝后必须重新读取并 rebase 修改意图。Bash、formatter、codegen 与直接外部写入不具备等价保证。

Team 领域不会推断 branch、创建 worktree、merge 变更或静默改变 cwd。deployment 或调用方可以另外使用显式工作树创建。这样保留既有 same-world subagent 与 sandbox 契约。

Web projection

Web panel 读取 Lead Session 的 agentTeam 传输投影,因为现有投影流能持续更新浏览器状态,无需独立的 Team 读取 API。Lead 与 teammate 会话均从共享的跨 Session store 选择值。面板不负责投影读取:Session 加载、列表缓存值与控制基线提供数据。

传输视图携带持久 roster phase、使用共享就绪与重叠派生逻辑的未删除任务视图,以及最后有效状态旁的 failure。实时活动来自 Session 状态。模型选择有自己的持久投影;面板展示共享 store 中可用的值,不打开成员会话。缺少的缓存值保持缺失,直到 Session 加载或实时更新提供它。

发布要求 apply 对每个已应用的 Team 事件返回新的 state 对象,并只替换被触及的集合。仅邮箱的事件保留视图引用,不发布 frame。现有传输层将每个变化后的完整视图发送给所有已连接浏览器;包 README 记录了这项开销。

Alternatives considered

用 peer id 扩展 direct-child subagent tool。 拒绝,因为 parent/child 权限与 Team peer membership 是不同领域。向 continuation seam 增加 peer access 会削弱 exact-parent authorization,仍无法为 roster 与 task 提供持久 owner。

在 Lead 日志入队前把 mail 存入每个 target Session。 拒绝,因为 Team 提交发送之后,target materialization 与准入仍可能失败。始终 live 的 Lead Session 是事务 owner;target recording 是 acknowledgement 与去重边界。

把 task ownership 或 write scope 当作锁。 拒绝,因为外部 writer 会绕过它们,崩溃 owner 会持久保留,而路径前缀重叠不能证明语义独立。虚假的互斥保证比明确 warning 更危险。

自动创建隔离 worktree。 拒绝,因为 worktree 创建、branch 命名、merge 策略、ignored file、构建产物与 cleanup 都是 deployment 选择;它也会改变既有 subagent 与 sandbox 暴露的 same-world 行为。

在默认工具目录中启用 Team。 拒绝,因为 scoped Team control 会覆盖同名旧全局工具,主动 delegation 也会给简单任务增加延迟和 token 成本。opt-in profile bundle 会插入 Team 并禁用旧 control,同时不向随附依赖图添加 Team 包。

使用内存 board 与 mailbox。 roster 与任务状态必须经受 settlement、HMR 与中断。历史 mailbox 记录保持可读;消息归属目标 inbox。

让 Team 工具返回未类型化 JSON。 拒绝,因为未声明的结果类型会让 execute 在没有编译错误的情况下偏离对模型的承诺,也会引入在每份 roster、task 与回执上都消耗 token 的缩进。因此每个 Team 工具都声明完整的结果 schema,并由一个共享 helper 紧凑渲染。

Testing

包测试覆盖 Team 权限、provisioning 持久性、任务转换、直接 inbox 接收、发送者归属、生命周期取消和历史 mailbox 可读性。无密钥 Team profile 快照固定 policy、工具与工作流;CLI 组合测试检查持久 Team 和 child 日志。

模型可见的成员身份与可用状态遵循工具投影说明;服务驻留状态和持久身份仍保持区分。

Consequences

Lead Session 保留完整 task/member 快照和历史 mailbox 记录。这优先支持可检查的恢复,而不是紧凑增量;deleted 与 delivered 历史在更广泛的 Session 保留策略生效前仍只追加。

active roster 成员可能不驻留,因此 inactive 不表示失败,发送可能产生冷恢复延迟。准入失败会拒绝新发送。历史 pending 邮件永远不会被投递。provisioning 失败永久占用其名称和成员槽位。

协调可以降低 checkout 冲突概率,但无法消除文件系统 CAS 工具之外的写入。最终 diff 与测试仍是 Lead 的集成边界。