From 1f4cfad6562914cbdb11107bc8f7e8174a8a9906 Mon Sep 17 00:00:00 2001 From: liukx0205 Date: Wed, 4 Nov 2026 20:48:40 +0800 Subject: [PATCH] fix(agent): close input admission and drain work after cancellation failures --- docs/architecture.i18n.yaml | 4 +- docs/architecture.md | 2 +- docs/architecture.zh.md | 2 +- docs/event-producer-consumer.md | 22 +- docs/event-producer-consumer.zh.md | 22 +- docs/subsystems/core.md | 7 +- docs/subsystems/core.zh.md | 7 +- packages/core/agent-loop/README.i18n.yaml | 4 +- packages/core/agent-loop/README.md | 2 +- packages/core/agent-loop/README.zh.md | 2 +- packages/core/agent-loop/src/agent.ts | 24 +- packages/core/agent-loop/src/index.ts | 9 +- packages/core/agent-loop/tests/cancel.spec.ts | 12 +- .../tests/fixtures/serial-created.mjs | 37 +- .../agent-loop/tests/input-lifecycle.spec.ts | 151 ++++++ .../agent-loop/tests/request-freeze.spec.ts | 6 +- packages/core/agent/README.i18n.yaml | 4 +- packages/core/agent/README.md | 2 +- packages/core/agent/README.zh.md | 2 +- packages/core/agent/src/closed-error.ts | 15 + packages/core/agent/src/index.ts | 1 + packages/core/agent/src/runtime-types.ts | 7 +- packages/subagent/subagent/README.i18n.yaml | 4 +- packages/subagent/subagent/README.md | 2 +- packages/subagent/subagent/README.zh.md | 2 +- packages/subagent/subagent/src/manager.ts | 4 +- scripts/smoke-python-runtime.py | 1 + .../advanced/result.json | 460 ++++++++++++++++-- .../advanced/session.1.v4.jsonl | 24 +- .../advanced/session.2.v4.jsonl | 24 +- snapshots/sdk/serial-created/session.v4.jsonl | 21 +- 31 files changed, 762 insertions(+), 124 deletions(-) create mode 100644 packages/core/agent-loop/tests/input-lifecycle.spec.ts create mode 100644 packages/core/agent/src/closed-error.ts diff --git a/docs/architecture.i18n.yaml b/docs/architecture.i18n.yaml index 19453e15a9..83a62a5480 100644 --- a/docs/architecture.i18n.yaml +++ b/docs/architecture.i18n.yaml @@ -21,8 +21,8 @@ en: 20ef882e44b06a10 zh: c10c3934ed5570e9 /deepseek-harness-architecture/events: - en: ddebac7cfd41ec73 - zh: 51e6d0866a9e7839 + en: 9a7297518f6b7bdf + zh: 883364ea3f1ac82b /deepseek-harness-architecture/turn-flow: en: 1a79a70b62c04cdb zh: c86d765e21430405 diff --git a/docs/architecture.md b/docs/architecture.md index 446c50e5bd..95eeab1b42 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -74,7 +74,7 @@ Events provide extension points in three domains. Subagent Activations own runtime tree membership. -AgentLoop rolls back failed initialization and awaits serial `agent/created` before queued work; unload retains resources through teardown ([ordering](../packages/core/agent-loop/README.md#understand-the-implementation)). +AgentLoop rolls back failed initialization and awaits serial `agent/created` before queued work; shutdown closes input admission, cancels and waits for admitted work despite queue-cleanup failures, then releases resources ([ordering](../packages/core/agent-loop/README.md#understand-the-implementation)). [Event producers and consumers](event-producer-consumer.md). diff --git a/docs/architecture.zh.md b/docs/architecture.zh.md index 275e1c863d..936a6493ff 100644 --- a/docs/architecture.zh.md +++ b/docs/architecture.zh.md @@ -76,7 +76,7 @@ Electron 使用 Electron Node 模式启动私有 Desktop Host。Host 调用共 Subagent Activation 拥有运行时树成员关系。 -AgentLoop 回滚失败的初始化,并在启动已排队工作前等待串行 `agent/created`;卸载在 teardown 期间保留所需资源([顺序](../packages/core/agent-loop/README.zh.md#understand-the-implementation))。 +AgentLoop 回滚失败的初始化,并在启动已排队工作前等待串行 `agent/created`;关闭先停止接收输入,在队列清理失败时仍取消并等待已接纳工作,然后释放资源([顺序](../packages/core/agent-loop/README.zh.md#understand-the-implementation))。 [事件生产者与消费者](event-producer-consumer.zh.md)。 diff --git a/docs/event-producer-consumer.md b/docs/event-producer-consumer.md index 7e9172cc37..a32683e672 100644 --- a/docs/event-producer-consumer.md +++ b/docs/event-producer-consumer.md @@ -12,17 +12,17 @@ Application listeners are outside this package matrix. Desktop Host subscribes t | --- | --- | --- | --- | --- | | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:198`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:66`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | [`cua`](../packages/experimental/cua), `remotes` | -| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:399`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | -| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | [`agent-team`](../packages/team/agent-team), `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), `liveshare`, [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller`, [`subagent`](../packages/subagent/subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | -| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:429`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | -| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:328`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:336`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:317`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `client-ui-forge`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:350`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, `client-ui-forge`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), `liveshare`, [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`subagent`](../packages/subagent/subagent), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | -| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | -| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | -| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:404`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | +| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:295`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | [`agent-team`](../packages/team/agent-team), `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), `liveshare`, [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:304`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller`, [`subagent`](../packages/subagent/subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | +| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:434`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | +| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:333`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | +| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:341`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | +| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:322`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `client-ui-forge`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:355`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, `client-ui-forge`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), `liveshare`, [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`subagent`](../packages/subagent/subagent), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | +| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:394`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | +| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:314`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | +| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:422`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:705`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:685`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:712`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | diff --git a/docs/event-producer-consumer.zh.md b/docs/event-producer-consumer.zh.md index 326beb8853..ff27d132ae 100644 --- a/docs/event-producer-consumer.zh.md +++ b/docs/event-producer-consumer.zh.md @@ -14,17 +14,17 @@ | --- | --- | --- | --- | --- | | `agent-loop/config-start-failed` | `emit` | [`packages/core/agent-loop/src/index.ts:198`](../packages/core/agent-loop/src/index.ts) | [`agent-loop`](../packages/core/agent-loop) (`events.dispatch`) | - | | `agent-preset/selected` | `emit` | [`packages/preset/agent-preset-registry/src/types.ts:66`](../packages/preset/agent-preset-registry/src/types.ts) | [`agent-preset-registry`](../packages/preset/agent-preset-registry) (`emit`) | [`cua`](../packages/experimental/cua), `remotes` | -| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:399`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | -| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:290`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | [`agent-team`](../packages/team/agent-team), `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), `liveshare`, [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, [`tool-subagent`](../packages/subagent/tool-subagent) | -| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:299`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller`, [`subagent`](../packages/subagent/subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | -| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:429`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | -| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:328`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:336`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | -| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:317`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `client-ui-forge`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | -| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:350`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, `client-ui-forge`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), `liveshare`, [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`subagent`](../packages/subagent/subagent), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | -| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:389`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | -| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:309`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | -| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:417`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | +| `agent/assistant-stream` | `emit` | [`packages/core/agent/src/runtime-types.ts:404`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`headless`](../packages/bundle/headless), `session-controller` | +| `agent/created` | `serial` | [`packages/core/agent/src/runtime-types.ts:295`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`serial`) | [`agent-team`](../packages/team/agent-team), `browser-use-runtime`, `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`cua`](../packages/experimental/cua), [`file-reference-local`](../packages/context/file-reference-local), [`goal`](../packages/goal/goal), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), `liveshare`, [`loader-smoke`](../packages/test-support/loader-smoke), [`permission-presets`](../packages/interaction/permission-presets), `session-controller`, [`tool-subagent`](../packages/subagent/tool-subagent) | +| `agent/disposed` | `emit` | [`packages/core/agent/src/runtime-types.ts:304`](../packages/core/agent/src/runtime-types.ts) | [`agent`](../packages/core/agent) (`events.dispatch`) | [`action-cards`](../packages/experimental/action-cards), [`agent-loop`](../packages/core/agent-loop), `claude-code-mods`, [`creation-recommendations`](../packages/experimental/creation-recommendations), [`file-reference-local`](../packages/context/file-reference-local), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller`, [`subagent`](../packages/subagent/subagent), [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | +| `agent/error` | `emit` | [`packages/core/agent/src/runtime-types.ts:434`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`deepseek-account`](../packages/credentials/deepseek-account), [`goal-round-driver`](../packages/goal/goal-round-driver), [`headless`](../packages/bundle/headless), `server`, `session-controller`, [`session-telemetry`](../packages/session/session-telemetry), [`subagent`](../packages/subagent/subagent) | +| `agent/inbox/claimed` | `emit` | [`packages/core/agent/src/runtime-types.ts:333`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`user-questions`](../packages/interaction/user-questions) | +| `agent/inbox/discarded` | `emit` | [`packages/core/agent/src/runtime-types.ts:341`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`user-questions`](../packages/interaction/user-questions) | +| `agent/inbox/inserted` | `emit` | [`packages/core/agent/src/runtime-types.ts:322`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | `client-ui-forge`, [`goal-round-driver`](../packages/goal/goal-round-driver), [`subagent`](../packages/subagent/subagent), [`tool-jobs`](../packages/jobs/tool-jobs), [`tool-subagent-control`](../packages/subagent/tool-subagent-control) | +| `agent/pre-step` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:355`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`action-cards`](../packages/experimental/action-cards), [`agent`](../packages/core/agent), [`agent-instructions`](../packages/context/agent-instructions), `claude-code-mods`, `client-ui-forge`, [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), `liveshare`, [`plan-mode`](../packages/plan/plan-mode), [`repeat-tool-reminder`](../packages/guard/repeat-tool-reminder), [`session-checkpoint-policy`](../packages/session/session-checkpoint-policy), `session-controller`, [`session-reference`](../packages/context/session-reference), [`subagent`](../packages/subagent/subagent), [`time-context`](../packages/context/time-context), [`tmux-context`](../packages/context/tmux-context), [`tool-skill`](../packages/skill/tool-skill) | +| `agent/request-error` | `waterfall` | [`packages/core/agent/src/runtime-types.ts:394`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`waterfall`) | [`compaction-basic`](../packages/compaction/compaction-basic), [`compaction-image-offload`](../packages/compaction/compaction-image-offload), [`llm-retry`](../packages/llm/llm-retry) | +| `agent/status` | `emit` | [`packages/core/agent/src/runtime-types.ts:314`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`emit`) | [`acp`](../packages/acp/acp), [`compaction-basic`](../packages/compaction/compaction-basic), [`goal-round-driver`](../packages/goal/goal-round-driver), `server`, `session-controller` | +| `agent/turn-stopping` | `serial` | [`packages/core/agent/src/runtime-types.ts:422`](../packages/core/agent/src/runtime-types.ts) | [`agent-loop`](../packages/core/agent-loop) (`serial`) | [`hooks-claude-code`](../packages/experimental/hooks-claude-code), [`hooks-codex`](../packages/experimental/hooks-codex), [`workspace-changes`](../packages/deliverables/workspace-changes) | | `api-session/activity` | `emit` | [`packages/api/session-controller/src/types.ts:705`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/added` | `emit` | [`packages/api/session-controller/src/types.ts:685`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | | `api-session/error` | `emit` | [`packages/api/session-controller/src/types.ts:712`](../packages/api/session-controller/src/types.ts) | `session-controller` (`emit`) | `remotes` | diff --git a/docs/subsystems/core.md b/docs/subsystems/core.md index 40264ec898..7b8503cb1a 100644 --- a/docs/subsystems/core.md +++ b/docs/subsystems/core.md @@ -78,6 +78,7 @@ interface Agent { * Clear queued and steering work — unless `keepInbox` — and abort the active * turn or between-turn task. The first cause wins for that activity. With no * active activity, cancellation is a no-op and does not arm later work. + * Queue-write failures still abort the activity and propagate to the caller. * @param cause - the stable caller intent carried by the active operation signal. * @param options - cancellation options; `keepInbox` preserves pending work. */ @@ -97,7 +98,7 @@ interface Agent { * inbox until the task settles, while public status stays `idle`. * `whenIdle()` follows both the task and any waking work released behind it. * @param task - operation whose fulfillment or rejection is preserved, with a signal aborted by {@link cancel}. - * @throws synchronously when turn-driving or another maintenance task already owns the agent. + * @throws AgentClosedError after disposal begins, or an Error when another activity owns the agent. * @returns the task promise. */ runMaintenance(task: (signal: AbortSignal) => Promise): Promise @@ -112,6 +113,7 @@ interface Agent { * @param message - identified content and the source that supplied it. * @param target - the preferred next-turn or next-step inbox boundary. * @param wakeup - whether delivery may wake the driver. + * @throws AgentClosedError after disposal begins; no input is inserted. */ send(message: UserMessage, target: InboxTarget, wakeup: boolean): void @@ -119,6 +121,7 @@ interface Agent { * Queue an ordinary follow-up turn and wake the driver. The item becomes the * sole ordinary message of its own turn. * @param message - identified prompt content and the source that supplied it. + * @throws AgentClosedError after disposal begins; no input is inserted. */ followup(message: UserMessage): void @@ -128,6 +131,7 @@ interface Agent { * A rejected step leaves steering parked in the inbox until the next * wake; cancellation or disposal may discard pending steering. * @param message - identified steering content and the source that supplied it. + * @throws AgentClosedError after disposal begins; no input is inserted. */ steer(message: UserMessage): void @@ -138,6 +142,7 @@ interface Agent { * wakes them. It may miss a request whose pre-step already claimed its * batch. Cancellation or disposal may discard pending context. * @param message - identified injected context and the source that supplied it. + * @throws AgentClosedError after disposal begins; no input is inserted. */ inject(message: UserMessage): void } diff --git a/docs/subsystems/core.zh.md b/docs/subsystems/core.zh.md index ff1d3b579e..9f0bf51061 100644 --- a/docs/subsystems/core.zh.md +++ b/docs/subsystems/core.zh.md @@ -82,6 +82,7 @@ interface Agent { * Clear queued and steering work — unless `keepInbox` — and abort the active * turn or between-turn task. The first cause wins for that activity. With no * active activity, cancellation is a no-op and does not arm later work. + * Queue-write failures still abort the activity and propagate to the caller. * @param cause - the stable caller intent carried by the active operation signal. * @param options - cancellation options; `keepInbox` preserves pending work. */ @@ -101,7 +102,7 @@ interface Agent { * inbox until the task settles, while public status stays `idle`. * `whenIdle()` follows both the task and any waking work released behind it. * @param task - operation whose fulfillment or rejection is preserved, with a signal aborted by {@link cancel}. - * @throws synchronously when turn-driving or another maintenance task already owns the agent. + * @throws AgentClosedError after disposal begins, or an Error when another activity owns the agent. * @returns the task promise. */ runMaintenance(task: (signal: AbortSignal) => Promise): Promise @@ -116,6 +117,7 @@ interface Agent { * @param message - identified content and the source that supplied it. * @param target - the preferred next-turn or next-step inbox boundary. * @param wakeup - whether delivery may wake the driver. + * @throws AgentClosedError after disposal begins; no input is inserted. */ send(message: UserMessage, target: InboxTarget, wakeup: boolean): void @@ -123,6 +125,7 @@ interface Agent { * Queue an ordinary follow-up turn and wake the driver. The item becomes the * sole ordinary message of its own turn. * @param message - identified prompt content and the source that supplied it. + * @throws AgentClosedError after disposal begins; no input is inserted. */ followup(message: UserMessage): void @@ -132,6 +135,7 @@ interface Agent { * A rejected step leaves steering parked in the inbox until the next * wake; cancellation or disposal may discard pending steering. * @param message - identified steering content and the source that supplied it. + * @throws AgentClosedError after disposal begins; no input is inserted. */ steer(message: UserMessage): void @@ -142,6 +146,7 @@ interface Agent { * wakes them. It may miss a request whose pre-step already claimed its * batch. Cancellation or disposal may discard pending context. * @param message - identified injected context and the source that supplied it. + * @throws AgentClosedError after disposal begins; no input is inserted. */ inject(message: UserMessage): void } diff --git a/packages/core/agent-loop/README.i18n.yaml b/packages/core/agent-loop/README.i18n.yaml index 08e35ca2a9..9c73797cbb 100644 --- a/packages/core/agent-loop/README.i18n.yaml +++ b/packages/core/agent-loop/README.i18n.yaml @@ -39,8 +39,8 @@ en: ec2bf804bd1a5439 zh: 316e767507e859ef /deepseek-ai-dsh-agent-loop/understand-the-implementation/creation-and-teardown: - en: e2cb7b6101f20425 - zh: a056c43886207083 + en: e66e878db8193219 + zh: 11a79267958b2f51 /deepseek-ai-dsh-agent-loop/understand-the-implementation/persistence-integration: en: 3b48a480036bd5df zh: 28cea2036b0dccee diff --git a/packages/core/agent-loop/README.md b/packages/core/agent-loop/README.md index 03fc5b2e16..c021ad2513 100644 --- a/packages/core/agent-loop/README.md +++ b/packages/core/agent-loop/README.md @@ -109,7 +109,7 @@ The loop deep-freezes each derived message identity on its first request and reu Fresh creation and resume share one rollback-covered initialization: construct the concrete agent and scoped context around the acquired Session; store constructor history, enter the SessionStore, and complete `session/created`; await optional setup and its commit; enter the AgentRegistry; await serial `agent/created` listeners; then release queued input. The caller Context owns the transaction and live handle; delegation services bind their runtime relationships during setup. Setup, commit, or listener failure and owner disposal roll back the prepared resources. The Session is writable during setup, and every removal emits `session/disposed`, including setup failure before Agent creation is announced. Failed initialization preserves restored inbox input and any events already stored; it rejects only after releasing its scope and acquired write handle. Cancellation while persistence is still creating a write handle can reject earlier; that handle closes when creation settles. Announcements already delivered remain observable and receive matching disposal notifications. Teardown stops and drains the driver, unwinds the scope, closes the session's write path, detaches the agent, then detaches the session. Every detach binds the exact entered object, so a stale disposer cannot remove a later same-id replacement. -The loop awaits its required registrations before publishing its factory. It calls `readerFor(session)` on its model-selection and inbox handles and passes the callbacks to the driver in one typed object; the model and inbox collaborators receive their respective callbacks. During factory unload, global registrations may disappear before teardown finishes; retained handles remain readable through scope cleanup, including cancellation from scoped abort listeners. Caller unload, factory unload, and handle disposal await the same teardown. +The loop awaits its required registrations before publishing its factory. It calls `readerFor(session)` on its model-selection and inbox handles and passes the callbacks to the driver in one typed object; the model and inbox collaborators receive their respective callbacks. During factory unload, global registrations may disappear before teardown finishes; retained handles remain readable through scope cleanup, including cancellation from scoped abort listeners. Caller unload, factory unload, and handle disposal await the same teardown. New input is rejected when teardown begins; queue-clear errors do not skip cancellation or the wait for admitted work, and resources remain available until that work settles. ### Persistence integration diff --git a/packages/core/agent-loop/README.zh.md b/packages/core/agent-loop/README.zh.md index 977911bbbe..394130f0c4 100644 --- a/packages/core/agent-loop/README.zh.md +++ b/packages/core/agent-loop/README.zh.md @@ -109,7 +109,7 @@ const handle = await ctx.agents.create({ 新建和恢复共用一个受回滚保护的初始化流程:围绕已取得的 Session 构造具象 agent 与带作用域上下文;存储构造历史、进入 SessionStore 并完成 `session/created`;等待可选 setup 及其 commit;进入 AgentRegistry;等待串行 `agent/created` 监听器;随后释放已排队输入。调用方 Context 拥有事务和存活句柄;委派服务在 setup 期间绑定自己的运行时关系。Setup、commit、监听器失败或所有者 dispose 都会回滚已准备的资源。setup 期间 Session 可写;每次移除都会发出 `session/disposed`,包括 Agent 创建宣告前的 setup 失败。初始化失败保留恢复出的收件箱输入及已存储事件,并在释放自身作用域及已取得的写句柄后才拒绝。持久化层仍在创建写句柄时发生取消,可以更早拒绝;该句柄在创建完成后关闭。已送达的宣告仍可被观察,并有配对的销毁通知。Teardown 停止并排空驱动器、撤销作用域、关闭会话写路径、detach agent,再 detach 会话。每次 detach 都绑定到确切进入的对象,因此陈旧 disposer 无法移除之后出现的同 id 替代项。 -loop 在发布工厂前等待必需的注册完成。它对模型选择和 inbox 句柄调用 `readerFor(session)`,将读取回调放入一个类型化对象传给驱动器;模型和 inbox 小组件接收各自的回调。工厂卸载时,全局注册可以先于 teardown 完成而消失;保留句柄在整个作用域清理期间仍可读取,包括作用域内 abort 监听器发起取消的时刻。调用方卸载、工厂卸载和句柄释放等待同一次 teardown。 +loop 在发布工厂前等待必需的注册完成。它对模型选择和 inbox 句柄调用 `readerFor(session)`,将读取回调放入一个类型化对象传给驱动器;模型和 inbox 小组件接收各自的回调。工厂卸载时,全局注册可以先于 teardown 完成而消失;保留句柄在整个作用域清理期间仍可读取,包括作用域内 abort 监听器发起取消的时刻。调用方卸载、工厂卸载和句柄释放等待同一次 teardown。teardown 开始后拒绝新输入;清队列错误不会跳过取消或等待已接纳工作,资源会保留到该工作结束。 ### 持久化集成 diff --git a/packages/core/agent-loop/src/agent.ts b/packages/core/agent-loop/src/agent.ts index c4daa50c47..c23cc401e3 100644 --- a/packages/core/agent-loop/src/agent.ts +++ b/packages/core/agent-loop/src/agent.ts @@ -18,7 +18,7 @@ import type { PreStepDecision, RequestErrorAction, } from '@deepseek-ai/dsh-agent' -import { AgentModel, agentEvents, assembleContextFor } from '@deepseek-ai/dsh-agent' +import { AgentClosedError, AgentModel, agentEvents, assembleContextFor } from '@deepseek-ai/dsh-agent' import type { GenerateOptions, LlmCallConfig, Message, PreparedLlmCall } from '@deepseek-ai/dsh-llm' import { LlmError, @@ -141,6 +141,7 @@ export class ReactLoopAgent implements Agent { public readonly session: Session, projections: LoopProjections, lastTurn: number, + private readonly lifetimeSignal: AbortSignal, ) { this.requestSurfaceGeneration = session.surface.contentGeneration this.dispatch = agentEvents(loopCtx, this) @@ -168,6 +169,7 @@ export class ReactLoopAgent implements Agent { } send(message: UserMessage, target: InboxTarget, wakeup: boolean): void { + this.assertOpen() // Waking input cannot join an aborted activity, so it starts the next turn. // Captured before the insertion so a reentrant cancel from a splice observer cannot reclassify it. const wakingAfterAbort = wakeup && this.phase.kind !== 'idle' && this.phase.abort.signal.aborted @@ -189,14 +191,19 @@ export class ReactLoopAgent implements Agent { } cancel(cause: AgentCancelCause, options: CancelOptions = {}): void { - if (!options.keepInbox) { - this.inbox.clear() - if (this.phase.kind !== 'idle') this.phase.wakeRequested = false + try { + if (!options.keepInbox) this.inbox.clear() + } finally { + // Queue persistence must not prevent cancellation of admitted work. + if (this.phase.kind !== 'idle') { + if (!options.keepInbox) this.phase.wakeRequested = false + this.phase.abort.abort(cause) + } } - if (this.phase.kind !== 'idle') this.phase.abort.abort(cause) } runMaintenance(job: (signal: AbortSignal) => Promise): Promise { + this.assertOpen() if (this.phase.kind !== 'idle') throw new Error(`agent "${this.id}" already has active work`) const done = Promise.withResolvers() const maintenance: Phase = { @@ -228,6 +235,7 @@ export class ReactLoopAgent implements Agent { * the inbox insertion so a reentrant cancel cannot reclassify it. */ private wakeDriver(wakeAfterAbort = false): void { + if (this.lifetimeSignal.aborted) return if (this.phase.kind !== 'idle') { // Maintenance and aborted drivers cannot deliver the wake: latch it for // replay at convergence. Live drivers claim queued work themselves; @@ -257,6 +265,12 @@ export class ReactLoopAgent implements Agent { } while (activity !== this.activityDone) } + private assertOpen(): void { + if (this.lifetimeSignal.aborted) { + throw new AgentClosedError(this.id, { cause: this.lifetimeSignal.reason }) + } + } + /** Report one failure at its live boundary, then preserve it for driver containment. */ private throwError(error: unknown): never { const turn = this.phase.kind === 'running' ? this.phase.turn : this.phase.lastTurn diff --git a/packages/core/agent-loop/src/index.ts b/packages/core/agent-loop/src/index.ts index 22b3192588..a0b02d4eee 100644 --- a/packages/core/agent-loop/src/index.ts +++ b/packages/core/agent-loop/src/index.ts @@ -514,7 +514,12 @@ export class AgentLoop extends Service implements AgentFactory { if (machine === undefined) await machineReady.promise /* v8 ignore next -- setup failure untracks this disposer before resolving without a machine. */ if (machine !== undefined) { - machine.cancel({ kind: 'disposed' }, { keepInbox: !ready }) + try { + machine.cancel({ kind: 'disposed' }, { keepInbox: !ready }) + } catch (error: unknown) { + failures.push(error) + } + // A failed queue write must not release resources underneath work. await machine.whenIdle() } } catch (error: unknown) { @@ -573,6 +578,7 @@ export class AgentLoop extends Service implements AgentFactory { loopCtx, id, agentOptions, session, projections, lastTurn, + abort.signal, ) machineReady.resolve() // Adopt the scope on its original fiber so it cannot unload before teardown. @@ -636,6 +642,7 @@ export class AgentLoop extends Service implements AgentFactory { await loopCtx.agents.announce(agent, source, abort.signal) assertLive() ready = true + callerSignal?.removeEventListener('abort', onCallerAbort) return { agent, dispose } } catch (error: unknown) { // Failed initialization must not release queued input into a new turn. diff --git a/packages/core/agent-loop/tests/cancel.spec.ts b/packages/core/agent-loop/tests/cancel.spec.ts index c702b64e89..2ca5eb0299 100644 --- a/packages/core/agent-loop/tests/cancel.spec.ts +++ b/packages/core/agent-loop/tests/cancel.spec.ts @@ -16,7 +16,7 @@ import SessionStore, { Session, SessionId, SessionLogOffset, TurnEndReason } fro import SystemPrompt from '@deepseek-ai/dsh-system-prompt' import ToolRuntime, { TOOL_ABORTED_BEFORE_DISPATCH } from '@deepseek-ai/dsh-tools' import { defineContentToolFixture } from '@deepseek-ai/dsh-tool-testkit' -import AgentRegistry, { type Agent, type AgentCancelCause } from '@deepseek-ai/dsh-agent' +import AgentRegistry, { AgentClosedError, type Agent, type AgentCancelCause } from '@deepseek-ai/dsh-agent' import AgentLoop from '@deepseek-ai/dsh-agent-loop' import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' import { MockAdapter, textResponse, toolCallResponse } from './mock-adapter.ts' @@ -319,14 +319,12 @@ describe('Agent.cancel()', () => { }) const agent = handle.agent + const streaming = Promise.withResolvers() + ctx.on('agent/assistant-stream', ({ frame }) => { if (frame.type === 'chunk') streaming.resolve(undefined) }) send(agent, 'active') - await new Promise(resolve => setTimeout(resolve, 30)) - - // Dispose cancels with `{ kind: 'disposed' }`; a wake landing in the - // abort-to-idle window must not latch, so `whenIdle()` does not wait on - // a model turn over the session being torn down. + await streaming.promise const disposal = handle.dispose() - setTimeout(() => { send(agent, 'late wake') }, 10) + expect(() =>{ send(agent, 'late wake') }).toThrow(AgentClosedError) await disposal expect(adapter.requests).toHaveLength(1) diff --git a/packages/core/agent-loop/tests/fixtures/serial-created.mjs b/packages/core/agent-loop/tests/fixtures/serial-created.mjs index 8cc624caae..138fd7d4b6 100644 --- a/packages/core/agent-loop/tests/fixtures/serial-created.mjs +++ b/packages/core/agent-loop/tests/fixtures/serial-created.mjs @@ -6,7 +6,42 @@ export const name = 'serial-created-fixture' export const inject = ['agents', 'sessions'] /** Install ordered creation listeners whose context must precede the first turn. */ -export function apply(ctx) { +export function apply(ctx, config = {}) { + const checkedCancellation = new WeakSet() + ctx.on('agent/pre-step', async ({ agent, messages, signal }, next) => { + if (checkedCancellation.has(agent) + || (config.checkRootCancellation === false && agent.session.header.origin !== 'subagent')) return next() + checkedCancellation.add(agent) + const pending = { + id: randomUUID(), role: 'user', + content: [{ type: 'text', text: 'Cancellation checkpoint probe.' }], + source: { kind: `plugin:${name}` }, + } + agent.inject(pending) + const failure = new Error('fixture queue-clear refusal') + const stopRejecting = ctx.on('internal/dispatch', (_mode, eventName, args) => { + if (eventName !== 'session/event') return + const [session, event] = args + if (session === agent.session && event.type === 'agent/inbox/spliced' + && event.data.outcome === 'canceled' && (event.data.removedCount ?? 0) > 0) throw failure + }) + try { + try { agent.cancel({ kind: 'user' }) } + catch (error) { if (error !== failure) throw error } + } finally { + stopRejecting() + } + if (!signal.aborted) throw new Error('queue-clear failure prevented cancellation') + agent.inbox.remove(pending.id) + for (const message of messages) { + const ordinary = message.source.kind === 'user' + const restored = message.source.kind === `plugin:${name}` + ? { ...message, content: [{ type: 'text', text: message.content[0].text + ' Queue-clear failures still cancel active work.' }] } + : message + agent.send(restored, ordinary ? 'next-turn' : 'next-step', ordinary) + } + return next() + }) const ready = new WeakSet() ctx.on('agent/created', async ({ agent }) => { await setImmediate() diff --git a/packages/core/agent-loop/tests/input-lifecycle.spec.ts b/packages/core/agent-loop/tests/input-lifecycle.spec.ts new file mode 100644 index 0000000000..2e87517e1f --- /dev/null +++ b/packages/core/agent-loop/tests/input-lifecycle.spec.ts @@ -0,0 +1,151 @@ +import * as NativeToolExecutor from '@deepseek-ai/dsh-tool-executor-native' +import { afterEach, expect, it, vi } from 'vitest' +import { mkdtemp, rm } from 'node:fs/promises' +import { tmpdir } from 'node:os' +import { join } from 'node:path' +import { Context } from '@deepseek-ai/cordis' +import AgentRegistry, { AgentClosedError, type Agent } from '@deepseek-ai/dsh-agent' +import AgentLoop from '@deepseek-ai/dsh-agent-loop' +import LlmRuntime, { createUserMessage } from '@deepseek-ai/dsh-llm' +import SessionStore, { Session, SessionId, type SessionEvent } from '@deepseek-ai/dsh-session' +import SessionProjectionRegistry from '@deepseek-ai/dsh-session-projection' +import * as JsonlSessionPersistence from '@deepseek-ai/dsh-session-persistence-jsonl' +import * as CheckpointPolicy from '@deepseek-ai/dsh-session-checkpoint-policy' +import SystemPrompt from '@deepseek-ai/dsh-system-prompt' +import ToolRuntime from '@deepseek-ai/dsh-tools' +import { MockAdapter, textResponse } from './mock-adapter.ts' + +const fixtures: { ctx: Context; agent?: Agent; root: string }[] = [] +afterEach(async () => { + for (const fixture of fixtures.splice(0)) { + try { + fixture.agent?.cancel({ kind: 'disposed' }, { keepInbox: true }) + await fixture.agent?.whenIdle() + await fixture.ctx.fiber.dispose() + } finally { + await rm(fixture.root, { recursive: true, force: true }) + } + } +}) + +const input = (text: string) => createUserMessage({ content: [{ type: 'text' as const, text }], source: { kind: 'user' as const } }) + +async function harness() { + const ctx = new Context() + const root = await mkdtemp(join(tmpdir(), 'dsh-input-atomicity-')) + const fixture: { ctx: Context; agent?: Agent; root: string } = { ctx, root } + fixtures.push(fixture) + await ctx.plugin(LlmRuntime) + await ctx.plugin(SessionStore) + await ctx.plugin(SessionProjectionRegistry) + await ctx.plugin(SystemPrompt) + await ctx.plugin(ToolRuntime) + await ctx.plugin(NativeToolExecutor) + await ctx.plugin(AgentRegistry) + await ctx.plugin(JsonlSessionPersistence, { root, compression: 'none' }) + await ctx.plugin(CheckpointPolicy) + await ctx.plugin(AgentLoop, { agents: [] }) + const adapter = new MockAdapter(Array.from({ length: 5 }, () => textResponse('ok'))) + ctx.llm.registerAdapter(['mock'], adapter) + const handle = await ctx.agents.create({ sessionId: SessionId('diagnostic'), agentOptions: { provider: 'mock', model: 'mock' } }) + fixture.agent = handle.agent + return { ctx, root, adapter, handle, agent: handle.agent } +} + + +it('cancellation still aborts the running request when queue clearing fails', async () => { + const { ctx, agent } = await harness() + const entered = Promise.withResolvers() + const release = Promise.withResolvers() + const hold = ctx.on('agent/pre-step', async ({ signal }, next) => { entered.resolve(signal); await release.promise; return next() }) + let stopRejecting = () => {} + try { + agent.followup(input('running')) + const signal = await entered.promise + agent.inject(input('steering')) + const queued = input('queued') + agent.followup(queued) + stopRejecting = ctx.on('internal/dispatch', (_mode, name, args) => { + if (name !== 'session/event') return + const [session, event] = args as [Session, SessionEvent] + if (session === agent.session && event.type === 'agent/inbox/spliced' && event.data.target === 'next-turn' + && (event.data.removedCount ?? 0) > 0) throw new Error('injected queue clear failure') + }) + expect(() =>{ agent.cancel({ kind: 'user' }) }).toThrow('injected queue clear failure') + expect(signal.aborted).toBe(true) + } finally { + stopRejecting() + hold() + agent.cancel({ kind: 'disposed' }, { keepInbox: true }) + release.resolve(undefined) + await agent.whenIdle() + } +}) + + +it('an Agent still in the registry rejects new input while its owner is closing', async () => { + const { ctx, agent, handle } = await harness() + const release = Promise.withResolvers() + const aborted = Promise.withResolvers() + const work = agent.runMaintenance(async (signal) => { + signal.addEventListener('abort', () => { aborted.resolve(undefined) }, { once: true }) + await release.promise + }) + const closing = handle.dispose() + try { + await aborted.promise + expect(ctx.agents.get(agent.id)).toBe(agent) + const message = input('during closing') + for (const submit of [() =>{ agent.followup(message) }, () =>{ agent.steer(message) }, () =>{ agent.inject(message) }, () =>{ agent.send(message, 'next-turn', true) }, () => agent.runMaintenance(async () => {})]) { + expect(submit).toThrow(AgentClosedError) + } + const pending = [...agent.inbox.nextTurn, ...agent.inbox.nextStep].some(entry => entry.id === message.id) + expect(pending).toBe(false) + } finally { + release.resolve(undefined) + await work + await closing + } +}) + + + +it('waits for admitted maintenance after a queue-clear failure before releasing its Session', async () => { + const { ctx, agent, handle } = await harness() + const release = Promise.withResolvers() + const waiting = Promise.withResolvers<'waiting'>() + const disposed = Promise.withResolvers<'disposed'>() + let scopeDisposed = false + agent.ctx.effect(() => () => { scopeDisposed = true; disposed.resolve('disposed') }) + const work = agent.runMaintenance(async () => { + await release.promise + agent.session.append('request/context', { provider: 'mock', model: 'mock' }) + }) + agent.inject(input('pending')) + const stopRejecting = ctx.on('internal/dispatch', (_mode, name, args) => { + if (name !== 'session/event') return + const [session, event] = args as [Session, SessionEvent] + if (session === agent.session && event.type === 'agent/inbox/spliced' && (event.data.removedCount ?? 0) > 0) { + throw new Error('queue clear failed') + } + }) + const whenIdle = agent.whenIdle.bind(agent) + const spy = vi.spyOn(agent, 'whenIdle').mockImplementation(() => { waiting.resolve('waiting'); return whenIdle() }) + const closing = handle.dispose().then(() => undefined, (error: unknown) => error) + try { + expect(await Promise.race([waiting.promise, disposed.promise])).toBe('waiting') + expect(scopeDisposed).toBe(false) + expect(ctx.sessions.get(agent.id)).toBe(agent.session) + release.resolve(undefined) + await work + expect(await closing).toEqual(new Error('queue clear failed')) + expect(scopeDisposed).toBe(true) + expect(agent.session.snapshotEvents().at(-1)?.type).toBe('request/context') + } finally { + stopRejecting() + spy.mockRestore() + release.resolve(undefined) + await work + await closing + } +}) diff --git a/packages/core/agent-loop/tests/request-freeze.spec.ts b/packages/core/agent-loop/tests/request-freeze.spec.ts index 8ed1c4d138..f4ef29eea3 100644 --- a/packages/core/agent-loop/tests/request-freeze.spec.ts +++ b/packages/core/agent-loop/tests/request-freeze.spec.ts @@ -79,7 +79,8 @@ describe('loop-owned request freezing', () => { const inbox = await loopCtx.sessionProjections.register(inboxProjectionDefinition) const model = await loopCtx.sessionProjections.register(modelSelectionProjectionDefinition) const agent = new ReactLoopAgent(loopCtx, id, { provider: 'mock', model: 'mock' }, session, - { modelSelection: model.readerFor(session), inbox: inbox.readerFor(session) }, ctx.agents.turnBoundaryOf(session).lastTurn) + { modelSelection: model.readerFor(session), inbox: inbox.readerFor(session) }, ctx.agents.turnBoundaryOf(session).lastTurn, + new AbortController().signal) cleanups.push(async () => { agent.cancel({ kind: 'disposed' }) await agent.whenIdle() @@ -129,7 +130,8 @@ describe('loop-owned request freezing', () => { expect(freeze.mock.calls.filter(([value]) => value === userEvent.data)).toHaveLength(1) expect(freeze.mock.calls.filter(([value]) => value === replacement.data)).toHaveLength(1) const resumed = new ReactLoopAgent(loopCtx, id, { provider: 'mock', model: 'mock' }, session, - { modelSelection: model.readerFor(session), inbox: inbox.readerFor(session) }, ctx.agents.turnBoundaryOf(session).lastTurn) + { modelSelection: model.readerFor(session), inbox: inbox.readerFor(session) }, ctx.agents.turnBoundaryOf(session).lastTurn, + new AbortController().signal) cleanups.push(async () => { resumed.cancel({ kind: 'disposed' }) await resumed.whenIdle() diff --git a/packages/core/agent/README.i18n.yaml b/packages/core/agent/README.i18n.yaml index 358d56ae02..df3e29c8a9 100644 --- a/packages/core/agent/README.i18n.yaml +++ b/packages/core/agent/README.i18n.yaml @@ -24,8 +24,8 @@ en: 85b0ff72a187110f zh: 3f61c1568d27b8bc /deepseek-ai-dsh-agent/use-this-package/drive-an-agent-s-conversation: - en: 71e258897e2e7d73 - zh: abf0991012faa17e + en: ff2b530bb20bcfe2 + zh: 4f9024c4881a4ba5 /deepseek-ai-dsh-agent/use-this-package/scope-registrations-to-one-agent: en: 2594a1e02adea801 zh: cae8860369728efb diff --git a/packages/core/agent/README.md b/packages/core/agent/README.md index c84ac3d237..9772578fcc 100644 --- a/packages/core/agent/README.md +++ b/packages/core/agent/README.md @@ -55,7 +55,7 @@ For scoped switch notices, call `installModelSelection(agentCtx)` from `dsh-agen ### Drive an agent's conversation -The handle's methods route identified user-role messages into the agent's inbox. `followup()` queues an ordinary next-turn prompt and wakes the driver; `steer()` submits next-step input and wakes it; `inject()` adds model-facing context without waking the driver, so it lands in the next admitted step. `cancel(cause)` aborts the active activity and, unless `keepInbox` is set, clears pending work; `whenIdle()` resolves after the whole agent reaches quiescence. +The handle's methods route identified user-role messages into the agent's inbox. `followup()` queues an ordinary next-turn prompt and wakes the driver; `steer()` submits next-step input and wakes it; `inject()` adds model-facing context without waking the driver, so it lands in the next admitted step. `cancel(cause)` aborts the active activity and, unless `keepInbox` is set, clears pending work; `whenIdle()` resolves after the whole agent reaches quiescence. Disposal rejects new input and maintenance with `AgentClosedError` before mutation. A queue-write failure during cancellation still aborts active work and propagates to the caller. ```text handle.agent.followup({ diff --git a/packages/core/agent/README.zh.md b/packages/core/agent/README.zh.md index a2fae55b56..b5b2af52d4 100644 --- a/packages/core/agent/README.zh.md +++ b/packages/core/agent/README.zh.md @@ -55,7 +55,7 @@ await handle.dispose() // stops the loop, unregisters, removes the session, un ### 驱动 agent 的对话 -句柄的方法把带标识的 user 角色消息路由进 agent 的收件箱。`followup()` 排队一条普通的下一个轮次提示词并唤醒驱动器;`steer()` 提交下一步输入并唤醒它;`inject()` 添加面向模型的上下文但不唤醒驱动器,因此它落在下一个被接纳的步骤中。`cancel(cause)` 中止当前活动,并在未设置 `keepInbox` 时清除待处理工作;`whenIdle()` 会在整个 agent 达到完全停稳后完成。 +句柄的方法把带标识的 user 角色消息路由进 agent 的收件箱。`followup()` 排队一条普通的下一个轮次提示词并唤醒驱动器;`steer()` 提交下一步输入并唤醒它;`inject()` 添加面向模型的上下文但不唤醒驱动器,因此它落在下一个被接纳的步骤中。`cancel(cause)` 中止当前活动,并在未设置 `keepInbox` 时清除待处理工作;`whenIdle()` 会在整个 agent 达到完全停稳后完成。释放开始后,新输入和维护任务会在修改状态前抛出 `AgentClosedError`。取消期间的队列写入失败仍会中止当前工作,并把错误传给调用方。 ```text handle.agent.followup({ diff --git a/packages/core/agent/src/closed-error.ts b/packages/core/agent/src/closed-error.ts new file mode 100644 index 0000000000..abcb335320 --- /dev/null +++ b/packages/core/agent/src/closed-error.ts @@ -0,0 +1,15 @@ +/** Rejection of new work after an Agent starts closing. @module @deepseek-ai/dsh-agent/closed-error */ + +import type { SessionId } from '@deepseek-ai/dsh-session' + +/** An Agent has stopped accepting new input and maintenance work. */ +export class AgentClosedError extends Error { + /** + * @param agentId - closing Agent identity. + * @param options - original lifecycle cancellation, when available. + */ + constructor(readonly agentId: SessionId, options?: ErrorOptions) { + super(`agent "${agentId}" is closing`, options) + this.name = 'AgentClosedError' + } +} diff --git a/packages/core/agent/src/index.ts b/packages/core/agent/src/index.ts index 374be30180..35fbdd6b1d 100644 --- a/packages/core/agent/src/index.ts +++ b/packages/core/agent/src/index.ts @@ -19,6 +19,7 @@ import type { AgentOptions, SessionStartSource } from './runtime-types.ts' import { turnBoundaryProjectionDefinition } from './projection.ts' export * from './runtime-types.ts' +export { AgentClosedError } from './closed-error.ts' export * from './types.ts' export { AgentModel, modelSelectionProjectionDefinition, installModelSelection } from './model-selection.ts' export type { ModelSelectionProjectionState } from './model-selection.ts' diff --git a/packages/core/agent/src/runtime-types.ts b/packages/core/agent/src/runtime-types.ts index 2b1698e39c..aa5efe6d0a 100644 --- a/packages/core/agent/src/runtime-types.ts +++ b/packages/core/agent/src/runtime-types.ts @@ -206,6 +206,7 @@ declare module './types.ts' { * Clear queued and steering work — unless `keepInbox` — and abort the active * turn or between-turn task. The first cause wins for that activity. With no * active activity, cancellation is a no-op and does not arm later work. + * Queue-write failures still abort the activity and propagate to the caller. * @param cause - the stable caller intent carried by the active operation signal. * @param options - cancellation options; `keepInbox` preserves pending work. */ @@ -225,7 +226,7 @@ declare module './types.ts' { * inbox until the task settles, while public status stays `idle`. * `whenIdle()` follows both the task and any waking work released behind it. * @param task - operation whose fulfillment or rejection is preserved, with a signal aborted by {@link cancel}. - * @throws synchronously when turn-driving or another maintenance task already owns the agent. + * @throws AgentClosedError after disposal begins, or an Error when another activity owns the agent. * @returns the task promise. */ runMaintenance(task: (signal: AbortSignal) => Promise): Promise @@ -240,6 +241,7 @@ declare module './types.ts' { * @param message - identified content and the source that supplied it. * @param target - the preferred next-turn or next-step inbox boundary. * @param wakeup - whether delivery may wake the driver. + * @throws AgentClosedError after disposal begins; no input is inserted. */ send(message: UserMessage, target: InboxTarget, wakeup: boolean): void @@ -247,6 +249,7 @@ declare module './types.ts' { * Queue an ordinary follow-up turn and wake the driver. The item becomes the * sole ordinary message of its own turn. * @param message - identified prompt content and the source that supplied it. + * @throws AgentClosedError after disposal begins; no input is inserted. */ followup(message: UserMessage): void @@ -256,6 +259,7 @@ declare module './types.ts' { * A rejected step leaves steering parked in the inbox until the next * wake; cancellation or disposal may discard pending steering. * @param message - identified steering content and the source that supplied it. + * @throws AgentClosedError after disposal begins; no input is inserted. */ steer(message: UserMessage): void @@ -266,6 +270,7 @@ declare module './types.ts' { * wakes them. It may miss a request whose pre-step already claimed its * batch. Cancellation or disposal may discard pending context. * @param message - identified injected context and the source that supplied it. + * @throws AgentClosedError after disposal begins; no input is inserted. */ inject(message: UserMessage): void } diff --git a/packages/subagent/subagent/README.i18n.yaml b/packages/subagent/subagent/README.i18n.yaml index 4ff564cb25..3b160b2782 100644 --- a/packages/subagent/subagent/README.i18n.yaml +++ b/packages/subagent/subagent/README.i18n.yaml @@ -54,8 +54,8 @@ en: 61cddba23f4e7d1c zh: 052c8331a747403f /deepseek-ai-dsh-subagent/understand-the-implementation/activation-settlement: - en: 9543250098033347 - zh: 3721af3e31409a32 + en: 152d969b7214b4cd + zh: 2ba2a43599b009d9 /deepseek-ai-dsh-subagent/understand-the-implementation/restoring-child-composition: en: 843f9f4319ca2ae3 zh: 218bf355816d4ff6 diff --git a/packages/subagent/subagent/README.md b/packages/subagent/subagent/README.md index b8fd869989..c3da369bed 100644 --- a/packages/subagent/subagent/README.md +++ b/packages/subagent/subagent/README.md @@ -124,7 +124,7 @@ External providers mint a fresh `SubagentRun.id` for each execution in the runti ### 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 and pending input 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. Host waits on the subagent tree root join progressing members and their cleanup, including failed executions; idle members with parked input remain resident without delaying host completion. Ordinary members do not wait for descendants. Closing admission and disposing the handle prevent late work from entering a released Agent. Parent delivery emits its notice after settlement only if the creator is still online; caller delivery leaves collection to the awaiting workflow. Headless hosts repeat `agent.whenIdle()` and `waitForTreeWork(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. +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 and pending input 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. Host waits on the subagent tree root join progressing members and their cleanup, including failed executions; idle members with parked input remain resident without delaying host completion. Ordinary members do not wait for descendants. Closing admission and disposing the handle prevent late work from entering a released Agent. Parent delivery emits its notice after settlement only if the creator is still online; caller delivery leaves collection to the awaiting workflow. Headless hosts repeat `agent.whenIdle()` and `waitForTreeWork(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. An Agent that has begun closing ends the local settlement observer rather than retrying maintenance admission. Settlement attribution records the exact execution stop reason without altering model-facing notice content. Historical notices can omit this field; readers must not infer it from the English summary. diff --git a/packages/subagent/subagent/README.zh.md b/packages/subagent/subagent/README.zh.md index e0f8f35bda..ed72da6c0a 100644 --- a/packages/subagent/subagent/README.zh.md +++ b/packages/subagent/subagent/README.zh.md @@ -124,7 +124,7 @@ kind: "package-reference" ### Activation 结算 -管理器预留子级身份与容量,创建本地 Agent 或外部执行,并接受初始任务。本地 Agent 活动和待处理输入结束后,管理器在最终 Session flush 后重新验证活动状态并关闭准入。本地与外部 activation 都在句柄清理、容量归还及结算通知结束后交付结果。宿主在 subagent 树根上等待仍在推进的成员及其清理,包括失败的执行;有停放输入的空闲成员继续驻留,但不延迟宿主完成。普通成员不等待后代。关闭准入并释放句柄可防止迟到工作进入已释放的 Agent。父级投递仅在创建者仍在线时于结算后发送通知;调用方投递由等待中的工作流收集。Headless 宿主交替等待 `agent.whenIdle()` 与 `waitForTreeWork(agent)`,直至没有子级工作,且父级在检查子级期间保持空闲、Session 序号不变,让完成通知可驱动父级生成最终答案。 +管理器预留子级身份与容量,创建本地 Agent 或外部执行,并接受初始任务。本地 Agent 活动和待处理输入结束后,管理器在最终 Session flush 后重新验证活动状态并关闭准入。本地与外部 activation 都在句柄清理、容量归还及结算通知结束后交付结果。宿主在 subagent 树根上等待仍在推进的成员及其清理,包括失败的执行;有停放输入的空闲成员继续驻留,但不延迟宿主完成。普通成员不等待后代。关闭准入并释放句柄可防止迟到工作进入已释放的 Agent。父级投递仅在创建者仍在线时于结算后发送通知;调用方投递由等待中的工作流收集。Headless 宿主交替等待 `agent.whenIdle()` 与 `waitForTreeWork(agent)`,直至没有子级工作,且父级在检查子级期间保持空闲、Session 序号不变,让完成通知可驱动父级生成最终答案。 Agent 开始关闭后,本地结算观察器会结束,不再重试维护准入。 结算归属信息记录确切的执行停止原因,不改变面向模型的通知内容。历史通知可以缺少该字段;读取方不得从英文摘要推断它。 diff --git a/packages/subagent/subagent/src/manager.ts b/packages/subagent/subagent/src/manager.ts index db6f04b019..e1b0bc6e68 100644 --- a/packages/subagent/subagent/src/manager.ts +++ b/packages/subagent/subagent/src/manager.ts @@ -11,6 +11,7 @@ import { isAbsolute, resolve } from 'node:path' import type {} from '@deepseek-ai/dsh-working-directory' import type { Context } from '@deepseek-ai/cordis' import type { Agent, AgentOptions, CreateAgentOptions } from '@deepseek-ai/dsh-agent' +import { AgentClosedError } from '@deepseek-ai/dsh-agent' import { brandString } from '@deepseek-ai/dsh-brand' import { ReasoningEffortId, contentHasImage, createUserMessage, errorChain } from '@deepseek-ai/dsh-llm' import type { ContentBlock, MessageId, MessageSource } from '@deepseek-ai/dsh-llm' @@ -991,7 +992,8 @@ export class SubagentManager { done = this.close(activation, () => this.finishDisposal(activation, false)) return Promise.resolve() }) - } catch (_error: unknown) { + } catch (error: unknown) { + if (error instanceof AgentClosedError) return Promise.resolve('closed') // Another activity already owns the Agent's idle phase. return Promise.resolve('retry') } diff --git a/scripts/smoke-python-runtime.py b/scripts/smoke-python-runtime.py index 311fa10ff6..49dca3772a 100644 --- a/scripts/smoke-python-runtime.py +++ b/scripts/smoke-python-runtime.py @@ -1787,6 +1787,7 @@ def smoke_sdk_snapshot_mode( {"insert": [{ "id": "serial-created-fixture", "name": str(Path(__file__).resolve().parents[1] / "packages/core/agent-loop/tests/fixtures/serial-created.mjs"), + "config": {"checkRootCancellation": False}, }]}, ]) with DeepSeekHarness( diff --git a/scripts/snapshots/python-sdk-single-exe/advanced/result.json b/scripts/snapshots/python-sdk-single-exe/advanced/result.json index 47c767ccd9..68401cbcf2 100644 --- a/scripts/snapshots/python-sdk-single-exe/advanced/result.json +++ b/scripts/snapshots/python-sdk-single-exe/advanced/result.json @@ -2880,11 +2880,205 @@ "payload": { "sessionId": "{{child-1}}", "event": { - "type": "step/start", + "type": "agent/inbox/spliced", "seq": 9, "time": 0, + "data": { + "target": "next-step", + "start": 0, + "inserted": [ + { + "id": "{{messageId}}", + "role": "user", + "content": [ + { + "type": "text", + "text": "Cancellation checkpoint probe." + } + ], + "source": { + "kind": "plugin:serial-created-fixture" + } + } + ] + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-1}}", + "event": { + "type": "agent/inbox/spliced", + "seq": 10, + "time": 0, + "data": { + "target": "next-step", + "start": 0, + "removedCount": 1, + "inserted": [], + "outcome": "canceled" + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-1}}", + "event": { + "type": "agent/inbox/spliced", + "seq": 11, + "time": 0, + "data": { + "target": "next-step", + "start": 0, + "inserted": [ + { + "id": "{{messageId}}", + "role": "user", + "content": [ + { + "type": "text", + "text": "Serial agent creation completed. Queue-clear failures still cancel active work." + } + ], + "source": { + "kind": "plugin:serial-created-fixture" + } + } + ] + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-1}}", + "event": { + "type": "agent/inbox/spliced", + "seq": 12, + "time": 0, + "data": { + "target": "next-turn", + "start": 0, + "inserted": [ + { + "content": [ + { + "type": "text", + "text": "Reply with exactly DIRECT_CHILD_OK and nothing else." + }, + { + "type": "text", + "text": "Your final answer is delivered automatically if your parent is still online. Include the result and relevant evidence in that answer; the parent does not receive your full transcript, tool output, or reasoning. If your last action is a message to your parent, the completion notice omits your final answer; include the result and relevant evidence in that message." + } + ], + "source": { + "kind": "user" + }, + "role": "user", + "id": "{{messageId}}" + } + ] + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-1}}", + "event": { + "type": "turn/end", + "seq": 13, + "time": 0, "data": { "turn": 1, + "reason": { + "kind": "aborted", + "reason": { + "kind": "user" + } + } + } + } + } + }, + { + "method": "session.status", + "payload": { + "sessionId": "{{child-1}}", + "status": "idle" + } + }, + { + "method": "session.status", + "payload": { + "sessionId": "{{child-1}}", + "status": "running" + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-1}}", + "event": { + "type": "turn/start", + "seq": 14, + "time": 0, + "data": { + "turn": 2 + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-1}}", + "event": { + "type": "agent/inbox/spliced", + "seq": 15, + "time": 0, + "data": { + "target": "next-step", + "start": 0, + "removedCount": 1, + "inserted": [] + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-1}}", + "event": { + "type": "agent/inbox/spliced", + "seq": 16, + "time": 0, + "data": { + "target": "next-turn", + "start": 0, + "removedCount": 1, + "inserted": [] + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-1}}", + "event": { + "type": "step/start", + "seq": 17, + "time": 0, + "data": { + "turn": 2, "step": 1 } } @@ -2896,10 +3090,10 @@ "sessionId": "{{child-1}}", "event": { "type": "system/message", - "seq": 10, + "seq": 18, "time": 0, "data": { - "turn": 1, + "turn": 2, "step": 1, "message": { "role": "system", @@ -2925,7 +3119,7 @@ "sessionId": "{{child-1}}", "event": { "type": "user/message", - "seq": 11, + "seq": 19, "time": 0, "data": { "id": "{{messageId}}", @@ -2933,7 +3127,7 @@ "content": [ { "type": "text", - "text": "Serial agent creation completed." + "text": "Serial agent creation completed. Queue-clear failures still cancel active work." } ], "source": { @@ -2950,7 +3144,7 @@ "sessionId": "{{child-1}}", "event": { "type": "user/message", - "seq": 12, + "seq": 20, "time": 0, "data": { "content": [ @@ -2979,7 +3173,7 @@ "sessionId": "{{child-1}}", "event": { "type": "user/message", - "seq": 13, + "seq": 21, "time": 0, "data": { "content": [ @@ -3027,7 +3221,7 @@ "sessionId": "{{child-1}}", "event": { "type": "request/header", - "seq": 14, + "seq": 22, "time": 0, "data": { "header": { @@ -3063,7 +3257,7 @@ "sessionId": "{{child-1}}", "event": { "type": "request/context", - "seq": 15, + "seq": 23, "time": 0, "data": { "provider": "deepseek-official", @@ -3079,12 +3273,12 @@ "sessionId": "{{child-1}}", "event": { "type": "session/title", - "seq": 16, + "seq": 24, "time": 0, "data": { "title": "Reply with exactly DIRECT_CHILD_OK and", "messageSeqs": [ - 12 + 20 ], "source": { "kind": "fallback" @@ -3099,12 +3293,12 @@ "sessionId": "{{child-1}}", "event": { "type": "session-log-deepseek/delivery-accepted", - "seq": 17, + "seq": 25, "time": 0, "data": { "sessionId": "{{child-1}}", "sessionFormatVersion": 4, - "throughSeq": 16 + "throughSeq": 24 } } } @@ -3115,10 +3309,10 @@ "sessionId": "{{child-1}}", "event": { "type": "assistant/message", - "seq": 18, + "seq": 26, "time": 0, "data": { - "turn": 1, + "turn": 2, "step": 1, "message": { "role": "assistant", @@ -3229,10 +3423,10 @@ "sessionId": "{{child-1}}", "event": { "type": "step/end", - "seq": 19, + "seq": 27, "time": 0, "data": { - "turn": 1, + "turn": 2, "step": 1 } } @@ -3244,10 +3438,10 @@ "sessionId": "{{child-1}}", "event": { "type": "turn/end", - "seq": 20, + "seq": 28, "time": 0, "data": { - "turn": 1, + "turn": 2, "reason": { "kind": "completed" } @@ -3712,11 +3906,201 @@ "payload": { "sessionId": "{{child-2}}", "event": { - "type": "step/start", + "type": "agent/inbox/spliced", "seq": 9, "time": 0, + "data": { + "target": "next-step", + "start": 0, + "inserted": [ + { + "id": "{{messageId}}", + "role": "user", + "content": [ + { + "type": "text", + "text": "Cancellation checkpoint probe." + } + ], + "source": { + "kind": "plugin:serial-created-fixture" + } + } + ] + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-2}}", + "event": { + "type": "agent/inbox/spliced", + "seq": 10, + "time": 0, + "data": { + "target": "next-step", + "start": 0, + "removedCount": 1, + "inserted": [], + "outcome": "canceled" + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-2}}", + "event": { + "type": "agent/inbox/spliced", + "seq": 11, + "time": 0, + "data": { + "target": "next-step", + "start": 0, + "inserted": [ + { + "id": "{{messageId}}", + "role": "user", + "content": [ + { + "type": "text", + "text": "Serial agent creation completed. Queue-clear failures still cancel active work." + } + ], + "source": { + "kind": "plugin:serial-created-fixture" + } + } + ] + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-2}}", + "event": { + "type": "agent/inbox/spliced", + "seq": 12, + "time": 0, + "data": { + "target": "next-turn", + "start": 0, + "inserted": [ + { + "content": [ + { + "type": "text", + "text": "Reply with exactly WORKFLOW_CHILD_OK and nothing else." + } + ], + "source": { + "kind": "user" + }, + "role": "user", + "id": "{{messageId}}" + } + ] + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-2}}", + "event": { + "type": "turn/end", + "seq": 13, + "time": 0, "data": { "turn": 1, + "reason": { + "kind": "aborted", + "reason": { + "kind": "user" + } + } + } + } + } + }, + { + "method": "session.status", + "payload": { + "sessionId": "{{child-2}}", + "status": "idle" + } + }, + { + "method": "session.status", + "payload": { + "sessionId": "{{child-2}}", + "status": "running" + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-2}}", + "event": { + "type": "turn/start", + "seq": 14, + "time": 0, + "data": { + "turn": 2 + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-2}}", + "event": { + "type": "agent/inbox/spliced", + "seq": 15, + "time": 0, + "data": { + "target": "next-step", + "start": 0, + "removedCount": 1, + "inserted": [] + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-2}}", + "event": { + "type": "agent/inbox/spliced", + "seq": 16, + "time": 0, + "data": { + "target": "next-turn", + "start": 0, + "removedCount": 1, + "inserted": [] + } + } + } + }, + { + "method": "session.event", + "payload": { + "sessionId": "{{child-2}}", + "event": { + "type": "step/start", + "seq": 17, + "time": 0, + "data": { + "turn": 2, "step": 1 } } @@ -3728,10 +4112,10 @@ "sessionId": "{{child-2}}", "event": { "type": "system/message", - "seq": 10, + "seq": 18, "time": 0, "data": { - "turn": 1, + "turn": 2, "step": 1, "message": { "role": "system", @@ -3757,7 +4141,7 @@ "sessionId": "{{child-2}}", "event": { "type": "user/message", - "seq": 11, + "seq": 19, "time": 0, "data": { "id": "{{messageId}}", @@ -3765,7 +4149,7 @@ "content": [ { "type": "text", - "text": "Serial agent creation completed." + "text": "Serial agent creation completed. Queue-clear failures still cancel active work." } ], "source": { @@ -3782,7 +4166,7 @@ "sessionId": "{{child-2}}", "event": { "type": "user/message", - "seq": 12, + "seq": 20, "time": 0, "data": { "content": [ @@ -3807,7 +4191,7 @@ "sessionId": "{{child-2}}", "event": { "type": "user/message", - "seq": 13, + "seq": 21, "time": 0, "data": { "content": [ @@ -3855,7 +4239,7 @@ "sessionId": "{{child-2}}", "event": { "type": "request/header", - "seq": 14, + "seq": 22, "time": 0, "data": { "header": { @@ -3891,7 +4275,7 @@ "sessionId": "{{child-2}}", "event": { "type": "request/context", - "seq": 15, + "seq": 23, "time": 0, "data": { "provider": "deepseek-official", @@ -3907,12 +4291,12 @@ "sessionId": "{{child-2}}", "event": { "type": "session/title", - "seq": 16, + "seq": 24, "time": 0, "data": { "title": "Reply with exactly WORKFLOW_CHILD_OK and", "messageSeqs": [ - 12 + 20 ], "source": { "kind": "fallback" @@ -3927,12 +4311,12 @@ "sessionId": "{{child-2}}", "event": { "type": "session-log-deepseek/delivery-accepted", - "seq": 17, + "seq": 25, "time": 0, "data": { "sessionId": "{{child-2}}", "sessionFormatVersion": 4, - "throughSeq": 16 + "throughSeq": 24 } } } @@ -3943,10 +4327,10 @@ "sessionId": "{{child-2}}", "event": { "type": "assistant/message", - "seq": 18, + "seq": 26, "time": 0, "data": { - "turn": 1, + "turn": 2, "step": 1, "message": { "role": "assistant", @@ -4057,10 +4441,10 @@ "sessionId": "{{child-2}}", "event": { "type": "step/end", - "seq": 19, + "seq": 27, "time": 0, "data": { - "turn": 1, + "turn": 2, "step": 1 } } @@ -4072,10 +4456,10 @@ "sessionId": "{{child-2}}", "event": { "type": "turn/end", - "seq": 20, + "seq": 28, "time": 0, "data": { - "turn": 1, + "turn": 2, "reason": { "kind": "completed" } diff --git a/scripts/snapshots/python-sdk-single-exe/advanced/session.1.v4.jsonl b/scripts/snapshots/python-sdk-single-exe/advanced/session.1.v4.jsonl index 46fbdba75d..e15e79f377 100644 --- a/scripts/snapshots/python-sdk-single-exe/advanced/session.1.v4.jsonl +++ b/scripts/snapshots/python-sdk-single-exe/advanced/session.1.v4.jsonl @@ -8,15 +8,23 @@ {"type":"turn/start","data":{"turn":1}} {"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} -{"type":"step/start","data":{"turn":1,"step":1}} -{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{messageId}}"}},"surfaceOp":"append"} -{"type":"user/message","data":{"id":"{{messageId}}","role":"user","content":[{"type":"text","text":"Serial agent creation completed."}],"source":{"kind":"plugin:serial-created-fixture"}},"surfaceOp":"append"} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"id":"{{messageId}}","role":"user","content":[{"type":"text","text":"Cancellation checkpoint probe."}],"source":{"kind":"plugin:serial-created-fixture"}}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[],"outcome":"canceled"}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"id":"{{messageId}}","role":"user","content":[{"type":"text","text":"Serial agent creation completed. Queue-clear failures still cancel active work."}],"source":{"kind":"plugin:serial-created-fixture"}}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Reply with exactly DIRECT_CHILD_OK and nothing else."},{"type":"text","text":"Your final answer is delivered automatically if your parent is still online. Include the result and relevant evidence in that answer; the parent does not receive your full transcript, tool output, or reasoning. If your last action is a message to your parent, the completion notice omits your final answer; include the result and relevant evidence in that message."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"}]}} +{"type":"turn/end","data":{"turn":1,"reason":{"kind":"aborted","reason":{"kind":"user"}}}} +{"type":"turn/start","data":{"turn":2}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} +{"type":"step/start","data":{"turn":2,"step":1}} +{"type":"system/message","data":{"turn":2,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{messageId}}"}},"surfaceOp":"append"} +{"type":"user/message","data":{"id":"{{messageId}}","role":"user","content":[{"type":"text","text":"Serial agent creation completed. Queue-clear failures still cancel active work."}],"source":{"kind":"plugin:serial-created-fixture"}},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly DIRECT_CHILD_OK and nothing else."},{"type":"text","text":"Your final answer is delivered automatically if your parent is still online. Include the result and relevant evidence in that answer; the parent does not receive your full transcript, tool output, or reasoning. If your last action is a message to your parent, the completion notice omits your final answer; include the result and relevant evidence in that message."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nDelegation depth for `subagent`: current 1, maximum 1, remaining levels 0. Do not call `subagent` at this depth; carry out the work directly.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"tool:subagent:depth","text":"Delegation depth for `subagent`: current 1, maximum 1, remaining levels 0. Do not call `subagent` at this depth; carry out the work directly."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","reasoningEffort":"high","maxTokens":256000},"adapterDefaults":{"maxTokens":true},"tools":["cordis_inspect_list","cordis_inspect_query","job_kill","job_list","job_output","snapshot_double","subagent","workflow","working_directory"]},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"smoke-model","contextWindow":1000000}} -{"type":"session/title","data":{"title":"Reply with exactly DIRECT_CHILD_OK and","messageSeqs":[12],"source":{"kind":"fallback"}}} -{"type":"session-log-deepseek/delivery-accepted","data":{"sessionId":"{{child-1}}","sessionFormatVersion":4,"throughSeq":16}} -{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["DIRECT_CHILD_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DIRECT_CHILD_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}}}]},"surfaceOp":"append"} -{"type":"step/end","data":{"turn":1,"step":1}} -{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} +{"type":"session/title","data":{"title":"Reply with exactly DIRECT_CHILD_OK and","messageSeqs":[20],"source":{"kind":"fallback"}}} +{"type":"session-log-deepseek/delivery-accepted","data":{"sessionId":"{{child-1}}","sessionFormatVersion":4,"throughSeq":24}} +{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"DIRECT_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["DIRECT_CHILD_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DIRECT_CHILD_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}}}]},"surfaceOp":"append"} +{"type":"step/end","data":{"turn":2,"step":1}} +{"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/scripts/snapshots/python-sdk-single-exe/advanced/session.2.v4.jsonl b/scripts/snapshots/python-sdk-single-exe/advanced/session.2.v4.jsonl index 9c7213f1fd..817f66b355 100644 --- a/scripts/snapshots/python-sdk-single-exe/advanced/session.2.v4.jsonl +++ b/scripts/snapshots/python-sdk-single-exe/advanced/session.2.v4.jsonl @@ -8,15 +8,23 @@ {"type":"turn/start","data":{"turn":1}} {"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} -{"type":"step/start","data":{"turn":1,"step":1}} -{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{messageId}}"}},"surfaceOp":"append"} -{"type":"user/message","data":{"id":"{{messageId}}","role":"user","content":[{"type":"text","text":"Serial agent creation completed."}],"source":{"kind":"plugin:serial-created-fixture"}},"surfaceOp":"append"} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"id":"{{messageId}}","role":"user","content":[{"type":"text","text":"Cancellation checkpoint probe."}],"source":{"kind":"plugin:serial-created-fixture"}}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[],"outcome":"canceled"}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"id":"{{messageId}}","role":"user","content":[{"type":"text","text":"Serial agent creation completed. Queue-clear failures still cancel active work."}],"source":{"kind":"plugin:serial-created-fixture"}}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Reply with exactly WORKFLOW_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"}]}} +{"type":"turn/end","data":{"turn":1,"reason":{"kind":"aborted","reason":{"kind":"user"}}}} +{"type":"turn/start","data":{"turn":2}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} +{"type":"step/start","data":{"turn":2,"step":1}} +{"type":"system/message","data":{"turn":2,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{messageId}}"}},"surfaceOp":"append"} +{"type":"user/message","data":{"id":"{{messageId}}","role":"user","content":[{"type":"text","text":"Serial agent creation completed. Queue-clear failures still cancel active work."}],"source":{"kind":"plugin:serial-created-fixture"}},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly WORKFLOW_CHILD_OK and nothing else."}],"source":{"kind":"user"},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"} {"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations.\n\nApproval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`).\n\nDelegation depth for `subagent`: current 1, maximum 1, remaining levels 0. Do not call `subagent` at this depth; carry out the work directly.\n\nYou are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: danger-full-access. The DSH file sandbox does not restrict file modifications by available operations."},{"name":"approval:policy","text":"Approval prompts are disabled in this session: actions that require approval are rejected automatically — do not request sandbox escalation (do not set `sandbox_permissions`)."},{"name":"tool:subagent:depth","text":"Delegation depth for `subagent`: current 1, maximum 1, remaining levels 0. Do not call `subagent` at this depth; carry out the work directly."},{"name":"subagent:delegation","text":"You are a delegated subagent: your permission scope was fixed when you were started and cannot be widened from inside this session — operations that require approval are rejected automatically. When the task needs access beyond that scope, do not retry the denied operation; state the limitation in your reply so the delegating agent can handle it."}]},"role":"user","id":"{{messageId}}"},"surfaceOp":"append"} {"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"smoke-model","reasoningEffort":"high","maxTokens":256000},"adapterDefaults":{"maxTokens":true},"tools":["cordis_inspect_list","cordis_inspect_query","job_kill","job_list","job_output","snapshot_double","subagent","workflow","working_directory"]},"reason":"initial"}} {"type":"request/context","data":{"provider":"deepseek-official","model":"smoke-model","contextWindow":1000000}} -{"type":"session/title","data":{"title":"Reply with exactly WORKFLOW_CHILD_OK and","messageSeqs":[12],"source":{"kind":"fallback"}}} -{"type":"session-log-deepseek/delivery-accepted","data":{"sessionId":"{{child-2}}","sessionFormatVersion":4,"throughSeq":16}} -{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"WORKFLOW_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["WORKFLOW_CHILD_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"WORKFLOW_CHILD_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}}}]},"surfaceOp":"append"} -{"type":"step/end","data":{"turn":1,"step":1}} -{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} +{"type":"session/title","data":{"title":"Reply with exactly WORKFLOW_CHILD_OK and","messageSeqs":[20],"source":{"kind":"fallback"}}} +{"type":"session-log-deepseek/delivery-accepted","data":{"sessionId":"{{child-2}}","sessionFormatVersion":4,"throughSeq":24}} +{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"WORKFLOW_CHILD_OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":0,"index":0,"dt":[],"texts":["WORKFLOW_CHILD_OK"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"WORKFLOW_CHILD_OK"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"stop"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"text"}]}}}]},"surfaceOp":"append"} +{"type":"step/end","data":{"turn":2,"step":1}} +{"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}} diff --git a/snapshots/sdk/serial-created/session.v4.jsonl b/snapshots/sdk/serial-created/session.v4.jsonl index 93cc0348f2..5109ba2c62 100644 --- a/snapshots/sdk/serial-created/session.v4.jsonl +++ b/snapshots/sdk/serial-created/session.v4.jsonl @@ -7,15 +7,12 @@ {"type":"turn/start","data":{"turn":1}} {"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}} {"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} -{"type":"step/start","data":{"turn":1,"step":1}} -{"type":"system/message","data":{"turn":1,"step":1,"message":{"role":"system","content":[{"type":"text","text":"{{system}}"}],"source":{"kind":"system-prompt"},"id":"{{message:3}}"}},"surfaceOp":"append"} -{"type":"user/message","data":{"id":"{{message:1}}","role":"user","content":[{"type":"text","text":"Serial agent creation completed. Ordinary and nested forks are independent; historical fork metadata remains readable."}],"source":{"kind":"plugin:serial-created-fixture"}},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly: SDK snapshot OK"}],"source":{"kind":"user"},"role":"user","id":"{{message:2}}"},"surfaceOp":"append"} -{"type":"user/message","data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\n\nCurrent working directory: \"{{cwd}}\".\n\nCurrent DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable.\n\nApproval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed.\n\nYou are the root agent. Your name is \"lead\".\n\nTo message other agents, use send_message({ agent: \"\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their names."}],"source":{"kind":"runtime-context","form":"snapshot","sections":[{"name":"working-directory:current","text":"Current working directory: \"{{cwd}}\"."},{"name":"sandbox:policy","text":"Current DSH file policy: workspace-write. Any available operation enforced by the DSH file sandbox may modify files under the session workspace: \"{{cwd}}\". Some platform temporary areas may also be writable."},{"name":"approval:policy","text":"Approval prompts are enabled in this session: operations that require approval may ask through the configured answerers; without an available answerer, the request fails closed."},{"name":"subagent:identity","text":"You are the root agent. Your name is \"lead\".\n\nTo message other agents, use send_message({ agent: \"\", message: \"...\" }).\nUse list_agents({}) to find your collaborator agents and their names."}]},"role":"user","id":"{{message:4}}"},"surfaceOp":"append"} -{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}} -{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}} -{"type":"session/title","data":{"title":"Reply with exactly: SDK snapshot","messageSeqs":[11],"source":{"kind":"fallback"}}} -{"type":"session-log-deepseek/delivery-accepted","data":{"sessionId":"{{session:1}}","sessionFormatVersion":4,"throughSeq":15}} -{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly \"SDK snapshot OK\". Let me do that."},{"type":"text","text":"SDK snapshot OK"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:5}}"},"usage":{"inputTokens":1769,"outputTokens":24,"cacheReadTokens":0,"reasoningTokens":19},"stream":[{"type":"chunk","time":1788943826008,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1788943826008,"index":0,"dt":[0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," \"","SD","K"," snapshot"," OK","\"."," Let"," me"," do"," that","."]},{"type":"chunk","time":1788943826009,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1788943826009,"index":1,"dt":[0,0,0],"texts":["SD","K"," snapshot"," OK"]},{"type":"chunk","time":1788943826009,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly \"SDK snapshot OK\". Let me do that."}}},{"type":"chunk","time":1788943826009,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"SDK snapshot OK"}}},{"type":"chunk","time":1788943826009,"chunk":{"type":"usage","usage":{"inputTokens":1769,"outputTokens":24,"cacheReadTokens":0,"reasoningTokens":19}}},{"type":"chunk","time":1788943826009,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"} -{"type":"step/end","data":{"turn":1,"step":1}} -{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"id":"{{message:3}}","role":"user","content":[{"type":"text","text":"Cancellation checkpoint probe."}],"source":{"kind":"plugin:serial-created-fixture"}}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[],"outcome":"canceled"}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"id":"{{message:1}}","role":"user","content":[{"type":"text","text":"Serial agent creation completed. Ordinary and nested forks are independent; historical fork metadata remains readable. Queue-clear failures still cancel active work."}],"source":{"kind":"plugin:serial-created-fixture"}}]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Reply with exactly: SDK snapshot OK"}],"source":{"kind":"user"},"role":"user","id":"{{message:2}}"}]}} +{"type":"turn/end","data":{"turn":1,"reason":{"kind":"aborted","reason":{"kind":"user"}}}} +{"type":"turn/start","data":{"turn":2}} +{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}} +{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}} +{"type":"turn/end","data":{"turn":2,"reason":{"kind":"aborted","reason":{"kind":"disposed"}}}}