fix(subagent): preserve published run failures
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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')
|
||||
|
||||
@@ -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." }
|
||||
]
|
||||
}
|
||||
@@ -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" } }
|
||||
]
|
||||
}
|
||||
]
|
||||
@@ -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"}}}
|
||||
@@ -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"}}
|
||||
Reference in New Issue
Block a user