fix(subagent): address codex review round 3

- Make host-user authority unforgeable. `{ kind: 'user' }` was a bare
  discriminant, so any plugin holding `ctx.subagents` — including
  model-generated cordis_mount code, which the advanced ACP composition ships
  alongside continuable subagents — could construct it and skip the
  direct-parent check for any known child id. It now carries an opaque grant
  that only SubagentService.userAuthority() mints, which composition hands to
  trusted host adapters; a model-facing tool uses parent authority from its own
  execution context.
- Reconcile a delivery discarded inside its own admission window. An enqueue
  listener that cancels fires the discard before followup() returns, so the
  discard listener could not clear an id it had not seen; submit() retained it
  and residency stayed `running` until an explicit drain.
- Recheck the caller signal after materialization. An abort landing between
  publication and inbox acceptance still submitted the prompt and returned both
  ids; it now rolls the child back.
- Stop promising the model transcript access that no shipped continuable config
  mounts. The tools now state only that a background child does not report back.
- Restate the implemented note as shipped state rather than a proposal, so it
  works as current authority.
This commit is contained in:
Dudu-0223
2026-07-30 16:48:42 +08:00
committed by Tianyi Cui
parent cbaceb73a9
commit 7428cdf41e
29 changed files with 297 additions and 141 deletions

View File

@@ -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 docs/core-data-structures/subagent.md
subagent.md: a58ecf13ba1f5df0e8e35c793eaf9aefc1e8a900
subagent.zh.md: 541eace7fc6c8ae10ee22639680918e12d7762b3
subagent.md: ceff3586bf6724bd6f47b71e9fb737361a2830f8
subagent.zh.md: aa39ea382fe1e2a52b6ee794cfa71d8abc945da3

View File

@@ -123,7 +123,7 @@ persisted Session
The Agent inbox is the only queue. Every continuation message becomes one `Agent.followup()` FIFO turn, so parent and user messages share one observable order and a follow-up cannot redirect a turn already underway. Successful delivery returns the accepted `MessageId`; the existing `agent/inbox/enqueue`, `agent/inbox/dequeue`, and `agent/inbox/discard` events remain the message-lifecycle observations, and the continuation layer defines no subagent-specific delivery route.
Authority is supplied by a trusted host interaction or an exact live Agent tool context. The parent variant is admitted only when the authenticated Agent is the durable child's direct parent recorded in `SessionHeader.parentSession`; only a trusted host adapter can supply user authority. `MessageSource` and `senderSessionId` are durable provenance after admission and grant no authority — the optional model-facing tool uses `CoordinatorMessageSource`, while a host adapter uses `{ kind: 'user' }`. User authority may cold-resume a child without loading its historical parent.
Authority is supplied by a trusted host interaction or an exact live Agent tool context. The parent variant is admitted only when the authenticated Agent is the durable child's direct parent recorded in `SessionHeader.parentSession`. User authority carries an opaque grant that only `SubagentService.userAuthority()` mints, so a caller cannot claim it by writing the discriminant — a plugin holding `ctx.subagents`, including model-generated mount code, would otherwise bypass the direct-parent check for any known child id. `MessageSource` and `senderSessionId` are durable provenance after admission and grant no authority — the optional model-facing tool uses `CoordinatorMessageSource`, while a host adapter uses `{ kind: 'user' }`. User authority may cold-resume a child without loading its historical parent.
For both operations the caller signal owns lookup, materialization, and admission only until inbox acceptance. Afterwards the manager owns the Activation independently: later caller cancellation neither cancels the accepted turn nor disposes the child, and the seam exposes no public subagent cancellation or steering operation.
@@ -149,8 +149,14 @@ interface CoordinatorMessageSource {
type SubagentAuthority =
/** The exact live parent Agent whose tool context is making the call. */
| { readonly kind: 'parent'; readonly agent: Agent }
/** A trusted host adapter acting for the human user. */
| { readonly kind: 'user' }
/**
* A trusted host adapter acting for the human user. The `grant` must be the
* exact token {@link SubagentService.userAuthority} minted, so a discriminant
* alone cannot claim this authority — any plugin holding `ctx.subagents`,
* including model-generated mount code, could otherwise forge it and bypass
* the direct-parent check.
*/
| { readonly kind: 'user'; readonly grant: UserAuthorityGrant }
```
```ts type-equiv

View File

@@ -149,8 +149,14 @@ interface CoordinatorMessageSource {
type SubagentAuthority =
/** The exact live parent Agent whose tool context is making the call. */
| { readonly kind: 'parent'; readonly agent: Agent }
/** A trusted host adapter acting for the human user. */
| { readonly kind: 'user' }
/**
* A trusted host adapter acting for the human user. The `grant` must be the
* exact token {@link SubagentService.userAuthority} minted, so a discriminant
* alone cannot claim this authority — any plugin holding `ctx.subagents`,
* including model-generated mount code, could otherwise forge it and bypass
* the direct-parent check.
*/
| { readonly kind: 'user'; readonly grant: UserAuthorityGrant }
```
```ts type-equiv