fix(subagent): preserve published run failures

This commit is contained in:
Dudu-0223
2026-07-31 14:02:03 +08:00
committed by Tianyi Cui
parent 8b0a7a5d8d
commit a977ef30ee
57 changed files with 444 additions and 208 deletions

View File

@@ -228,6 +228,17 @@ const SCENARIOS: Scenario[] = [
recorded: false,
configPath: SUBAGENT_DURABILITY_FAILURE_CONFIG,
},
// The in-process child is published before its first follow-up fails. The
// foreground tool retains both that run-result failure and an independent
// published-handle disposal failure.
{
name: 'subagent-published-run-failure',
env: { DSH_SUBAGENT_PUBLISHED_FAILURE: '1' },
hasModelTurn: true,
recorded: false,
overridden: true,
configPath: SUBAGENT_DURABILITY_FAILURE_CONFIG,
},
// Authored durable-catalog transcript: the snapshot-only lifecycle marker
// fences the second parent turn behind the child's Activation end, so
// `list_agents` deterministically reads the persisted child as complete.

View File

@@ -2,13 +2,13 @@ import type { Context } from 'cordis'
import { SessionId } from '@deepseek-ai/dsh-session'
export const name = 'subagent-durability-failure'
export const inject = ['sessionPersistence', 'subagents']
export const inject = ['agents', 'sessionPersistence', 'subagents']
/**
* The authored parent transcript names the background child by a stable
* placeholder id, but the live continuable child is minted with a fresh random
* session id at run time. This snapshot-only overlay bridges that gap and forces
* a deterministic ordering plus a failing final child durability checkpoint:
* deterministic ordering plus authored child durability failures:
*
* - `PLACEHOLDER_CHILD_ID` in a scripted `send_message` is remapped to the real
* child so both follow-ups queue onto the same live inbox in FIFO order.
@@ -18,6 +18,10 @@ export const inject = ['sessionPersistence', 'subagents']
* - The child's final continuation turn fails its durability checkpoint with a
* fixed message, so the scenario proves child-first disposal survives a failed
* last flush.
* - Under `DSH_SUBAGENT_PUBLISHED_FAILURE`, a one-shot child's first
* follow-up fails after publication, so its model prompt never runs; its
* published handle then also fails disposal, so the parent observes both
* independent failures.
*/
const PLACEHOLDER_CHILD_ID = '33333333-3333-4333-8333-333333333333'
const UNKNOWN_CHILD_ID = '22222222-2222-4222-8222-222222222222'
@@ -27,8 +31,26 @@ const FAILED_CHECKPOINT_TURN = 3
/** Fail the child checkpoint and stabilize the authored follow-up failure ordering. */
export function apply(ctx: Context): void {
const followupsAccepted = Promise.withResolvers<undefined>()
const publishedFailure = process.env.DSH_SUBAGENT_PUBLISHED_FAILURE === '1'
const persistence = ctx.sessionPersistence
const load = persistence.load.bind(persistence)
const agents = ctx.agents
const create = agents.create.bind(agents)
agents.create = async (options) => {
const handle = await create(options)
if (!publishedFailure || options.meta?.parentSession === undefined) return handle
handle.agent.followup = () => {
throw new Error('snapshot published run failed')
}
return {
...handle,
async dispose() {
await handle.dispose()
throw new Error('snapshot published handle disposal failed')
},
}
}
// The unavailable-child lookup is real asynchronous I/O. Fence it behind both
// authored follow-ups so runner speed cannot reorder the exact log.
@@ -37,6 +59,7 @@ export function apply(ctx: Context): void {
return load.call(persistence, id)
}
ctx.effect(() => () => {
agents.create = create
persistence.load = load
followupsAccepted.resolve(undefined)
}, 'subagent snapshot ordering')

View File

@@ -0,0 +1,7 @@
{
"steps": [
{ "op": "initialize" },
{ "op": "newSession" },
{ "op": "prompt", "text": "Delegate one foreground subagent. Its published run will fail; report that failure as PARENT_OBSERVED_ERROR." }
]
}

View File

@@ -0,0 +1,22 @@
[
{
"kind": "chunks",
"chunks": [
{ "type": "block-start", "index": 0, "blockType": "tool-call" },
{ "type": "tool-call-delta", "index": 0, "id": "call_published_failure", "name": "subagent", "argumentsDelta": "{\"description\":\"Fail published run\",\"prompt\":\"This child prompt must never run.\"}" },
{ "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "call_published_failure", "name": "subagent", "arguments": "{\"description\":\"Fail published run\",\"prompt\":\"This child prompt must never run.\"}" } },
{ "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 5 } },
{ "type": "finish", "reason": { "kind": "tool-calls" } }
]
},
{
"kind": "chunks",
"chunks": [
{ "type": "block-start", "index": 0, "blockType": "text" },
{ "type": "text-delta", "index": 0, "text": "PARENT_OBSERVED_ERROR" },
{ "type": "block-end", "index": 0, "block": { "type": "text", "text": "PARENT_OBSERVED_ERROR" } },
{ "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 2 } },
{ "type": "finish", "reason": { "kind": "stop" } }
]
}
]

View File

@@ -0,0 +1,26 @@
{"type":"session","version":0,"id":"11111111-1111-4111-8111-111111111111","createdAt":1000,"cwd":"{{cwd}}","delegationDepth":0}
{"type":"turn/start","seq":0,"time":0,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}}
{"type":"user/message","seq":1,"time":0,"data":{"content":[{"type":"text","text":"Delegate one foreground subagent. Its published run will fail; report that failure as PARENT_OBSERVED_ERROR."}],"source":{"kind":"user"},"role":"user","id":"8e9c68d6-2472-4db2-af41-2a3fb9f319ac"},"surfaceOp":"append"}
{"type":"session/title","seq":2,"time":0,"data":{"title":"Delegate one foreground subagent. Its","messageSeqs":[1],"source":{"kind":"fallback"}}}
{"type":"user/message","seq":3,"time":1785537317609,"data":{"content":[{"type":"text","text":"Current runtime context. This snapshot supersedes earlier runtime-context snapshots.\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`)."}],"source":{"kind":"plugin","plugin":"@deepseek-ai/dsh-system-prompt"},"role":"user","id":"f36ed168-0e1b-4043-83ab-dc6416e50133"},"surfaceOp":"append"}
{"type":"step/start","seq":4,"time":1785537317609,"data":{"turn":1,"step":1}}
{"type":"request/header","seq":5,"time":1785537317610,"data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","seq":6,"time":1785537317610,"data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"assistant/chunk","seq":7,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}}}
{"type":"assistant/chunk","seq":8,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"tool-call-delta","index":0,"id":"call_published_failure","name":"subagent","argumentsDelta":"{\"description\":\"Fail published run\",\"prompt\":\"This child prompt must never run.\"}"}}}
{"type":"assistant/chunk","seq":9,"time":0,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_published_failure","name":"subagent","arguments":"{\"description\":\"Fail published run\",\"prompt\":\"This child prompt must never run.\"}"}}}}
{"type":"assistant/chunk","seq":10,"time":1785537317619,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}}}
{"type":"assistant/chunk","seq":11,"time":1785537317619,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}}
{"type":"assistant/message","seq":12,"time":1785537317619,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_published_failure","name":"subagent","arguments":"{\"description\":\"Fail published run\",\"prompt\":\"This child prompt must never run.\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"b2a2fabd-99fa-46e2-bd27-15f6588d3a83"},"usage":{"inputTokens":10,"outputTokens":5}},"sourceEventSeqs":[7,8,9,10,11],"surfaceOp":"append"}
{"type":"tool/call","seq":13,"time":1785537317619,"data":{"turn":1,"step":1,"callId":"call_published_failure","name":"subagent","arguments":"{\"description\":\"Fail published run\",\"prompt\":\"This child prompt must never run.\"}"}}
{"type":"tool/result","seq":14,"time":1785537317629,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_published_failure"},"content":[{"type":"tool-result","toolCallId":"call_published_failure","content":[{"type":"text","text":"Error: subagent run failed: Error: snapshot published run failed; dispose failed: Error: snapshot published handle disposal failed"}],"isError":true}],"role":"user","id":"4fa4173b-33b1-4c3e-a9fd-1e1a0670aeca"}},"sourceEventSeqs":[13],"surfaceOp":"append"}
{"type":"step/end","seq":15,"time":1785537317629,"data":{"turn":1,"step":1}}
{"type":"step/start","seq":16,"time":1785537317637,"data":{"turn":1,"step":2}}
{"type":"assistant/chunk","seq":17,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"text"}}}
{"type":"assistant/chunk","seq":18,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"text-delta","index":0,"text":"PARENT_OBSERVED_ERROR"}}}
{"type":"assistant/chunk","seq":19,"time":0,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"PARENT_OBSERVED_ERROR"}}}}
{"type":"assistant/chunk","seq":20,"time":1785537317641,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":2}}}}
{"type":"assistant/chunk","seq":21,"time":1785537317641,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}}
{"type":"assistant/message","seq":22,"time":1785537317642,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"PARENT_OBSERVED_ERROR"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"5c686beb-7780-404a-818e-e51f4f233f16"},"usage":{"inputTokens":10,"outputTokens":2}},"sourceEventSeqs":[17,18,19,20,21],"surfaceOp":"append"}
{"type":"step/end","seq":23,"time":1785537317642,"data":{"turn":1,"step":2}}
{"type":"turn/end","seq":24,"time":1785537317642,"data":{"turn":1,"reason":{"kind":"completed"}}}

View File

@@ -0,0 +1,4 @@
{"jsonrpc":"2.0","id":1,"result":{"protocolVersion":1,"agentInfo":{"name":"deepseek-harness-acp","version":"0.0.1"},"agentCapabilities":{"promptCapabilities":{"image":false,"audio":false,"embeddedContext":false}},"authMethods":[]}}
{"jsonrpc":"2.0","id":2,"result":{"sessionId":"{{sessionId}}"}}
{"jsonrpc":"2.0","method":"session/update","params":{"sessionId":"{{sessionId}}","update":{"sessionUpdate":"agent_message_chunk","content":{"type":"text","text":"PARENT_OBSERVED_ERROR"}}}}
{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}}