From a70f1a2b7a8afaa87c0eeff0087b58fad892bf09 Mon Sep 17 00:00:00 2001 From: Tianyi Cui <53024+tianyicui@users.noreply.github.com> Date: Tue, 28 Jul 2026 21:32:10 +0800 Subject: [PATCH] test(acp): remove vacuous policy inheritance scenario The ACP fixture configured read-only as the deployment default for both parent and child. Its delegated write therefore remained denied even with inheritance disabled, so the scenario could not fail on the regression it claimed to protect. Delete the overlay, scenario registration, sessions, prompt, and 473-line tool-schema sidecar. The Loader-booted headless snapshot remains the real composition guard: only its parent carries read-only while the deployment default is workspace-write, so removing inheritance makes the child write reach disk and fails the test. Keeping one discriminating snapshot avoids 662 lines of duplicated fixture data and makes the review evidence correspond to the actual security boundary. --- .../subagent-inheritance.cordis.snapshot.yml | 45 -- .../acp-agent/subagent-inheritance.cordis.yml | 25 - examples/acp-agent/tests/acp.snapshot.ts | 17 - .../subagent-sandbox-inheritance/input.json | 14 - .../session.1.jsonl | 30 -- .../session.jsonl | 30 -- .../stdout.expected.jsonl | 4 - .../system-prompt.expected.md | 24 - .../tool-schemas.expected.json | 473 ------------------ 9 files changed, 662 deletions(-) delete mode 100644 examples/acp-agent/subagent-inheritance.cordis.snapshot.yml delete mode 100644 examples/acp-agent/subagent-inheritance.cordis.yml delete mode 100644 examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/input.json delete mode 100644 examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/session.1.jsonl delete mode 100644 examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/session.jsonl delete mode 100644 examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/stdout.expected.jsonl delete mode 100644 examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/system-prompt.expected.md delete mode 100644 examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/tool-schemas.expected.json diff --git a/examples/acp-agent/subagent-inheritance.cordis.snapshot.yml b/examples/acp-agent/subagent-inheritance.cordis.snapshot.yml deleted file mode 100644 index a7a9302822..0000000000 --- a/examples/acp-agent/subagent-inheritance.cordis.snapshot.yml +++ /dev/null @@ -1,45 +0,0 @@ -# Keyless replay counterpart of subagent-inheritance.cordis.yml: the same -# flash pin plus the standard replay swaps (disable the key-requiring adapter, -# passthrough sandbox runner, insert llm-replay). Patches do not compose -# across nested includes, so everything applies together over the live tree. -- id: base - name: '@cordisjs/plugin-include' - config: - path: ./cordis.yml - patches: - - id: llm-deepseek - name: '@deepseek-ai/dsh-llm-deepseek' - disabled: true - - id: sandbox - name: '@deepseek-ai/dsh-sandbox-local' - config: - runnerCommand: - - bash - - -c - - while [ "$1" != "--" ]; do shift; done; shift; exec "$@" - - passthrough-runner - runnerFailureSignatures: - - 'passthrough-runner: profile rejected' - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: none - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. - - Verify your work by running the code or tests. Keep answers brief and factual. - - insert: - - id: llm-replay - name: '@deepseek-ai/dsh-llm-replay' - config: - providers: - - id: deepseek - name: DeepSeek - models: - - id: deepseek-v4-flash - - id: deepseek-v4-pro diff --git a/examples/acp-agent/subagent-inheritance.cordis.yml b/examples/acp-agent/subagent-inheritance.cordis.yml deleted file mode 100644 index 2757d95d1f..0000000000 --- a/examples/acp-agent/subagent-inheritance.cordis.yml +++ /dev/null @@ -1,25 +0,0 @@ -# Subagent-under-confinement snapshot overlay: pin the recorded model to -# deepseek-v4-flash so this scenario's request headers match the recorded -# sandbox-class corpus (cordis.yml ships deepseek-v4-pro for live use). The -# read-only policy itself comes from the scenario's DSH_PERMISSION_MODE env — -# the automation protocol has no session-scoped picker, so deployment policy -# is the lever ([downgrade rationale in the scenario table]). A config patch -# replaces the whole target config, so base fields are restated verbatim. -- id: base - name: '@cordisjs/plugin-include' - config: - path: ./cordis.yml - patches: - - id: acp-agent - name: '@deepseek-ai/dsh-acp-demo' - config: - provider: deepseek - model: deepseek-v4-flash - persistenceRoot: !!js process.env.DSH_SNAPSHOT_SESSIONS_ROOT ?? './.sessions' - persistenceCompression: !!js "process.env.DSH_SNAPSHOT === undefined ? 'zstd' : 'none'" - workspaceContext: - maxBytes: 65536 - persona: | - You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. - - Verify your work by running the code or tests. Keep answers brief and factual. diff --git a/examples/acp-agent/tests/acp.snapshot.ts b/examples/acp-agent/tests/acp.snapshot.ts index 9f3007b770..a6c91bd5cd 100644 --- a/examples/acp-agent/tests/acp.snapshot.ts +++ b/examples/acp-agent/tests/acp.snapshot.ts @@ -38,7 +38,6 @@ const SESSION_QUERY_CONFIG = fileURLToPath(new URL('../session-query.cordis.yml' const PTY_CONFIG = fileURLToPath(new URL('../pty.cordis.yml', import.meta.url)) const DEPTH_TWO_CONFIG = fileURLToPath(new URL('../depth-two.cordis.yml', import.meta.url)) const SESSION_SANDBOX_ROOT_CONFIG = fileURLToPath(new URL('../session-sandbox-root.cordis.yml', import.meta.url)) -const SUBAGENT_INHERITANCE_CONFIG = fileURLToPath(new URL('../subagent-inheritance.cordis.yml', import.meta.url)) const RETRY_CONFIG = fileURLToPath(new URL('../retry.cordis.yml', import.meta.url)) const SESSION_TITLE_CONFIG = fileURLToPath(new URL('../session-title.cordis.yml', import.meta.url)) const LSP_CONFIG = fileURLToPath(new URL('./lsp.cordis.yml', import.meta.url)) @@ -264,22 +263,6 @@ const SCENARIOS: Scenario[] = [ headerClass: 'sandbox', env: { DSH_PERMISSION_MODE: 'workspace-write' }, }, - // Delegation under a read-only DEPLOYMENT policy: the child's real write is - // denied by the fs fence and its denial report surfaces in the parent - // transcript. This is the strength the automation protocol can express — no - // session-scoped picker exists to tighten only the parent, so the - // parent-override-vs-wider-default half of the inheritance contract stays - // pinned by the real-wall unit suite (the subagent policy-inheritance Agent - // Note records the downgrade). - { - name: 'subagent-sandbox-inheritance', - hasModelTurn: true, - recorded: true, - pinsHeader: true, - headerClass: 'subagent-inheritance', - configPath: SUBAGENT_INHERITANCE_CONFIG, - env: { DSH_PERMISSION_MODE: 'read-only' }, - }, // Unlike ordinary snapshots, this session cwd is outside the platform temp // roots that workspace-write always grants. The overlay points the // deployment fallback at /tmp, so a successful relative write proves the diff --git a/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/input.json b/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/input.json deleted file mode 100644 index d0be3ce419..0000000000 --- a/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/input.json +++ /dev/null @@ -1,14 +0,0 @@ -{ - "steps": [ - { - "op": "initialize" - }, - { - "op": "newSession" - }, - { - "op": "prompt", - "text": "Use the subagent tool exactly once to delegate this subtask to a child agent: 'Use the write tool exactly once with file_path set to exactly the relative path inherited.txt (never an absolute path) and content escaped. If the write is denied, do not retry and do not request escalation; reply with the single word CHILD_DENIED and the exact denial marker line from the tool result. If it succeeds, reply with the single word CHILD_WROTE.' After the subagent returns, state in one short sentence whether the child was denied by the sandbox, quoting the denial marker if there is one, then reply with the single word PARENT_DONE and stop. Do not use the bash or write tools yourself and do not request escalation." - } - ] -} diff --git a/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/session.1.jsonl b/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/session.1.jsonl deleted file mode 100644 index 577d03330d..0000000000 --- a/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/session.1.jsonl +++ /dev/null @@ -1,30 +0,0 @@ -{"type":"session","version":0,"id":"7fcdaf99-35c9-4ad6-a872-fc04fbfe4ee6","createdAt":1784961244926,"cwd":"/var/folders/2g/b32ct0qn1d728l_v6tdkjytr0000gn/T/acp-snap-cwd-XKPvGB","parentSession":"03d6a514-045b-4f61-9a8f-1b5165c3a648","delegationDepth":1} -{"type":"turn/start","seq":0,"time":1784961244928,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}} -{"type":"user/message","seq":1,"time":1784961244928,"data":{"content":[{"type":"text","text":"You have access to the write tool. Use the write tool exactly once with file_path set to exactly the relative path \"inherited.txt\" (never an absolute path) and content set to \"Child agent wrote this file.\" (escaped as needed). \n\nIf the write is denied by the sandbox (look for \"[sandbox: file access denied\" in the result), do NOT retry and do NOT request escalation. Reply with the single word CHILD_DENIED followed by a space and then the exact denial marker line from the tool result.\n\nIf the write succeeds, reply with the single word CHILD_WROTE.\n\nDo not use any other tools or do anything else."}],"source":{"kind":"user"},"role":"user","id":"794cb9e6-5770-40bf-a0c6-69da4a71fe01"},"surfaceOp":"append"} -{"type":"session/title","seq":2,"time":1784961244929,"data":{"title":"You have access to the","messageSeqs":[1],"source":{"kind":"fallback"}}} -{"type":"step/start","seq":3,"time":1784961244932,"data":{"turn":1,"step":1}} -{"type":"request/header","seq":4,"time":1784961244933,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} -{"type":"assistant/chunk","seq":5,"time":1784961245908,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} -{"type":"reasoning-chunks","seq0":6,"time0":1784961245909,"data":{"turn":1,"step":1,"index":0,"dt":[145,8,0,1,0,46,1,0,0,16,1,0,30,1,32,1,0,0,0,1,29,0,1,34,0,1,0,30,1,30,0,0,32,33,1,0,0,1,0,28,33,1,0,0,33,0,1,0,0,31,0,1,0,0,30,0],"texts":["The"," user"," wants"," me"," to"," use"," the"," write"," tool"," exactly"," once"," with"," file","_path","=\"","inher","ited",".txt","\""," and"," content","=\"","Child"," agent"," wrote"," this"," file",".\"."," If"," denied",","," I"," should"," reply"," with"," CH","ILD","_D","EN","IED"," followed"," by"," the"," denial"," marker","."," If"," successful",","," reply"," with"," CH","ILD","_W","RO","TE","."]}} -{"type":"assistant/chunk","seq":63,"time":1784961246638,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}} -{"type":"tool-call-chunks","seq0":64,"time0":1784961246639,"data":{"turn":1,"step":1,"index":1,"dt":[1,0,31,1,0,0,31,1,0,0,1,63,1,0,0,1,32,1,0,0,0,1,32,0,1],"id":"call_00_7iJutQqZ95RcVbXUefTC5135","name":"write","args":["","{","\"","file","_path","\"",": ","\"","inher","ited",".txt","\"",", ","\"","content","\"",": ","\"","Child"," agent"," wrote"," this"," file",".","\"","}"]}} -{"type":"assistant/chunk","seq":90,"time":1784961246903,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to use the write tool exactly once with file_path=\"inherited.txt\" and content=\"Child agent wrote this file.\". If denied, I should reply with CHILD_DENIED followed by the denial marker. If successful, reply with CHILD_WROTE."}}}} -{"type":"assistant/chunk","seq":91,"time":1784961246903,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_7iJutQqZ95RcVbXUefTC5135","name":"write","arguments":"{\"file_path\": \"inherited.txt\", \"content\": \"Child agent wrote this file.\"}"}}}} -{"type":"assistant/chunk","seq":92,"time":1784961246903,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":5377,"outputTokens":123,"cacheReadTokens":0,"reasoningTokens":57}}}} -{"type":"assistant/chunk","seq":93,"time":1784961246904,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","seq":94,"time":1784961246905,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to use the write tool exactly once with file_path=\"inherited.txt\" and content=\"Child agent wrote this file.\". If denied, I should reply with CHILD_DENIED followed by the denial marker. If successful, reply with CHILD_WROTE."},{"type":"tool-call","id":"call_00_7iJutQqZ95RcVbXUefTC5135","name":"write","arguments":"{\"file_path\": \"inherited.txt\", \"content\": \"Child agent wrote this file.\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"aacd17fa-e534-4a19-8e8a-db123fc0b7b1"},"usage":{"inputTokens":5377,"outputTokens":123,"cacheReadTokens":0,"reasoningTokens":57}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93],"surfaceOp":"append"} -{"type":"tool/call","seq":95,"time":1784961246905,"data":{"turn":1,"step":1,"callId":"call_00_7iJutQqZ95RcVbXUefTC5135","name":"write","arguments":"{\"file_path\": \"inherited.txt\", \"content\": \"Child agent wrote this file.\"}"}} -{"type":"tool/result","seq":96,"time":1784961246918,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_00_7iJutQqZ95RcVbXUefTC5135"},"content":[{"type":"tool-result","toolCallId":"call_00_7iJutQqZ95RcVbXUefTC5135","content":[{"type":"text","text":"Error: [sandbox: file access denied under read-only mode]\n[sandbox: escalation available — retry this exact operation once with sandbox_permissions (the narrowest wider mode that suffices) + justification; the approval prompt asks the user]"}],"isError":true}],"role":"user","id":"2c6046db-7862-4010-b614-4fd09850bedb"},"error":{"name":"FsError","code":"FS_SANDBOX_DENIED"}},"sourceEventSeqs":[95],"surfaceOp":"append"} -{"type":"step/end","seq":97,"time":1784961246924,"data":{"turn":1,"step":1}} -{"type":"step/start","seq":98,"time":1784961246925,"data":{"turn":1,"step":2}} -{"type":"assistant/chunk","seq":99,"time":1784961247934,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} -{"type":"reasoning-chunks","seq0":100,"time0":1784961247934,"data":{"turn":1,"step":2,"index":0,"dt":[119,43,2,1,0,20,0,0,1,0,33,1,0,0,0,0,36,0,0,1,32,2,1,0,0,0,25,0,0,0,1,36,1,0,0,0,25,1,0,0,0,0,31,1,0,0,0,34,1,0,1,0,0,33,0,0,1,32,1,0,0,0,32,0,1,0,0,35,0,0,0,0,1,32,1,0,0,0,1,32,38,0,0,1,0,29,1,0,0,1,0,30,0,0,1,0,32,0,0,1,36,1,28,1,0,0,0,1,34,1,0,0,0,0,29,1,0,0,0],"texts":["The"," write"," was"," denied"," by"," the"," sand","box"," under"," read","-only"," mode","."," According"," to"," the"," instructions",":"," \"","If"," the"," write"," is"," denied"," by"," the"," sand","box"," (","look"," for"," \"[","sand","box",":"," file"," access"," denied","\""," in"," the"," result","),"," do"," NOT"," ret","ry"," and"," do"," NOT"," request"," escalation","."," Reply"," with"," the"," single"," word"," CH","ILD","_D","EN","IED"," followed"," by"," a"," space"," and"," then"," the"," exact"," denial"," marker"," line"," from"," the"," tool"," result",".\"\n\n","The"," denial"," marker"," line"," is",":"," \"[","sand","box",":"," file"," access"," denied"," under"," read","-only"," mode","]","\"\n\n","So"," I"," should"," reply",":"," CH","ILD","_D","EN","IED"," [","sand","box",":"," file"," access"," denied"," under"," read","-only"," mode","]"]}} -{"type":"assistant/chunk","seq":220,"time":1784961248883,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"text"}}} -{"type":"text-chunks","seq0":221,"time0":1784961248884,"data":{"turn":1,"step":2,"index":1,"dt":[0,0,0,0,0,30,1,0,0,0,0,35,0,0,0,1],"texts":["CH","ILD","_D","EN","IED"," [","sand","box",":"," file"," access"," denied"," under"," read","-only"," mode","]"]}} -{"type":"assistant/chunk","seq":238,"time":1784961248951,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The write was denied by the sandbox under read-only mode. According to the instructions: \"If the write is denied by the sandbox (look for \"[sandbox: file access denied\" in the result), do NOT retry and do NOT request escalation. Reply with the single word CHILD_DENIED followed by a space and then the exact denial marker line from the tool result.\"\n\nThe denial marker line is: \"[sandbox: file access denied under read-only mode]\"\n\nSo I should reply: CHILD_DENIED [sandbox: file access denied under read-only mode]"}}}} -{"type":"assistant/chunk","seq":239,"time":1784961248951,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}}}} -{"type":"assistant/chunk","seq":240,"time":1784961248951,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":188,"outputTokens":138,"cacheReadTokens":5376,"reasoningTokens":120}}}} -{"type":"assistant/chunk","seq":241,"time":1784961248951,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} -{"type":"assistant/message","seq":242,"time":1784961248952,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The write was denied by the sandbox under read-only mode. According to the instructions: \"If the write is denied by the sandbox (look for \"[sandbox: file access denied\" in the result), do NOT retry and do NOT request escalation. Reply with the single word CHILD_DENIED followed by a space and then the exact denial marker line from the tool result.\"\n\nThe denial marker line is: \"[sandbox: file access denied under read-only mode]\"\n\nSo I should reply: CHILD_DENIED [sandbox: file access denied under read-only mode]"},{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"7fb28534-4a44-435d-8d15-403f7ee1bebd"},"usage":{"inputTokens":188,"outputTokens":138,"cacheReadTokens":5376,"reasoningTokens":120}},"sourceEventSeqs":[99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241],"surfaceOp":"append"} -{"type":"step/end","seq":243,"time":1784961248963,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":244,"time":1784961248963,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/session.jsonl b/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/session.jsonl deleted file mode 100644 index 383e179fe5..0000000000 --- a/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/session.jsonl +++ /dev/null @@ -1,30 +0,0 @@ -{"type":"session","version":0,"id":"03d6a514-045b-4f61-9a8f-1b5165c3a648","createdAt":1784961240155,"cwd":"/var/folders/2g/b32ct0qn1d728l_v6tdkjytr0000gn/T/acp-snap-cwd-XKPvGB","delegationDepth":0} -{"type":"turn/start","seq":0,"time":1784961240159,"data":{"turn":1,"trigger":{"kind":"message","source":{"kind":"user"}}}} -{"type":"user/message","seq":1,"time":1784961240159,"data":{"content":[{"type":"text","text":"Use the subagent tool exactly once to delegate this subtask to a child agent: 'Use the write tool exactly once with file_path set to exactly the relative path inherited.txt (never an absolute path) and content escaped. If the write is denied, do not retry and do not request escalation; reply with the single word CHILD_DENIED and the exact denial marker line from the tool result. If it succeeds, reply with the single word CHILD_WROTE.' After the subagent returns, state in one short sentence whether the child was denied by the sandbox, quoting the denial marker if there is one, then reply with the single word PARENT_DONE and stop. Do not use the bash or write tools yourself and do not request escalation."}],"source":{"kind":"user"},"role":"user","id":"6ac76657-3b3d-4661-ac5f-973dbf9a8b82"},"surfaceOp":"append"} -{"type":"session/title","seq":2,"time":1784961240160,"data":{"title":"Use the subagent tool exactly","messageSeqs":[1],"source":{"kind":"fallback"}}} -{"type":"step/start","seq":3,"time":1784961240164,"data":{"turn":1,"step":1}} -{"type":"request/header","seq":4,"time":1784961240164,"data":{"header":{"config":{"provider":"deepseek","model":"deepseek-v4-flash"},"system":"{{system}}","tools":"{{tools}}"},"reason":"initial"}} -{"type":"assistant/chunk","seq":5,"time":1784961241327,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} -{"type":"reasoning-chunks","seq0":6,"time0":1784961241328,"data":{"turn":1,"step":1,"index":0,"dt":[11,32,1,0,0,0,0,32,0,0,1,0,0,57,1,2,0,33,0,0,44,0,0,29,1,31,26,1,0,0,0,40,31,1,0,0,25,1,0,45,19,0,0,1,0,0,41,1,0,0,0,22,1,0,0,0,0,33,0,0,1,30,0,0,0,0,33,1,0,0,0,49,0,0,1,0,17,1,0,0,0,0,28,1,0,0,0,0,39,0,1,0,0,30,1,0,26,1,0,0,31,1,0,41,1,0,0,0,0,25,0,0,0,34,1,0,0,0,0,39,0,31,0,0,0,0,0,30,1,0,0,0,30,1,24,0,0,1,0,0,44,1,18,2,1,0,0,0,31,1,0,0,0,30,39,1,0,0,0,1,27,1,0,0,0,0,29,1,0,0,0,0,37,1,0,0,26,1,0,32,1,0,31,1,0,0,33,0,0,33,1,0,0,31,1,0,1,30,1,0,33,1,0,0,30,33,31,1,32,1,0,1,30,33,0,1],"texts":["The"," user"," wants"," me"," to"," delegate"," a"," subt","ask"," to"," a"," sub","agent","."," The"," subt","ask"," is",":"," use"," the"," write"," tool"," to"," write"," to"," `","inher","ited",".txt","`"," with"," content"," escaped","."," If"," denied",","," reply"," with"," CH","ILD","_D","EN","IED"," and"," the"," denial"," marker","."," If"," succeeds",","," reply"," with"," CH","ILD","_W","RO","TE",".\n\n","After"," the"," sub","agent"," returns",","," I"," need"," to"," state"," whether"," the"," child"," was"," denied"," by"," the"," sand","box",","," quoting"," the"," denial"," marker"," if"," there"," is"," one",","," then"," reply"," with"," PAR","ENT","_D","ONE",".\n\n","Let"," me"," carefully"," construct"," the"," sub","agent"," prompt",".\n\n","The"," sub","agent"," should",":\n","1","."," Use"," the"," write"," tool"," with"," file","_path"," ="," \"","inher","ited",".txt","\""," (","relative"," path",")\n","2","."," If"," the"," write"," is"," denied",","," reply"," with"," \"","CH","ILD","_D","EN","IED","\""," and"," the"," exact"," denial"," marker"," line"," from"," the"," tool"," result","\n","3","."," If"," it"," succeeds",","," reply"," with"," \"","CH","ILD","_W","RO","TE","\"\n\n","I"," need"," to"," make"," sure"," the"," sub","agent"," prompt"," is"," self","-contained"," and"," clear","."," The"," sub","agent"," doesn","'t"," share"," this"," conversation"," context",","," so"," I"," need"," to"," explain"," what"," tools"," it"," has"," available",".\n\n","Let"," me"," craft"," the"," prompt"," carefully","."]}} -{"type":"assistant/chunk","seq":223,"time":1784961243256,"data":{"turn":1,"step":1,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}}} -{"type":"tool-call-chunks","seq0":224,"time0":1784961243257,"data":{"turn":1,"step":1,"index":1,"dt":[31,1,0,0,0,31,0,1,0,31,34,31,1,0,0,0,32,2,0,0,30,0,1,31,1,0,0,32,0,31,1,0,0,0,34,0,1,0,30,1,0,0,1,0,32,1,0,0,1,0,31,1,0,31,1,32,1,32,1,33,1,41,1,0,23,35,0,34,29,1,42,1,1,0,21,1,34,1,0,0,33,31,1,32,0,0,1,31,1,0,31,0,0,33,1,0,0,0,1,31,1,0,0,31,0,1,38,1,0,25,0,0,33,1,1,32,1,32,1,0,1,39,0,1,0,25,1,0,0,1,30,1,0,0,0,32,1,0,0,0,0,31,0,0,33,1,0,0,0,33,0,0,43,0,25,0,0,0,30],"id":"call_00_ZydZ4RS0mQwZaEcUr82s4359","name":"subagent","args":["","{","\"","description","\"",": ","\"","Write"," inherited",".txt"," task","\"",", ","\"","prom","pt","\"",": ","\"","You"," have"," access"," to"," the"," write"," tool","."," Use"," the"," write"," tool"," exactly"," once"," with"," file","_path"," set"," to"," exactly"," the"," relative"," path"," \\\"","inher","ited",".txt","\\\""," (","never"," an"," absolute"," path",")"," and"," content"," set"," to"," \\\"","Child"," agent"," wrote"," this"," file",".\\\""," (","esc","aped"," as"," needed",")."," \\n\\n","If"," the"," write"," is"," denied"," by"," the"," sand","box"," (","look"," for"," \\\"[","sand","box",":"," file"," access"," denied","\\\""," in"," the"," result","),"," do"," NOT"," ret","ry"," and"," do"," NOT"," request"," escalation","."," Reply"," with"," the"," single"," word"," CH","ILD","_D","EN","IED"," followed"," by"," a"," space"," and"," then"," the"," exact"," denial"," marker"," line"," from"," the"," tool"," result",".\\n\\n","If"," the"," write"," succeeds",","," reply"," with"," the"," single"," word"," CH","ILD","_W","RO","TE",".\\n\\n","Do"," not"," use"," any"," other"," tools"," or"," do"," anything"," else",".","\"","}"]}} -{"type":"assistant/chunk","seq":384,"time":1784961244901,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to delegate a subtask to a subagent. The subtask is: use the write tool to write to `inherited.txt` with content escaped. If denied, reply with CHILD_DENIED and the denial marker. If succeeds, reply with CHILD_WROTE.\n\nAfter the subagent returns, I need to state whether the child was denied by the sandbox, quoting the denial marker if there is one, then reply with PARENT_DONE.\n\nLet me carefully construct the subagent prompt.\n\nThe subagent should:\n1. Use the write tool with file_path = \"inherited.txt\" (relative path)\n2. If the write is denied, reply with \"CHILD_DENIED\" and the exact denial marker line from the tool result\n3. If it succeeds, reply with \"CHILD_WROTE\"\n\nI need to make sure the subagent prompt is self-contained and clear. The subagent doesn't share this conversation context, so I need to explain what tools it has available.\n\nLet me craft the prompt carefully."}}}} -{"type":"assistant/chunk","seq":385,"time":1784961244902,"data":{"turn":1,"step":1,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_ZydZ4RS0mQwZaEcUr82s4359","name":"subagent","arguments":"{\"description\": \"Write inherited.txt task\", \"prompt\": \"You have access to the write tool. Use the write tool exactly once with file_path set to exactly the relative path \\\"inherited.txt\\\" (never an absolute path) and content set to \\\"Child agent wrote this file.\\\" (escaped as needed). \\n\\nIf the write is denied by the sandbox (look for \\\"[sandbox: file access denied\\\" in the result), do NOT retry and do NOT request escalation. Reply with the single word CHILD_DENIED followed by a space and then the exact denial marker line from the tool result.\\n\\nIf the write succeeds, reply with the single word CHILD_WROTE.\\n\\nDo not use any other tools or do anything else.\"}"}}}} -{"type":"assistant/chunk","seq":386,"time":1784961244902,"data":{"turn":1,"step":1,"chunk":{"type":"usage","usage":{"inputTokens":5393,"outputTokens":418,"cacheReadTokens":0,"reasoningTokens":217}}}} -{"type":"assistant/chunk","seq":387,"time":1784961244902,"data":{"turn":1,"step":1,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}} -{"type":"assistant/message","seq":388,"time":1784961244908,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to delegate a subtask to a subagent. The subtask is: use the write tool to write to `inherited.txt` with content escaped. If denied, reply with CHILD_DENIED and the denial marker. If succeeds, reply with CHILD_WROTE.\n\nAfter the subagent returns, I need to state whether the child was denied by the sandbox, quoting the denial marker if there is one, then reply with PARENT_DONE.\n\nLet me carefully construct the subagent prompt.\n\nThe subagent should:\n1. Use the write tool with file_path = \"inherited.txt\" (relative path)\n2. If the write is denied, reply with \"CHILD_DENIED\" and the exact denial marker line from the tool result\n3. If it succeeds, reply with \"CHILD_WROTE\"\n\nI need to make sure the subagent prompt is self-contained and clear. The subagent doesn't share this conversation context, so I need to explain what tools it has available.\n\nLet me craft the prompt carefully."},{"type":"tool-call","id":"call_00_ZydZ4RS0mQwZaEcUr82s4359","name":"subagent","arguments":"{\"description\": \"Write inherited.txt task\", \"prompt\": \"You have access to the write tool. Use the write tool exactly once with file_path set to exactly the relative path \\\"inherited.txt\\\" (never an absolute path) and content set to \\\"Child agent wrote this file.\\\" (escaped as needed). \\n\\nIf the write is denied by the sandbox (look for \\\"[sandbox: file access denied\\\" in the result), do NOT retry and do NOT request escalation. Reply with the single word CHILD_DENIED followed by a space and then the exact denial marker line from the tool result.\\n\\nIf the write succeeds, reply with the single word CHILD_WROTE.\\n\\nDo not use any other tools or do anything else.\"}"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"4e48758d-52ba-437d-aa4a-8f0a9b02eb23"},"usage":{"inputTokens":5393,"outputTokens":418,"cacheReadTokens":0,"reasoningTokens":217}},"sourceEventSeqs":[5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120,121,122,123,124,125,126,127,128,129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148,149,150,151,152,153,154,155,156,157,158,159,160,161,162,163,164,165,166,167,168,169,170,171,172,173,174,175,176,177,178,179,180,181,182,183,184,185,186,187,188,189,190,191,192,193,194,195,196,197,198,199,200,201,202,203,204,205,206,207,208,209,210,211,212,213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229,230,231,232,233,234,235,236,237,238,239,240,241,242,243,244,245,246,247,248,249,250,251,252,253,254,255,256,257,258,259,260,261,262,263,264,265,266,267,268,269,270,271,272,273,274,275,276,277,278,279,280,281,282,283,284,285,286,287,288,289,290,291,292,293,294,295,296,297,298,299,300,301,302,303,304,305,306,307,308,309,310,311,312,313,314,315,316,317,318,319,320,321,322,323,324,325,326,327,328,329,330,331,332,333,334,335,336,337,338,339,340,341,342,343,344,345,346,347,348,349,350,351,352,353,354,355,356,357,358,359,360,361,362,363,364,365,366,367,368,369,370,371,372,373,374,375,376,377,378,379,380,381,382,383,384,385,386,387],"surfaceOp":"append"} -{"type":"tool/call","seq":389,"time":1784961244910,"data":{"turn":1,"step":1,"callId":"call_00_ZydZ4RS0mQwZaEcUr82s4359","name":"subagent","arguments":"{\"description\": \"Write inherited.txt task\", \"prompt\": \"You have access to the write tool. Use the write tool exactly once with file_path set to exactly the relative path \\\"inherited.txt\\\" (never an absolute path) and content set to \\\"Child agent wrote this file.\\\" (escaped as needed). \\n\\nIf the write is denied by the sandbox (look for \\\"[sandbox: file access denied\\\" in the result), do NOT retry and do NOT request escalation. Reply with the single word CHILD_DENIED followed by a space and then the exact denial marker line from the tool result.\\n\\nIf the write succeeds, reply with the single word CHILD_WROTE.\\n\\nDo not use any other tools or do anything else.\"}"}} -{"type":"tool/result","seq":390,"time":1784961248973,"data":{"turn":1,"step":1,"message":{"source":{"kind":"tool","callId":"call_00_ZydZ4RS0mQwZaEcUr82s4359"},"content":[{"type":"tool-result","toolCallId":"call_00_ZydZ4RS0mQwZaEcUr82s4359","content":[{"type":"text","text":"CHILD_DENIED [sandbox: file access denied under read-only mode]"}],"isError":false}],"role":"user","id":"65c6c0b4-8566-4e90-9095-2f72c69ce227"}},"sourceEventSeqs":[389],"surfaceOp":"append"} -{"type":"step/end","seq":391,"time":1784961248981,"data":{"turn":1,"step":1}} -{"type":"step/start","seq":392,"time":1784961248982,"data":{"turn":1,"step":2}} -{"type":"assistant/chunk","seq":393,"time":1784961249963,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}}} -{"type":"reasoning-chunks","seq0":394,"time0":1784961249964,"data":{"turn":1,"step":2,"index":0,"dt":[107,31,1,0,32,0,0,0,1,0,33,0,0,1,0,0,33,1,0,0,0,0,36,1,0,0,29,1,33,1,0,0,0,34,2,0,1,0,32,1,0,0,32,1,0,0,0,34,0,1,0,0,0,35,0,1,0,0,0,31,1,0,0,0,0,33,1,0,0,1,0,32,0,1,0,0,32,1,0,32,1,0,1,0,32],"texts":["The"," sub","agent"," returned"," \"","CH","ILD","_D","EN","IED"," [","sand","box",":"," file"," access"," denied"," under"," read","-only"," mode","]","\"."," So"," the"," write"," was"," denied"," by"," the"," sand","box"," because"," it","'s"," in"," read","-only"," mode",".\n\n","I"," need"," to",":\n","1","."," State"," in"," one"," short"," sentence"," whether"," the"," child"," was"," denied"," by"," the"," sand","box",","," quoting"," the"," denial"," marker"," if"," there"," is"," one",".\n","2","."," Then"," reply"," with"," \"","PAR","ENT","_D","ONE","\".\n\n","Let"," me"," do"," that","."]}} -{"type":"assistant/chunk","seq":480,"time":1784961250679,"data":{"turn":1,"step":2,"chunk":{"type":"block-start","index":1,"blockType":"text"}}} -{"type":"text-chunks","seq0":481,"time0":1784961250679,"data":{"turn":1,"step":2,"index":1,"dt":[0,0,0,33,1,0,0,0,1,32,1,0,0,0,0,32,1,0,1,0,0,33,1,0,0,1,36,1,1],"texts":["The"," child"," was"," denied"," by"," the"," sand","box"," –"," denial"," marker",":"," `","[","sand","box",":"," file"," access"," denied"," under"," read","-only"," mode","]","`.\n\n","PAR","ENT","_D","ONE"]}} -{"type":"assistant/chunk","seq":511,"time":1784961250854,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The subagent returned \"CHILD_DENIED [sandbox: file access denied under read-only mode]\". So the write was denied by the sandbox because it's in read-only mode.\n\nI need to:\n1. State in one short sentence whether the child was denied by the sandbox, quoting the denial marker if there is one.\n2. Then reply with \"PARENT_DONE\".\n\nLet me do that."}}}} -{"type":"assistant/chunk","seq":512,"time":1784961250854,"data":{"turn":1,"step":2,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"The child was denied by the sandbox – denial marker: `[sandbox: file access denied under read-only mode]`.\n\nPARENT_DONE"}}}} -{"type":"assistant/chunk","seq":513,"time":1784961250854,"data":{"turn":1,"step":2,"chunk":{"type":"usage","usage":{"inputTokens":80,"outputTokens":117,"cacheReadTokens":5760,"reasoningTokens":86}}}} -{"type":"assistant/chunk","seq":514,"time":1784961250854,"data":{"turn":1,"step":2,"chunk":{"type":"finish","reason":{"kind":"stop"}}}} -{"type":"assistant/message","seq":515,"time":1784961250855,"data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The subagent returned \"CHILD_DENIED [sandbox: file access denied under read-only mode]\". So the write was denied by the sandbox because it's in read-only mode.\n\nI need to:\n1. State in one short sentence whether the child was denied by the sandbox, quoting the denial marker if there is one.\n2. Then reply with \"PARENT_DONE\".\n\nLet me do that."},{"type":"text","text":"The child was denied by the sandbox – denial marker: `[sandbox: file access denied under read-only mode]`.\n\nPARENT_DONE"}],"source":{"kind":"model","provider":"deepseek","model":"deepseek-v4-flash"},"id":"258d0ae6-5800-4f06-8f3d-d9988b8dedde"},"usage":{"inputTokens":80,"outputTokens":117,"cacheReadTokens":5760,"reasoningTokens":86}},"sourceEventSeqs":[393,394,395,396,397,398,399,400,401,402,403,404,405,406,407,408,409,410,411,412,413,414,415,416,417,418,419,420,421,422,423,424,425,426,427,428,429,430,431,432,433,434,435,436,437,438,439,440,441,442,443,444,445,446,447,448,449,450,451,452,453,454,455,456,457,458,459,460,461,462,463,464,465,466,467,468,469,470,471,472,473,474,475,476,477,478,479,480,481,482,483,484,485,486,487,488,489,490,491,492,493,494,495,496,497,498,499,500,501,502,503,504,505,506,507,508,509,510,511,512,513,514],"surfaceOp":"append"} -{"type":"step/end","seq":516,"time":1784961250868,"data":{"turn":1,"step":2}} -{"type":"turn/end","seq":517,"time":1784961250869,"data":{"turn":1,"reason":{"kind":"completed"}}} diff --git a/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/stdout.expected.jsonl b/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/stdout.expected.jsonl deleted file mode 100644 index 6f0c6507ad..0000000000 --- a/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/stdout.expected.jsonl +++ /dev/null @@ -1,4 +0,0 @@ -{"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":"The child was denied by the sandbox – denial marker: `[sandbox: file access denied under read-only mode]`.\n\nPARENT_DONE"}}}} -{"jsonrpc":"2.0","id":3,"result":{"stopReason":"end_turn"}} diff --git a/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/system-prompt.expected.md b/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/system-prompt.expected.md deleted file mode 100644 index e3437ad61a..0000000000 --- a/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/system-prompt.expected.md +++ /dev/null @@ -1,24 +0,0 @@ -You are an AI agent powered by the DeepSeek Harness SDK. - -You are a coding assistant powered by the deepseek-v4-flash model. Your working directory is {{cwd}}. Your bash tool runs under a file sandbox — a `[sandbox: file access denied …]` result is policy, not a command bug. - -Verify your work by running the code or tests. Keep answers brief and factual. - - -Use the read tool — not shell commands like cat — to inspect text files. Results include line numbers. Use offset and limit to continue reading large files. - -Use the write tool to create files or completely replace file contents. Existing files are overwritten, so read an existing file first (the default fs-policy requires it) and prefer edit for targeted changes. - -Use the edit tool for targeted changes to existing UTF-8 text files. It replaces literal old_string with new_string; by default old_string must appear exactly once. If old_string appears multiple times, provide a more specific old_string or set replace_all to true. Read the file first (the default fs-policy requires it), unless you just created or edited it in this session. - -Check the [exit code: N] marker on every bash result; investigate failures before moving on. - -Track every background task id you start. You are notified in-session when a task finishes — do not busy-poll or sleep on one; keep working on independent steps and do not duplicate a running task's work. Before giving a final answer, collect every still-relevant task with task_output (set wait: true only when you are genuinely blocked on it), and task_kill tasks that stopped mattering. - -Use goal tools for one long-running completion objective in the current session. create_goal may infer goal intent from a direct human request in any language; do not create a goal for routine single-turn work. Call get_goal before update_goal and copy its exact goal_id and revision. After session resume or fork, an active goal is disarmed: when a human asks to continue or resume in any wording or language, use update_goal action resume to rearm it. Mark complete only when the objective is actually achieved. Mark blocked only after the same blocking condition persists for at least 3 consecutive goal rounds, and report that concrete condition in blocked_reason; difficulty, uncertainty, or useful remaining work is not blocked. - - - -Use the workflow tool ONLY when the user explicitly asks for a workflow or for large multi-agent orchestration: you write a JavaScript script (the tool description documents the exact format) that fans work out across many subagents with phases and structured results. For one or two delegations, prefer plain subagent calls. - -Use the ralph tool ONLY when the direct human explicitly asks for a Ralph loop or fresh-agent iterative execution. Each Ralph round starts a fresh child with no conversation seed and uses the shared workspace as durable memory. Completion and blockers are worker reports, not independent evaluation. Use same-session goal tools for ordinary long-running objectives, and plain subagents or workflows for bounded delegation and fan-out. diff --git a/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/tool-schemas.expected.json b/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/tool-schemas.expected.json deleted file mode 100644 index 01ac777a42..0000000000 --- a/examples/acp-agent/tests/snapshots/subagent-sandbox-inheritance/tool-schemas.expected.json +++ /dev/null @@ -1,473 +0,0 @@ -{ - "initial": [ - { - "name": "bash", - "description": "Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Set `run_in_background: true` for long-running commands: the call returns a task id immediately; read its output with `task_output` and stop it with `task_kill`. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", - "parameters": { - "type": "object", - "properties": { - "command": { - "type": "string", - "description": "The bash command to execute." - }, - "description": { - "type": "string", - "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"npm install\" → \"Install package dependencies\"." - }, - "timeoutMs": { - "type": "number", - "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." - }, - "workdir": { - "type": "string", - "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." - }, - "run_in_background": { - "type": "boolean", - "description": "Run in the background and return a task id immediately (collect with task_output, stop with task_kill). No timeout applies." - }, - "sandbox_permissions": { - "type": "string", - "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", - "enum": [ - "workspace-write", - "danger-full-access" - ] - }, - "justification": { - "type": "string", - "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." - } - }, - "required": [ - "command", - "description" - ] - } - }, - { - "name": "create_goal", - "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", - "parameters": { - "type": "object", - "properties": { - "objective": { - "type": "string", - "description": "The concrete completion objective inferred from the direct human request." - }, - "max_goal_rounds": { - "type": "number", - "description": "Optional positive safe-integer limit on automatic continuation rounds." - } - }, - "required": [ - "objective" - ] - } - }, - { - "name": "edit", - "description": "Edit an existing UTF-8 text file by replacing literal text.", - "parameters": { - "type": "object", - "properties": { - "file_path": { - "type": "string", - "description": "Path to edit, resolved by the filesystem backend." - }, - "old_string": { - "type": "string", - "description": "Literal text to replace. Must match exactly." - }, - "new_string": { - "type": "string", - "description": "Literal replacement text. Use an empty string to delete the match." - }, - "replace_all": { - "type": "boolean", - "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." - }, - "sandbox_permissions": { - "type": "string", - "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", - "enum": [ - "workspace-write", - "danger-full-access" - ] - }, - "justification": { - "type": "string", - "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." - } - }, - "required": [ - "file_path", - "old_string", - "new_string" - ] - } - }, - { - "name": "get_goal", - "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", - "parameters": { - "type": "object", - "properties": {} - } - }, - { - "name": "ralph", - "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", - "parameters": { - "type": "object", - "properties": { - "objective": { - "type": "string", - "description": "The immutable completion objective for every fresh Ralph round." - }, - "maxRounds": { - "type": "number", - "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." - } - }, - "required": [ - "objective" - ] - } - }, - { - "name": "read", - "description": "Read a UTF-8 text file and return line-numbered content.", - "parameters": { - "type": "object", - "properties": { - "file_path": { - "type": "string", - "description": "Path to read, resolved by the filesystem backend." - }, - "offset": { - "type": "number", - "description": "1-based first line to return. Defaults to 1." - }, - "limit": { - "type": "number", - "description": "Maximum number of lines to return. Defaults to 2000." - } - }, - "required": [ - "file_path" - ] - } - }, - { - "name": "skill", - "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", - "parameters": { - "type": "object", - "properties": { - "name": { - "type": "string", - "description": "The exact skill name from the available skills list." - } - }, - "required": [ - "name" - ] - } - }, - { - "name": "subagent", - "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) and return its final result. Use this to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent runs to completion and you receive only its final answer, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. Set `run_in_background: true` to return a task id; collect with `task_output` and stop with `task_kill`.", - "parameters": { - "type": "object", - "properties": { - "description": { - "type": "string", - "description": "A short (3-5 word) description of the delegated task, for display." - }, - "prompt": { - "type": "string", - "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." - }, - "run_in_background": { - "type": "boolean", - "description": "Run as a background task and return its id; collect with task_output or stop with task_kill." - } - }, - "required": [ - "description", - "prompt" - ] - } - }, - { - "name": "subagent_fork", - "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn), returning only its final result. Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive only its final answer, not its intermediate steps. Set `run_in_background: true` to return a task id; collect with `task_output` and stop with `task_kill`.", - "parameters": { - "type": "object", - "properties": { - "description": { - "type": "string", - "description": "A short (3-5 word) description of the delegated task, for display." - }, - "prompt": { - "type": "string", - "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." - }, - "run_in_background": { - "type": "boolean", - "description": "Run as a background task and return its id; collect with task_output or stop with task_kill." - } - }, - "required": [ - "description", - "prompt" - ] - } - }, - { - "name": "task_kill", - "description": "Request cancellation of a running background task by task id. Returns immediately; the task settles as killed once its work actually stops.", - "parameters": { - "type": "object", - "properties": { - "task_id": { - "type": "string", - "description": "Task id returned by the tool that started the background work." - }, - "reason": { - "type": "string", - "description": "Optional short reason, recorded in the log and forwarded to the task." - } - }, - "required": [ - "task_id" - ] - } - }, - { - "name": "task_list", - "description": "List your background tasks (running and finished) with their ids, kinds, and statuses.", - "parameters": { - "type": "object", - "properties": {} - } - }, - { - "name": "task_output", - "description": "Read a background task. Stream tasks return only output since the previous read; final-output tasks return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", - "parameters": { - "type": "object", - "properties": { - "task_id": { - "type": "string", - "description": "Task id returned by the tool that started the background work." - }, - "wait": { - "type": "boolean", - "description": "Block until the task reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the task alive." - }, - "timeout_ms": { - "type": "number", - "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." - } - }, - "required": [ - "task_id" - ] - } - }, - { - "name": "todo_write", - "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Keep AT MOST ONE todo `in_progress` at a time; while work remains, exactly one active task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", - "parameters": { - "type": "object", - "properties": { - "todos": { - "type": "array", - "description": "The COMPLETE task list, replacing any previous list.", - "items": { - "type": "object", - "additionalProperties": false, - "properties": { - "content": { - "type": "string", - "description": "What the task is — a short imperative line." - }, - "status": { - "type": "string", - "description": "pending (not started) | in_progress (now) | completed (done).", - "enum": [ - "pending", - "in_progress", - "completed" - ] - } - }, - "required": [ - "content", - "status" - ] - } - } - }, - "required": [ - "todos" - ] - } - }, - { - "name": "update_goal", - "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", - "parameters": { - "type": "object", - "properties": { - "goal_id": { - "type": "string", - "description": "Exact id returned by get_goal." - }, - "revision": { - "type": "number", - "description": "Exact positive revision returned by get_goal." - }, - "action": { - "type": "string", - "description": "edit | pause | resume | complete | blocked", - "enum": [ - "edit", - "pause", - "resume", - "complete", - "blocked" - ] - }, - "objective": { - "type": "string", - "description": "Replacement objective; valid only with action edit." - }, - "max_goal_rounds": { - "type": "number", - "description": "Replacement cap; valid only with action edit." - }, - "blocked_reason": { - "type": "string", - "description": "Concrete blocking condition; required only with action blocked." - } - }, - "required": [ - "goal_id", - "revision", - "action" - ] - } - }, - { - "name": "workflow", - "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", - "parameters": { - "type": "object", - "properties": { - "script": { - "type": "string", - "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." - }, - "meta": { - "type": "object", - "description": "The workflow identity block (plain JSON — never code).", - "additionalProperties": true, - "properties": { - "name": { - "type": "string", - "description": "Short kebab-case workflow name." - }, - "description": { - "type": "string", - "description": "One-line description of what the workflow does." - }, - "whenToUse": { - "type": "string", - "description": "Optional guidance on when this workflow applies." - }, - "phases": { - "type": "array", - "description": "Optional phase declarations matched by phase() calls.", - "items": { - "type": "object", - "additionalProperties": true, - "properties": { - "title": { - "type": "string", - "description": "The phase title phase() calls match by exact string." - }, - "detail": { - "type": "string", - "description": "Optional one-line description of the phase." - }, - "provider": { - "type": "string", - "description": "Optional provider override this phase is expected to use." - }, - "model": { - "type": "string", - "description": "Optional model override this phase is expected to use." - } - }, - "required": [ - "title" - ] - } - } - }, - "required": [ - "name", - "description" - ] - }, - "args": { - "type": "object", - "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", - "additionalProperties": true - } - }, - "required": [ - "script", - "meta" - ] - } - }, - { - "name": "write", - "description": "Create or fully replace a UTF-8 text file.", - "parameters": { - "type": "object", - "properties": { - "file_path": { - "type": "string", - "description": "Path to write, resolved by the filesystem backend." - }, - "content": { - "type": "string", - "description": "Full UTF-8 text content to write." - }, - "sandbox_permissions": { - "type": "string", - "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", - "enum": [ - "workspace-write", - "danger-full-access" - ] - }, - "justification": { - "type": "string", - "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." - } - }, - "required": [ - "file_path", - "content" - ] - } - } - ], - "changes": [] -}