diff --git a/docs/persistence-changes/2026-10-05-working-directory-attribution.i18n.yaml b/docs/persistence-changes/2026-10-05-working-directory-attribution.i18n.yaml
index 1ae5d09e4f..a986760966 100644
--- a/docs/persistence-changes/2026-10-05-working-directory-attribution.i18n.yaml
+++ b/docs/persistence-changes/2026-10-05-working-directory-attribution.i18n.yaml
@@ -21,8 +21,8 @@
en: 39494aa3af994e6f
zh: 07c2880503668176
/2026-10-05-working-directory-attribution/verification:
- en: 5963942c0d87e518
- zh: 5dbb6ac0fca1f279
+ en: 4b2565ea453bbf60
+ zh: 5932a25f63ebd2bc
/2026-10-05-working-directory-attribution/dev-note:
en: b75c8458928b72b9
zh: 003ba4e17b4b5269
diff --git a/docs/persistence-changes/2026-10-05-working-directory-attribution.md b/docs/persistence-changes/2026-10-05-working-directory-attribution.md
index 80996eee5c..2ab35d3f52 100644
--- a/docs/persistence-changes/2026-10-05-working-directory-attribution.md
+++ b/docs/persistence-changes/2026-10-05-working-directory-attribution.md
@@ -52,7 +52,7 @@ Existing records remain valid. The additive working-directory source kind is att
## Verification
-pnpm exec vitest run packages/session/working-directory/tests packages/shell/tool-bash/tests/tools.spec.ts packages/shell/tool-pwsh/tests/tools.spec.ts packages/subagent/subagent-in-process-driver/tests/schedule-tools.spec.ts: 4 files and 203 tests passed.
+pnpm exec vitest run packages/session/working-directory/tests packages/shell/tool-bash/tests/tools.spec.ts packages/shell/tool-pwsh/tests/tools.spec.ts packages/subagent/subagent/tests/schedule-tools.spec.ts: 4 files and 203 tests passed.
## Dev Note
diff --git a/docs/persistence-changes/2026-10-05-working-directory-attribution.zh.md b/docs/persistence-changes/2026-10-05-working-directory-attribution.zh.md
index e7ed00e08f..6aa1f6f0b0 100644
--- a/docs/persistence-changes/2026-10-05-working-directory-attribution.zh.md
+++ b/docs/persistence-changes/2026-10-05-working-directory-attribution.zh.md
@@ -52,7 +52,7 @@ changes:
## 验证
-pnpm exec vitest run packages/session/working-directory/tests packages/shell/tool-bash/tests/tools.spec.ts packages/shell/tool-pwsh/tests/tools.spec.ts packages/subagent/subagent-in-process-driver/tests/schedule-tools.spec.ts:4 个文件、203 个测试通过。
+pnpm exec vitest run packages/session/working-directory/tests packages/shell/tool-bash/tests/tools.spec.ts packages/shell/tool-pwsh/tests/tools.spec.ts packages/subagent/subagent/tests/schedule-tools.spec.ts:4 个文件、203 个测试通过。
## 开发备注
diff --git a/packages/workflow/workflow-ptc/src/host.ts b/packages/workflow/workflow-ptc/src/host.ts
index f5f1019a74..7a2266e778 100644
--- a/packages/workflow/workflow-ptc/src/host.ts
+++ b/packages/workflow/workflow-ptc/src/host.ts
@@ -282,11 +282,11 @@ export class PtcWorkflowRun implements WorkflowRun {
private async drive(): Promise {
let result: WorkflowResult
try {
+ const cwd = await this.cwd
+ this.requireActive()
if (this.runtime.sandboxMode === undefined && this.policy.mode !== 'danger-full-access') {
throw new Error(`workflow runtime cannot enforce the ${this.policy.mode} file policy`)
}
- const cwd = await this.cwd
- this.requireActive()
const outcome = await this.runtime.run(this.runtime.resolve({
program: this.runtime.programFormat === 'repl' ? `await (async () => {\n${PROGRAM}\n})()` : PROGRAM,
bindings: [{ global: 'workflowHost', functions: this.bindings() }],
diff --git a/packages/workflow/workflow-ptc/tests/host.spec.ts b/packages/workflow/workflow-ptc/tests/host.spec.ts
index 09430f3cf6..a849d1c140 100644
--- a/packages/workflow/workflow-ptc/tests/host.spec.ts
+++ b/packages/workflow/workflow-ptc/tests/host.spec.ts
@@ -61,6 +61,22 @@ async function setup(execute?: (bindings: HostBindings, spec: PtcRunSpec) => Pro
}
describe('workflow host callback validation', () => {
+ it('settles a failed directory lookup when the runtime cannot confine execution', async () => {
+ const { ctx, runtime, start } = await setup()
+ runtime.policyMode = undefined
+ const ensureDirectory = vi.spyOn(ctx.workingDirectory, 'ensure').mockRejectedValue(new Error('selected directory is unavailable'))
+ const execute = vi.spyOn(runtime, 'run')
+ const run = start()
+ try {
+ expect(await run.result).toMatchObject({ stopReason: 'error', error: expect.stringContaining('selected directory is unavailable') })
+ expect(execute).not.toHaveBeenCalled()
+ } finally {
+ await run.dispose()
+ ensureDirectory.mockRestore()
+ execute.mockRestore()
+ }
+ })
+
it('retains its selected directory for the process and later children when the parent moves', async () => {
const entered = Promise.withResolvers()
const release = Promise.withResolvers()
diff --git a/snapshots/session/both-mode-turn/system-prompt.expected.md b/snapshots/session/both-mode-turn/system-prompt.expected.md
index 329e451ce8..2c3daed342 100644
--- a/snapshots/session/both-mode-turn/system-prompt.expected.md
+++ b/snapshots/session/both-mode-turn/system-prompt.expected.md
@@ -33,16 +33,16 @@ Start independent delegations with `subagent` or `subagent_fork` together in one
## Writing code for run_code
-`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
+`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
-`run_code({ description: "Show current directory", code: "return await tools.bash({ description: 'Show current directory', command: 'pwd' })" })`
+`run_code({ description: "Show current directory", code: "console.log(await tools.bash({ description: 'Show current directory', command: 'pwd' }))" })`
Inside the program:
- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.
- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.
- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.
-- Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
+- Only values emitted as program output enter the conversation. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
Program-only SDK bindings:
diff --git a/snapshots/session/both-mode-turn/tool-schemas.expected.json b/snapshots/session/both-mode-turn/tool-schemas.expected.json
index 44b97b6340..e0b6795176 100644
--- a/snapshots/session/both-mode-turn/tool-schemas.expected.json
+++ b/snapshots/session/both-mode-turn/tool-schemas.expected.json
@@ -2,7 +2,7 @@
"initial": [
{
"name": "run_code",
- "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work). Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return is program output — curate it. Image-bearing subtool results are attached after the run. Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
+ "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. Call tools as `await tools.name(args)` per the declarations in the system prompt. Curate the program output. Image-bearing subtool results are attached after the run. The program is the body of an async TypeScript function; only erasable syntax is accepted, with no enum or namespaces. Type annotations are advisory. Top-level await and return are supported. Emit output with console.log(...) or return a lossless JSON value. Only printed values and the returned value become program output. Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
"parameters": {
"type": "object",
"properties": {
@@ -12,7 +12,7 @@
},
"code": {
"type": "string",
- "description": "The program: the body of an async TypeScript function."
+ "description": "The TypeScript program to execute, following this tool's execution instructions."
},
"timeoutMs": {
"type": "number",
diff --git a/snapshots/session/mcp-resources-ptc/system-prompt.expected.md b/snapshots/session/mcp-resources-ptc/system-prompt.expected.md
index a6b0b07911..168684cc1f 100644
--- a/snapshots/session/mcp-resources-ptc/system-prompt.expected.md
+++ b/snapshots/session/mcp-resources-ptc/system-prompt.expected.md
@@ -41,16 +41,16 @@ MCP_RESOURCE_INSTRUCTION: keep {{braces}} literal. Read resources from the catal
## Writing code for run_code
-`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
+`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
-`run_code({ description: "Show current directory", code: "return await tools.bash({ description: 'Show current directory', command: 'pwd' })" })`
+`run_code({ description: "Show current directory", code: "console.log(await tools.bash({ description: 'Show current directory', command: 'pwd' }))" })`
Inside the program:
- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.
- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.
- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.
-- Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
+- Only values emitted as program output enter the conversation. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
Program-only SDK bindings:
diff --git a/snapshots/session/mcp-resources-ptc/tool-schemas.expected.json b/snapshots/session/mcp-resources-ptc/tool-schemas.expected.json
index 44b97b6340..e0b6795176 100644
--- a/snapshots/session/mcp-resources-ptc/tool-schemas.expected.json
+++ b/snapshots/session/mcp-resources-ptc/tool-schemas.expected.json
@@ -2,7 +2,7 @@
"initial": [
{
"name": "run_code",
- "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work). Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return is program output — curate it. Image-bearing subtool results are attached after the run. Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
+ "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. Call tools as `await tools.name(args)` per the declarations in the system prompt. Curate the program output. Image-bearing subtool results are attached after the run. The program is the body of an async TypeScript function; only erasable syntax is accepted, with no enum or namespaces. Type annotations are advisory. Top-level await and return are supported. Emit output with console.log(...) or return a lossless JSON value. Only printed values and the returned value become program output. Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
"parameters": {
"type": "object",
"properties": {
@@ -12,7 +12,7 @@
},
"code": {
"type": "string",
- "description": "The program: the body of an async TypeScript function."
+ "description": "The TypeScript program to execute, following this tool's execution instructions."
},
"timeoutMs": {
"type": "number",
diff --git a/snapshots/session/ptc-node-read-only/system-prompt.expected.md b/snapshots/session/ptc-node-read-only/system-prompt.expected.md
index 39529ce115..b8950c4116 100644
--- a/snapshots/session/ptc-node-read-only/system-prompt.expected.md
+++ b/snapshots/session/ptc-node-read-only/system-prompt.expected.md
@@ -33,16 +33,16 @@ Start independent delegations with `subagent` or `subagent_fork` together in one
## Writing code for run_code
-`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
+`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
-`run_code({ description: "Show current directory", code: "return await tools.bash({ description: 'Show current directory', command: 'pwd' })" })`
+`run_code({ description: "Show current directory", code: "console.log(await tools.bash({ description: 'Show current directory', command: 'pwd' }))" })`
Inside the program:
- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.
- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.
- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.
-- Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
+- Only values emitted as program output enter the conversation. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
Program-only SDK bindings:
diff --git a/snapshots/session/ptc-node-read-only/tool-schemas.expected.json b/snapshots/session/ptc-node-read-only/tool-schemas.expected.json
index 44b97b6340..e0b6795176 100644
--- a/snapshots/session/ptc-node-read-only/tool-schemas.expected.json
+++ b/snapshots/session/ptc-node-read-only/tool-schemas.expected.json
@@ -2,7 +2,7 @@
"initial": [
{
"name": "run_code",
- "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work). Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return is program output — curate it. Image-bearing subtool results are attached after the run. Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
+ "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. Call tools as `await tools.name(args)` per the declarations in the system prompt. Curate the program output. Image-bearing subtool results are attached after the run. The program is the body of an async TypeScript function; only erasable syntax is accepted, with no enum or namespaces. Type annotations are advisory. Top-level await and return are supported. Emit output with console.log(...) or return a lossless JSON value. Only printed values and the returned value become program output. Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
"parameters": {
"type": "object",
"properties": {
@@ -12,7 +12,7 @@
},
"code": {
"type": "string",
- "description": "The program: the body of an async TypeScript function."
+ "description": "The TypeScript program to execute, following this tool's execution instructions."
},
"timeoutMs": {
"type": "number",
diff --git a/snapshots/session/ptc-node-workspace/system-prompt.expected.md b/snapshots/session/ptc-node-workspace/system-prompt.expected.md
index 39529ce115..b8950c4116 100644
--- a/snapshots/session/ptc-node-workspace/system-prompt.expected.md
+++ b/snapshots/session/ptc-node-workspace/system-prompt.expected.md
@@ -33,16 +33,16 @@ Start independent delegations with `subagent` or `subagent_fork` together in one
## Writing code for run_code
-`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
+`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
-`run_code({ description: "Show current directory", code: "return await tools.bash({ description: 'Show current directory', command: 'pwd' })" })`
+`run_code({ description: "Show current directory", code: "console.log(await tools.bash({ description: 'Show current directory', command: 'pwd' }))" })`
Inside the program:
- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.
- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.
- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.
-- Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
+- Only values emitted as program output enter the conversation. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
Program-only SDK bindings:
diff --git a/snapshots/session/ptc-node-workspace/tool-schemas.expected.json b/snapshots/session/ptc-node-workspace/tool-schemas.expected.json
index 44b97b6340..e0b6795176 100644
--- a/snapshots/session/ptc-node-workspace/tool-schemas.expected.json
+++ b/snapshots/session/ptc-node-workspace/tool-schemas.expected.json
@@ -2,7 +2,7 @@
"initial": [
{
"name": "run_code",
- "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work). Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return is program output — curate it. Image-bearing subtool results are attached after the run. Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
+ "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. Call tools as `await tools.name(args)` per the declarations in the system prompt. Curate the program output. Image-bearing subtool results are attached after the run. The program is the body of an async TypeScript function; only erasable syntax is accepted, with no enum or namespaces. Type annotations are advisory. Top-level await and return are supported. Emit output with console.log(...) or return a lossless JSON value. Only printed values and the returned value become program output. Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
"parameters": {
"type": "object",
"properties": {
@@ -12,7 +12,7 @@
},
"code": {
"type": "string",
- "description": "The program: the body of an async TypeScript function."
+ "description": "The TypeScript program to execute, following this tool's execution instructions."
},
"timeoutMs": {
"type": "number",
diff --git a/snapshots/session/ptc-persistent/cordis.snapshot.yml b/snapshots/session/ptc-persistent/cordis.snapshot.yml
index 22392c070b..d11c1b5551 100644
--- a/snapshots/session/ptc-persistent/cordis.snapshot.yml
+++ b/snapshots/session/ptc-persistent/cordis.snapshot.yml
@@ -37,7 +37,7 @@
name: '@deepseek-ai/dsh-system-prompt'
config:
personaPrefix: |
- You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+ You are a coding assistant powered by the {{model}} model.
Verify your work by running the code or tests. Keep answers brief and factual.
diff --git a/snapshots/session/ptc-persistent/cordis.yml b/snapshots/session/ptc-persistent/cordis.yml
index 14e82fab98..e9ae813596 100644
--- a/snapshots/session/ptc-persistent/cordis.yml
+++ b/snapshots/session/ptc-persistent/cordis.yml
@@ -31,7 +31,7 @@
name: '@deepseek-ai/dsh-system-prompt'
config:
personaPrefix: |
- You are a coding assistant powered by the {{model}} model. Your working directory is {{cwd}}.
+ You are a coding assistant powered by the {{model}} model.
Verify your work by running the code or tests. Keep answers brief and factual.
diff --git a/snapshots/session/ptc-persistent/session.v4.jsonl b/snapshots/session/ptc-persistent/session.v4.jsonl
index da086aa3a5..e048c13789 100644
--- a/snapshots/session/ptc-persistent/session.v4.jsonl
+++ b/snapshots/session/ptc-persistent/session.v4.jsonl
@@ -8,7 +8,7 @@
{"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:2}}"}},"surfaceOp":"append"}
{"type":"user/message","data":{"content":[{"type":"text","text":"Retain a counter, imported module and tool method across run_code calls, then recover after the execution process exits. Save the observed counter and recovery in persistent-count.txt."}],"source":{"kind":"user"},"role":"user","id":"{{message:1}}"},"surfaceOp":"append"}
-{"type":"user/message","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":"runtime-context","form":"snapshot","sections":[{"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`)."}]},"role":"user","id":"{{message:3}}"},"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`)."}],"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`)."}]},"role":"user","id":"{{message:3}}"},"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":"Retain a counter, imported module","messageSeqs":[8],"source":{"kind":"fallback"}}}
@@ -20,7 +20,7 @@
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_reuse","name":"run_code","arguments":"{\"code\":\"const childProcess = await import(\\\"node:child_process\\\"); childProcess.execFileSync(process.execPath, [\\\"-e\\\", \\\"process.stdout.write(\\\\\\\"raw child output\\\\\\\")\\\"], { stdio: \\\"inherit\\\" }); await fs.writeFile(\\\"persistent-count.txt\\\", String(increment()) + \\\"\\\\n\\\"); const result = await report({command:\\\"pwd\\\",description:\\\"Read current working directory\\\"}); ({ value: counter.value, same: alias === counter, sameDirectory: process.cwd() === result.stdout.text.trim() })\",\"description\":\"Reuse retained objects and current tool bindings\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1790153754377,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1790153754377,"index":0,"dt":[],"id":"call_reuse","name":"run_code","args":["{\"code\":\"const childProcess = await import(\\\"node:child_process\\\"); childProcess.execFileSync(process.execPath, [\\\"-e\\\", \\\"process.stdout.write(\\\\\\\"raw child output\\\\\\\")\\\"], { stdio: \\\"inherit\\\" }); await fs.writeFile(\\\"persistent-count.txt\\\", String(increment()) + \\\"\\\\n\\\"); const result = await report({command:\\\"pwd\\\",description:\\\"Read current working directory\\\"}); ({ value: counter.value, same: alias === counter, sameDirectory: process.cwd() === result.stdout.text.trim() })\",\"description\":\"Reuse retained objects and current tool bindings\"}"]},{"type":"chunk","time":1790153754377,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_reuse","name":"run_code","arguments":"{\"code\":\"const childProcess = await import(\\\"node:child_process\\\"); childProcess.execFileSync(process.execPath, [\\\"-e\\\", \\\"process.stdout.write(\\\\\\\"raw child output\\\\\\\")\\\"], { stdio: \\\"inherit\\\" }); await fs.writeFile(\\\"persistent-count.txt\\\", String(increment()) + \\\"\\\\n\\\"); const result = await report({command:\\\"pwd\\\",description:\\\"Read current working directory\\\"}); ({ value: counter.value, same: alias === counter, sameDirectory: process.cwd() === result.stdout.text.trim() })\",\"description\":\"Reuse retained objects and current tool bindings\"}"}}},{"type":"chunk","time":1790153754377,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1790153754377,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_reuse","name":"run_code","arguments":"{\"code\":\"const childProcess = await import(\\\"node:child_process\\\"); childProcess.execFileSync(process.execPath, [\\\"-e\\\", \\\"process.stdout.write(\\\\\\\"raw child output\\\\\\\")\\\"], { stdio: \\\"inherit\\\" }); await fs.writeFile(\\\"persistent-count.txt\\\", String(increment()) + \\\"\\\\n\\\"); const result = await report({command:\\\"pwd\\\",description:\\\"Read current working directory\\\"}); ({ value: counter.value, same: alias === counter, sameDirectory: process.cwd() === result.stdout.text.trim() })\",\"description\":\"Reuse retained objects and current tool bindings\"}"}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_reuse","parentCallId":"call_reuse","subCallId":"call_reuse:ptc:1","name":"bash","arguments":{"command":"pwd","description":"Read current working directory"}}}
-{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_reuse","parentCallId":"call_reuse","subCallId":"call_reuse:ptc:1","name":"bash","arguments":{"command":"pwd","description":"Read current working directory"},"isError":false,"content":[{"type":"text","text":"{{cwd}}\n"}]}}
+{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_reuse","parentCallId":"call_reuse","subCallId":"call_reuse:ptc:1","name":"bash","arguments":{"command":"pwd","description":"Read current working directory"},"isError":false,"content":[{"type":"text","text":"{{cwd}}\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"call_reuse"},"toolCallId":"call_reuse","content":[{"type":"text","text":"raw child output\n{\n \"value\": 2,\n \"same\": true,\n \"sameDirectory\": true\n}"}],"isError":false,"id":"{{message:7}}"}},"sourceEventSeqs":[19],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":2}}
{"type":"step/start","data":{"turn":1,"step":3}}
diff --git a/snapshots/session/ptc-persistent/system-prompt.expected.md b/snapshots/session/ptc-persistent/system-prompt.expected.md
index 5f5efcbdec..b8950c4116 100644
--- a/snapshots/session/ptc-persistent/system-prompt.expected.md
+++ b/snapshots/session/ptc-persistent/system-prompt.expected.md
@@ -1,6 +1,6 @@
You are an AI agent powered by DeepSeek Harness.
-You are a coding assistant powered by the deepseek-v4-flash model. Your working directory is {{cwd}}.
+You are a coding assistant powered by the deepseek-v4-flash model.
Verify your work by running the code or tests. Keep answers brief and factual.
@@ -29,13 +29,13 @@ create_goal may infer goal intent from a direct human request in any language. A
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.
-Start independent subagent delegations together in one assistant message and continue useful work while they run.
+Start independent delegations with `subagent` or `subagent_fork` together in one assistant message and continue useful work while they run.
## Writing code for run_code
-`run_code` takes two required arguments: `code` — a TypeScript program following the execution instructions in its tool description — and `description`, a short summary of what the program does. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
+`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
-`run_code({ code: "console.log(await tools.bash({ command: 'pwd', description: 'Show current directory' }))", description: "Show current directory" })`
+`run_code({ description: "Show current directory", code: "console.log(await tools.bash({ description: 'Show current directory', command: 'pwd' }))" })`
Inside the program:
@@ -50,12 +50,12 @@ Program-only SDK bindings:
type JsonValue = null | boolean | number | string | JsonValue[] | { [key: string]: JsonValue }
interface ToolArgsMap {
- /** Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell; pass `workdir` instead of using `cd`. Managed `$DSH_*` variables expose current harness environment facts. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Before any delete or move, verify that the resolved absolute target path is the intended one; never run it against a computed path you have not checked. An unset variable expands to an empty string, so guard variables in such paths with `${VAR:?}`. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]`, a policy denial: do not retry another way. */
+ /** Execute a bash command (`bash -c`) and return its stdout/stderr. Each call runs in a fresh shell; pass `workdir` instead of using `cd`. Managed `$DSH_*` variables expose current harness environment facts. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. Provide `description` before `command` in the arguments. Before any delete or move, verify that the resolved absolute target path is the intended one; never run it against a computed path you have not checked. An unset variable expands to an empty string, so guard variables in such paths with `${VAR:?}`. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]`, a policy denial: do not retry another way. */
bash: {
- /** The bash command to execute. */
- command: string;
/** 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". */
description: string;
+ /** The bash command to execute. */
+ command: string;
/** Timeout in milliseconds. The executor applies its configured default and cap; on expiry the command moves to the background as a job instead of being killed. */
timeoutMs?: number;
/** Working directory for this command. Defaults to the session workspace; a relative path is resolved against it. */
@@ -76,7 +76,7 @@ interface ToolArgsMap {
} & Record;
/** Edit an existing UTF-8 text file by replacing literal text. */
edit: {
- /** Path to edit, resolved by the filesystem backend. */
+ /** Path to edit, resolved by the filesystem backend. Provide `file_path` before `old_string` and `new_string` in the arguments. */
file_path: string;
/** Literal text to replace. */
old_string: string;
@@ -112,7 +112,7 @@ interface ToolArgsMap {
/** One glob filter for which files to search (e.g. "*.ts", "*.{js,jsx}"). Not a list; negation is not supported. */
include?: string;
} & Record;
- /** Ask a subagent to stop its current work. This call returns without waiting for it to stop. You can continue a direct child's conversation later with send_message. Subagents it started will keep running. */
+ /** Ask a subagent to stop its current work. This call returns without waiting for it to stop. You can continue a local direct child's conversation later with send_message. External executions stop permanently and cannot receive follow-ups. Subagents it started will keep running. */
interrupt_agent: {
/** The id of an agent created under you: your direct child or a deeper descendant. */
agent_id: string;
@@ -166,17 +166,19 @@ interface ToolArgsMap {
/** The exact skill name from the available skills list. */
name: string;
} & Record;
- /** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. It runs in the background by default and returns a subagent id you can continue with `send_message`; you are notified when the run settles. */
+ /** Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. This tool starts an independently managed subagent and immediately returns its id. The runtime notifies you when it finishes. The child reports results with `send_message`; use `send_message` to steer it while running or continue its conversation after it finishes. */
subagent: {
+ /** Initial child working directory. Relative paths use your current directory; omitted inherits it. Later directory changes in either agent are independent. */
+ cwd?: string;
/** A short (3-5 word) description of the delegated task, for display. */
description: string;
/** The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs. */
prompt: string;
- /** Defaults to true. Set false only when your next action depends on the result. */
- run_in_background?: boolean;
} & Record;
- /** 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). 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 its result, not its intermediate steps. This call waits for the subagent and returns its result. */
+ /** 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). 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 its result, not its intermediate steps. This tool starts an independently managed subagent and immediately returns its id. The runtime notifies you when it finishes. The child reports results with `send_message`; use `send_message` to steer it while running or continue its conversation after it finishes. */
subagent_fork: {
+ /** Initial child working directory. Relative paths use your current directory; omitted inherits it. Later directory changes in either agent are independent. */
+ cwd?: string;
/** A short (3-5 word) description of the delegated task, for display. */
description: string;
/** The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new. */
@@ -250,9 +252,14 @@ interface ToolArgsMap {
/** Run as a background job: return a job id immediately instead of waiting; the return value arrives with the completion notice. */
run_in_background?: boolean;
} & Record;
+ /** Read the current working directory, or change it with cd. Relative paths use the current directory. Existing shells and running processes keep their own directories. */
+ working_directory: {
+ /** Existing directory to enter. Omit to read the current directory. */
+ cd?: string;
+ } & Record;
/** Create or fully replace a UTF-8 text file. */
write: {
- /** Path to write, resolved by the filesystem backend. */
+ /** Path to write, resolved by the filesystem backend. Provide `file_path` before `content` in the arguments. */
file_path: string;
/** Full UTF-8 text content to write. */
content: string;
@@ -267,13 +274,16 @@ interface ToolOutputMap {
bash: {
kind: "background";
jobId: string;
+ cwd: string;
} | {
kind: "promoted";
+ cwd: string;
jobId: string;
timeoutMs: number;
output: string;
} | {
kind: "foreground";
+ cwd: string;
exitCode: number | null;
signal: string | null;
timedOut: boolean;
@@ -315,6 +325,7 @@ interface ToolOutputMap {
activation: "armed" | "disarmed";
};
edit: {
+ /** Canonical absolute path in the filesystem execution world. */
path: string;
before: string;
after: string;
@@ -401,6 +412,7 @@ interface ToolOutputMap {
depth?: number;
})[];
read: {
+ /** Canonical absolute path in the filesystem execution world. */
path: string;
offset: number;
lines: {
@@ -410,6 +422,7 @@ interface ToolOutputMap {
totalLines: number;
};
read_image: {
+ /** Canonical absolute path in the filesystem execution world. */
path: string;
image: {
attachmentId: string;
@@ -443,26 +456,12 @@ interface ToolOutputMap {
content: string;
};
subagent: {
- kind: "background";
- jobId: string;
- } | {
- kind: "continuable";
+ kind: "activation";
subagentId: string;
- } | {
- kind: "foreground";
- runId: string;
- output: JsonValue[];
};
subagent_fork: {
- kind: "background";
- jobId: string;
- } | {
- kind: "continuable";
+ kind: "activation";
subagentId: string;
- } | {
- kind: "foreground";
- runId: string;
- output: JsonValue[];
};
template_echo: string;
todo_write: {
@@ -525,7 +524,12 @@ interface ToolOutputMap {
agentsStarted: number;
result: JsonValue;
};
+ working_directory: {
+ /** Current absolute working directory. */
+ cwd: string;
+ };
write: {
+ /** Canonical absolute path in the filesystem execution world. */
path: string;
operation: "create" | "update";
before: string | null;
diff --git a/snapshots/session/ptc-persistent/tool-schemas.expected.json b/snapshots/session/ptc-persistent/tool-schemas.expected.json
index 3bf5ec0f18..a1911e5d6d 100644
--- a/snapshots/session/ptc-persistent/tool-schemas.expected.json
+++ b/snapshots/session/ptc-persistent/tool-schemas.expected.json
@@ -2,26 +2,26 @@
"initial": [
{
"name": "run_code",
- "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `code`, the program to execute, and `description`, a short summary of what the program does. Call tools as `await tools.name(args)` per the declarations in the system prompt. Curate the program output. Image-bearing subtool results are attached after the run. The program contains TypeScript REPL statements; only erasable syntax is accepted, with no enum or namespaces. Type annotations are advisory. Top-level declarations and await are supported; top-level return is not supported. Emit output with console.log(...) or a final lossless JSON expression. Only printed values and that final expression become program output. Variables, functions, and imported modules remain in memory for this Agent across calls. Use await import(...) for modules. This environment has full host access without a file sandbox; process.env starts empty. Restart, Agent disposal, cancellation, timeout, output overflow, or process failure clears the environment. If a variable is undefined, recreate it and reacquire its resources. State is not restored from the conversation, and failed evaluations can leave earlier mutations in memory. Redeclaring an existing let, const, or class name fails; assign to a mutable binding or use a new name. Await all tool calls; background callbacks cannot call tools after their evaluation ends. Await child-process completion before ending an evaluation; raw subprocess output belongs to whichever evaluation is active when it arrives. The working directory is the Session's current directory.",
+ "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. Call tools as `await tools.name(args)` per the declarations in the system prompt. Curate the program output. Image-bearing subtool results are attached after the run. The program contains TypeScript REPL statements; only erasable syntax is accepted, with no enum or namespaces. Type annotations are advisory. Top-level declarations and await are supported; top-level return is not supported. Emit output with console.log(...) or a final lossless JSON expression. Only printed values and that final expression become program output. Variables, functions, and imported modules remain in memory for this Agent across calls. Use await import(...) for modules. This environment has full host access without a file sandbox; process.env starts empty. Restart, Agent disposal, cancellation, timeout, output overflow, or process failure clears the environment. If a variable is undefined, recreate it and reacquire its resources. State is not restored from the conversation, and failed evaluations can leave earlier mutations in memory. Redeclaring an existing let, const, or class name fails; assign to a mutable binding or use a new name. Await all tool calls; background callbacks cannot call tools after their evaluation ends. Await child-process completion before ending an evaluation; raw subprocess output belongs to whichever evaluation is active when it arrives. Each program starts in the Session's current directory. Running programs keep their initial directory.",
"parameters": {
"type": "object",
"properties": {
+ "description": {
+ "type": "string",
+ "description": "Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Provide `description` before `code` in the arguments. Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\"."
+ },
"code": {
"type": "string",
"description": "The TypeScript program to execute, following this tool's execution instructions."
},
- "description": {
- "type": "string",
- "description": "Clear, concise description of what this program does in active voice, 5-10 words (shown in the UI). Examples: \"Count TODO markers across packages\"; \"Read failing test and its fixture\"; \"Rename config key in every cordis.yml\"."
- },
"timeoutMs": {
"type": "number",
"description": "Positive elapsed-time budget in milliseconds, including nested tool and approval waits. Default 120000; capped at 600000. Zero does not disable the deadline."
}
},
"required": [
- "code",
- "description"
+ "description",
+ "code"
]
}
}
diff --git a/snapshots/session/ptc-python-turn/system-prompt.expected.md b/snapshots/session/ptc-python-turn/system-prompt.expected.md
index 23dc2e1494..6b014fafa8 100644
--- a/snapshots/session/ptc-python-turn/system-prompt.expected.md
+++ b/snapshots/session/ptc-python-turn/system-prompt.expected.md
@@ -31,12 +31,12 @@ Start independent delegations with `subagent` or `subagent_fork` together in one
## Writing code for run_code
-`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code` — the body of an async Python function (top-level `await` and `return` both work). At run time exactly two of the names declared below are bound: `tools` and `ToolCallError`. Everything else is a STATIC STUB describing argument and return types — in particular the `TypedDict` classes do NOT exist at run time, so build arguments as plain `dict`/`list` JSON values: `await tools.name({"field": 1})`, never `FooArgs(field=1)`, which raises `NameError`. Inside the program:
+`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. At run time exactly two of the names declared below are bound: `tools` and `ToolCallError`. Everything else is a STATIC STUB describing argument and return types — in particular the `TypedDict` classes do NOT exist at run time, so build arguments as plain `dict`/`list` JSON values: `await tools.name({"field": 1})`, never `FooArgs(field=1)`, which raises `NameError`. Inside the program:
- Call tools as `await tools.name(args)` — subscript access for exotic, reserved, or underscore-leading names: `await tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value (each method's return type below). Tool arguments must be lossless JSON.
- A FAILED tool call raises `ToolCallError`, whose `toolName` identifies the failed tool and whose message is human-readable — wrap in `try/except` to handle and continue.
- Independent read-only calls MAY overlap under `asyncio.gather` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.
-- Emit the run's answer with `print(...)` and/or a top-level `return `; the returned value must be lossless JSON. Only what you print and return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
+- Only values emitted as program output enter the conversation. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
The available tools:
diff --git a/snapshots/session/ptc-python-turn/tool-schemas.expected.json b/snapshots/session/ptc-python-turn/tool-schemas.expected.json
index 1e9cefecde..c621ae94ae 100644
--- a/snapshots/session/ptc-python-turn/tool-schemas.expected.json
+++ b/snapshots/session/ptc-python-turn/tool-schemas.expected.json
@@ -2,7 +2,7 @@
"initial": [
{
"name": "run_code",
- "description": "Execute a Python program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the BODY of an async function (top-level `await` and `return` work). Call tools as `await tools.name(args)` per the declarations in the system prompt. Use `print(...)` and/or `return ` for program output — curate it. Image-bearing subtool results are attached after the run. Each call runs in a fresh Python process. Relative paths use the supplied working directory; only TMPDIR is set in the environment. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
+ "description": "Execute a Python program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. Call tools as `await tools.name(args)` per the declarations in the system prompt. Curate the program output. Image-bearing subtool results are attached after the run. The program is the body of an async Python function. Top-level await and return are supported. Emit output with print(...) or return a lossless JSON value. Only printed values and the returned value become program output. Each call runs in a fresh Python process; variables and imported modules do not persist across calls. Relative paths use the supplied working directory; only TMPDIR is set in the environment. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
"parameters": {
"type": "object",
"properties": {
@@ -12,7 +12,7 @@
},
"code": {
"type": "string",
- "description": "The program: the body of an async Python function."
+ "description": "The Python program to execute, following this tool's execution instructions."
},
"sandbox_permissions": {
"type": "string",
diff --git a/snapshots/session/ptc-read-image/system-prompt.expected.md b/snapshots/session/ptc-read-image/system-prompt.expected.md
index f0a3c2426f..40e9fc9f78 100644
--- a/snapshots/session/ptc-read-image/system-prompt.expected.md
+++ b/snapshots/session/ptc-read-image/system-prompt.expected.md
@@ -33,16 +33,16 @@ Start independent delegations with `subagent` or `subagent_fork` together in one
## Writing code for run_code
-`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
+`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
-`run_code({ description: "Show current directory", code: "return await tools.bash({ description: 'Show current directory', command: 'pwd' })" })`
+`run_code({ description: "Show current directory", code: "console.log(await tools.bash({ description: 'Show current directory', command: 'pwd' }))" })`
Inside the program:
- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.
- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.
- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.
-- Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
+- Only values emitted as program output enter the conversation. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
Program-only SDK bindings:
diff --git a/snapshots/session/ptc-turn/system-prompt.expected.md b/snapshots/session/ptc-turn/system-prompt.expected.md
index 39529ce115..b8950c4116 100644
--- a/snapshots/session/ptc-turn/system-prompt.expected.md
+++ b/snapshots/session/ptc-turn/system-prompt.expected.md
@@ -33,16 +33,16 @@ Start independent delegations with `subagent` or `subagent_fork` together in one
## Writing code for run_code
-`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
+`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
-`run_code({ description: "Show current directory", code: "return await tools.bash({ description: 'Show current directory', command: 'pwd' })" })`
+`run_code({ description: "Show current directory", code: "console.log(await tools.bash({ description: 'Show current directory', command: 'pwd' }))" })`
Inside the program:
- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.
- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.
- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.
-- Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
+- Only values emitted as program output enter the conversation. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
Program-only SDK bindings:
diff --git a/snapshots/session/ptc-turn/tool-schemas.expected.json b/snapshots/session/ptc-turn/tool-schemas.expected.json
index 44b97b6340..e0b6795176 100644
--- a/snapshots/session/ptc-turn/tool-schemas.expected.json
+++ b/snapshots/session/ptc-turn/tool-schemas.expected.json
@@ -2,7 +2,7 @@
"initial": [
{
"name": "run_code",
- "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work). Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return is program output — curate it. Image-bearing subtool results are attached after the run. Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
+ "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. Call tools as `await tools.name(args)` per the declarations in the system prompt. Curate the program output. Image-bearing subtool results are attached after the run. The program is the body of an async TypeScript function; only erasable syntax is accepted, with no enum or namespaces. Type annotations are advisory. Top-level await and return are supported. Emit output with console.log(...) or return a lossless JSON value. Only printed values and the returned value become program output. Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
"parameters": {
"type": "object",
"properties": {
@@ -12,7 +12,7 @@
},
"code": {
"type": "string",
- "description": "The program: the body of an async TypeScript function."
+ "description": "The TypeScript program to execute, following this tool's execution instructions."
},
"timeoutMs": {
"type": "number",
diff --git a/snapshots/web/ptc-round/system-prompt.expected.md b/snapshots/web/ptc-round/system-prompt.expected.md
index 47cc71cb02..c0ca0dd175 100644
--- a/snapshots/web/ptc-round/system-prompt.expected.md
+++ b/snapshots/web/ptc-round/system-prompt.expected.md
@@ -30,16 +30,16 @@ Start independent delegations with `subagent` or `subagent_fork` together in one
## Writing code for run_code
-`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code` — the body of an async TypeScript function (erasable syntax only — no `enum` or namespaces; type annotations are advisory, the code runs type-stripped). The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
+`run_code` takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. The declarations below are SDK bindings for this program. A declaration does not make its name a directly callable tool; only names supplied as separate tool schemas may be called directly. When no separate `bash` schema is supplied, invoke a declared `bash` binding inside `run_code`:
-`run_code({ description: "Show current directory", code: "return await tools.bash({ description: 'Show current directory', command: 'pwd' })" })`
+`run_code({ description: "Show current directory", code: "console.log(await tools.bash({ description: 'Show current directory', command: 'pwd' }))" })`
Inside the program:
- Call tools as `await tools.name(args)` — quoted access for exotic names: `tools["my-tool"](args)`. Every call resolves to the tool's typed canonical JSON value. Tool arguments must be lossless JSON.
- A FAILED tool call rejects with `ToolCallError`, whose `toolName` identifies the failed tool and whose `message` is human-readable — `try/catch` it to handle and continue.
- Independent read-only calls MAY overlap under `Promise.all` (safe calls run concurrently; mutating calls run alone, in submission order). Sequence dependent work with `await`.
-- Emit results with `return` and/or `console.log(...)`. Only what you print or return is program output. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
+- Only values emitted as program output enter the conversation. A successful tool result containing an image is attached after the run so you can inspect it on the next step; every other intermediate result stays out of the conversation, so extract just what you need.
Program-only SDK bindings:
diff --git a/snapshots/web/ptc-round/tool-schemas.expected.json b/snapshots/web/ptc-round/tool-schemas.expected.json
index 44b97b6340..e0b6795176 100644
--- a/snapshots/web/ptc-round/tool-schemas.expected.json
+++ b/snapshots/web/ptc-round/tool-schemas.expected.json
@@ -2,7 +2,7 @@
"initial": [
{
"name": "run_code",
- "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the BODY of an async function (erasable syntax only; top-level `await` and `return` work). Call tools as `await tools.name(args)` per the declarations in the system prompt. Only what you print or return is program output — curate it. Image-bearing subtool results are attached after the run. Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
+ "description": "Execute a TypeScript program against the available tools. Takes two required arguments: `description`, a short summary of what the program does, and `code`, the program to execute. Call tools as `await tools.name(args)` per the declarations in the system prompt. Curate the program output. Image-bearing subtool results are attached after the run. The program is the body of an async TypeScript function; only erasable syntax is accepted, with no enum or namespaces. Type annotations are advisory. Top-level await and return are supported. Emit output with console.log(...) or return a lossless JSON value. Only printed values and the returned value become program output. Each call runs in a fresh Node process. Node APIs are available through await import(...). Relative paths use the supplied working directory; process.env starts empty. Direct filesystem access follows this execution's sandbox policy. Each program starts in the Session's current directory. Running programs keep their initial directory. A sandbox escalation approves this complete program for one execution only. Nested tools retain their own policies and approvals. Request wider access only after evidence of a denial. Earlier effects may already have completed: inspect them before explicitly retrying. Programs are never replayed automatically.",
"parameters": {
"type": "object",
"properties": {
@@ -12,7 +12,7 @@
},
"code": {
"type": "string",
- "description": "The program: the body of an async TypeScript function."
+ "description": "The TypeScript program to execute, following this tool's execution instructions."
},
"timeoutMs": {
"type": "number",