docs: replace vague provenance prose with recorded facts
This commit is contained in:
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/subagent/subagent/README.md
|
||||
README.md: cd25bace16c91e8b44331dc8e5eab0987607e83a
|
||||
README.zh.md: 52971add9646fd45a33661bb33b3f7d8c452219c
|
||||
README.md: 7658dad2fc8d79ca178ed5467448de9527f7bc49
|
||||
README.zh.md: 8d3bc4b96ac366e919256f4409ebd54d3505de87
|
||||
|
||||
@@ -27,7 +27,7 @@ The [subagent family overview](../README.md) maps implementations and model-faci
|
||||
|
||||
`SubagentStartRequest.label` is an optional short durable display label for a session-backed one-shot child. Model-facing delegation supplies its existing `description`; lower-level callers need not invent presentation metadata. Continuable starts always carry their own required label. `signal` is required and is the canonical cancellation channel for a one-shot `start`. An abort before publication makes `start()` reject after rollback; an abort after publication cancels the returned run's remaining turn work without hiding its id. The request may also select a model, require structured output, cap delegation depth, restrict child tools, or set a child persona. For a continuable start or follow-up, the caller signal owns lookup, materialization, and admission only until inbox acceptance; afterward the manager owns the Activation independently, so later caller cancellation neither cancels the accepted turn nor disposes the child.
|
||||
|
||||
Follow-up authority comes from the exact live direct parent recorded in the child's durable header. Cold resume checks that authority before reconstruction and again in the final no-await inbox-admission span, so a parent unregistered or replaced during materialization cannot authorize delivery. The `source` on a follow-up is durable provenance retained on the delivered message and grants no authority.
|
||||
Follow-up authority comes from the exact live direct parent recorded in the child's durable header. Cold resume checks that authority before reconstruction and again in the final no-await inbox-admission span, so a parent unregistered or replaced during materialization cannot authorize delivery. The `source` on a follow-up records who supplied the delivered message and grants no authority.
|
||||
|
||||
Same-process requests, descriptors, results, and event payloads are trusted typed values borrowed as immutable. The service does not clone or freeze them; serialization and hostile-input validation belong at actual process, worker, persistence, and model boundaries.
|
||||
|
||||
@@ -72,13 +72,13 @@ A continuation-managed parent Activation records each child Session id in an `ow
|
||||
|
||||
## Lifecycle events
|
||||
|
||||
The service emits a `subagent/start`/`subagent/end` pair for each one-shot run and each resident continuable Activation epoch, so continuable children are observable with the same vocabulary as one-shot runs without exposing whether the manager materialized, woke, or cold-resumed them. For a one-shot start it attaches the result observer before the synchronous `subagent/start`, so even an already-settled child still produces `subagent/start` before `subagent/end`; a continuable epoch that fails before residency emits neither edge. The pair shares a service-minted `runId`; the `local` flag is snapshotted from the provider's exact `localAgent` (always true for a continuable child), so observers never infer run identity or locality from reusable provider/session names. The `provider` field is lifecycle provenance rather than a live-registry claim: an accepted one-shot run may settle after provider removal, and a cold-resumed epoch retains its descriptor's initial provider name without requiring that provider to be registered.
|
||||
The service emits a `subagent/start`/`subagent/end` pair for each one-shot run and each resident continuable Activation epoch, so continuable children are observable with the same vocabulary as one-shot runs without exposing whether the manager materialized, woke, or cold-resumed them. For a one-shot start it attaches the result observer before the synchronous `subagent/start`, so even an already-settled child still produces `subagent/start` before `subagent/end`; a continuable epoch that fails before residency emits neither edge. The pair shares a service-minted `runId`; the `local` flag is snapshotted from the provider's exact `localAgent` (always true for a continuable child), so observers never infer run identity or locality from reusable provider/session names. The `provider` field names the provider that started the run or Activation epoch rather than claiming current registration: an accepted one-shot run may settle after provider removal, and a cold-resumed epoch retains its descriptor's initial provider name without requiring that provider to be registered.
|
||||
|
||||
Run events are scoped to the delegating parent. Every listener is independently contained: a synchronous throw or rejected returned promise is logged without starving peer listeners or changing the run.
|
||||
|
||||
Provider additions and removals also emit `subagent/provider-added` and `subagent/provider-removed`. Consumers such as the model-facing tool use those events because Cordis may load sibling plugins concurrently; configuration order does not prove registration order.
|
||||
|
||||
Continuable children do not create `SubagentRun` or Tasks. The continuation manager directly owns one process-local Activation and retained `AgentHandle` per resident child Session, uses the Agent inbox as the only FIFO, and cold-resumes from the durable descriptor. Exact live direct-parent identity authorizes parent-to-child delivery. Exact live child identity authorizes reports; the manager derives the recipient from durable `parentSession`, and `MessageSource` remains provenance rather than authority. Interrupt authority is deliberately wider than delivery authority: a human presents the durable direct-parent address so a live child stays stoppable while its parent Agent is offline, and any exact live ancestor recorded in the Activation's materialization lineage may stop its descendant, because stopping a turn is idempotent and delivers no content.
|
||||
Continuable children do not create `SubagentRun` or Tasks. The continuation manager directly owns one process-local Activation and retained `AgentHandle` per resident child Session, uses the Agent inbox as the only FIFO, and cold-resumes from the durable descriptor. Exact live direct-parent identity authorizes parent-to-child delivery. Exact live child identity authorizes reports; the manager derives the recipient from durable `parentSession`, and `MessageSource` records the sender without granting authority. Interrupt authority is deliberately wider than delivery authority: a human presents the durable direct-parent address so a live child stays stoppable while its parent Agent is offline, and any exact live ancestor recorded in the Activation's materialization lineage may stop its descendant, because stopping a turn is idempotent and delivers no content.
|
||||
|
||||
When `ctx.sessionProjections` is available, the service registers two projection units. `subagentTiming` resets at each descriptor so a fork seed's ancestor work cannot enter the child's total, then accumulates `turn/start` → `turn/end` active time and retains same-cut `active.since` and `active.through` bounds for an open turn; while that turn remains open, `active.through` follows the latest folded event, giving an inactive consumer a conservative crash bound without mixing in newer session metadata. `subagent` folds the durable identity — mode plus creation label — from `subagent/descriptor` events with the same last-wins reset discipline, so a fork seed's ancestor descriptor stands only until the child's own overrides it; a malformed or unrecognized-version payload folds to the serializable `null` sentinel — indistinguishable from a log with no descriptor, and surviving every JSON push frame so a consumer replaces a stale identity instead of keeping it — and never throws.
|
||||
|
||||
@@ -105,6 +105,6 @@ No direct invalidation; the named consumers own any request-prefix changes.
|
||||
- **No current-turn steering** — continuable messages and waking reports enqueue later turns; neither redirects an open turn.
|
||||
- **Wake gap during cancellation convergence** — a waking follow-up accepted after the interrupt signal is issued but before the active driver becomes idle remains queued until another waking send. Issue #1838 owns the agent-loop wake latch, which also affects ordinary session cancellation.
|
||||
- **Process-local residency** — the Activation inbox and ownership graph do not coordinate two harness processes; concurrent access to one persistence store still requires a durable mailbox and cross-process lease protocol.
|
||||
- **No replay of accepted-but-unlogged messages** — only messages written to the child Session log are reconstructable with their admitted provenance. A crash may lose an accepted initial prompt or follow-up that never reached the log; a later authorized message can cold-resume the child, but the lost message is not replayed automatically.
|
||||
- **No replay of accepted-but-unlogged messages** — only messages written to the child Session log are reconstructable with the source that supplied them. A crash may lose an accepted initial prompt or follow-up that never reached the log; a later authorized message can cold-resume the child, but the lost message is not replayed automatically.
|
||||
- **No durable report mailbox** — reports require a live direct parent and provide acceptance identity rather than exactly-once delivery or a read receipt.
|
||||
- **Lifecycle events are observe-only** — a run-affecting `subagent/end` continuation or decision surface waits for a concrete consumer.
|
||||
|
||||
@@ -27,7 +27,7 @@ subagent seam 允许一个 agent(智能体)通过具名提供方把工作委
|
||||
|
||||
`SubagentStartRequest.label` 是由会话支撑的一次性 child 所使用的可选简短持久化显示标签。面向模型的委派会提供其已有的 `description`;底层调用方无需凭空构造展示元数据。可继续启动始终携带自身的必填标签。`signal` 是必填项,也是一次性 `start` 的规范取消通道。发布前中止会使 `start()` 在回滚后拒绝;发布后中止会取消已返回 run 的剩余轮次工作,但不会隐藏其 id。请求还可以选择模型、要求结构化输出、限制委派深度、约束子 agent 工具或设置子 agent persona。对于可继续启动或后续操作,调用方信号只在 inbox 接受之前掌管查找、物化和准入;此后由管理器独立拥有 Activation,因此调用方后续取消既不会取消已接受的轮次,也不会 dispose(资源释放)子 agent。
|
||||
|
||||
后续操作的权限来自子 agent 持久化 header 中记录的确切在线直接父级。冷恢复会在重建前检查该权限,并在最终无 await 的 inbox 准入区间再次检查,因此在物化期间被注销或替换的 parent 无法授权投递。后续操作上的 `source` 是保留在所投递消息上的持久化来源,不授予任何权限。
|
||||
后续操作的权限来自子 agent 持久化 header 中记录的确切在线直接父级。冷恢复会在重建前检查该权限,并在最终无 await 的 inbox 准入区间再次检查,因此在物化期间被注销或替换的 parent 无法授权投递。后续操作上的 `source` 记录谁提供了所投递的消息,不授予任何权限。
|
||||
|
||||
同进程请求、描述符、结果和事件 payload 都是以不可变方式借用的可信类型值。服务不会克隆或冻结它们;序列化和不可信输入校验属于真实的进程、worker、持久化和模型边界。
|
||||
|
||||
@@ -72,13 +72,13 @@ subagent seam 允许一个 agent(智能体)通过具名提供方把工作委
|
||||
|
||||
## 生命周期事件
|
||||
|
||||
服务会为每次一次性运行以及每个已驻留的可继续 Activation 时段发出一对 `subagent/start`/`subagent/end`,因此可继续子 agent 可用与一次性运行相同的词汇观察,且不会暴露管理器是物化、唤醒还是冷恢复了它们。对于一次性启动,它会在同步的 `subagent/start` 之前附加结果观察器,因此即使子 agent 已经结算,也仍会先产生 `subagent/start`,再产生 `subagent/end`;在驻留前失败的可继续时段不会发出这对生命周期事件中的任何一个。这对事件共享服务生成的 `runId`;`local` 标志取自提供方准确 `localAgent` 的快照(可继续子 agent 恒为 true),因此观察器绝不会从可复用的提供方/会话名称推断运行身份或本地性。`provider` 字段是生命周期来源信息,而非提供方仍在注册的声明:已接受的一次性 run 可在提供方移除后才结算,冷恢复时段也会保留描述符中的初始提供方名称,而不要求该提供方仍处于注册状态。
|
||||
服务会为每次一次性运行以及每个已驻留的可继续 Activation 时段发出一对 `subagent/start`/`subagent/end`,因此可继续子 agent 可用与一次性运行相同的词汇观察,且不会暴露管理器是物化、唤醒还是冷恢复了它们。对于一次性启动,它会在同步的 `subagent/start` 之前附加结果观察器,因此即使子 agent 已经结算,也仍会先产生 `subagent/start`,再产生 `subagent/end`;在驻留前失败的可继续时段不会发出这对生命周期事件中的任何一个。这对事件共享服务生成的 `runId`;`local` 标志取自提供方准确 `localAgent` 的快照(可继续子 agent 恒为 true),因此观察器绝不会从可复用的提供方/会话名称推断运行身份或本地性。`provider` 字段指明启动该运行或 Activation 时段的提供方,不表示该提供方当前仍在注册:已接受的一次性 run 可在提供方移除后才结算,冷恢复时段也会保留描述符中的初始提供方名称,而不要求该提供方仍处于注册状态。
|
||||
|
||||
运行事件受执行委派的父级作用域约束。每个监听器都独立隔离:同步抛出或返回的 promise 被拒绝时,只会记录日志,不会阻塞同级监听器或改变运行。
|
||||
|
||||
提供方新增和移除还会发出 `subagent/provider-added` 与 `subagent/provider-removed`。面向模型的工具等消费方使用这些事件,因为 Cordis 可能并发加载同级插件;配置顺序不能证明注册顺序。
|
||||
|
||||
可继续子级不会创建 `SubagentRun` 或 Task。继续执行管理器为每个驻留子 Session 直接拥有一个仅存在于当前进程的 Activation 和一个留存的 `AgentHandle`,使用 Agent inbox 作为唯一 FIFO,并从持久化描述符冷恢复。父到子投递由确切在线的直接父级身份授权。上报则由确切在线的子级身份授权;管理器根据持久化的 `parentSession` 推导接收方,`MessageSource` 仍只表示来源,不表示权限。中断权限被刻意设计得比投递权限更宽:人类出示持久化直接 parent 地址,因此即使 parent Agent 离线,在线 child 仍可被停止;Activation 物化时记录的任何确切在线 ancestor 也可以停止其后代,因为停止一个轮次是幂等的,且不投递任何内容。
|
||||
可继续子级不会创建 `SubagentRun` 或 Task。继续执行管理器为每个驻留子 Session 直接拥有一个仅存在于当前进程的 Activation 和一个留存的 `AgentHandle`,使用 Agent inbox 作为唯一 FIFO,并从持久化描述符冷恢复。父到子投递由确切在线的直接父级身份授权。上报则由确切在线的子级身份授权;管理器根据持久化的 `parentSession` 推导接收方,`MessageSource` 记录发送方,但不授予权限。中断权限被刻意设计得比投递权限更宽:人类出示持久化直接 parent 地址,因此即使 parent Agent 离线,在线 child 仍可被停止;Activation 物化时记录的任何确切在线 ancestor 也可以停止其后代,因为停止一个轮次是幂等的,且不投递任何内容。
|
||||
|
||||
当 `ctx.sessionProjections` 可用时,服务会注册两个投影单元。`subagentTiming` 会在每个描述符处重置,使 fork 种子中的祖先工作不会计入 child 总量,随后累加 `turn/start` → `turn/end` 活跃时间,并为未结束的轮次保留同一切面的 `active.since` 和 `active.through` 边界;在该轮次保持未结束期间,`active.through` 会跟随最近折叠的事件,从而为 inactive 消费方提供保守的崩溃上界,又不会混入更新的会话元数据。`subagent` 以同样的 last-wins 重置规则从 `subagent/descriptor` 事件折叠持久化身份——模式与创建标签——因此 fork 种子中的祖先描述符只在 child 自身的描述符覆盖之前有效;畸形或版本不识别的载荷折叠为可序列化的 `null` 哨兵——与没有描述符的日志不可区分,且能完好通过每个 JSON 推送帧,使消费方替换陈旧身份而不是将其保留——绝不抛错。
|
||||
|
||||
@@ -105,6 +105,6 @@ subagent seam 允许一个 agent(智能体)通过具名提供方把工作委
|
||||
- **不对当前轮次进行 steering**:可继续消息和唤醒式 report 会排入后续轮次,均不会重定向正在进行的轮次。
|
||||
- **取消收敛期间存在唤醒缺口**:中断信号发出后、活动 driver 进入 idle 前被接受的唤醒型 follow-up 会保持排队,直到另一条唤醒发送到达。Issue #1838 负责 agent-loop 的唤醒锁存;普通会话取消也受此影响。
|
||||
- **驻留仅限进程内**:Activation inbox 与所有权图不会在两个 harness 进程之间协调;对单个持久化存储的并发访问仍然需要持久化邮箱和跨进程租约协议。
|
||||
- **不重放已接受但未记录的消息**:只有写入子 agent Session 日志的消息才能连同其被接受时的来源一起重建。崩溃可能丢失从未写入日志、已被接受的初始提示词或后续消息;此后一条经授权的消息可以冷恢复该子 agent,但丢失的消息不会自动重放。
|
||||
- **不重放已接受但未记录的消息**:只有写入子 agent Session 日志的消息才能连同提供该消息的来源一起重建。崩溃可能丢失从未写入日志、已被接受的初始提示词或后续消息;此后一条经授权的消息可以冷恢复该子 agent,但丢失的消息不会自动重放。
|
||||
- **没有持久化的上报 mailbox**:上报需要实时直接父级,提供的是接受标识,不保证恰好一次投递,也不提供已读回执。
|
||||
- **生命周期事件只供观察**:影响运行的 `subagent/end` 延续或决策接口仍需等待具体消费方。
|
||||
|
||||
@@ -386,7 +386,7 @@ export class SubagentContinuationManager {
|
||||
* @param parent - the exact live direct parent authorizing this delivery.
|
||||
* @param childId - the durable child session id.
|
||||
* @param content - the user-role content to deliver.
|
||||
* @param options - durable provenance and caller cancellation.
|
||||
* @param options - the message source fields and caller cancellation.
|
||||
* @returns the accepted message's inbox id.
|
||||
* @throws when parent authority, availability, or admission rejects the delivery.
|
||||
*/
|
||||
@@ -826,7 +826,7 @@ export class SubagentContinuationManager {
|
||||
* Submit to a freshly materialized Activation or roll it back completely.
|
||||
* @param activation - the just-published Activation to admit or release.
|
||||
* @param content - the initial or resumed message content.
|
||||
* @param source - durable provenance for the accepted message.
|
||||
* @param source - durable fields naming who supplied the accepted message.
|
||||
* @param parent - the live direct parent authorizing admission.
|
||||
* @param signal - caller cancellation owning admission until acceptance.
|
||||
* @returns the accepted inbox message id.
|
||||
|
||||
@@ -218,7 +218,7 @@ export class SubagentService extends Service {
|
||||
* @param parent - the exact live direct parent authorizing this delivery.
|
||||
* @param childId - durable child session id.
|
||||
* @param content - user-role content to deliver.
|
||||
* @param options - durable provenance and caller cancellation, which stops the
|
||||
* @param options - the message source fields and caller cancellation, which stops the
|
||||
* operation only before inbox acceptance.
|
||||
* @returns the accepted message's inbox id.
|
||||
* @throws when continuation services are unavailable, parent authority is
|
||||
|
||||
@@ -45,7 +45,7 @@ const install: InvariantInstaller = Object.assign((ctx: Context, fail: Invariant
|
||||
const info = args[0] as SubagentRunInfo
|
||||
// Provider availability is an admission-time relationship. A published
|
||||
// one-shot run may outlive provider removal, and a cold-resumed Activation
|
||||
// carries durable provider provenance without dispatching through it.
|
||||
// records the initial provider name without dispatching through it.
|
||||
if (info.provider.length === 0 || String(info.runId).length === 0 || String(info.id).length === 0) {
|
||||
fail('subagent/start provider, runId, and child id must be non-empty')
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@ export interface SubagentRunInfo {
|
||||
/** Unique identity shared with the paired terminal event. */
|
||||
readonly runId: SubagentRunId
|
||||
/**
|
||||
* Provider provenance for this run or Activation epoch. The named provider
|
||||
* Provider that started this run or Activation epoch. The named provider
|
||||
* may be absent when an accepted run becomes ready or a persisted Activation
|
||||
* cold-resumes, because neither lifecycle depends on continued registration.
|
||||
*/
|
||||
@@ -55,7 +55,7 @@ export interface SubagentRunInfo {
|
||||
export interface SubagentRunEndInfo {
|
||||
/** Unique identity shared with the paired start event. */
|
||||
readonly runId: SubagentRunId
|
||||
/** The same provider provenance carried by the paired start event. */
|
||||
/** The same provider name carried by the paired start event. */
|
||||
readonly provider: string
|
||||
/** The child agent's id. */
|
||||
readonly id: SessionId
|
||||
|
||||
@@ -80,7 +80,7 @@ describe('subagent invariants', () => {
|
||||
.toThrow(/identity diverges/)
|
||||
})
|
||||
|
||||
it('accepts historical provider provenance after registration ends', async () => {
|
||||
it('accepts the recorded provider name after registration ends', async () => {
|
||||
const ctx = await setup()
|
||||
const historical = provider('historical')
|
||||
ctx.emit('subagent/provider-added', historical)
|
||||
|
||||
Reference in New Issue
Block a user