docs: finalize list_agents RFC contract
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 .agents/notes/proposed/feature/2026-07-22-durable-subagent-catalog-and-list-agents.md
|
||||
2026-07-22-durable-subagent-catalog-and-list-agents.md: b8bca3208541fa8154c3587db4c0520d6a2e3d04
|
||||
2026-07-22-durable-subagent-catalog-and-list-agents.zh.md: 6ee737212da52df21fd30c4fe78d680dbeaf8a5f
|
||||
2026-07-22-durable-subagent-catalog-and-list-agents.md: 23d5b3924a20ae84132048b26e12779a98a6f2bb
|
||||
2026-07-22-durable-subagent-catalog-and-list-agents.zh.md: cac14271515d031a39bf6690f199c97be7bb9fa9
|
||||
|
||||
@@ -12,12 +12,12 @@ Enumeration must cross-check immutable session lineage, descriptor validity, and
|
||||
|
||||
## Proposal
|
||||
|
||||
Treat parent-to-child enumeration and `list_agents` as one separately reviewed feature built on the durable child-session contract. `SubagentService.listChildren(parent)` must:
|
||||
Treat parent-to-child enumeration and `list_agents` as one separately reviewed feature built on the durable child-session contract. `SubagentService.listChildren(parentSessionId: SessionId)` must:
|
||||
|
||||
- use `ctx.sessionQuery.traceSession(parent.session.id)` to obtain the caller's direct live-preferred child sessions;
|
||||
- use `ctx.sessionQuery.traceSession(parentSessionId)` to obtain the parent's direct live-preferred child sessions;
|
||||
- read and validate each candidate's `subagent/descriptor` event without activating the child;
|
||||
- omit one-shot children without a diagnostic, and omit a candidate that becomes unavailable or has a corrupt or unsupported descriptor with a per-child diagnostic;
|
||||
- expose only children whose descriptor carries a durable creation `label`;
|
||||
- silently omit candidates without a descriptor, and omit a candidate that becomes unavailable or has a corrupt or unsupported descriptor with a per-child diagnostic;
|
||||
- expose every child whose supported valid descriptor carries a durable creation `label`, regardless of whether its provider is currently registered;
|
||||
- report a live child as `running` and a persisted-only child as `complete`;
|
||||
- return every resulting child in stable `createdAt` ascending, child-id ascending order.
|
||||
|
||||
@@ -25,30 +25,34 @@ Descriptor persistence, by-id lookup, direct-parent authorization, and provider-
|
||||
|
||||
### Enumeration decision
|
||||
|
||||
The first implementation consumes `ctx.sessionQuery.traceSession(parent.session.id)` and considers only the trace's first-level descendants. Session query already merges `ctx.sessions` with `ctx.sessionPersistence` using live precedence, preserves immutable-header consistency, derives direct-child lineage from `SessionHeader.parentSession`, and sorts siblings by `createdAt` ascending and child id ascending. `listChildren()` does not reproduce that corpus logic or inspect the continuation manager's process-local Activation map.
|
||||
The first implementation consumes `ctx.sessionQuery.traceSession(parentSessionId)` and considers only the trace's first-level descendants. The target may be live or persisted; tracing the logical corpus does not load or resume an Agent. Session query already merges `ctx.sessions` with `ctx.sessionPersistence` using live precedence, preserves immutable-header consistency, derives direct-child lineage from `SessionHeader.parentSession`, and sorts siblings by `createdAt` ascending and child id ascending. `listChildren()` does not reproduce that corpus logic or inspect the continuation manager's process-local Activation map.
|
||||
|
||||
Corpus construction precedes per-child descriptor inspection. A failure while building the initial trace, including persistence listing failure, a live/persisted header conflict anywhere in the observed corpus, or invalid target lineage, fails the whole `list_agents` call because no trustworthy candidate set exists. Only failures after a successful trace are isolated to one candidate; "corrupt child" in that per-child contract therefore means corrupt loaded event surface or descriptor data, not a corpus-level header conflict.
|
||||
|
||||
Session lineage is broader than subagent identity: an ordinary `ctx.sessions.fork()` and a one-shot subagent also create direct children. Each candidate must therefore contain exactly one valid `subagent/descriptor` event. The activation contract writes it only during initial creation and cold resume appends no further descriptor; a second event is corruption rather than evidence of another activation. The event distinguishes a continuable background subagent from an ordinary fork or one-shot child; its short creation `label` comes from the delegation's `description`, while its continuation fields remain the reconstruction input for provider-independent cold resume. A candidate without the event is omitted without a diagnostic.
|
||||
Session lineage is broader than subagent identity: an ordinary `ctx.sessions.fork()` and a one-shot subagent also create direct children. The session header gains no `kind` discriminator; each candidate must instead contain exactly one valid `subagent/descriptor` event. The Activation contract writes it only during initial creation and cold resume appends no further descriptor; a second event is corruption rather than evidence of another Activation. The event is the sole evidence that a traced child is a continuable background subagent; its short creation `label` comes from the delegation's `description`, while its continuation fields remain the reconstruction input for provider-independent cold resume. A candidate without the event is an ordinary fork, one-shot child, or another non-continuable session and is omitted without a diagnostic.
|
||||
|
||||
The published logical record is also the status source: `SessionRecord.live` means `running`, while `live: false, persisted: true` means `complete`. `complete` means that no Activation is live; it encodes neither successful completion nor a permanently closed child, and `send_message` may materialize another Activation. Conversely, `running` says only that the session is live: a live Agent outside the continuation manager's matching Activation still appears as `running`, but `send_message` rejects rather than adopting it. A child is not visible before its session is published, and no process-local Activation entry is added as a second candidate or status source. Listing is a snapshot that may race publication, disposal, or a later message; `send_message` remains the authoritative delivery-time operation.
|
||||
The published logical record is also the status source: `SessionRecord.live` means `running`, while `live: false, persisted: true` means `complete`. This status comes directly from the trace and causes no additional child-log load. `complete` means that no Activation is live; it encodes neither successful completion nor a permanently closed child, and `send_message` may materialize another Activation. Conversely, `running` says only that the session is live: a live Agent outside the continuation manager's matching Activation still appears as `running`, but `send_message` rejects rather than adopting it. A child is not visible before its session is published, and no process-local Activation entry is added as a second candidate or status source. Listing is a snapshot that may race publication, disposal, or a later message; `send_message` remains the authoritative delivery-time operation.
|
||||
|
||||
The subagent service keeps `sessionQuery` optional so start and follow-up remain available without it. Its public `listChildren()` method resolves the optional service when called and throws `SubagentError` with stable code `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` before doing any work when it is absent. `@deepseek-ai/dsh-tool-subagent-control` exports separately loadable tool plugins: the `send_message` adapter requires only `subagents`, while the `list_agents` adapter requires both `subagents` and `sessionQuery` at load. A deployment may therefore use `send_message` without loading session query; the list tool catches misconfiguration at plugin load, while another direct service consumer receives the same explicit call-time contract.
|
||||
The subagent service keeps `sessionQuery` optional so start and follow-up remain available without it. Its public `listChildren(parentSessionId: SessionId)` method resolves the optional service when called and throws `SubagentError` with stable code `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` before doing any work when it is absent. `@deepseek-ai/dsh-tool-subagent-control` exports separately loadable tool plugins: the `send_message` adapter requires only `subagents`, while the `list_agents` adapter requires both `subagents` and `sessionQuery` at load. A deployment may therefore use `send_message` without loading session query; the list tool catches misconfiguration at plugin load, while another direct service consumer receives the same explicit call-time contract.
|
||||
|
||||
This descriptor-read path is the correctness baseline, not a claim that work is linear only in the number of direct children. Let D be the number of direct-child candidates, C the number of persisted sessions scanned by each persistence listing, and L_i the size of candidate i's full log. One corpus trace is followed by two exact reads per candidate. `listChildren()` uses `sessionQuery.listEvents(childId)` to locate the sole descriptor event and `sessionQuery.readEvent({ sessionId: childId, seq })` to read it, and each operation independently loads the logical session. In the persisted-only worst case, every exact read repeats `persistence.list()`, loads the full child log, and clones its events, for O(D × C + Σ L_i) work up to constant factors; a live child instead takes two detached in-memory snapshots of its full log. The persisted path may durably repair an interrupted child log by appending synthetic closing events. The first version accepts the repeated reads as the no-index correctness baseline, but deployments must treat total corpus and child-log size—not only direct-child count—as the capacity constraint. Listing creates no Agent and appends no catalog or descriptor event itself, but it is not a storage-read-only operation. The model-hidden descriptor remains outside the conversation surface and survives compaction, so compacted and uncompacted children must enumerate identically.
|
||||
This descriptor-read path is the correctness baseline, not a claim that work is linear only in the number of direct children. Let D be the number of direct-child candidates, C be the number of persisted sessions scanned by each persistence listing, and L_i be the size of candidate i's full log. One corpus trace is followed by `sessionQuery.listEvents(childId)` for every candidate. A candidate with no descriptor is omitted, and one with multiple descriptors is diagnosed without another read; only a candidate with exactly one descriptor is loaded again through `sessionQuery.readEvent({ sessionId: childId, seq })`. The read must return the same immutable session header observed by the trace, including the direct-parent relationship, and its target must still be the located descriptor event; a mismatch is per-child corruption. In the persisted-only worst case, each exact read repeats `persistence.list()`, loads the full child log, and clones its events, for O(D × C + Σ L_i) work up to constant factors; a candidate with exactly one descriptor pays those costs twice, while other candidates pay them once. A live candidate similarly takes one detached in-memory snapshot of its full log, or two when its descriptor is read. The persisted path may durably repair an interrupted child log by appending synthetic closing events. The first version accepts these repeated reads as the no-index correctness baseline, but deployments must treat total corpus and child-log size—not only direct-child count—as the capacity constraint. Listing creates no Agent and appends no catalog or descriptor event itself, but it is not a storage-read-only operation. The model-hidden descriptor remains outside the conversation surface and survives compaction, so compacted and uncompacted children must enumerate identically.
|
||||
|
||||
If measured scale later requires an index, that index is derived state: session headers and child descriptors remain authoritative, and rebuilding or corruption fallback must reproduce the same results. An index cannot become a second authorization source or make an unpublished child visible.
|
||||
|
||||
### `list_agents` contract
|
||||
|
||||
`SubagentService.listChildren(parent)` returns every direct continuable child found in the session trace plus non-fatal diagnostics for candidates it could not read or validate. Each child carries its session id, descriptor `label`, and one of two snapshot states:
|
||||
`SubagentService.listChildren(parentSessionId: SessionId)` returns `Promise<SubagentListEntry[]>`, one array preserving the trace's candidate order rather than separate child and diagnostic arrays. `SubagentListEntry` is a closed union discriminated by its readonly `kind`:
|
||||
|
||||
- `running`: the logical session record is live in `ctx.sessions`;
|
||||
- `complete`: the logical session record exists only in persistence and may be resumed by `send_message`.
|
||||
- `kind: 'child'` carries readonly `id: SessionId`, durable `label: string`, and `status: 'running' | 'complete'`;
|
||||
- `kind: 'diagnostic'` carries readonly `id: SessionId` and `reason: 'corrupt' | 'unsupported' | 'unavailable'`.
|
||||
|
||||
These values are neither `AgentStatus` nor the manager's internal Activation state. Children are sorted by `SessionHeader.createdAt` ascending, then child id ascending; diagnostics follow their candidate's same key. The model-facing `list_agents` tool takes no arguments and is a thin adapter in `@deepseek-ai/dsh-tool-subagent-control` that renders the complete sorted children and diagnostics together.
|
||||
A valid descriptor produces one child entry, a per-child inspection failure produces one diagnostic entry, and a candidate without a descriptor produces no entry. Child status `running` means that the logical record is live in `ctx.sessions`; `complete` means that it exists only in persistence. These values are neither `AgentStatus` nor the manager's internal Activation state, and the result does not expose the internal `createdAt` sorting key. Exact Activation states and durable outcomes such as successful completion, failure, cancellation, and stop reason require a separate durable activation record and are outside this proposal.
|
||||
|
||||
Diagnostics use three fixed reasons. Malformed event surfaces, conflicting headers discovered during an exact child load, malformed descriptor content, and multiple descriptor events map to `corrupt`. An unknown descriptor version maps to `unsupported`. `SESSION_QUERY_SESSION_NOT_FOUND`, `SESSION_QUERY_EVENT_NOT_FOUND`, and `SESSION_QUERY_PERSISTENCE_FAILED` from a per-child read map to `unavailable`. This phase boundary is intentional: a persistence outage during the initial trace fails the operation, while the same outage beginning during candidate reads may produce one identical `unavailable` diagnostic per affected child; v1 neither coalesces those diagnostics nor promotes them to a global failure. A missing descriptor is instead a one-shot exclusion without a diagnostic. Configuration/window errors and unrecognized failures are not child diagnostics and propagate as operation failures. Each diagnostic identifies the child id and reason without exposing model-hidden descriptor content; the candidate is omitted while healthy siblings remain visible. Sessions outside the trace's direct descendants are never read and produce no diagnostic.
|
||||
The model-facing `list_agents` tool takes no arguments, derives `parentSessionId` from the current execution Agent, and is a thin adapter in `@deepseek-ai/dsh-tool-subagent-control`. It renders entries in array order as `<id> [<status>] — <label>` for a child and `<id> [diagnostic: <reason>]` for a diagnostic; an empty array renders `(no subagents)`.
|
||||
|
||||
Diagnostics use three fixed reasons. Malformed event surfaces, conflicting headers discovered during an exact child load, a read result whose immutable header differs from the traced candidate or no longer names the requested direct parent, a target that is no longer the located descriptor event, malformed descriptor content, and multiple descriptor events map to `corrupt`. An unknown descriptor version maps to `unsupported`. `SESSION_QUERY_SESSION_NOT_FOUND`, `SESSION_QUERY_EVENT_NOT_FOUND`, and `SESSION_QUERY_PERSISTENCE_FAILED` from a per-child read map to `unavailable`. This phase boundary is intentional: a persistence outage during the initial trace fails the operation, while the same outage beginning during candidate reads may produce one identical `unavailable` diagnostic per affected child; v1 neither coalesces those diagnostics nor promotes them to a global failure. A missing descriptor is instead a non-continuable-child exclusion without a diagnostic. Configuration/window errors and unrecognized failures are not child diagnostics and propagate as operation failures. Each diagnostic identifies the child id and reason without exposing model-hidden descriptor content; the candidate is omitted while healthy siblings remain visible. Sessions outside the trace's direct descendants are never read and produce no diagnostic.
|
||||
|
||||
Diagnostics are transient query results, not session events or catalog state. Deriving a diagnostic performs no additional load beyond the `listEvents()` or conditional `readEvent()` operation whose result produced it.
|
||||
|
||||
The first version has no child deletion operation. If later product behavior deletes child sessions, persistence listing naturally drops a deleted child; any future derived index must remove or tombstone the same entry so `list_agents` cannot retain stale state.
|
||||
|
||||
@@ -60,38 +64,45 @@ The first version has no child deletion operation. If later product behavior del
|
||||
|
||||
**List every traced child session.** `parentSession` proves lineage but does not prove that the child is a continuable subagent: ordinary session forks and one-shot subagents share that header field. Listing must also read and validate the descriptor.
|
||||
|
||||
**Add a `kind` discriminator to `SessionHeader`.** The header would still not carry the reconstruction data required to validate or resume a continuable subagent, so listing would have to read the descriptor anyway. Treating the descriptor as the sole subagent discriminator avoids a second classification source.
|
||||
|
||||
**Use the live Agent registry as the catalog.** Activations are deliberately disposed after settlement, and registry state disappears on restart. It cannot support durable discovery.
|
||||
|
||||
**Use the process-local Activation map as a second catalog.** This exposes manager residency but couples a session-discovery query to materialization and settlement, introduces another ordering clock, and makes the same child change candidate source during its lifetime. The first version lists published logical sessions only and treats `SessionRecord.live` as its snapshot status.
|
||||
|
||||
**Filter by current provider availability.** Provider registration is process-local and may change while the descriptor remains durable. Filtering can hide both a persisted child and a live child even though continuation is provider-independent. Listing therefore establishes durable identity from the descriptor, while `send_message` performs the authoritative delivery-time authority and residency checks.
|
||||
|
||||
**Persist a parent-session catalog event.** Direct-child headers already provide the durable enumeration seed, and the child descriptor is the reconstruction authority. A second parent log duplicates state and creates cross-session ordering and stale-entry behavior without helping by-id resume.
|
||||
|
||||
**Fail the whole listing when one child cannot be loaded.** This makes corruption impossible to overlook, but one damaged sibling removes visibility into every healthy child. Per-child diagnostics preserve discovery while keeping each omission explicit.
|
||||
|
||||
**Return separate child and diagnostic arrays.** Separate arrays introduce two ordering domains or require exposing another sort key to reconstruct candidate order. One discriminated entry array preserves the trace order while keeping child and diagnostic fields type-safe.
|
||||
|
||||
**Add a repair-free descriptor inspection API.** This would make discovery strictly storage-read-only, but expands the persistence seam solely to avoid the interrupted-tail repair that normal session load and eventual resume already require. The first version accepts `load()` semantics and documents the side effect.
|
||||
|
||||
**Paginate or cap the model-facing result.** This bounds one tool result, but makes discovery stateful and can hide older children unless the model follows a cursor. The first version has no arguments and returns the complete stably ordered set; deployments with many durable children accept the corresponding context cost.
|
||||
**Paginate or cap the query now (deferred).** This bounds one result, but makes model discovery stateful and can hide older children unless the model follows a cursor. The first version has no cursor, page arguments, or candidate-limit configuration and returns the complete stably ordered set; a service-level bound remains a later decision if measured scale requires it.
|
||||
|
||||
## Acceptance criteria
|
||||
|
||||
- Enumeration uses `ctx.sessionQuery.traceSession(parent.session.id)`, considers only direct descendants, and does not duplicate corpus merging, lineage reconstruction, or sibling ordering.
|
||||
- Listing loads no Agent, materializes no Activation, and appends no catalog or descriptor event itself. After the initial trace it performs two independent exact session-query reads per candidate; persisted reads may trigger interrupted-tail repair, and compacted and uncompacted logs return the same children.
|
||||
- A valid descriptor includes the delegation's durable `label`; ordinary session forks and one-shot children lack that descriptor and are omitted without a diagnostic. Provider registration does not affect discovery or provider-independent cold resume.
|
||||
- `listChildren(parentSessionId: SessionId)` uses `ctx.sessionQuery.traceSession(parentSessionId)`, accepts a live or persisted target, considers only direct descendants, and does not duplicate corpus merging, lineage reconstruction, or sibling ordering.
|
||||
- Listing loads no Agent, materializes no Activation, and appends no catalog or descriptor event itself. After the initial trace it calls `listEvents()` once per candidate and calls `readEvent()` only for a candidate with exactly one descriptor; persisted reads may trigger interrupted-tail repair, and compacted and uncompacted logs return the same children.
|
||||
- The session header gains no subagent `kind`; a supported valid descriptor is the sole subagent discriminator and includes the delegation's durable `label`. Ordinary session forks and one-shot children lack that descriptor and are omitted without a diagnostic.
|
||||
- Initial creation writes exactly one descriptor event, cold resume writes none, and a candidate with more than one descriptor event is diagnosed as `corrupt`.
|
||||
- `list_agents` takes no arguments and returns every valid direct continuable child with its id, label, and `running` or `complete` snapshot state, plus per-child diagnostics, sorted by `createdAt` ascending and child id ascending.
|
||||
- A live logical session is `running`; a persisted-only logical session is `complete` and remains eligible for a later `send_message`. The result does not consult the process-local Activation map.
|
||||
- `listChildren()` returns one ordered `SubagentListEntry[]`; its closed `kind: 'child' | 'diagnostic'` union carries only the fields defined above. Each valid child or per-child diagnostic occupies its traced candidate's `createdAt`-then-id position, while a missing descriptor produces no entry. Diagnostics are transient and require no query beyond the candidate operation that produced them.
|
||||
- `list_agents` takes no arguments, derives the parent id from the current execution Agent, and renders the complete result without a cursor. Child, diagnostic, and empty results use the fixed text forms defined above.
|
||||
- A live logical session is `running`; a persisted-only logical session is `complete`. The result does not inspect the process-local Activation map or provider availability and leaves delivery-time authority and residency checks to `send_message`.
|
||||
- Parent resume does not activate children. A child is absent until its session is published, and listing may race publication, disposal, or later delivery without weakening `send_message`'s execution-time checks.
|
||||
- `list_agents` uses only `corrupt`, `unsupported`, or `unavailable` diagnostic reasons and never exposes descriptor contents in a diagnostic.
|
||||
- After a successful initial trace, a corrupt, unsupported, disappeared, or unreadable descriptor candidate cannot hide healthy siblings: it is omitted with an id-and-reason diagnostic. Corpus-level persistence, header-consistency, or lineage failure during that initial trace fails the whole call.
|
||||
- Per-child session-query failures map deterministically: invalid surfaces and exact-load source conflicts are `corrupt`; missing sessions or events and persistence failures are `unavailable`; unknown descriptor versions are `unsupported`; and missing descriptors are omitted as one-shot children.
|
||||
- Per-child session-query failures map deterministically: invalid surfaces, exact-load source conflicts, immutable-header or direct-parent mismatches against the trace, and a changed read target are `corrupt`; missing sessions or events and persistence failures are `unavailable`; unknown descriptor versions are `unsupported`; and missing descriptors are omitted as non-continuable children.
|
||||
- The list tool requires `sessionQuery` at plugin load; a direct `listChildren()` call without it fails before enumeration with `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE`, while by-id `send_message` remains usable without that service.
|
||||
- Keyless tests cover fresh and compacted discovery, ordinary fork and one-shot exclusion, live-to-complete transition, unmanaged-live-session snapshots, provider-independent discovery, durable `label` values, stable ordering, restart, direct-child tracing, duplicate descriptor rejection, isolated child diagnostics, phase-dependent persistence failure, load repair, snapshot races, and scan behavior. The model-facing complete-list-plus-diagnostics result has runnable snapshot coverage.
|
||||
- Keyless tests cover fresh and compacted discovery, ordinary fork and one-shot exclusion, live-to-complete transition, unmanaged-live-session snapshots, provider absence without child omission, durable `label` values, stable ordering, restart, direct-child tracing, duplicate descriptor rejection, isolated child diagnostics, phase-dependent persistence failure, load repair, snapshot races, and scan behavior. The model-facing complete-list-plus-diagnostics result has runnable snapshot coverage.
|
||||
|
||||
## Risks
|
||||
|
||||
- Session tracing observes the complete logical corpus, then descriptor validation reads each direct-child log twice. In the persisted-only worst case, work is O(D × C + Σ L_i), not merely O(D), because each exact read rescans persistence and loads and clones the full candidate log. A later derived index must preserve the same authorization, per-child diagnostic, and fallback behavior.
|
||||
- Session tracing observes the complete logical corpus, then descriptor validation reads every direct-child log once and candidates with exactly one descriptor twice. In the persisted-only worst case, work is O(D × C + Σ L_i), not merely O(D), because each exact read rescans persistence and loads and clones the full candidate log. A later derived index must preserve the same authorization, per-child diagnostic, and fallback behavior.
|
||||
- Corpus construction is an all-or-nothing trust boundary: one live/persisted header conflict can fail the initial trace and hide otherwise healthy siblings. Per-child isolation begins only after that trace succeeds.
|
||||
- Session-query reads may repair interrupted child logs and persist synthetic closing events even though listing creates no Agent. This is the existing persistence-load contract, not a hidden catalog write.
|
||||
- The first version has no deletion operation, so persisted children remain listed for as long as their sessions remain in persistence even though live Agent resources remain bounded by resident Activations.
|
||||
- The no-argument tool returns every direct continuable child and diagnostic. Stable ordering makes the result deterministic but does not bound model-context growth; pagination or deletion remains a later product decision.
|
||||
- The query returns every direct continuable child and diagnostic without a service cursor or candidate cap. Stable ordering makes the result deterministic but does not bound model-context growth; service pagination or deletion remains a later product decision.
|
||||
- `running` and `complete` are process-local corpus snapshots, not delivery promises. Another process may activate a persisted child while this process reports it as `complete`; cross-process accuracy requires a shared lease.
|
||||
|
||||
@@ -12,12 +12,12 @@ Status: proposed
|
||||
|
||||
## 提案
|
||||
|
||||
将 parent 到 child 的枚举与 `list_agents` 作为一个基于持久化 child Session 契约、单独评审的功能。`SubagentService.listChildren(parent)` 必须:
|
||||
将 parent 到 child 的枚举与 `list_agents` 作为一个基于持久化 child Session 契约、单独评审的功能。`SubagentService.listChildren(parentSessionId: SessionId)` 必须:
|
||||
|
||||
- 使用 `ctx.sessionQuery.traceSession(parent.session.id)` 获取调用方直接且实时优先的 child 会话;
|
||||
- 使用 `ctx.sessionQuery.traceSession(parentSessionId)` 获取 parent 的直接且实时优先的 child 会话;
|
||||
- 读取并校验每个候选会话的 `subagent/descriptor` 事件,但不激活 child;
|
||||
- 排除一次性 child 且不产生 diagnostic;如果候选在枚举后变得不可用,或其描述符损坏或版本不受支持,则排除该候选并产生对应 child 的 diagnostic;
|
||||
- 只公开描述符带有持久化创建 `label` 的 child;
|
||||
- 默默排除不含描述符的候选;如果候选变得不可用,或其描述符损坏或版本不受支持,则排除该候选并产生对应 child 的 diagnostic;
|
||||
- 公开每个拥有受支持且有效描述符的 child;该描述符必须带有持久化的创建 `label`,而其提供方当前是否已注册不影响公开;
|
||||
- 将存活 child 报告为 `running`,只存在于持久化存储中的 child 报告为 `complete`;
|
||||
- 按 `createdAt` 升序、再按 child id 升序稳定返回所有结果 child。
|
||||
|
||||
@@ -25,30 +25,34 @@ Status: proposed
|
||||
|
||||
### 枚举决策
|
||||
|
||||
第一版消费 `ctx.sessionQuery.traceSession(parent.session.id)`,并且只考虑追踪结果的第一层后代。会话查询已经使用实时优先规则合并 `ctx.sessions` 与 `ctx.sessionPersistence`,保持不可变 header 一致性,根据 `SessionHeader.parentSession` 推导直接 child 谱系,并按 `createdAt` 升序、child id 升序排列 sibling。`listChildren()` 不会重复实现这套语料逻辑,也不会检查继续执行管理器的进程内 Activation map。
|
||||
第一版消费 `ctx.sessionQuery.traceSession(parentSessionId)`,并且只考虑追踪结果的第一层后代。目标可以存活,也可以只存在于持久化存储中;追踪逻辑语料不会加载或恢复 Agent。会话查询已经使用实时优先规则合并 `ctx.sessions` 与 `ctx.sessionPersistence`,保持不可变 header 一致性,根据 `SessionHeader.parentSession` 推导直接 child 谱系,并按 `createdAt` 升序、child id 升序排列 sibling。`listChildren()` 不会重复实现这套语料逻辑,也不会检查继续执行管理器的进程内 Activation map。
|
||||
|
||||
语料构建先于逐 child 描述符检查。构建初始追踪时如果发生持久化列表查询失败、所观测语料中任意位置的存活/持久化 header 冲突或目标谱系无效,整个 `list_agents` 调用都会失败,因为此时不存在可信的候选集。只有初始追踪成功后的失败才会被隔离到单个候选;因此,这项逐 child 契约中的“损坏 child”是指已加载的事件 surface 或描述符数据损坏,而不是语料级 header 冲突。
|
||||
|
||||
会话谱系涵盖的范围比 subagent 身份更广:普通 `ctx.sessions.fork()` 和一次性 subagent 也会创建直接 child。因此,每个候选都必须恰好包含一个有效的 `subagent/descriptor` 事件。激活契约只在初始创建期间写入该事件,从持久化存储恢复时不会追加其他描述符;第二个事件属于损坏,而不是另一次激活的证据。该事件用于区分可继续的后台 subagent 与普通 fork 或一次性 child;其简短创建 `label` 来自委派的 `description`,其余继续执行字段仍是不依赖提供方的冷恢复所使用的重建输入。缺少该事件的候选会被排除,且不产生 diagnostic。
|
||||
会话谱系涵盖的范围比 subagent 身份更广:普通 `ctx.sessions.fork()` 和一次性 subagent 也会创建直接 child。会话 header 不新增 `kind` 判别字段;每个候选必须改为恰好包含一个有效的 `subagent/descriptor` 事件。Activation 契约只在初始创建期间写入该事件,从持久化存储恢复时不会追加其他描述符;第二个事件属于损坏,而不是另一次 Activation 的证据。该事件是已追踪 child 属于可继续后台 subagent 的唯一证据;其简短创建 `label` 来自委派的 `description`,其余继续执行字段仍是不依赖提供方的冷恢复所使用的重建输入。缺少该事件的候选属于普通 fork、一次性 child 或其他不可继续的会话,系统会将其排除且不产生 diagnostic。
|
||||
|
||||
已发布的逻辑记录同时也是状态来源:`SessionRecord.live` 表示 `running`,而 `live: false, persisted: true` 表示 `complete`。`complete` 表示当前没有存活的 Activation,既不表示执行成功,也不表示 child 已永久关闭;`send_message` 仍可物化另一次 Activation。反过来,`running` 只表示会话存活:位于继续执行管理器对应 Activation 之外的存活 Agent 仍会显示为 `running`,但 `send_message` 会拒绝,而不会接管它。child 会话发布前不可见,也不会添加进程内 Activation 条目作为第二个候选来源或状态来源。列表查询是一份快照,可能与发布、dispose 或后续消息发生竞态;`send_message` 仍是消息送达时的权威操作。
|
||||
已发布的逻辑记录同时也是状态来源:`SessionRecord.live` 表示 `running`,而 `live: false, persisted: true` 表示 `complete`。该状态直接来自追踪结果,不会导致额外加载 child 日志。`complete` 表示当前没有存活的 Activation,既不表示执行成功,也不表示 child 已永久关闭;`send_message` 仍可物化另一次 Activation。反过来,`running` 只表示会话存活:位于继续执行管理器对应 Activation 之外的存活 Agent 仍会显示为 `running`,但 `send_message` 会拒绝,而不会接管它。child 会话发布前不可见,也不会添加进程内 Activation 条目作为第二个候选来源或状态来源。列表查询是一份快照,可能与发布、dispose 或后续消息发生竞态;`send_message` 仍是消息送达时的权威操作。
|
||||
|
||||
subagent 服务将 `sessionQuery` 保持为可选依赖,因此没有该服务时仍可执行 start 和 follow-up。其公开的 `listChildren()` 方法在调用时解析这个可选服务;如果服务缺失,该方法会在执行任何工作前抛出 `SubagentError`,并携带稳定错误码 `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE`。`@deepseek-ai/dsh-tool-subagent-control` 导出可分别加载的工具插件:`send_message` 适配器只要求 `subagents`,而 `list_agents` 适配器在加载时同时要求 `subagents` 和 `sessionQuery`。因此,部署可以在不加载会话查询的情况下使用 `send_message`;列表工具会在插件加载时捕获配置错误,而其他直接服务消费方会收到同一项明确的调用时契约。
|
||||
subagent 服务将 `sessionQuery` 保持为可选依赖,因此没有该服务时仍可执行 start 和 follow-up。其公开的 `listChildren(parentSessionId: SessionId)` 方法在调用时解析这个可选服务;如果服务缺失,该方法会在执行任何工作前抛出 `SubagentError`,并携带稳定错误码 `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE`。`@deepseek-ai/dsh-tool-subagent-control` 导出可分别加载的工具插件:`send_message` 适配器只要求 `subagents`,而 `list_agents` 适配器在加载时同时要求 `subagents` 和 `sessionQuery`。因此,部署可以在不加载会话查询的情况下使用 `send_message`;列表工具会在插件加载时捕获配置错误,而其他直接服务消费方会收到同一项明确的调用时契约。
|
||||
|
||||
这条描述符读取路径是正确性基线,并不声称工作量只与直接 child 数量呈线性关系。令 D 为直接 child 候选数量,C 为每次持久化列表查询所扫描的持久化会话数量,L_i 为候选 i 的完整日志大小。一次语料追踪后,每个候选会执行两次精确读取。`listChildren()` 使用 `sessionQuery.listEvents(childId)` 定位唯一的描述符事件,并使用 `sessionQuery.readEvent({ sessionId: childId, seq })` 读取该事件;每项操作都会独立加载逻辑会话。对于只存在于持久化存储中的最坏情况,每次精确读取都会重复执行 `persistence.list()`、加载完整 child 日志并克隆其中的事件,因此忽略常数因子后的工作量为 O(D × C + Σ L_i);存活 child 则会对其完整日志取得两份分离的内存快照。持久化路径可能通过追加合成的结束事件,持久修复中断的 child 日志。第一版接受这些重复读取,将其作为无索引的正确性基线,但部署必须将语料总量和 child 日志大小,而不仅是直接 child 数量,视为容量约束。列表查询不会创建 Agent,也不会自行追加目录或描述符事件,但它并非严格的存储只读操作。对模型隐藏的描述符始终位于对话 surface 之外,并且会在压缩后保留,因此经过压缩和未经压缩的 child 必须枚举出相同结果。
|
||||
这条描述符读取路径是正确性基线,并不声称工作量只与直接 child 数量呈线性关系。令 D 为直接 child 候选数量,C 为每次持久化列表查询所扫描的持久化会话数量,L_i 为候选 i 的完整日志大小。一次语料追踪后,每个候选都会执行 `sessionQuery.listEvents(childId)`。没有描述符的候选会被排除;含有多个描述符的候选会直接产生 diagnostic,无需再次读取;只有恰好含有一个描述符的候选才会通过 `sessionQuery.readEvent({ sessionId: childId, seq })` 再次加载。此次读取返回的不可变会话 header 必须与追踪时观测到的相同,包括直接 parent 关系,并且读取目标仍必须是先前定位的描述符事件;任何不一致均视为该 child 损坏。对于只存在于持久化存储中的最坏情况,每次精确读取都会重复执行 `persistence.list()`、加载完整 child 日志并克隆其中的事件,因此忽略常数因子后的工作量为 O(D × C + Σ L_i);恰好含有一个描述符的候选承担两次这类成本,其他候选只承担一次。存活候选同样会对其完整日志取得一份分离的内存快照;读取其描述符时则会取得两份。持久化路径可能通过追加合成的结束事件,持久修复中断的 child 日志。第一版接受这些重复读取,将其作为无索引的正确性基线,但部署必须将语料总量和 child 日志大小,而不仅是直接 child 数量,视为容量约束。列表查询不会创建 Agent,也不会自行追加目录或描述符事件,但它并非严格的存储只读操作。对模型隐藏的描述符始终位于对话 surface 之外,并且会在压缩后保留,因此经过压缩和未经压缩的 child 必须枚举出相同结果。
|
||||
|
||||
如果实测规模日后需要索引,该索引属于派生状态:会话 header 和 child 描述符仍是权威信息,重建或损坏回退必须复现相同结果。索引不能成为第二个鉴权来源,也不能让尚未发布的 child 变得可见。
|
||||
|
||||
### `list_agents` 契约
|
||||
|
||||
`SubagentService.listChildren(parent)` 返回会话追踪中找到的每个直接可继续 child,以及无法读取或校验候选时产生的非致命 diagnostic。每个 child 都携带自己的 session id、描述符 `label`,以及两种快照状态之一:
|
||||
`SubagentService.listChildren(parentSessionId: SessionId)` 返回 `Promise<SubagentListEntry[]>`,其中的单个数组不会将 child 与 diagnostic 分开,而是保留追踪结果中的候选顺序。`SubagentListEntry` 是一个由只读 `kind` 判别的封闭联合类型:
|
||||
|
||||
- `running`:逻辑会话记录在 `ctx.sessions` 中存活;
|
||||
- `complete`:逻辑会话记录只存在于持久化存储中,并且可以由 `send_message` 恢复。
|
||||
- `kind: 'child'` 携带只读的 `id: SessionId`、持久化 `label: string` 和 `status: 'running' | 'complete'`;
|
||||
- `kind: 'diagnostic'` 携带只读的 `id: SessionId` 和 `reason: 'corrupt' | 'unsupported' | 'unavailable'`。
|
||||
|
||||
这些值既不是 `AgentStatus`,也不是管理器内部的 Activation 状态。child 按 `SessionHeader.createdAt` 升序、再按 child id 升序排序;diagnostic 使用其候选的同一排序键。面向模型的 `list_agents` 工具不接受参数,它是 `@deepseek-ai/dsh-tool-subagent-control` 中的轻量适配器,会一并渲染完整的已排序 child 和 diagnostic。
|
||||
有效描述符产生一个 child 条目,逐 child 检查失败产生一个 diagnostic 条目,缺少描述符的候选不产生条目。child 状态 `running` 表示逻辑记录在 `ctx.sessions` 中存活;`complete` 表示该记录只存在于持久化存储中。这些值既不是 `AgentStatus`,也不是管理器内部的 Activation 状态,结果不公开内部 `createdAt` 排序键。成功完成、失败、取消和停止原因等精确 Activation 状态与持久化结果需要单独的持久化激活记录,不在本提案范围内。
|
||||
|
||||
diagnostic 使用三种固定原因。格式错误的事件 surface、精确加载 child 时发现的 header 冲突、格式错误的描述符内容和多个描述符事件映射为 `corrupt`。未知描述符版本映射为 `unsupported`。逐 child 读取产生的 `SESSION_QUERY_SESSION_NOT_FOUND`、`SESSION_QUERY_EVENT_NOT_FOUND` 和 `SESSION_QUERY_PERSISTENCE_FAILED` 映射为 `unavailable`。这项阶段边界是有意为之:初始追踪期间发生持久化故障会让操作失败,而同一故障如果始于候选读取期间,可能会让每个受影响的 child 分别产生一条相同的 `unavailable` diagnostic;第一版既不合并这些 diagnostic,也不会把它们提升为全局失败。缺少描述符则视为一次性 child,直接排除且不产生 diagnostic。配置错误、窗口错误和未识别的失败不属于 child diagnostic,会作为操作失败继续向上传播。每条 diagnostic 都标识 child id 及原因,不暴露对模型隐藏的描述符内容;系统会排除该候选,而其他健康的 sibling 仍然可见。系统绝不会读取不属于追踪结果直接后代的会话,也不会为它们产生 diagnostic。
|
||||
面向模型的 `list_agents` 工具不接受参数,从当前正在执行的 Agent 推导 `parentSessionId`,并作为 `@deepseek-ai/dsh-tool-subagent-control` 中的轻量适配器。它按数组顺序将 child 渲染为 `<id> [<status>] — <label>`,将 diagnostic 渲染为 `<id> [diagnostic: <reason>]`;空数组渲染为 `(no subagents)`。
|
||||
|
||||
diagnostic 使用三种固定原因。格式错误的事件 surface、精确加载 child 时发现的 header 冲突、读取结果中的不可变 header 与追踪到的候选不一致或不再指向请求的直接 parent、读取目标不再是先前定位的描述符事件、格式错误的描述符内容和多个描述符事件映射为 `corrupt`。未知描述符版本映射为 `unsupported`。逐 child 读取产生的 `SESSION_QUERY_SESSION_NOT_FOUND`、`SESSION_QUERY_EVENT_NOT_FOUND` 和 `SESSION_QUERY_PERSISTENCE_FAILED` 映射为 `unavailable`。这项阶段边界是有意为之:初始追踪期间发生持久化故障会让操作失败,而同一故障如果始于候选读取期间,可能会让每个受影响的 child 分别产生一条相同的 `unavailable` diagnostic;第一版既不合并这些 diagnostic,也不会把它们提升为全局失败。缺少描述符则作为不可继续 child 排除,且不产生 diagnostic。配置错误、窗口错误和未识别的失败不属于 child diagnostic,会作为操作失败继续向上传播。每条 diagnostic 都标识 child id 及原因,不暴露对模型隐藏的描述符内容;系统会排除该候选,而其他健康的 sibling 仍然可见。系统绝不会读取不属于追踪结果直接后代的会话,也不会为它们产生 diagnostic。
|
||||
|
||||
diagnostic 是瞬时查询结果,不属于会话事件或目录状态。推导 diagnostic 时,除了产生该结果的 `listEvents()` 或条件性 `readEvent()` 操作外,不会执行额外加载。
|
||||
|
||||
第一版不提供 child 删除操作。如果后续产品行为会删除 child 会话,持久化列表会自然移除已删除的 child;任何未来的派生索引都必须移除或 tombstone 同一条目,避免 `list_agents` 保留陈旧状态。
|
||||
|
||||
@@ -60,38 +64,45 @@ diagnostic 使用三种固定原因。格式错误的事件 surface、精确加
|
||||
|
||||
**列出每个已追踪的 child 会话。** `parentSession` 能证明谱系,却不能证明 child 是可继续的 subagent:普通会话 fork 和一次性 subagent 也使用这个 header 字段。列表查询还必须读取并校验描述符。
|
||||
|
||||
**为 `SessionHeader` 添加 `kind` 判别字段。** header 仍不会携带校验或恢复可继续 subagent 所需的重建数据,因此列表查询无论如何都必须读取描述符。将描述符作为唯一的 subagent 判别信息,可避免引入第二个分类来源。
|
||||
|
||||
**使用存活的 Agent 注册表作为目录。** 系统会在 Activation 结算后有意 dispose 它,而且注册表状态会在重启时消失,因此无法支持持久化发现。
|
||||
|
||||
**使用进程内 Activation map 作为第二个目录。** 这种做法能公开管理器驻留状态,却会让会话发现查询与物化及结算耦合,引入另一套排序时钟,并让同一个 child 在其生命周期内改变候选来源。第一版只列出已经发布的逻辑会话,并将 `SessionRecord.live` 视为其快照状态。
|
||||
|
||||
**按当前提供方可用性过滤。** 提供方注册状态属于进程本地状态,即使描述符仍然持久存在,该状态也可能发生变化。即使继续执行不依赖提供方,过滤仍可能隐藏持久化或存活 child。因此,列表查询根据描述符确立持久化身份,而 `send_message` 在消息送达时执行权威的鉴权与驻留状态检查。
|
||||
|
||||
**持久化 parent 会话目录事件。** 直接 child header 已经提供持久化枚举种子,child 描述符则是重建的权威信息。第二份 parent 日志会重复状态,并造成跨会话顺序和陈旧条目行为,却无助于按 id 恢复。
|
||||
|
||||
**某个 child 无法加载时让整次列表查询失败。** 这种做法不会让损坏问题被忽略,但一个损坏的 sibling 会让每个健康 child 都不再可见。逐 child diagnostic 在保持每次排除明确可见的同时,也保留了发现能力。
|
||||
|
||||
**分别返回 child 和 diagnostic 数组。** 分离的数组会引入两个排序域,或者要求公开另一个排序键才能重建候选顺序。一个带判别字段的条目数组既能保留追踪顺序,也能保证 child 与 diagnostic 字段的类型安全。
|
||||
|
||||
**添加不会触发修复的描述符检查 API。** 这能使发现严格保持存储只读,但仅为避免中断尾部修复就扩展持久化 seam,而普通会话加载和最终恢复原本就需要执行该修复。第一版接受 `load()` 的语义,并记录这项副作用。
|
||||
|
||||
**对面向模型的结果分页或设置上限。** 这可以限制一次工具结果的大小,但会使发现成为有状态操作,而且除非模型继续跟随 cursor,否则可能隐藏更早的 child。第一版不接受参数,并返回经稳定排序的完整集合;拥有大量持久化 child 的部署需要接受相应的上下文成本。
|
||||
**立即为查询分页或设置上限(暂缓)。** 这可以限制一次结果的大小,但会使模型发现成为有状态操作,而且除非模型继续跟随 cursor,否则可能隐藏更早的 child。第一版没有 cursor、分页参数或候选数量上限配置,而是返回经稳定排序的完整集合;如果实测规模需要限制,服务级限制仍留待后续决策。
|
||||
|
||||
## 验收标准
|
||||
|
||||
- 枚举使用 `ctx.sessionQuery.traceSession(parent.session.id)`,只考虑直接后代,并且不重复实现语料合并、谱系重建或 sibling 排序。
|
||||
- 列表查询不会加载 Agent、物化 Activation,也不会自行追加目录或描述符事件。初始追踪完成后,它会对每个候选执行两次相互独立的会话查询精确读取;持久化读取可能触发中断尾部修复,且经过压缩和未经压缩的日志会返回相同的 child。
|
||||
- 有效描述符包含委派的持久化 `label`;普通会话 fork 和一次性 child 缺少该描述符,因此会被排除且不产生 diagnostic。提供方注册状态不影响发现,也不影响不依赖提供方的冷恢复。
|
||||
- `listChildren(parentSessionId: SessionId)` 使用 `ctx.sessionQuery.traceSession(parentSessionId)`,接受存活或只存在于持久化存储中的目标,只考虑直接后代,并且不重复实现语料合并、谱系重建或 sibling 排序。
|
||||
- 列表查询不会加载 Agent、物化 Activation,也不会自行追加目录或描述符事件。初始追踪完成后,它会对每个候选调用一次 `listEvents()`,且只对恰好含有一个描述符的候选调用 `readEvent()`;持久化读取可能触发中断尾部修复,且经过压缩和未经压缩的日志会返回相同的 child。
|
||||
- 会话 header 不新增 subagent `kind`;受支持且有效的描述符是唯一的 subagent 判别信息,并包含委派的持久化 `label`。普通会话 fork 和一次性 child 缺少该描述符,因此会被排除且不产生 diagnostic。
|
||||
- 初始创建恰好写入一个描述符事件,从持久化存储恢复时不写入任何描述符;如果候选包含多个描述符事件,则将其诊断为 `corrupt`。
|
||||
- `list_agents` 不接受参数,返回每个有效的直接可继续 child 及其 id、label 和 `running` 或 `complete` 快照状态,并返回逐 child diagnostic;结果按 `createdAt` 升序、child id 升序排序。
|
||||
- 存活的逻辑会话为 `running`;只存在于持久化存储中的逻辑会话为 `complete`,并且仍可在之后通过 `send_message` 恢复。结果不查询进程内 Activation map。
|
||||
- `listChildren()` 返回一个有序的 `SubagentListEntry[]`;其封闭的 `kind: 'child' | 'diagnostic'` 联合类型只携带上文定义的字段。每个有效 child 或逐 child diagnostic 占据其追踪候选按 `createdAt`、再按 id 排列的位置,缺少描述符则不产生条目。diagnostic 是瞬时结果,除了产生该结果的候选操作外不需要其他查询。
|
||||
- `list_agents` 不接受参数,从当前正在执行的 Agent 推导 parent id,并在没有 cursor 的情况下渲染完整结果。child、diagnostic 和空结果使用上文定义的固定文本形式。
|
||||
- 存活的逻辑会话为 `running`;只存在于持久化存储中的逻辑会话为 `complete`。结果不检查进程内 Activation map 或提供方可用性,并将消息送达时的鉴权与驻留状态检查留给 `send_message`。
|
||||
- 恢复 parent 不会激活 child。child 会话发布前不会出现,列表查询可能与发布、dispose 或后续消息送达发生竞态,但不会削弱 `send_message` 在执行时进行的检查。
|
||||
- `list_agents` 只使用 `corrupt`、`unsupported` 或 `unavailable` 作为 diagnostic 原因,且绝不在 diagnostic 中暴露描述符内容。
|
||||
- 初始追踪成功后,描述符损坏、不受支持、已消失或无法读取的候选不能隐藏健康的 sibling:系统会排除该候选,并生成一条含 id 和原因的 diagnostic。初始追踪期间发生的语料级持久化、header 一致性或谱系失败会让整次调用失败。
|
||||
- 逐 child 会话查询失败采用固定映射:无效 surface 和精确加载时的来源冲突映射为 `corrupt`;会话或事件缺失以及持久化失败映射为 `unavailable`;未知描述符版本映射为 `unsupported`;缺少描述符则作为一次性 child 排除。
|
||||
- 逐 child 会话查询失败采用固定映射:无效 surface、精确加载时的来源冲突、相对于追踪结果的不可变 header 或直接 parent 不匹配,以及已变化的读取目标映射为 `corrupt`;会话或事件缺失以及持久化失败映射为 `unavailable`;未知描述符版本映射为 `unsupported`;缺少描述符则作为不可继续 child 排除。
|
||||
- 列表工具在插件加载时要求 `sessionQuery`;直接调用 `listChildren()` 时如果缺少该服务,则会在枚举前以 `SUBAGENT_CONTROL_SESSION_QUERY_UNAVAILABLE` 失败,而按 id 的 `send_message` 在没有该服务时仍可使用。
|
||||
- 无密钥测试覆盖压缩前后的发现、排除普通 fork 和一次性 child、从存活到 complete 的转换、未受管理的存活会话快照、不依赖提供方的发现、持久化 `label` 值、稳定排序、重启、直接 child 追踪、重复描述符拒绝、单个 child diagnostic 隔离、依阶段而异的持久化失败、加载修复、快照竞态和扫描行为。面向模型的完整列表加 diagnostic 结果具有可运行的快照覆盖。
|
||||
- 无密钥测试覆盖压缩前后的发现、排除普通 fork 和一次性 child、从存活到 complete 的转换、未受管理的存活会话快照、提供方缺失时不排除 child、持久化 `label` 值、稳定排序、重启、直接 child 追踪、重复描述符拒绝、单个 child diagnostic 隔离、依阶段而异的持久化失败、加载修复、快照竞态和扫描行为。面向模型的完整列表加 diagnostic 结果具有可运行的快照覆盖。
|
||||
|
||||
## 风险
|
||||
|
||||
- 会话追踪会观察完整的逻辑语料,随后描述符校验会读取每个直接 child 的日志两次。对于只存在于持久化存储中的最坏情况,工作量为 O(D × C + Σ L_i),而不只是 O(D),因为每次精确读取都会重新扫描持久化存储,并加载和克隆候选的完整日志。后续的派生索引必须保持相同的鉴权、逐 child diagnostic 和回退行为。
|
||||
- 会话追踪会观察完整的逻辑语料,随后描述符校验会读取每个直接 child 的日志一次,并对恰好含有一个描述符的候选读取两次。对于只存在于持久化存储中的最坏情况,工作量为 O(D × C + Σ L_i),而不只是 O(D),因为每次精确读取都会重新扫描持久化存储,并加载和克隆候选的完整日志。后续的派生索引必须保持相同的鉴权、逐 child diagnostic 和回退行为。
|
||||
- 语料构建是一个全有或全无的信任边界:一处存活/持久化 header 冲突就可能导致初始追踪失败,并隐藏原本健康的 sibling。只有初始追踪成功后,逐 child 隔离才会生效。
|
||||
- 会话查询读取可能修复中断的 child 日志并持久化合成的结束事件,即使列表查询不创建 Agent。这是现有的持久化加载契约,而非隐藏的目录写入。
|
||||
- 第一版没有删除操作,因此只要 child 会话仍保留在持久化存储中,它们就会继续出现在列表里,但存活 Agent 资源仍由驻留 Activation 数量限制。
|
||||
- 无参数工具会返回每个直接可继续 child 和 diagnostic。稳定排序可使结果确定,但不会限制模型上下文的增长;分页或删除仍是后续的产品决策。
|
||||
- 查询会返回每个直接可继续 child 和 diagnostic,不设服务 cursor 或候选数量上限。稳定排序可使结果确定,但不会限制模型上下文的增长;服务分页或删除仍是后续的产品决策。
|
||||
- `running` 和 `complete` 是进程内语料快照,而非消息送达承诺。另一个进程可能在当前进程将某个持久化 child 报告为 `complete` 时激活它;跨进程准确性需要共享租约。
|
||||
|
||||
Reference in New Issue
Block a user