docs(subagent): require runtime-wide external run identities
SubagentRun.id previously promised uniqueness only within one parent, but the unified manager indexes every external activation by that id in one runtime-wide resident map. Publication also checks the live Agent and Session registries. Two compliant-looking provider results could therefore collide even when they belonged to different parents. Document the existing shared Session id namespace across all parents, providers, and local Sessions. Update the package reference, bilingual type-equivalent declaration, and the current upgrade guide so custom providers know to replace parent-local counters with fresh execution ids. The shipped ACP, DSH SDK, Codex, and Claude Code providers already mint UUIDs; no runtime mapping or additional retained identity state is needed. Add a cross-parent collision test through startActivation. It proves that publication rejects the duplicate, disposes only the new external handle, leaves the second parent catalog empty, and preserves the first catalog entry and execution result through normal cleanup. Validation: all seven external-lifecycle tests pass, as do scoped bilingual pairing, verify-upgrade-guides, doc-sync, and full lint. Cordis catalog and inspection-catalog regeneration produces no tracked artifact changes.
This commit is contained in:
1 parent
4b3c174615
commit
b49deaa8ee
10 files changed
+41
-7
No files matched your search
@@ -291,7 +291,7 @@ interface SubagentStopReasonMap {
|
||||
* through result.
|
||||
*/
|
||||
interface SubagentRun {
|
||||
/** Provider-minted run id, unique in the parent namespace. */
|
||||
/** Provider-minted id, unique across all parents, providers, and local Sessions in this runtime. */
|
||||
readonly id: SessionId
|
||||
/**
|
||||
* Resolves with the child's terminal {@link SubagentResult} when the run
|
||||
|
||||
@@ -291,7 +291,7 @@ interface SubagentStopReasonMap {
|
||||
* through result.
|
||||
*/
|
||||
interface SubagentRun {
|
||||
/** Provider-minted run id, unique in the parent namespace. */
|
||||
/** Provider-minted id, unique across all parents, providers, and local Sessions in this runtime. */
|
||||
readonly id: SessionId
|
||||
/**
|
||||
* Resolves with the child's terminal {@link SubagentResult} when the run
|
||||
|
||||
@@ -12,5 +12,5 @@
|
||||
en: 17add29d9f80674e
|
||||
zh: 4d4050e99f019cca
|
||||
/migrate-delegation-to-managed-subagent-activations/migration:
|
||||
en: a5c2da78a56490af
|
||||
zh: d98609d020e7a40f
|
||||
en: 53bcb0305da1ac42
|
||||
zh: 110419459ba6e01d
|
||||
@@ -52,4 +52,6 @@ The published `@deepseek-ai/dsh-subagent-in-process-driver` package is removed.
|
||||
|
||||
4. Remove the driver dependency and imports, including `startInProcessRun`. Mount the existing Spawn or Fork provider and call the service as above. Custom local providers implement `prepareContinuable()` using the [provider API](../../../../docs/subsystems/subagent.md); there is no replacement standalone driver.
|
||||
|
||||
External providers keep `start()`, but each returned `SubagentRun.id` must be unique across parents, providers, and local Sessions in the runtime. Generate a fresh id for each execution instead of using a parent-local counter.
|
||||
|
||||
5. Confirm a delegation returns the activation result variant, its completion notice contains the final answer, and caller delivery returns the result without adding that notice. Confirm local children remain listed after completion and external children cannot be continued.
|
||||
@@ -52,4 +52,6 @@ description: "受管理的子 agent activation 替代前台与 Job 后台委派
|
||||
|
||||
4. 删除驱动包依赖及其导入,包括 `startInProcessRun`。挂载现有的 Spawn 或 Fork 提供方,并按上例调用服务。自定义本地提供方按[提供方 API](../../../../docs/subsystems/subagent.zh.md)实现 `prepareContinuable()`;没有替代的独立驱动包。
|
||||
|
||||
外部提供方保留 `start()`,但每次返回的 `SubagentRun.id` 必须在运行时内跨父级、提供方和本地 Session 保持唯一。为每次执行生成新的 id,替代父级独立计数器。
|
||||
|
||||
5. 确认委派返回 activation 结果变体,完成通知包含最终答案,且调用方投递返回结果而不添加该通知。确认本地子级在完成后仍可列出,外部子级不支持继续执行。
|
||||
@@ -45,8 +45,8 @@
|
||||
en: 410b6215e3a29221
|
||||
zh: d17d6ea102fe8f87
|
||||
/deepseek-ai-dsh-subagent/understand-the-implementation/provider-preparation-and-structured-output:
|
||||
en: ce09e1ec9a3a64f7
|
||||
zh: cacec4415df89305
|
||||
en: 78773aa77acfabd4
|
||||
zh: 3849dc8f17d9e37f
|
||||
/deepseek-ai-dsh-subagent/understand-the-implementation/activation-settlement:
|
||||
en: 964a7caf81c0ed20
|
||||
zh: 094cb601091410f5
|
||||
|
||||
@@ -106,6 +106,8 @@ This section explains how the service is built and where the observable behavior
|
||||
|
||||
The service validates requested capabilities before creation. Local providers supply only `prepareContinuable()`: spawn returns fresh state and fork returns a completed-turn seed. The manager captures the parent model settings, delegated permissions, and composition before creating the child. External providers retain `start()` as a transport adapter and retain their own permission systems. The common manager owns their published execution. Structured tools, instructions, validation, and terminal guards belong to one local activation; they are not persisted as resume configuration. The child can submit its structured result after owned descendants release. A captured result closes further input to that activation, while later cold resume has no structured schema.
|
||||
|
||||
External providers mint a fresh `SubagentRun.id` for each execution in the runtime's shared Session id namespace, across all parents and providers. A collision with a resident Agent, Session, or activation rejects startup with `DUPLICATE_CHILD` and disposes the new external handle without replacing the existing execution.
|
||||
|
||||
### Activation settlement
|
||||
|
||||
The manager reserves child identity and capacity, materializes a local Agent or external execution, and accepts the initial task. Local admission closes after Agent activity, pending input, and owned descendants finish, with activity revalidated after the final Session flush. Both local and external activations deliver their result after handle cleanup, capacity release, and settlement notifications. Parent waits observe the same completion promise even when execution fails. Closing admission and disposing the handle prevent late work from entering a released Agent. Parent delivery emits its notice after settlement; caller delivery leaves collection to the awaiting workflow. Headless hosts repeat `agent.whenIdle()` and `waitForChildren(agent)` until no child work remains and the parent stays idle with an unchanged Session sequence across the child check, so completion notices can produce the final parent answer.
|
||||
|
||||
@@ -106,6 +106,8 @@ kind: "package-reference"
|
||||
|
||||
服务在创建前校验请求能力。本地提供方只提供 `prepareContinuable()`:spawn 返回全新状态,fork 返回已完成轮次的初始内容。管理器在创建子级前捕获父级模型设置、委派权限和组合。外部提供方保留 `start()` 作为传输适配器,并保留各自的权限系统;统一管理器拥有已发布的执行。结构化工具、指令、校验与终止保护仅属于一次本地 activation,不作为恢复配置持久化。子代理只能在所属后代释放后提交结构化结果。结果捕获后,该 activation 拒绝继续输入;后续冷恢复不带结构化 schema。
|
||||
|
||||
外部提供方为每次执行生成新的 `SubagentRun.id`,在运行时共享的 Session id 命名空间中跨所有父级和提供方保持唯一。与驻留 Agent、Session 或 activation 冲突时,启动以 `DUPLICATE_CHILD` 拒绝,并释放新的外部句柄,不替换已有执行。
|
||||
|
||||
### Activation 结算
|
||||
|
||||
管理器预留子级身份与容量,创建本地 Agent 或外部执行,并接受初始任务。本地 Agent 活动、待处理输入及所拥有的后代均结束后,管理器在最终 Session flush 后重新验证活动状态并关闭准入。本地与外部 activation 都在句柄清理、容量归还及结算通知结束后交付结果。父级通过同一个完成 promise 等待,执行失败也不会使等待提前结束。关闭准入并释放句柄可防止迟到工作进入已释放的 Agent。父级投递在结算后发送通知;调用方投递由等待中的工作流收集。Headless 宿主交替等待 `agent.whenIdle()` 与 `waitForChildren(agent)`,直至没有子级工作,且父级在检查子级期间保持空闲、Session 序号不变,让完成通知可驱动父级生成最终答案。
|
||||
|
||||
@@ -286,7 +286,7 @@ export interface SubagentResult {
|
||||
* through result.
|
||||
*/
|
||||
export interface SubagentRun {
|
||||
/** Provider-minted run id, unique in the parent namespace. */
|
||||
/** Provider-minted id, unique across all parents, providers, and local Sessions in this runtime. */
|
||||
readonly id: SessionId
|
||||
/**
|
||||
* Resolves with the child's terminal {@link SubagentResult} when the run
|
||||
|
||||
@@ -90,6 +90,32 @@ describe('external activation ownership', () => {
|
||||
expect(fixture.ctx.sessionProjections.snapshot(fixture.parent.session, ['subagentCatalog']).values.subagentCatalog).toEqual([])
|
||||
})
|
||||
|
||||
it('rejects a repeated external id across parents without disturbing its first execution', async () => {
|
||||
const firstResult = Promise.withResolvers<SubagentResult>()
|
||||
const firstDispose = vi.fn(() => Promise.resolve())
|
||||
const duplicateDispose = vi.fn(() => Promise.resolve())
|
||||
let starts = 0
|
||||
const fixture = await setup(() => Promise.resolve(starts++ === 0
|
||||
? run('shared-external-id', firstResult.promise, firstDispose)
|
||||
: run('shared-external-id', Promise.resolve(complete), duplicateDispose)), 'parent')
|
||||
const otherParent = await externalTestParent(fixture.ctx)
|
||||
const first = await fixture.start()
|
||||
try {
|
||||
await expect(fixture.start(otherParent)).rejects.toMatchObject({ code: 'DUPLICATE_CHILD' })
|
||||
expect(duplicateDispose).toHaveBeenCalledOnce()
|
||||
expect(firstDispose).not.toHaveBeenCalled()
|
||||
expect(fixture.ctx.sessionProjections.snapshot(otherParent.session, ['subagentCatalog']).values.subagentCatalog).toEqual([])
|
||||
expect(fixture.ctx.sessionProjections.snapshot(fixture.parent.session, ['subagentCatalog']).values.subagentCatalog)
|
||||
.toMatchObject([{ id: first.childId, mode: 'external' }])
|
||||
await expect(fixture.ctx.subagents.waitForChildren(otherParent)).resolves.toBe(false)
|
||||
} finally {
|
||||
firstResult.resolve(complete)
|
||||
await first.dispose()
|
||||
}
|
||||
await expect(first.result).resolves.toEqual(complete)
|
||||
expect(firstDispose).toHaveBeenCalledOnce()
|
||||
})
|
||||
|
||||
it('rejects messages to a resident external activation without affecting its result', async () => {
|
||||
const result = Promise.withResolvers<SubagentResult>()
|
||||
const fixture = await setup(() => Promise.resolve(run('no-continuation', result.promise)))
|
||||
|
||||
Reference in new issue
Block a user