diff --git a/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.i18n.yaml b/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.i18n.yaml
index cfc39fad5f..8ef8eac30c 100644
--- a/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.i18n.yaml
+++ b/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.i18n.yaml
@@ -9,11 +9,11 @@
en: b3d0591cc559e466
zh: aa4c385e84f7eaa1
/agent-note-persist-owned-job-lifecycle-facts/decision:
- en: b7a176ff0f75ef42
- zh: 1170c9e32612d24a
+ en: c982a59cbe5202d3
+ zh: 9315962d2e4b8f0a
/agent-note-persist-owned-job-lifecycle-facts/alternatives-considered:
- en: 0e3b8c785146662f
- zh: 1a248326522600c8
+ en: 720a8c39eadd09aa
+ zh: ed09e09eb6ea9b99
/agent-note-persist-owned-job-lifecycle-facts/consequences:
- en: e5014d6bef085dd2
- zh: e2865641b7c8e2e8
+ en: 306ba072e0c690c0
+ zh: 2775f26c9206a386
diff --git a/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.md b/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.md
index c6a344380c..a879efed57 100644
--- a/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.md
+++ b/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.md
@@ -10,7 +10,7 @@ SDK consumers receive Session events, while job registration and settlement prev
## Decision
-The local provider captures `job/registered` and `job/settled` facts for owned jobs, retaining the registry's epoch-millisecond timestamps, original owner, producer kind, label, and optional creating tool call. An execution UUID pairs records across restarts and copied history without changing the ids accepted by job controls. Unowned work has no Session journal.
+The local provider captures `job/registered` and `job/settled` facts for owned jobs, retaining the registry's epoch-millisecond timestamps, original owner, producer kind, label, and optional creating tool call. A UUID-backed `JobId` identifies both live controls and historical records across registry restarts and copied history. Unowned work has no Session journal.
The terminal record distinguishes a producer report from forced bookkeeping: `producerReported` is true when `JobHooks.done` fulfills, and false when that promise rejects or teardown cancellation throws. It reports fulfillment, not independently confirmed resource release. Missing terminal records remain incomplete and are not reconstructed from later reads or process restart.
@@ -22,12 +22,16 @@ The [registry contract](../../../../packages/jobs/jobs/README.md) and [seam cons
**Add timestamps only to `job_output`.** A model may never collect a job or may collect it after a long delay. Logging lifecycle commits records both cases without additional model calls or consuming output.
-**Pair by job id alone.** The per-kind counter restarts with the registry, and Session history can outlive it. Execution UUIDs preserve equality across both lifecycle records without treating copied history as new execution.
+**Keep a reusable control id alongside an execution UUID.** Both values would identify one job. A unique `JobId` serves live controls and historical pairing.
+
+**Pass cleanup only to `appExit`.** Signal exit bypasses the normal exit request and can close persistence before owned jobs settle. Ordered launcher cleanup serves both exit paths.
**Defer publication or propagate recording errors.** Deferral lets a synchronous hub subscriber journal a completion notice before the settlement that caused it. Throwing after the starter succeeds can hide an already-running job from its caller. Synchronous, contained recording preserves causal order without abandoning execution.
## Consequences
-The log and both SDKs expose execution intervals without changing tool schemas, model messages, output cursors, completion notices, or job scheduling. These intervals run from registration commit through registry settlement, including the producer work and cleanup between those points; they are neither CPU time nor a directly additive rollout-latency metric. Hard process loss or an append failure can leave incomplete history. Persisted facts do not resume an external process or its output ring.
+The launcher drains registered resource disposers before providers on both normal and signal exit, within its existing five-second bound. Headless registers its Agent while acquisition is pending so job settlement can reach the Session writer.
-Focused tests cover original timestamps, nested call identity, output preservation, delayed cancellation, producer rejection, forced teardown, late completion, reused control ids, causal publication order, and recording-failure containment. SDK snapshots cover the durable and protocol projections.
+The log and both SDKs expose execution intervals without changing tool schemas, output cursors, or job scheduling. Model-facing controls and notices carry the unique JobId; a bounded notice points to job_list when the full id cannot fit. These intervals run from registration commit through registry settlement, including the producer work and cleanup between those points; they are neither CPU time nor a directly additive rollout-latency metric. Hard process loss or an append failure can leave incomplete history. Persisted facts do not resume an external process or its output ring.
+
+Focused tests cover original timestamps, nested call identity, output preservation, delayed cancellation, producer rejection, forced teardown, late completion, unique control ids, causal publication order, and recording-failure containment. SDK snapshots cover the durable and protocol projections.
diff --git a/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.zh.md b/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.zh.md
index faf36e5486..3e1697371b 100644
--- a/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.zh.md
+++ b/.agents/notes/implemented/bug-fix/2026-09-30-job-session-lifecycle.zh.md
@@ -10,7 +10,7 @@ SDK 消费方接收 Session 事件,而 job 注册和结算此前只存在于
## Decision
-本地提供方为有所有者的 job 捕获 `job/registered` 与 `job/settled` 事实,保留注册表的 Unix 毫秒时间戳、原始所有者、生产方 kind、label 和可选的创建工具调用。执行 UUID 在重启和历史复制后仍能配对记录,同时不改变 job 控制接口接受的 id。无主工作没有 Session 日志。
+本地提供方为有所有者的 job 捕获 `job/registered` 与 `job/settled` 事实,保留注册表的 Unix 毫秒时间戳、原始所有者、生产方 kind、label 和可选的创建工具调用。由 UUID 构成的 `JobId` 在注册表重启和历史复制后同时标识运行中的控制对象和历史记录。无主工作没有 Session 日志。
终态记录区分生产方报告与强制记账:`producerReported` 在 `JobHooks.done` 兑现时为 true,在该 promise 拒绝或销毁取消抛错时为 false。它记录兑现情况,不独立确认资源已释放。缺少终态记录时保持不完整,不根据较晚的读取或进程重启重建。
@@ -22,12 +22,16 @@ SDK 消费方接收 Session 事件,而 job 注册和结算此前只存在于
**仅向 `job_output` 添加时间戳。** 模型可能永远不收集某个 job,也可能很晚才收集。记录生命周期提交能覆盖这两种情况,无需额外模型调用或消费输出。
-**仅按 job id 配对。** 每个 kind 的计数器随注册表重启,而 Session 历史可能更长久。执行 UUID 保留两条生命周期记录的相等关系,不将复制的历史视为新执行。
+**保留可重用的控制 id,另设执行 UUID。** 两个值会标识同一个 job。唯一的 `JobId` 同时服务运行期控制和历史配对。
+
+**仅通过 `appExit` 传递清理。** 信号退出不调用正常退出请求,可能在有所有者的 job 结算前关闭持久化。启动器的有序清理覆盖两条退出路径。
**延后发布或传播记录错误。** 延后发布让同步 hub 订阅者可能先记录完成通知、后记录产生通知的结算事实。starter 成功后抛错可能使调用方拿不到已运行 job 的 id。同步并隔离记录错误既保留因果顺序,也不会放弃执行。
## Consequences
-日志与两种 SDK 暴露执行区间,不改变工具 schema、模型消息、输出游标、完成通知或 job 调度。区间从注册提交延续到注册表结算,包含这两个时刻之间的生产方工作与清理;它既不是 CPU 时间,也不是可直接相加的 rollout 延迟指标。进程硬退出或追加失败可能留下不完整历史。持久化事实不会恢复外部进程或其输出环。
+启动器在正常退出和信号退出时,都在已有的五秒时限内先排空已注册资源,再销毁提供方。Headless 在取得 Agent 的操作仍在等待时注册清理,使 job 结算能够写入 Session。
-定向测试覆盖原始时间戳、嵌套调用标识、输出保留、延后取消、生产方拒绝、强制销毁、较晚完成、控制 id 重用、因果发布顺序,以及记录错误隔离。SDK 快照覆盖持久化与协议投影。
+日志与两种 SDK 暴露执行区间,不改变工具 schema、输出游标或 job 调度。模型侧控制和通知使用唯一的 JobId;有界通知无法容纳完整 id 时指向 job_list。区间从注册提交延续到注册表结算,包含这两个时刻之间的生产方工作与清理;它既不是 CPU 时间,也不是可直接相加的 rollout 延迟指标。进程硬退出或追加失败可能留下不完整历史。持久化事实不会恢复外部进程或其输出环。
+
+定向测试覆盖原始时间戳、嵌套调用标识、输出保留、延后取消、生产方拒绝、强制销毁、较晚完成、唯一控制 id、因果发布顺序,以及记录错误隔离。SDK 快照覆盖持久化与协议投影。
diff --git a/.agents/notes/implemented/process/2026-07-06-node-engine-floor.i18n.yaml b/.agents/notes/implemented/process/2026-07-06-node-engine-floor.i18n.yaml
index 79e7511b3d..18ab194298 100644
--- a/.agents/notes/implemented/process/2026-07-06-node-engine-floor.i18n.yaml
+++ b/.agents/notes/implemented/process/2026-07-06-node-engine-floor.i18n.yaml
@@ -9,11 +9,11 @@
en: d7bf1b1699ce0dc8
zh: f10dfd0e4787bb44
/agent-note-raise-the-node-lts-engine-floor-to-22-19/decision:
- en: 355366989e655caf
- zh: 51ac6466f65e6d58
+ en: 0051c0d8c1948f63
+ zh: cbab7aa0f53f7200
/agent-note-raise-the-node-lts-engine-floor-to-22-19/consequences:
- en: cf076eee91c2ec6a
- zh: d4dea1eb9535ed98
+ en: 0b9fbe932d9a8b1f
+ zh: 642e19d06caecef2
/agent-note-raise-the-node-lts-engine-floor-to-22-19/alternatives-considered:
en: c433389b2064eaf9
zh: 57de2af812def1c4
diff --git a/.agents/notes/implemented/process/2026-07-06-node-engine-floor.md b/.agents/notes/implemented/process/2026-07-06-node-engine-floor.md
index bfcc5e7afd..e2fe7661f4 100644
--- a/.agents/notes/implemented/process/2026-07-06-node-engine-floor.md
+++ b/.agents/notes/implemented/process/2026-07-06-node-engine-floor.md
@@ -10,11 +10,7 @@ The Node 22 branch of the root `engines.node` range is a contract for the instal
## Decision
-Set `engines.node` to `^22.19.0 || >=24.0.0` and test keyless CI on `['22.19', 24, 26]`. The primary Linux Node 24 jobs own the complete typecheck and unit coverage inventory; every version runs focused source-worker, Zstandard, source-launch, and [jsdom storage](../../archived/testing/2026-07-30-vitest-jsdom-webstorage-ownership.md) smokes without repeating that inventory. The real-API e2e workflow stays on Node 24 because it exercises API integration rather than the runtime floor.
-
-
-
-Windows PR coverage uses Node 26.10.0, which includes the [cppgc Realm cleanup fix](https://github.com/nodejs/node/pull/65778). Windows build and native-process checks use Node 24. Moving coverage back requires a Node 24 release containing the [pending backport](https://github.com/nodejs/node/pull/66168); the full test inventory, isolation and coverage thresholds remain required.
+Set `engines.node` to `^22.19.0 || >=24.0.0` and test keyless CI on `['22.19', 24, 26]`. The primary Node 24 jobs own the complete typecheck and unit coverage inventory; every version runs focused source-worker, Zstandard, source-launch, and [jsdom storage](../../archived/testing/2026-07-30-vitest-jsdom-webstorage-ownership.md) smokes without repeating that inventory. The real-API e2e workflow stays on Node 24 because it exercises API integration rather than the runtime floor.
Two Node features gate the source runtime:
@@ -28,7 +24,7 @@ Those source features clear on the 22.x line at **22.18**, but the installed Pi
## Consequences
- The advertised LTS branch no longer undercuts the Pi adapter dependency floor.
-- CI proves the Node 22 LTS floor directly with Node 22.19, keeps primary Linux coverage on `node: 24`, and exercises Node 26 as the next even line; focused compatibility smokes run on all three versions.
+- CI proves the Node 22 LTS floor directly with Node 22.19, keeps primary coverage on `node: 24`, and exercises Node 26 as the next even line; focused compatibility smokes run on all three versions.
- The built-mode smoke needs no version-conditional flag: at 22.19 type-stripping is already the default, so the example-owned TypeScript driver stays a plain `node fixture.ts` path.
- A future dependency or source API that raises the runtime floor must move `engines.node`, the compatibility matrix, and this Agent Note in the same change.
diff --git a/.agents/notes/implemented/process/2026-07-06-node-engine-floor.zh.md b/.agents/notes/implemented/process/2026-07-06-node-engine-floor.zh.md
index 47e414d105..e7d41e8128 100644
--- a/.agents/notes/implemented/process/2026-07-06-node-engine-floor.zh.md
+++ b/.agents/notes/implemented/process/2026-07-06-node-engine-floor.zh.md
@@ -10,11 +10,7 @@ Status: implemented
## 决策
-将 `engines.node` 设为 `^22.19.0 || >=24.0.0`,并在 `['22.19', 24, 26]` 上运行 keyless CI。主要的 Linux Node 24 任务负责整套类型检查和单元测试覆盖率任务;三个版本均运行 source-worker、Zstandard、source-launch 和 [jsdom 存储](../../archived/testing/2026-07-30-vitest-jsdom-webstorage-ownership.md) 专项冒烟测试,不重复这套类型检查和覆盖率任务。真实 API 的 e2e 工作流保持在 Node 24 上,因为它验证的是 API 集成而非运行时下限。
-
-
-
-Windows PR 覆盖率使用 Node 26.10.0,其中包含 [cppgc Realm 清理修复](https://github.com/nodejs/node/pull/65778)。Windows 构建和原生进程检查使用 Node 24。将覆盖率切回 Node 24 需要采用包含[待合入回移](https://github.com/nodejs/node/pull/66168)的正式版本;完整测试清单、隔离和覆盖率阈值仍是必需项。
+将 `engines.node` 设为 `^22.19.0 || >=24.0.0`,并在 `['22.19', 24, 26]` 上运行 keyless CI。主要的 Node 24 任务负责整套类型检查和单元测试覆盖率任务;三个版本均运行 source-worker、Zstandard、source-launch 和 [jsdom 存储](../../archived/testing/2026-07-30-vitest-jsdom-webstorage-ownership.md) 专项冒烟测试,不重复这套类型检查和覆盖率任务。真实 API 的 e2e 工作流保持在 Node 24 上,因为它验证的是 API 集成而非运行时下限。
两个 Node 特性决定了源码运行时的门槛:
@@ -28,7 +24,7 @@ Windows PR 覆盖率使用 Node 26.10.0,其中包含 [cppgc Realm 清理修复
## 后果
- 宣传的 LTS 分支不再低于 Pi 适配器依赖的下限。
-- CI 通过 Node 22.19 直接验证 Node 22 LTS 下限,将主要 Linux 覆盖率任务保留在 `node: 24`,并用 Node 26 验证下一个偶数线;三个版本均运行聚焦的兼容性冒烟测试。
+- CI 通过 Node 22.19 直接验证 Node 22 LTS 下限,将主要覆盖率任务保留在 `node: 24`,并用 Node 26 验证下一个偶数线;三个版本均运行聚焦的兼容性冒烟测试。
- 构建模式冒烟测试无需版本条件标志:在 22.19 上类型剥离已是默认行为,因此示例自有的 TypeScript driver 保持使用纯 `node fixture.ts` 路径。
- 未来若依赖或源码 API 提高运行时下限,必须在同一变更中同步调整 `engines.node`、兼容性矩阵和本 Agent Note。
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index c8f2afcf8b..d9176179b3 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -584,7 +584,7 @@ jobs:
&& github.event.pull_request.user.login != 'dependabot[bot]'
&& fromJSON('["self-hosted", "dsh-win-ci", "windows"]')
|| 'dsh-windows-2025-16core' }}
- name: windows node 26 / coverage
+ name: windows node 24 / coverage
timeout-minutes: 120
env:
# Shared hosts expose host CPUs, not an isolated allocation.
@@ -620,12 +620,9 @@ jobs:
- uses: pnpm/action-setup@v5.0.0
with:
dest: ${{ runner.temp }}/setup-pnpm-js-${{ github.run_id }}-${{ github.run_attempt }}-${{ github.job }}
- # Node 24 still awaits the cppgc Realm-lifetime backport (#66168).
- # 26.10.0 contains nodejs/node#65778; an exact version prevents a
- # runner cache from selecting an older Node 26 with the same crash.
- uses: actions/setup-node@v7.0.0
with:
- node-version: '26.10.0'
+ node-version: ${{ env.PRIMARY_NODE_VERSION }}
- name: Install (immutable)
shell: pwsh
# See 2026-08-30-windows-refs-store-block-clone-install for the
diff --git a/apps/cli/src/process-shutdown.ts b/apps/cli/src/process-shutdown.ts
index 7e5a0794ae..49ec860e5a 100644
--- a/apps/cli/src/process-shutdown.ts
+++ b/apps/cli/src/process-shutdown.ts
@@ -1,12 +1,14 @@
/** Bounded, escalating process shutdown for the long-lived CLI surfaces. */
+import type { AppShutdown } from '@deepseek-ai/dsh-cmdline'
+
/** Maximum grace allowed for the application tree to dispose before process exit. */
export const PROCESS_SHUTDOWN_TIMEOUT_MS = 5_000
/** Process-exit controller shared by normal completion and Unix signal handlers. */
-export interface ProcessShutdown {
- /** Start or join graceful disposal; caller-owned cleanup runs under the same bound before the tree. */
- shutdown(code: number, beforeDispose?: () => Promise): Promise
+export interface ProcessShutdown extends AppShutdown {
+ /** Start or join graceful disposal before allowing natural completion with `code`. */
+ shutdown(code: number): Promise
/** Start graceful disposal followed by exit, or force exit when shutdown is already running. */
interrupt(code: number): void
}
@@ -29,6 +31,7 @@ export function createProcessShutdown(
let timeout: ReturnType | undefined
let completed = false
let forceExited = false
+ const disposers = new Set<{ dispose: () => Promise }>()
const clearExitTimeout = (): void => {
/* v8 ignore else -- shutdown() arms the timer before any asynchronous exit path can run. */
@@ -49,15 +52,17 @@ export function createProcessShutdown(
complete(code)
}
- const start = (code: number, forceAfterDispose: boolean, beforeDispose?: () => Promise): Promise => {
+ const start = (code: number, forceAfterDispose: boolean): Promise => {
if (pending !== undefined) return pending
timeout = setTimeout(() => { forceExitOnce(code) }, timeoutMs)
pending = Promise.resolve().then(async () => {
- try {
- if (beforeDispose !== undefined) await beforeDispose()
- } finally {
- await dispose()
+ const failures: unknown[] = []
+ for (const entry of disposers) {
+ disposers.delete(entry)
+ try { await entry.dispose() } catch (error) { failures.push(error) }
}
+ try { await dispose() } catch (error) { failures.push(error) }
+ if (failures.length > 0) throw new AggregateError(failures, 'process cleanup failed')
}).then(
() => {
if (forceAfterDispose) forceExitOnce(code)
@@ -69,8 +74,14 @@ export function createProcessShutdown(
}
return {
- shutdown(code, beforeDispose) {
- return start(code, false, beforeDispose)
+ addDisposer(dispose) {
+ if (pending !== undefined) throw new Error('cannot register cleanup after shutdown starts')
+ const entry = { dispose }
+ disposers.add(entry)
+ return () => { disposers.delete(entry) }
+ },
+ shutdown(code) {
+ return start(code, false)
},
interrupt(code) {
if (pending !== undefined) {
diff --git a/apps/cli/src/profile-boot.ts b/apps/cli/src/profile-boot.ts
index 4986cc63ae..4da4f1d290 100644
--- a/apps/cli/src/profile-boot.ts
+++ b/apps/cli/src/profile-boot.ts
@@ -306,7 +306,8 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
// to every app plugin that injects the argument snapshot.
provideCmdline(hostCtx, {
args: options.args,
- exit: (code, beforeDispose) => void shutdown.shutdown(code, beforeDispose),
+ exit: code => void shutdown.shutdown(code),
+ shutdown,
ready: appReady.service,
})
})
diff --git a/apps/cli/tests/fixtures/never-dispose.mjs b/apps/cli/tests/fixtures/never-dispose.mjs
index 9e81b32738..a45910ca90 100644
--- a/apps/cli/tests/fixtures/never-dispose.mjs
+++ b/apps/cli/tests/fixtures/never-dispose.mjs
@@ -8,6 +8,14 @@ import { existsSync } from 'node:fs'
*/
export function apply(ctx) {
const keepAlive = setInterval(() => {}, 60_000)
+ const shutdown = ctx.get('appShutdown')
+ if (shutdown === undefined) throw new Error('missing ordered shutdown')
+ ctx.effect(() => shutdown.addDisposer(async () => {
+ const armFile = process.env.DSH_TEST_SHUTDOWN_ARM_FILE
+ if (armFile !== undefined && existsSync(armFile)) {
+ process.stderr.write('dsh-test: ordered cleanup started\n')
+ }
+ }))
process.stderr.write('dsh-test: never-dispose ready\n')
ctx.effect(() => async () => {
clearInterval(keepAlive)
diff --git a/apps/cli/tests/headless-shutdown.e2e.ts b/apps/cli/tests/headless-shutdown.e2e.ts
index 42f2aad709..5b4d3ef212 100644
--- a/apps/cli/tests/headless-shutdown.e2e.ts
+++ b/apps/cli/tests/headless-shutdown.e2e.ts
@@ -126,6 +126,9 @@ describe.skipIf(process.platform === 'win32')('headless process shutdown (real L
const output = await runHeadlessPtySmoke()
expect(output).not.toContain('dsh: observing at ')
expect(output).toContain('dsh-test: never-dispose ready')
+ expect(output).toContain('dsh-test: ordered cleanup started')
expect(output).toContain('dsh-test: never-dispose started')
+ expect(output.indexOf('dsh-test: ordered cleanup started'))
+ .toBeLessThan(output.indexOf('dsh-test: never-dispose started'))
}, LOADER_SMOKE_TEST_TIMEOUT_MS)
})
diff --git a/apps/cli/tests/process-shutdown.spec.ts b/apps/cli/tests/process-shutdown.spec.ts
index 2452a51617..d7ac7412b8 100644
--- a/apps/cli/tests/process-shutdown.spec.ts
+++ b/apps/cli/tests/process-shutdown.spec.ts
@@ -20,6 +20,30 @@ afterEach(() => {
})
describe('process shutdown', () => {
+ it.each(['normal', 'signal'] as const)('runs registered cleanup in order for %s exit', async (mode) => {
+ const release = deferred()
+ const entered = deferred()
+ const order: string[] = []
+ const exit = vi.fn()
+ const complete = vi.fn()
+ const shutdown = createProcessShutdown(async () => { order.push('providers') }, exit, complete)
+ shutdown.addDisposer(async () => { order.push('first'); entered.resolve(); await release.promise })
+ const removed = vi.fn(() => Promise.resolve())
+ shutdown.addDisposer(removed)()
+ shutdown.addDisposer(async () => { order.push('second') })
+ if (mode === 'signal') shutdown.interrupt(130)
+ else void shutdown.shutdown(0)
+ await entered.promise
+ expect(order).toEqual(['first'])
+ expect(() => shutdown.addDisposer(() => Promise.resolve())).toThrow('after shutdown starts')
+ release.resolve()
+ await shutdown.shutdown(0)
+ expect(order).toEqual(['first', 'second', 'providers'])
+ expect(removed).not.toHaveBeenCalled()
+ if (mode === 'signal') expect(exit).toHaveBeenCalledWith(130)
+ else expect(complete).toHaveBeenCalledWith(0)
+ })
+
it('releases caller-owned work before disposing its providers within the exit bound', async () => {
vi.useFakeTimers()
const release = deferred()
@@ -28,7 +52,8 @@ describe('process shutdown', () => {
const complete = vi.fn()
const shutdown = createProcessShutdown(dispose, exit, complete)
const cleanup = vi.fn(() => release.promise)
- const pending = shutdown.shutdown(0, cleanup)
+ shutdown.addDisposer(cleanup)
+ const pending = shutdown.shutdown(0)
await vi.advanceTimersByTimeAsync(0)
expect(cleanup).toHaveBeenCalledOnce()
expect(dispose).not.toHaveBeenCalled()
@@ -44,7 +69,11 @@ describe('process shutdown', () => {
const dispose = vi.fn(() => Promise.resolve())
const exit = vi.fn()
const shutdown = createProcessShutdown(dispose, exit, vi.fn())
- await shutdown.shutdown(1, () => Promise.reject(new Error('cleanup failed')))
+ shutdown.addDisposer(() => Promise.reject(new Error('cleanup failed')))
+ const later = vi.fn(() => Promise.resolve())
+ shutdown.addDisposer(later)
+ await shutdown.shutdown(1)
+ expect(later).toHaveBeenCalledOnce()
expect(dispose).toHaveBeenCalledOnce()
expect(exit).toHaveBeenCalledWith(1)
})
diff --git a/apps/cli/tests/profiles/headless/tests/expected/headless-profile/session.expected.jsonl b/apps/cli/tests/profiles/headless/tests/expected/headless-profile/session.expected.jsonl
index 90633e1398..bfcfc3ca75 100644
--- a/apps/cli/tests/profiles/headless/tests/expected/headless-profile/session.expected.jsonl
+++ b/apps/cli/tests/profiles/headless/tests/expected/headless-profile/session.expected.jsonl
@@ -15,8 +15,8 @@
{"type":"session/title-llm-request","data":{"titleProvider":"session-title-first-prompt-llm","messageSeqs":[8],"route":{"provider":"cli-mock","model":"cli-mock"},"system":"Create a concise title for an AI coding-assistant session from the supplied human messages.\nReturn only the title on one line, **in plain text of natural language**, with no quotes, prefix, explanation, Markdown, XML, or terminal control codes. No code is allowed.\nUse the language of the messages.\nAim for about 5 words in non-CJK languages or 10 CJK characters.\nIf the messages give little to name, still return a short best-effort title, such as Greeting, instead of explaining.","messages":[{"content":[{"type":"text","text":"Generate the session title from this JSON array of human messages:\n[{\"seq\":8,\"text\":\"Prove the product headless profile path with one real tool round trip.\"}]"}],"source":{"kind":"dsh-session-title-llm"},"role":"user","id":"{{sessionId}}"}],"maxTokens":4096,"reasoningEffort":"off"}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"Inspecting the task before the tool call."},{"type":"tool-call","id":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}],"source":{"kind":"model","provider":"cli-mock","model":"cli-mock"},"id":"{{sessionId}}"},"usage":{"inputTokens":11,"outputTokens":3,"cacheReadTokens":2},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":0,"index":0,"dt":[],"texts":["Inspecting the task before the tool call."]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"Inspecting the task before the tool call."}}},{"type":"chunk","time":0,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":1,"dt":[],"id":"cli-smoke-call","name":"bash","args":["{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":11,"outputTokens":3,"cacheReadTokens":2}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"cli-smoke-call","name":"bash","arguments":"{\"command\":\"printf CLI_TOOL_ROUND_TRIP\",\"description\":\"Prove the CLI tool round trip.\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{sessionId}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{sessionId}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{sessionId}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{sessionId}}","kind":"bash","label":"printf CLI_TOOL_ROUND_TRIP","startedAt":0,"toolCall":{"callId":"cli-smoke-call","rootCallId":"cli-smoke-call"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"cli-smoke-call"},"toolCallId":"cli-smoke-call","content":[{"type":"text","text":"CLI_TOOL_ROUND_TRIP"}],"isError":false,"id":"{{sessionId}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts b/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts
index 806f081a77..35c3e974bd 100644
--- a/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts
+++ b/apps/cli/tests/profiles/headless/tests/ptc.e2e.ts
@@ -212,7 +212,7 @@ describe('PTC mode typed values: keyless real-process contracts', () => {
});
return started.jobId;
`))
- expect(jobId).toBe('bash-1')
+ expect(jobId).toMatch(/^bash-[0-9a-f-]{36}$/)
const polled = completion(await runCode(ctx, `
return await tools.job_output({ job_id: ${JSON.stringify(jobId)}, wait: true, timeout_ms: 5000 });
@@ -257,7 +257,8 @@ describe('PTC mode typed values: keyless real-process contracts', () => {
}),
])
const job = ctx.jobs.list()[0]
- expect(job).toMatchObject({ id: 'bash-1', status: 'running' })
+ expect(job).toMatchObject({ status: 'running' })
+ expect(job?.id).toMatch(/^bash-[0-9a-f-]{36}$/)
afterPublication.abort('outer-call-cancelled')
expect((await running).isError).toBe(true)
expect(ctx.jobs.list()[0]).toMatchObject({ id: job!.id, status: 'running' })
diff --git a/docs/persistence-catalog.i18n.yaml b/docs/persistence-catalog.i18n.yaml
index 12f762cbb0..9007cfd7b4 100644
--- a/docs/persistence-catalog.i18n.yaml
+++ b/docs/persistence-catalog.i18n.yaml
@@ -9,8 +9,8 @@
en: 1387c4505dde2faf
zh: 48570fa1c13d6910
/session-persistence-event-catalog/persistence-type-fingerprints:
- en: 5efd64128ee0beba
- zh: a3aeb74af3e4ceed
+ en: f6421cacbfe510db
+ zh: 6c24e981ac836d0c
/session-persistence-event-catalog/event-envelope:
en: 7ebb1a2e0dd1f916
zh: 8946f415dda5c1b8
@@ -123,11 +123,11 @@
en: 941358a5fef6a5b4
zh: 941358a5fef6a5b4
/session-persistence-event-catalog/events/job/job-registered-log-only:
- en: 9bc39178c96d5299
- zh: d6111271de612006
+ en: 37ab7f962b199555
+ zh: af05a3274adbe530
/session-persistence-event-catalog/events/job/job-settled-log-only:
- en: ea26934b0ef807b0
- zh: 3083ab90282177e2
+ en: dde416e1c0d32dfb
+ zh: fa08197cad8acb9d
/session-persistence-event-catalog/events/llm:
en: 8a5c09e48ec0a6ae
zh: 8a5c09e48ec0a6ae
@@ -1068,17 +1068,17 @@
en: a3087ab7bebe9371
zh: abf6afc2f9f5b692
/session-persistence-event-catalog/resolved-persistence-types/inboxtarget:
- en: f6262b716c38eda8
- zh: 5283994136fa5c84
+ en: 938113c1b01efad1
+ zh: d6829dbe979e6819
/session-persistence-event-catalog/resolved-persistence-types/jobregistereddata:
- en: ba731d919da94e90
- zh: 0a5c909faed0d777
+ en: f604eebb973fe2cf
+ zh: 0c0aba1e17ecf66e
/session-persistence-event-catalog/resolved-persistence-types/jobsettlecause:
- en: 15b854ca2279b10a
- zh: bd5812e35d8e9e30
+ en: 748a4269d86e007a
+ zh: 4ce04c4451bdcfe8
/session-persistence-event-catalog/resolved-persistence-types/jobsettleddata:
- en: 2e881aee85a405a0
- zh: aecc2aee48d2e151
+ en: fc8caf270a7581f7
+ zh: b467e1d4191106eb
/session-persistence-event-catalog/resolved-persistence-types/jobtoolcall:
en: 7ffee3cc96604a88
zh: 026abfab0e46c21a
@@ -1335,11 +1335,11 @@
en: 5e7d82a493a1d4d8
zh: d2d785891dd766fc
/session-persistence-event-catalog/resolved-persistence-types/number~2:
- en: a80b187744cc8374
- zh: c115c644aac2c802
+ en: 2786fef560851bb4
+ zh: 5d787475356b2606
/session-persistence-event-catalog/resolved-persistence-types/string:
- en: c2d10e67ca221c64
- zh: b7f58ee3db797d07
+ en: b6921e3641b63763
+ zh: ee8ef743a532417b
/session-persistence-event-catalog/resolved-persistence-types/string~2:
en: 0089620bf4638263
zh: 1922bef1713094bb
@@ -1986,14 +1986,14 @@
en: 8b523e0be5327519
zh: 86ec891087b8bf24
/session-persistence-event-catalog/resolved-persistence-types/type-image-offload:
- en: 56e0870404de34d8
- zh: b1350f62badf6ed7
+ en: bf512693267d4262
+ zh: 805710898b4e913a
/session-persistence-event-catalog/resolved-persistence-types/type-job-registered:
- en: 9724ff355d3c4eb7
- zh: e1d476636ddfc348
+ en: 734d3bb283cc6f66
+ zh: 75bcb55f71673e34
/session-persistence-event-catalog/resolved-persistence-types/type-job-settled:
- en: a57217bb1b7e1f57
- zh: cf5ec59f480511e7
+ en: 742c3a1543414ecb
+ zh: e256fb92d72b8184
/session-persistence-event-catalog/resolved-persistence-types/type-llm-retry:
en: 6d026baeac30a0f1
zh: 55131683e3d5ed27
diff --git a/docs/persistence-catalog.md b/docs/persistence-catalog.md
index 582e6e8a3e..48c5c25cb3 100644
--- a/docs/persistence-catalog.md
+++ b/docs/persistence-catalog.md
@@ -44,8 +44,8 @@ The [format references](persistence-changes/historical-formats/README.md) cover
| `event:hook/invoked` | event | `8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0` | [`{ type: "hook/invoked" }`](#persistence-type-sha256-8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0) |
| `event:hook/result` | event | `e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4` | [`{ type: "hook/result" }`](#persistence-type-sha256-e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4) |
| `event:image/offload` | event | `b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e` | [`{ type: "image/offload" }`](#persistence-type-sha256-b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e) |
-| `event:job/registered` | event | `f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5` | [`{ type: "job/registered" }`](#persistence-type-sha256-f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5) |
-| `event:job/settled` | event | `a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648` | [`{ type: "job/settled" }`](#persistence-type-sha256-a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648) |
+| `event:job/registered` | event | `f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1` | [`{ type: "job/registered" }`](#persistence-type-sha256-f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1) |
+| `event:job/settled` | event | `edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf` | [`{ type: "job/settled" }`](#persistence-type-sha256-edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf) |
| `event:llm/retry` | event | `525254db03b1d1e6b74cf55aced52817568331ac1f96c0818728910b6692e336` | [`{ type: "llm/retry" }`](#persistence-type-sha256-525254db03b1d1e6b74cf55aced52817568331ac1f96c0818728910b6692e336) |
| `event:llm/retry-started` | event | `48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3` | [`{ type: "llm/retry-started" }`](#persistence-type-sha256-48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3) |
| `event:model/selection` | event | `35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc` | [`{ type: "model/selection" }`](#persistence-type-sha256-35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc) |
@@ -643,7 +643,7 @@ Source: [`packages/compaction/compaction-image-offload/src/projection.ts:25`](..
'job/registered': JobRegisteredData
```
-Source: [`packages/jobs/jobs/src/types.ts:61`](../packages/jobs/jobs/src/types.ts)
+Source: [`packages/jobs/jobs/src/types.ts:59`](../packages/jobs/jobs/src/types.ts)
@@ -651,13 +651,13 @@ Source: [`packages/jobs/jobs/src/types.ts:61`](../packages/jobs/jobs/src/types.t
```ts persistence-catalog
/**
- * Log-only terminal record, paired by executionId rather than a reusable jobId.
+ * Log-only terminal record, paired by the same unique jobId used for live control.
* @param data - Registration facts plus settlement time, outcome, and producer acknowledgement.
*/
'job/settled': JobSettledData
```
-Source: [`packages/jobs/jobs/src/types.ts:66`](../packages/jobs/jobs/src/types.ts)
+Source: [`packages/jobs/jobs/src/types.ts:64`](../packages/jobs/jobs/src/types.ts)
### `llm/*`
@@ -3776,7 +3776,7 @@ One of:
- `"next-step"`
- `"next-turn"`
-
+
@@ -3784,13 +3784,12 @@ One of:
### `JobRegisteredData`
-SHA-256: `c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2`
+SHA-256: `e3056e0dda3a379e895386e368c3ce42131460ea65e410e1d091cca98a347305`
Sources: [`packages/jobs/jobs/src/types.ts:24`](../packages/jobs/jobs/src/types.ts)
| Property | Presence | Type |
|---|---|---|
-| `executionId` | required | `string` |
| `jobId` | required | `string` |
| `kind` | required | `string` |
| `label` | required | `string` |
@@ -3808,7 +3807,7 @@ Sources: [`packages/jobs/jobs/src/types.ts:24`](../packages/jobs/jobs/src/types.
SHA-256: `750a2b129d67704df675c06823c20c80b65cbae89d5aaa914fd5a72e3df395b5`
-Sources: [`packages/jobs/jobs/src/types.ts:249`](../packages/jobs/jobs/src/types.ts)
+Sources: [`packages/jobs/jobs/src/types.ts:247`](../packages/jobs/jobs/src/types.ts)
One of:
@@ -3816,7 +3815,7 @@ One of:
- `"producer"`
- `"teardown"`
-
+
@@ -3824,15 +3823,14 @@ One of:
### `JobSettledData`
-SHA-256: `9aaca69bf3c44a4486ee23a54aa9ec8e7189a935f818c8ed670de2352f3c12c9`
+SHA-256: `611d82d2dc50df706cf08a0fc04bc8eaf78083391fe6f10313de943e615dd2da`
-Sources: [`packages/jobs/jobs/src/types.ts:42`](../packages/jobs/jobs/src/types.ts)
+Sources: [`packages/jobs/jobs/src/types.ts:40`](../packages/jobs/jobs/src/types.ts)
| Property | Presence | Type |
|---|---|---|
| `cause` | required | [`JobSettleCause`](#persistence-type-sha256-750a2b129d67704df675c06823c20c80b65cbae89d5aaa914fd5a72e3df395b5) |
| `detail` | optional | `string` |
-| `executionId` | required | `string` |
| `finishedAt` | required | `number` |
| `jobId` | required | `string` |
| `kind` | required | `string` |
@@ -5462,8 +5460,6 @@ Array of `number`.
-
-
@@ -5498,8 +5494,6 @@ Array of `number`.
-
-
@@ -5554,7 +5548,7 @@ Array of `number`.
SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b`
-Sources: [`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/jobs/jobs/src/brand.ts:31`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
+Sources: [`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
`string`
@@ -8307,33 +8301,33 @@ SHA-256: `b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e`
| `time` | required | `number` |
| `type` | required | `"image/offload"` |
-
+
### `{ type: "job/registered" }`
-SHA-256: `f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5`
+SHA-256: `f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1`
| Property | Presence | Type |
|---|---|---|
-| `data` | required | [`JobRegisteredData`](#persistence-type-sha256-c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2) |
+| `data` | required | [`JobRegisteredData`](#persistence-type-sha256-e3056e0dda3a379e895386e368c3ce42131460ea65e410e1d091cca98a347305) |
| `ignorable` | optional | `true` |
| `seq` | required | `number` |
| `time` | required | `number` |
| `type` | required | `"job/registered"` |
-
+
### `{ type: "job/settled" }`
-SHA-256: `a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648`
+SHA-256: `edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf`
| Property | Presence | Type |
|---|---|---|
-| `data` | required | [`JobSettledData`](#persistence-type-sha256-9aaca69bf3c44a4486ee23a54aa9ec8e7189a935f818c8ed670de2352f3c12c9) |
+| `data` | required | [`JobSettledData`](#persistence-type-sha256-611d82d2dc50df706cf08a0fc04bc8eaf78083391fe6f10313de943e615dd2da) |
| `ignorable` | optional | `true` |
| `seq` | required | `number` |
| `time` | required | `number` |
diff --git a/docs/persistence-catalog.zh.md b/docs/persistence-catalog.zh.md
index 1b590a5cf6..c8e5d8cb0e 100644
--- a/docs/persistence-catalog.zh.md
+++ b/docs/persistence-catalog.zh.md
@@ -46,8 +46,8 @@
| `event:hook/invoked` | event | `8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0` | [`{ type: "hook/invoked" }`](#persistence-type-sha256-8a6e1ec9e8db346b0e02f027db73c07a94f067a26d40c1aef1abd09c47ce7ba0) |
| `event:hook/result` | event | `e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4` | [`{ type: "hook/result" }`](#persistence-type-sha256-e75916628f3f10c2d50658bd143052a46285fbf1a9a700ba54947614603d26b4) |
| `event:image/offload` | event | `b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e` | [`{ type: "image/offload" }`](#persistence-type-sha256-b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e) |
-| `event:job/registered` | event | `f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5` | [`{ type: "job/registered" }`](#persistence-type-sha256-f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5) |
-| `event:job/settled` | event | `a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648` | [`{ type: "job/settled" }`](#persistence-type-sha256-a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648) |
+| `event:job/registered` | event | `f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1` | [`{ type: "job/registered" }`](#persistence-type-sha256-f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1) |
+| `event:job/settled` | event | `edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf` | [`{ type: "job/settled" }`](#persistence-type-sha256-edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf) |
| `event:llm/retry` | event | `525254db03b1d1e6b74cf55aced52817568331ac1f96c0818728910b6692e336` | [`{ type: "llm/retry" }`](#persistence-type-sha256-525254db03b1d1e6b74cf55aced52817568331ac1f96c0818728910b6692e336) |
| `event:llm/retry-started` | event | `48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3` | [`{ type: "llm/retry-started" }`](#persistence-type-sha256-48e5c9861f16ac07e78cb7b5ae9dabdf7bb85c58baed5a51b4ad275050ea58e3) |
| `event:model/selection` | event | `35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc` | [`{ type: "model/selection" }`](#persistence-type-sha256-35203ba7ad5ef6f97d556b85df20ae98f04f09c65748cecdf8eefdb8b6405ffc) |
@@ -645,7 +645,7 @@ export type SessionEvent = {
'job/registered': JobRegisteredData
```
-来源:[`packages/jobs/jobs/src/types.ts:61`](../packages/jobs/jobs/src/types.ts)
+来源:[`packages/jobs/jobs/src/types.ts:59`](../packages/jobs/jobs/src/types.ts)
@@ -653,13 +653,13 @@ export type SessionEvent = {
```ts persistence-catalog
/**
- * Log-only terminal record, paired by executionId rather than a reusable jobId.
+ * Log-only terminal record, paired by the same unique jobId used for live control.
* @param data - Registration facts plus settlement time, outcome, and producer acknowledgement.
*/
'job/settled': JobSettledData
```
-来源:[`packages/jobs/jobs/src/types.ts:66`](../packages/jobs/jobs/src/types.ts)
+来源:[`packages/jobs/jobs/src/types.ts:64`](../packages/jobs/jobs/src/types.ts)
### `llm/*`
@@ -3778,7 +3778,7 @@ SHA-256: `96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c`
- `"next-step"`
- `"next-turn"`
-
+
@@ -3786,13 +3786,12 @@ SHA-256: `96f4a9c81fc0f940ef71528a8cc66731ae4cf1f40d79680c5fe3c39f96d7723c`
### `JobRegisteredData`
-SHA-256: `c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2`
+SHA-256: `e3056e0dda3a379e895386e368c3ce42131460ea65e410e1d091cca98a347305`
来源:[`packages/jobs/jobs/src/types.ts:24`](../packages/jobs/jobs/src/types.ts)
| 属性 | 存在性 | 类型 |
|---|---|---|
-| `executionId` | 必需 | `string` |
| `jobId` | 必需 | `string` |
| `kind` | 必需 | `string` |
| `label` | 必需 | `string` |
@@ -3810,7 +3809,7 @@ SHA-256: `c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2`
SHA-256: `750a2b129d67704df675c06823c20c80b65cbae89d5aaa914fd5a72e3df395b5`
-来源:[`packages/jobs/jobs/src/types.ts:249`](../packages/jobs/jobs/src/types.ts)
+来源:[`packages/jobs/jobs/src/types.ts:247`](../packages/jobs/jobs/src/types.ts)
以下类型之一:
@@ -3818,7 +3817,7 @@ SHA-256: `750a2b129d67704df675c06823c20c80b65cbae89d5aaa914fd5a72e3df395b5`
- `"producer"`
- `"teardown"`
-
+
@@ -3826,15 +3825,14 @@ SHA-256: `750a2b129d67704df675c06823c20c80b65cbae89d5aaa914fd5a72e3df395b5`
### `JobSettledData`
-SHA-256: `9aaca69bf3c44a4486ee23a54aa9ec8e7189a935f818c8ed670de2352f3c12c9`
+SHA-256: `611d82d2dc50df706cf08a0fc04bc8eaf78083391fe6f10313de943e615dd2da`
-来源:[`packages/jobs/jobs/src/types.ts:42`](../packages/jobs/jobs/src/types.ts)
+来源:[`packages/jobs/jobs/src/types.ts:40`](../packages/jobs/jobs/src/types.ts)
| 属性 | 存在性 | 类型 |
|---|---|---|
| `cause` | 必需 | [`JobSettleCause`](#persistence-type-sha256-750a2b129d67704df675c06823c20c80b65cbae89d5aaa914fd5a72e3df395b5) |
| `detail` | 可选 | `string` |
-| `executionId` | 必需 | `string` |
| `finishedAt` | 必需 | `number` |
| `jobId` | 必需 | `string` |
| `kind` | 必需 | `string` |
@@ -5464,8 +5462,6 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78`
-
-
@@ -5500,8 +5496,6 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78`
-
-
@@ -5556,7 +5550,7 @@ SHA-256: `5d03ba38734809bcbd2a55221bd938b3b3fc34b49bb686c6113e9e4931e3aa78`
SHA-256: `4cd48f3e5108bd6ebedf7301c3638839b8b2b9db8dbd493974177fbdeb91a01b`
-来源:[`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/jobs/jobs/src/brand.ts:31`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
+来源:[`packages/api/session-controller/src/types.ts:398`](../packages/api/session-controller/src/types.ts) · [`packages/attachment/attachment/src/brand.ts:6`](../packages/attachment/attachment/src/brand.ts) · [`packages/compaction/compaction/src/brand.ts:4`](../packages/compaction/compaction/src/brand.ts) · [`packages/core/session/src/types.ts:20`](../packages/core/session/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:20`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:32`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/agent-team/src/types.ts:8`](../packages/experimental/agent-team/src/types.ts) · [`packages/experimental/webhook/src/brand.ts:12`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:6`](../packages/experimental/webhook/src/brand.ts) · [`packages/experimental/webhook/src/brand.ts:9`](../packages/experimental/webhook/src/brand.ts) · [`packages/feedback/message-feedback/src/types.ts:14`](../packages/feedback/message-feedback/src/types.ts) · [`packages/goal/goal/src/types.ts:17`](../packages/goal/goal/src/types.ts) · [`packages/interaction/commands/src/brand.ts:31`](../packages/interaction/commands/src/brand.ts) · [`packages/interaction/user-approval/src/types.ts:17`](../packages/interaction/user-approval/src/types.ts) · [`packages/jobs/jobs/src/brand.ts:19`](../packages/jobs/jobs/src/brand.ts) · [`packages/llm/llm-retry/src/brand.ts:4`](../packages/llm/llm-retry/src/brand.ts) · [`packages/llm/llm/src/brand.ts:16`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:31`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:43`](../packages/llm/llm/src/brand.ts) · [`packages/llm/llm/src/brand.ts:67`](../packages/llm/llm/src/brand.ts) · [`packages/schedule/schedule/src/types.ts:14`](../packages/schedule/schedule/src/types.ts) · [`packages/session/session-title/src/index.ts:42`](../packages/session/session-title/src/index.ts) · [`packages/session/session-title/src/types.ts:17`](../packages/session/session-title/src/types.ts) · [`packages/workflow/workflow/src/types.ts:13`](../packages/workflow/workflow/src/types.ts)
`string`
@@ -8309,33 +8303,33 @@ SHA-256: `b222069eea2d768065161c1147b1f2c78c1b54328c84b3586ae5c8f91b8ed35e`
| `time` | 必需 | `number` |
| `type` | 必需 | `"image/offload"` |
-
+
### `{ type: "job/registered" }`
-SHA-256: `f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5`
+SHA-256: `f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1`
| 属性 | 存在性 | 类型 |
|---|---|---|
-| `data` | 必需 | [`JobRegisteredData`](#persistence-type-sha256-c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2) |
+| `data` | 必需 | [`JobRegisteredData`](#persistence-type-sha256-e3056e0dda3a379e895386e368c3ce42131460ea65e410e1d091cca98a347305) |
| `ignorable` | 可选 | `true` |
| `seq` | 必需 | `number` |
| `time` | 必需 | `number` |
| `type` | 必需 | `"job/registered"` |
-
+
### `{ type: "job/settled" }`
-SHA-256: `a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648`
+SHA-256: `edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf`
| 属性 | 存在性 | 类型 |
|---|---|---|
-| `data` | 必需 | [`JobSettledData`](#persistence-type-sha256-9aaca69bf3c44a4486ee23a54aa9ec8e7189a935f818c8ed670de2352f3c12c9) |
+| `data` | 必需 | [`JobSettledData`](#persistence-type-sha256-611d82d2dc50df706cf08a0fc04bc8eaf78083391fe6f10313de943e615dd2da) |
| `ignorable` | 可选 | `true` |
| `seq` | 必需 | `number` |
| `time` | 必需 | `number` |
diff --git a/docs/persistence-changes/2026-09-30-job-session-lifecycle.i18n.yaml b/docs/persistence-changes/2026-09-30-job-session-lifecycle.i18n.yaml
index 3d551fe9f4..f1b6f36401 100644
--- a/docs/persistence-changes/2026-09-30-job-session-lifecycle.i18n.yaml
+++ b/docs/persistence-changes/2026-09-30-job-session-lifecycle.i18n.yaml
@@ -9,8 +9,8 @@
en: 9be8dc8cb5ce646a
zh: 6ce0040813c36e92
/2026-09-30-job-session-lifecycle/summary:
- en: 0c13281672f78e6f
- zh: 4caef968f21ba1d5
+ en: 7d98020db3395e2e
+ zh: abe692ef42327e49
/2026-09-30-job-session-lifecycle/table-of-contents:
en: 6cf314a069ceb451
zh: 9adff678e9ecab3b
@@ -18,11 +18,11 @@
en: 6907136f28e48c90
zh: 02f8298485f3d8e7
/2026-09-30-job-session-lifecycle/compatibility:
- en: ffa5be6797f3f89f
- zh: d1b2e288aa6c45d3
+ en: 546fe003ad5293b6
+ zh: 86d78c9704a03718
/2026-09-30-job-session-lifecycle/verification:
- en: 934e86bda5522670
- zh: 03a773c06e4a7b52
+ en: 2dd22e091ab487c1
+ zh: fcae964bdfa6f32a
/2026-09-30-job-session-lifecycle/dev-note:
en: b75c8458928b72b9
zh: 003ba4e17b4b5269
diff --git a/docs/persistence-changes/2026-09-30-job-session-lifecycle.md b/docs/persistence-changes/2026-09-30-job-session-lifecycle.md
index aeed17438e..b2420469d4 100644
--- a/docs/persistence-changes/2026-09-30-job-session-lifecycle.md
+++ b/docs/persistence-changes/2026-09-30-job-session-lifecycle.md
@@ -9,7 +9,7 @@ English | [中文](2026-09-30-job-session-lifecycle.zh.md)
## Summary
-Records owned job registration and settlement as log-only Session events with execution identity, original timestamps, creating tool calls, and producer completion reports.
+Records owned job registration and settlement with one unique JobId, original timestamps, creating tool calls, and producer completion reports.
## Table of Contents
@@ -28,23 +28,23 @@ baseline: false
changes:
- root: "event:job/registered"
previous: null
- after: "f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5"
+ after: "f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1"
decision: same-version
- root: "event:job/settled"
previous: null
- after: "a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648"
+ after: "edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf"
decision: same-version
```
## Compatibility
-Adds event kinds without changing existing payloads or the Session envelope. Historical sessions remain valid and receive no fabricated job history. Older builds reject unknown required event types. producerReported describes fulfillment of the producer completion promise, not verified resource release.
+Adds required job event kinds without changing the Session envelope. New JobIds use a kind-prefixed UUID for both live controls and durable pairing; historical sessions receive no fabricated job history. Older builds reject unknown required events. producerReported describes completion-promise fulfillment, not verified resource release.
## Verification
-Focused registry, normalizer, shutdown, and headless regressions cover causal publication order, distinct execution identities, and cleanup within the existing exit bound. Real CLI snapshot and SDK replay verify registration/settlement persistence and notifications.
+Registry, snapshot-normalizer, shutdown, and headless tests cover causal order, unique JobIds, and ordered cleanup for normal and signal exit. CLI and SDK snapshots verify durable registration/settlement and notices.
## Dev Note
diff --git a/docs/persistence-changes/2026-09-30-job-session-lifecycle.schema.json b/docs/persistence-changes/2026-09-30-job-session-lifecycle.schema.json
index 2b701c6321..f31a0696c3 100644
--- a/docs/persistence-changes/2026-09-30-job-session-lifecycle.schema.json
+++ b/docs/persistence-changes/2026-09-30-job-session-lifecycle.schema.json
@@ -6,7 +6,7 @@
"kind": "event",
"event": "job/registered",
"surface": false,
- "digest": "f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5",
+ "digest": "f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1",
"schema": {
"root": 0,
"nodes": [
@@ -44,11 +44,6 @@
{
"kind": "object",
"properties": [
- {
- "name": "executionId",
- "type": 2,
- "optional": false
- },
{
"name": "jobId",
"type": 2,
@@ -122,7 +117,7 @@
"kind": "event",
"event": "job/settled",
"surface": false,
- "digest": "a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648",
+ "digest": "edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf",
"schema": {
"root": 0,
"nodes": [
@@ -170,11 +165,6 @@
"type": 6,
"optional": true
},
- {
- "name": "executionId",
- "type": 6,
- "optional": false
- },
{
"name": "finishedAt",
"type": 7,
diff --git a/docs/persistence-changes/2026-09-30-job-session-lifecycle.zh.md b/docs/persistence-changes/2026-09-30-job-session-lifecycle.zh.md
index 15edebc215..8add75fd8c 100644
--- a/docs/persistence-changes/2026-09-30-job-session-lifecycle.zh.md
+++ b/docs/persistence-changes/2026-09-30-job-session-lifecycle.zh.md
@@ -9,7 +9,7 @@ kind: persistence-change
## 概述
-将有所有者 job 的注册与结算记录为仅日志 Session 事件,包含执行标识、原始时间戳、创建工具调用及生产方完成报告。
+以唯一 JobId 将有所有者 job 的注册与结算记录为 Session 事件,包含原始时间戳、创建工具调用和生产方完成报告。
## 目录
@@ -28,23 +28,23 @@ baseline: false
changes:
- root: "event:job/registered"
previous: null
- after: "f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5"
+ after: "f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1"
decision: same-version
- root: "event:job/settled"
previous: null
- after: "a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648"
+ after: "edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf"
decision: same-version
```
## 兼容性
-增加事件类型,不修改现有负载或 Session 封装。历史会话仍有效,不补造 job 历史。旧构建会拒绝未知的必需事件类型。producerReported 描述生产方完成 promise 的兑现情况,不确认资源已释放。
+增加必需的 job 事件类型,不改变 Session 封装。新 JobId 用带 kind 前缀的 UUID 同时进行运行期控制与持久化配对;不为历史会话补造 job 事件。旧构建拒绝未知的必需事件。producerReported 表示完成 promise 兑现,不确认资源已释放。
## 验证
-定向注册表、归一化、关停与 headless 回归覆盖因果发布顺序、不同执行标识,以及已有退出时限内的清理。真实 CLI 快照和 SDK 回放验证注册/结算持久化与通知。
+注册表、快照归一化、关停与 headless 测试覆盖因果顺序、唯一 JobId,以及正常退出和信号退出的有序清理。CLI 和 SDK 快照验证持久化注册、结算和通知。
## 开发备注
diff --git a/docs/persistence-schema.json b/docs/persistence-schema.json
index 27c107e97c..dc2611e871 100644
--- a/docs/persistence-schema.json
+++ b/docs/persistence-schema.json
@@ -10344,7 +10344,7 @@
"kind": "event",
"event": "job/registered",
"surface": false,
- "digest": "f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5",
+ "digest": "f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1",
"schema": {
"root": 0,
"nodes": [
@@ -10382,11 +10382,6 @@
{
"kind": "object",
"properties": [
- {
- "name": "executionId",
- "type": 2,
- "optional": false
- },
{
"name": "jobId",
"type": 2,
@@ -10460,7 +10455,7 @@
"kind": "event",
"event": "job/settled",
"surface": false,
- "digest": "a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648",
+ "digest": "edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf",
"schema": {
"root": 0,
"nodes": [
@@ -10508,11 +10503,6 @@
"type": 6,
"optional": true
},
- {
- "name": "executionId",
- "type": 6,
- "optional": false
- },
{
"name": "finishedAt",
"type": 7,
@@ -23446,7 +23436,6 @@
"packages/goal/goal/src/types.ts#GoalId",
"packages/interaction/commands/src/brand.ts#CommandId",
"packages/interaction/user-approval/src/types.ts#ApprovalRequestId",
- "packages/jobs/jobs/src/brand.ts#JobExecutionId",
"packages/jobs/jobs/src/brand.ts#JobId",
"packages/llm/llm-retry/src/brand.ts#RetryId",
"packages/llm/llm/src/brand.ts#MessageId",
@@ -23474,7 +23463,6 @@
"packages/interaction/commands/src/brand.ts:31",
"packages/interaction/user-approval/src/types.ts:17",
"packages/jobs/jobs/src/brand.ts:19",
- "packages/jobs/jobs/src/brand.ts:31",
"packages/llm/llm-retry/src/brand.ts:4",
"packages/llm/llm/src/brand.ts:16",
"packages/llm/llm/src/brand.ts:31",
@@ -23801,6 +23789,15 @@
"names": [],
"sources": []
},
+ {
+ "digest": "611d82d2dc50df706cf08a0fc04bc8eaf78083391fe6f10313de943e615dd2da",
+ "names": [
+ "packages/jobs/jobs/src/types.ts#JobSettledData"
+ ],
+ "sources": [
+ "packages/jobs/jobs/src/types.ts:40"
+ ]
+ },
{
"digest": "616f93a5047de668e1c30d57b1786abd76beeb4123599637ec1dc5290320ede5",
"names": [],
@@ -24071,7 +24068,7 @@
"packages/jobs/jobs/src/types.ts#JobSettleCause"
],
"sources": [
- "packages/jobs/jobs/src/types.ts:249"
+ "packages/jobs/jobs/src/types.ts:247"
]
},
{
@@ -24634,15 +24631,6 @@
"packages/core/session/src/types.ts:240"
]
},
- {
- "digest": "9aaca69bf3c44a4486ee23a54aa9ec8e7189a935f818c8ed670de2352f3c12c9",
- "names": [
- "packages/jobs/jobs/src/types.ts#JobSettledData"
- ],
- "sources": [
- "packages/jobs/jobs/src/types.ts:42"
- ]
- },
{
"digest": "9ac52f0f84fec2eb9f71c2937e497d759db5ca0da4836d93a679d113684141f2",
"names": [],
@@ -24713,11 +24701,6 @@
"names": [],
"sources": []
},
- {
- "digest": "a103c03b3bc0ca7a7809d1eb2572dec08899f7a15a90072736777acd4e517648",
- "names": [],
- "sources": []
- },
{
"digest": "a10c17474eaf2ddab7095a099e0fe3d046fc18e56c3e344fc8894c05ff9ef97b",
"names": [],
@@ -25352,15 +25335,6 @@
"names": [],
"sources": []
},
- {
- "digest": "c98cb9b944f83bf77ad098039861cbad71c3b8377e19c2bfccf72e1db16a6be2",
- "names": [
- "packages/jobs/jobs/src/types.ts#JobRegisteredData"
- ],
- "sources": [
- "packages/jobs/jobs/src/types.ts:24"
- ]
- },
{
"digest": "c99e20441bf15341e9db54385e558e5c03d21dff2eb1ac08ccbf47c2d8747954",
"names": [],
@@ -25626,6 +25600,15 @@
"names": [],
"sources": []
},
+ {
+ "digest": "e3056e0dda3a379e895386e368c3ce42131460ea65e410e1d091cca98a347305",
+ "names": [
+ "packages/jobs/jobs/src/types.ts#JobRegisteredData"
+ ],
+ "sources": [
+ "packages/jobs/jobs/src/types.ts:24"
+ ]
+ },
{
"digest": "e308ccf867a5398e316e0af8cb6ce238a8d33a63b9b384c8250a686786285f72",
"names": [],
@@ -25767,6 +25750,11 @@
"names": [],
"sources": []
},
+ {
+ "digest": "edd37b1e4974af648633014435572089b1e8fb71910468a0767a7e28fdd22abf",
+ "names": [],
+ "sources": []
+ },
{
"digest": "ee577a0cc18664c9daee50e9455153321155604f8f1817362f8f4a4b6fb9e1ad",
"names": [],
@@ -25804,11 +25792,6 @@
"names": [],
"sources": []
},
- {
- "digest": "f05be3e066ef054a610f0825251f19ee3fcfc98908b5ceaa869ee143d7e91fa5",
- "names": [],
- "sources": []
- },
{
"digest": "f05fb85e715af4ab4de0b6ba111166dd0435bd8bb74a0fa0f32b9d174113fc7c",
"names": [],
@@ -25886,6 +25869,11 @@
"names": [],
"sources": []
},
+ {
+ "digest": "f5520c1d261ca86ea03735799c311a0a9ede8babb588cf0a65422576d2ee0ad1",
+ "names": [],
+ "sources": []
+ },
{
"digest": "f56c5b59ca0161310c338d31dd16cf14287ccd602ff4181058df178c1c286d7f",
"names": [
diff --git a/docs/subsystems/jobs.i18n.yaml b/docs/subsystems/jobs.i18n.yaml
index dd5a7645c9..00861e6f06 100644
--- a/docs/subsystems/jobs.i18n.yaml
+++ b/docs/subsystems/jobs.i18n.yaml
@@ -6,11 +6,11 @@
en: dd515d8548864a91
zh: 16a117d39989a373
/background-job-runtime/ids-and-status:
- en: 21cd01e194bd3a83
- zh: 33cf767ac89e3406
+ en: 3cb96fdb6ae34bd2
+ zh: 7c85929b455f9431
/background-job-runtime/session-lifecycle-records:
- en: 763d819a047751a0
- zh: 8204f81e8f4a686f
+ en: 1b6b65e4c2983518
+ zh: 12bd4c7158d5e55e
/background-job-runtime/producer-contract:
en: af0be338a0ba5e60
zh: fc0eefb8c31f3a98
diff --git a/docs/subsystems/jobs.md b/docs/subsystems/jobs.md
index 960923b989..05d01cf75e 100644
--- a/docs/subsystems/jobs.md
+++ b/docs/subsystems/jobs.md
@@ -6,7 +6,7 @@ Types shared by long-running producers, `ctx.jobs`, and job controls. The [seam
## Ids and status
-`JobId` is a [branded id](core.md#branded-ids) generated as `-N`. Access control relies on owner authorization, not id secrecy. `JobKind` derives from a merge-extensible map; the registry treats kinds as opaque id namespaces.
+`JobId` is a [branded id](core.md#branded-ids) generated as `-`. Access control relies on owner authorization, not id secrecy. `JobKind` derives from a merge-extensible map; the registry treats kinds as opaque id namespaces.
```ts type-equiv
/**
@@ -24,16 +24,14 @@ interface JobKindMap {
## Session lifecycle records
-Owned jobs publish `job/registered` and `job/settled` in their original owner Session. `executionId` identifies one execution across registry restarts and copied history; `jobId` remains the process-local control id. The SDK forwards these log-only records through `session.event`, independently of output reads. Timestamps describe registry commits, not CPU time or exact OS process boundaries; a missing settlement remains incomplete. `producerReported` means the producer fulfilled `done` with a terminal outcome; it is false for forced teardown or rejection and does not independently certify resource release.
+Owned jobs publish `job/registered` and `job/settled` in their original owner Session. The globally unique `jobId` pairs those records and controls the live job. The SDK forwards these log-only records through `session.event`, independently of output reads. Timestamps describe registry commits, not CPU time or exact OS process boundaries; a missing settlement remains incomplete. `producerReported` means the producer fulfilled `done` with a terminal outcome; it is false for forced teardown or rejection and does not independently certify resource release.
`job/settled` may follow `turn/end`. An SDK run result covers only its own observation window; consumers needing later settlements retain a Session subscription or read persisted history. Turn completion does not imply that every owned job stopped.
```ts type-equiv
/** Durable identity and registration time of one owned job execution. */
interface JobRegisteredData {
- /** UUID unique to this execution, even when the registry reuses a display id. */
- readonly executionId: JobExecutionId
- /** Runtime-local id accepted by job controls. */
+ /** Unique execution id accepted by job controls and retained in Session history. */
readonly jobId: JobId
/** Original owner; copied history does not transfer execution ownership. */
readonly owner: SessionId
@@ -132,7 +130,7 @@ interface JobSpec {
* throwing, so a producer's trailing flush cannot break its own teardown path.
*/
interface JobHandle {
- /** The registry-issued id (`-N`). */
+ /** The registry-issued id (`-`). */
readonly id: JobId
/**
* Append one chunk to the output ring. Offsets advance by the chunk's UTF-8
@@ -275,7 +273,7 @@ interface JobOutputRead {
* stream all consume this one shape.
*/
interface JobView {
- /** The registry-issued id (`-N`). */
+ /** The registry-issued id (`-`). */
readonly id: JobId
/**
* The producer kind the job was registered with: a Host-registered
@@ -461,7 +459,7 @@ Implementations must honor these semantics:
* rejection leaves no job id or execution resource. A throwing starter
* leaves nothing registered; after it returns, registration cannot fail.
* @param spec - job identity, owner, output sources, and synchronous starter.
- * @returns the registry-issued `-N` id.
+ * @returns the registry-issued `-` id.
*/
abstract start(spec: JobSpec): JobId
diff --git a/docs/subsystems/jobs.zh.md b/docs/subsystems/jobs.zh.md
index 93e8ca6cb8..a55b25b484 100644
--- a/docs/subsystems/jobs.zh.md
+++ b/docs/subsystems/jobs.zh.md
@@ -6,7 +6,7 @@
## ID 与状态
-`JobId` 是按 `-N` 生成的[品牌化 id](core.zh.md#branded-ids)。访问控制依赖拥有者授权,而非 id 的保密性。`JobKind` 派生自可合并扩展的 map;注册表将各个 kind 视为不透明的 id 命名空间。
+`JobId` 是按 `-` 生成的[品牌化 id](core.zh.md#branded-ids)。访问控制依赖拥有者授权,而非 id 的保密性。`JobKind` 派生自可合并扩展的 map;注册表将各个 kind 视为不透明的 id 命名空间。
```ts type-equiv
/**
@@ -24,16 +24,14 @@ interface JobKindMap {
## Session 生命周期记录
-有所有者的 job 在原始所有者 Session 中发布 `job/registered` 与 `job/settled`。`executionId` 在注册表重启和历史复制后仍标识同一次执行;`jobId` 仍是进程内控制 id。SDK 通过 `session.event` 转发这些仅日志记录,不依赖输出读取。时间戳描述注册表提交时刻,不是 CPU 时间或精确的 OS 进程边界;缺少结算时保持不完整。`producerReported` 表示生产方通过兑现 `done` 报告了终态;强制销毁或 promise 拒绝时为 false,不独立确认资源已释放。
+有所有者的 job 在原始所有者 Session 中发布 `job/registered` 与 `job/settled`。全局唯一的 `jobId` 同时配对这两条记录并控制运行中的 job。SDK 通过 `session.event` 转发这些仅日志记录,不依赖输出读取。时间戳描述注册表提交时刻,不是 CPU 时间或精确的 OS 进程边界;缺少结算时保持不完整。`producerReported` 表示生产方通过兑现 `done` 报告了终态;强制销毁或 promise 拒绝时为 false,不独立确认资源已释放。
`job/settled` 可能出现在 `turn/end` 之后。SDK run 结果只覆盖自身观测窗口;需要较晚结算的消费方应保留 Session 订阅或读取持久化历史。轮次完成不表示所有有主 job 都已停止。
```ts type-equiv
/** Durable identity and registration time of one owned job execution. */
interface JobRegisteredData {
- /** UUID unique to this execution, even when the registry reuses a display id. */
- readonly executionId: JobExecutionId
- /** Runtime-local id accepted by job controls. */
+ /** Unique execution id accepted by job controls and retained in Session history. */
readonly jobId: JobId
/** Original owner; copied history does not transfer execution ownership. */
readonly owner: SessionId
@@ -132,7 +130,7 @@ interface JobSpec {
* throwing, so a producer's trailing flush cannot break its own teardown path.
*/
interface JobHandle {
- /** The registry-issued id (`-N`). */
+ /** The registry-issued id (`-`). */
readonly id: JobId
/**
* Append one chunk to the output ring. Offsets advance by the chunk's UTF-8
@@ -275,7 +273,7 @@ interface JobOutputRead {
* stream all consume this one shape.
*/
interface JobView {
- /** The registry-issued id (`-N`). */
+ /** The registry-issued id (`-`). */
readonly id: JobId
/**
* The producer kind the job was registered with: a Host-registered
@@ -461,7 +459,7 @@ Implementations must honor these semantics:
* rejection leaves no job id or execution resource. A throwing starter
* leaves nothing registered; after it returns, registration cannot fail.
* @param spec - job identity, owner, output sources, and synchronous starter.
- * @returns the registry-issued `-N` id.
+ * @returns the registry-issued `-` id.
*/
abstract start(spec: JobSpec): JobId
diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/job-id-identity/guide.i18n.yaml b/docs/upgrade-guide/v0.2.1-alpha.1/job-id-identity/guide.i18n.yaml
new file mode 100644
index 0000000000..d5fc8af6f3
--- /dev/null
+++ b/docs/upgrade-guide/v0.2.1-alpha.1/job-id-identity/guide.i18n.yaml
@@ -0,0 +1,16 @@
+# Bilingual-pair consistency record for guide.md (docs/i18n/README.md): per heading
+# section, a hash of its English and Chinese blocks outside code blocks and generated regions.
+# After editing either side, bring the other along and re-record with:
+# pnpm run verify-translation-pairing --write docs/upgrade-guide/v0.2.1-alpha.1/job-id-identity/guide.md
+/:
+ en: d6fdce6b58c67691
+ zh: a103e5028f5442d0
+/background-job-ids-identify-one-execution:
+ en: 9b06780be69e93f6
+ zh: c33945fd584c6a23
+/background-job-ids-identify-one-execution/change:
+ en: 93ae25c874b916b9
+ zh: 38dee1fc3edd2873
+/background-job-ids-identify-one-execution/migration:
+ en: c5ec322772f41799
+ zh: 8e9f27d749995be2
diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/job-id-identity/guide.md b/docs/upgrade-guide/v0.2.1-alpha.1/job-id-identity/guide.md
new file mode 100644
index 0000000000..2ff0d63d42
--- /dev/null
+++ b/docs/upgrade-guide/v0.2.1-alpha.1/job-id-identity/guide.md
@@ -0,0 +1,18 @@
+---
+kind: upgrade-guide
+description: "JobId now identifies one background execution across registry lifetimes."
+---
+
+# Background job ids identify one execution
+
+English | [中文](guide.zh.md)
+
+## Change
+
+The job registry returns `-` instead of `-N`. The same `JobId` appears in live controls, `job/registered`, and `job/settled`; lifecycle records no longer carry `executionId`. The identifier remains owner-authorized. Existing Session histories remain unchanged and do not resume their jobs.
+
+## Migration
+
+1. Pass the exact id returned by a start result or `jobs.list()` to `job_output`, `job_wait`, and `job_kill`. Remove code that constructs an id from the kind and an ordinal.
+2. Pair SDK lifecycle events by `data.jobId`. Remove imports of `JobExecutionId` and code that maps it to a process-local `jobId`.
+3. Verify a new job's registration and settlement contain the same `jobId`, and a later registry instance issues a different one.
diff --git a/docs/upgrade-guide/v0.2.1-alpha.1/job-id-identity/guide.zh.md b/docs/upgrade-guide/v0.2.1-alpha.1/job-id-identity/guide.zh.md
new file mode 100644
index 0000000000..0e313ecb98
--- /dev/null
+++ b/docs/upgrade-guide/v0.2.1-alpha.1/job-id-identity/guide.zh.md
@@ -0,0 +1,18 @@
+---
+kind: upgrade-guide
+description: "JobId 现在跨注册表生命周期标识一次后台执行。"
+---
+
+# 后台 job id 标识一次执行
+
+[English](guide.md) | 中文
+
+## 变更
+
+job 注册表返回 `-`,不再返回 `-N`。运行期控制、`job/registered` 和 `job/settled` 使用同一个 `JobId`;生命周期记录不再携带 `executionId`。访问仍按所有者授权。已有 Session 历史保持原样,不会恢复其中的 job。
+
+## 迁移
+
+1. 把启动结果或 `jobs.list()` 返回的原始 id 传给 `job_output`、`job_wait` 和 `job_kill`。移除按 kind 和序号构造 id 的代码。
+2. 使用 `data.jobId` 配对 SDK 生命周期事件。移除 `JobExecutionId` 导入,以及将它映射为进程内 `jobId` 的代码。
+3. 核对新 job 的注册与结算含有相同 `jobId`,后续注册表实例发放不同 id。
diff --git a/packages/boot/cmdline/README.i18n.yaml b/packages/boot/cmdline/README.i18n.yaml
index 9dfb2621b7..c1a153c690 100644
--- a/packages/boot/cmdline/README.i18n.yaml
+++ b/packages/boot/cmdline/README.i18n.yaml
@@ -18,8 +18,8 @@
en: ad6f55231a2cb77d
zh: 9039fa43deeaa458
/deepseek-ai-dsh-cmdline/use-this-package/the-launcher-values:
- en: ee861ab9618f1d80
- zh: d0ebe531322e1eac
+ en: db6e8aa9f1d9c4a5
+ zh: 3b0729d44a4b4558
/deepseek-ai-dsh-cmdline/use-this-package/parsing-your-flags:
en: e88383d7638cb933
zh: 0d0608be857be763
@@ -36,8 +36,8 @@
en: 0753fe84c14e7d0b
zh: acc5c2224fac229e
/deepseek-ai-dsh-cmdline/understand-the-implementation/parsing-contract:
- en: 720b38c4c9afeb93
- zh: b43160d7176e5799
+ en: b551c55a0dac339e
+ zh: 4248c18756cf0b18
/deepseek-ai-dsh-cmdline/understand-the-implementation/source-map:
en: a5f51481ca8e8751
zh: c512261b9a3bca74
diff --git a/packages/boot/cmdline/README.md b/packages/boot/cmdline/README.md
index c2ae8fbc49..5567739e18 100644
--- a/packages/boot/cmdline/README.md
+++ b/packages/boot/cmdline/README.md
@@ -29,13 +29,14 @@ Your app reads the invocation's inner arguments at startup, and any number of it
### The launcher values
-The launcher makes three things available to your app:
+The launcher makes four things available to your app:
- `ctx.cmdlineArgs` — the inner arguments of your invocation. Reading them returns an immutable snapshot and never consumes or changes them: `dsh --profile tui --resume abc` gives your app `['--resume', 'abc']`.
- `ctx.appExit` — a way to ask the process to exit once the tree has shut down, wired to the launcher's shutdown controller.
+- `ctx.appShutdown` — register owned cleanup before provider disposal for normal and signal exit.
- `ctx.appReady` — the successful-startup signal, committed only after the Loader tree and launcher-owned setup succeed.
-`ctx.appExit(code, beforeDispose)` can release caller-owned resources while their providers remain mounted. The callback and whole-tree disposal share the launcher's existing exit bound; rejecting cleanup still attempts tree disposal.
+Register cleanup with `ctx.appShutdown.addDisposer()` while providers are active. The launcher runs entries in registration order on both normal and signal exit under its existing bound, then disposes the provider tree. A rejection does not skip later cleanup or the tree.
An app launched with no arguments sees an empty list — that is the honest answer, not a missing value.
@@ -92,7 +93,7 @@ This section explains how the outcomes above are realized and points at the code
### Parsing contract
-The parse path is one small family with two owners: `provideCmdline` freezes the host arguments and provides `cmdlineArgs` and `appExit` before any tree entry mounts, and `parseCmdline` runs your commander program against the immutable arguments, routing every command's help, version, and error output through the launcher. A rejected value, `--help`, or `--version` prints commander's text and requests `ctx.appExit` without publishing anything, so dependent rows never activate; Loader defers each row's `!!js` interpolation until its declared injections are active. Per-export contracts live in the code, not this README — see [`src/index.ts`](src/index.ts).
+The parse path is one small family with two owners: `provideCmdline` freezes the host arguments and provides `cmdlineArgs`, `appExit`, and `appShutdown` before any tree entry mounts, and `parseCmdline` runs your commander program against the immutable arguments, routing every command's help, version, and error output through the launcher. A rejected value, `--help`, or `--version` prints commander's text and requests `ctx.appExit` without publishing anything, so dependent rows never activate; Loader defers each row's `!!js` interpolation until its declared injections are active. Per-export contracts live in the code, not this README — see [`src/index.ts`](src/index.ts).
### Source map
diff --git a/packages/boot/cmdline/README.zh.md b/packages/boot/cmdline/README.zh.md
index cedc4fd083..f48595bb35 100644
--- a/packages/boot/cmdline/README.zh.md
+++ b/packages/boot/cmdline/README.zh.md
@@ -33,9 +33,10 @@ kind: "package-library"
- `ctx.cmdlineArgs`——本次调用的内层参数。读取它返回一份不可变快照,且绝不会消费或修改它们:`dsh --profile tui --resume abc` 给你的应用 `['--resume', 'abc']`。
- `ctx.appExit`——在整棵树关闭后请求进程退出的方式,接到启动器的关停控制器上。
+- `ctx.appShutdown`——在正常退出或信号退出时,先于提供方销毁运行已注册的有序清理。
- `ctx.appReady`——成功启动信号,只在 Loader 树与启动器自有设置成功后提交。
-`ctx.appExit(code, beforeDispose)` 可以在提供方仍挂载时释放调用方拥有的资源。该回调与整棵树销毁共用启动器已有的退出时限;清理拒绝时仍会尝试销毁树。
+在提供方仍有效时,用 `ctx.appShutdown.addDisposer()` 注册清理。正常退出和信号退出共用启动器已有时限,按注册顺序先运行清理,再销毁提供方树。某个清理拒绝不会跳过后续清理或整棵树。
没有参数的启动会看到空列表——这是诚实的答案,而不是缺失的值。
@@ -92,7 +93,7 @@ kind: "package-library"
### 解析约定
-解析流程由两部分负责:`provideCmdline` 冻结宿主参数,并在任何配置树条目挂载前提供 `cmdlineArgs` 与 `appExit`;`parseCmdline` 针对不可变参数运行你的 commander program,把每个命令的 help、version 与错误输出都接到启动器上。被拒绝的值、`--help` 或 `--version` 会打印 commander 文本并请求 `ctx.appExit`,且不发布任何内容,因此依赖行绝不会激活;Loader 会把每行的 `!!js` 插值推迟到该行声明的注入全部激活之后。各导出的约定在代码中,不在本 README——见 [`src/index.ts`](src/index.ts)。
+解析流程由两部分负责:`provideCmdline` 冻结宿主参数,并在任何配置树条目挂载前提供 `cmdlineArgs`、`appExit` 与 `appShutdown`;`parseCmdline` 针对不可变参数运行你的 commander program,把每个命令的 help、version 与错误输出都接到启动器上。被拒绝的值、`--help` 或 `--version` 会打印 commander 文本并请求 `ctx.appExit`,且不发布任何内容,因此依赖行绝不会激活;Loader 会把每行的 `!!js` 插值推迟到该行声明的注入全部激活之后。各导出的约定在代码中,不在本 README——见 [`src/index.ts`](src/index.ts)。
### 源码地图
diff --git a/packages/boot/cmdline/src/index.ts b/packages/boot/cmdline/src/index.ts
index c7938cd8e6..13246322c2 100644
--- a/packages/boot/cmdline/src/index.ts
+++ b/packages/boot/cmdline/src/index.ts
@@ -35,11 +35,21 @@ export interface CmdlineArgs {
/** Request bounded process exit; the launcher wires it to its shutdown controller. */
export interface AppExit {
/**
- * Request exit after caller-owned cleanup and whole-tree disposal within the launcher's bound.
+ * Request exit after registered cleanup and whole-tree disposal within the launcher's bound.
* @param code - the process exit code.
- * @param beforeDispose - optional owned-resource cleanup that needs the tree's providers alive.
*/
- (code: number, beforeDispose?: () => Promise): void
+ (code: number): void
+}
+
+/** Ordered resource cleanup shared by normal and signal-triggered process exit. */
+export interface AppShutdown {
+ /**
+ * Register cleanup that runs once before application providers dispose, in registration order.
+ * A failed cleanup does not skip later entries or whole-tree disposal; the exit bound covers all stages.
+ * @param dispose - owned-resource cleanup requiring live application providers.
+ * @returns an unregister function; registrations are refused after shutdown starts.
+ */
+ addDisposer(dispose: () => Promise): () => void
}
/** Successful application-startup signal owned by the launcher. */
@@ -59,6 +69,8 @@ declare module '@deepseek-ai/cordis' {
cmdlineArgs?: CmdlineArgs
/** Bounded process-exit request; provided by a launcher before the tree mounts. */
appExit?: AppExit
+ /** Ordered cleanup before provider disposal on normal or signal exit. */
+ appShutdown?: AppShutdown
/** Successful startup signal; provided by a launcher before the tree mounts. */
appReady?: AppReady
}
@@ -70,6 +82,8 @@ export interface CmdlineHost {
args: readonly string[]
/** Bounded process-exit request. */
exit: AppExit
+ /** Ordered cleanup registry for normal and signal exit. */
+ shutdown?: AppShutdown
/** Successful startup signal for lifecycle work that must not mask boot failure. */
ready?: AppReady
}
@@ -86,6 +100,7 @@ export function provideCmdline(ctx: Context, host: CmdlineHost): void {
const snapshot: readonly string[] = Object.freeze([...host.args])
ctx.provide('cmdlineArgs', { get: () => snapshot })
ctx.provide('appExit', host.exit)
+ if (host.shutdown !== undefined) ctx.provide('appShutdown', host.shutdown)
if (host.ready !== undefined) ctx.provide('appReady', host.ready)
}
diff --git a/packages/boot/cmdline/tests/cmdline.spec.ts b/packages/boot/cmdline/tests/cmdline.spec.ts
index 687a219a2c..e44fd80264 100644
--- a/packages/boot/cmdline/tests/cmdline.spec.ts
+++ b/packages/boot/cmdline/tests/cmdline.spec.ts
@@ -220,6 +220,13 @@ describe('parseCmdline', () => {
})
describe('provideCmdline', () => {
+ it('shares launcher-owned cleanup registration with application plugins', () => {
+ const ctx = new Context()
+ const shutdown = { addDisposer: () => () => {} }
+ provideCmdline(ctx, { args: [], exit: () => {}, shutdown })
+ expect(ctx.get('appShutdown')).toBe(shutdown)
+ })
+
it('hands the app a snapshot the caller cannot mutate afterwards', () => {
const ctx = new Context()
const args = ['--resume', 'abc']
diff --git a/packages/bundle/headless/README.i18n.yaml b/packages/bundle/headless/README.i18n.yaml
index 2922e9b552..33e81509b8 100644
--- a/packages/bundle/headless/README.i18n.yaml
+++ b/packages/bundle/headless/README.i18n.yaml
@@ -36,8 +36,8 @@
en: be1b73a6d0ee3902
zh: bdba099685d68b27
/deepseek-ai-dsh-headless/understand-the-implementation/run-flow:
- en: 495441a707bf8cd6
- zh: b9ea418971ba8e42
+ en: a559f832b862382b
+ zh: 52f92dc8fe33c4c2
/deepseek-ai-dsh-headless/understand-the-implementation/patch-surface-over-base:
en: a22752c052878579
zh: 1e42c946d8673eeb
diff --git a/packages/bundle/headless/README.md b/packages/bundle/headless/README.md
index b7e22b1a52..13e32db0c5 100644
--- a/packages/bundle/headless/README.md
+++ b/packages/bundle/headless/README.md
@@ -77,7 +77,7 @@ The runner is a direct driver over the core API carrier: it resolves the Agent i
### Run flow
-The runner awaits the complete application (`ctx.get('loader')?.await()`) so the composed tools and adapters are not half-mounted, reads the shared [`agentDefaultModel`](../../core/agent-default-model/README.md) selection, resolves the task from config or stdin, then resolves the Agent identity: a fresh `session-` by default, or the persisted Session `--session-id` names, which it adopts through [`sessionQuery`](../../session-query/session-query/README.md) and refuses when no log exists. It submits the task as an ordinary user message. Without `--json` it streams that Agent's non-empty reasoning deltas to stderr; with `--json` it projects the run instead. It waits for child work and rechecks that the parent remains idle with an unchanged Session sequence before finishing, so settlement notices can drive a final parent answer. Idle children with parked inbox messages do not delay completion; process shutdown discards their unclaimed messages. It then flushes the Session and folds the owned interval (`firstSeq` onward) into the last non-empty `assistant/message` text and final `turn/end` reason. It writes the final text to stdout (or the `final` event) and requests exit with its owned Agent disposal. The launcher drains that Agent, including owned jobs and its Session write handle, before disposing the providers; this uses the existing bounded shutdown deadline.
+The runner awaits the complete application (`ctx.get('loader')?.await()`) so the composed tools and adapters are not half-mounted, reads the shared [`agentDefaultModel`](../../core/agent-default-model/README.md) selection, resolves the task from config or stdin, then resolves the Agent identity: a fresh `session-` by default, or the persisted Session `--session-id` names, which it adopts through [`sessionQuery`](../../session-query/session-query/README.md) and refuses when no log exists. It submits the task as an ordinary user message. Without `--json` it streams that Agent's non-empty reasoning deltas to stderr; with `--json` it projects the run instead. It waits for child work and rechecks that the parent remains idle with an unchanged Session sequence before finishing, so settlement notices can drive a final parent answer. Idle children with parked inbox messages do not delay completion; process shutdown discards their unclaimed messages. It then flushes the Session and folds the owned interval (`firstSeq` onward) into the last non-empty `assistant/message` text and final `turn/end` reason. It registers Agent disposal with `appShutdown` before awaiting acquisition, writes the final text to stdout (or the `final` event), and requests exit. Normal and signal exit drain that Agent, including owned jobs and its Session write handle, before the providers under the existing shutdown deadline.
### Patch surface over base
diff --git a/packages/bundle/headless/README.zh.md b/packages/bundle/headless/README.zh.md
index f843c057d1..22c4823be8 100644
--- a/packages/bundle/headless/README.zh.md
+++ b/packages/bundle/headless/README.zh.md
@@ -77,7 +77,7 @@ runner 是核心 API 载体之上的直接驱动器:它确定 Agent 标识—
### 运行流程
-runner 等待整个应用结算(`ctx.get('loader')?.await()`),确保已组合的工具与适配器不会半挂载,读取共享的 [`agentDefaultModel`](../../core/agent-default-model/README.zh.md) 选择,从配置或 stdin 解析任务,然后确定 Agent 标识:默认是全新的 `session-`,或是 `--session-id` 指名的持久化 Session——通过 [`sessionQuery`](../../session-query/session-query/README.zh.md) 沿用,日志不存在时拒绝。它把任务作为普通用户消息提交。不带 `--json` 时,它把该 Agent 的非空推理增量流式写入 stderr;带 `--json` 时改为投影本次运行。它等待子级工作,并在结束前复核父级仍空闲且 Session 序号未变,让结算通知可以驱动父级生成最终答案。收件箱消息处于停放状态的空闲子级不会延迟完成;进程关闭时丢弃这些未领取消息。随后它对会话执行 flush,并把所属区间(从 `firstSeq` 起)折叠为最后一条非空 `assistant/message` 文本与最终 `turn/end` 原因。最后,它把最终文本写入 stdout(或 `final` 事件),并携带自己拥有的 Agent 销毁动作请求退出。启动器先排空该 Agent(包括有所有者的 job 与 Session 写句柄),再销毁提供方;这共用已有的关停时限。
+runner 等待整个应用结算(`ctx.get('loader')?.await()`),确保已组合的工具与适配器不会半挂载,读取共享的 [`agentDefaultModel`](../../core/agent-default-model/README.zh.md) 选择,从配置或 stdin 解析任务,然后确定 Agent 标识:默认是全新的 `session-`,或是 `--session-id` 指名的持久化 Session——通过 [`sessionQuery`](../../session-query/session-query/README.zh.md) 沿用,日志不存在时拒绝。它把任务作为普通用户消息提交。不带 `--json` 时,它把该 Agent 的非空推理增量流式写入 stderr;带 `--json` 时改为投影本次运行。它等待子级工作,并在结束前复核父级仍空闲且 Session 序号未变,让结算通知可以驱动父级生成最终答案。收件箱消息处于停放状态的空闲子级不会延迟完成;进程关闭时丢弃这些未领取消息。随后它对会话执行 flush,并把所属区间(从 `firstSeq` 起)折叠为最后一条非空 `assistant/message` 文本与最终 `turn/end` 原因。runner 在等待取得 Agent 之前将销毁动作注册到 `appShutdown`,随后把最终文本写入 stdout(或 `final` 事件)并请求退出。正常退出与信号退出都会在已有时限内先排空 Agent(包括有所有者的 job 与 Session 写句柄),再销毁提供方。
### 基于 base 的 patch 内容
diff --git a/packages/bundle/headless/src/index.ts b/packages/bundle/headless/src/index.ts
index 905ff021d5..a5c677a29f 100644
--- a/packages/bundle/headless/src/index.ts
+++ b/packages/bundle/headless/src/index.ts
@@ -66,7 +66,7 @@ interface HeadlessIo {
stdout: { write(chunk: string): unknown }
stderr: { write(chunk: string): unknown }
/** Request process exit with `code` after the tree disposes. */
- exit: (code: number, beforeDispose?: () => Promise) => void
+ exit: (code: number) => void
}
/** Aggregate the last assistant text and turn outcome in one owned interval. */
@@ -340,17 +340,22 @@ async function run(ctx: Context, config: Config, io: HeadlessIo): Promise
const sessionId = brandString(config.sessionId ?? `session-${randomUUID()}`)
const fs = ctx.get('fs')
const cwd = fs === undefined ? process.cwd() : fs.processPath(await fs.resolve('.'))
- const handle = config.sessionId === undefined
- ? await agents.create({
+ const shutdown = ctx.get('appShutdown')
+ if (shutdown === undefined) throw new Error('headless-runner: the launcher must provide ctx.appShutdown')
+ const pendingHandle = config.sessionId === undefined
+ ? agents.create({
sessionId,
meta: { cwd },
agentOptions,
setup,
})
- : await resolveAgent(ctx, agents, sessionId, agentOptions, setup, cwd)
- const agent = handle.agent
- const exit = io.exit
- io.exit = (code) => { exit(code, () => handle.dispose()) }
+ : resolveAgent(ctx, agents, sessionId, agentOptions, setup, cwd)
+ ctx.effect(() => shutdown.addDisposer(async () => {
+ // A failed acquisition owns its own rollback; the run reports the original error.
+ const handle = await pendingHandle.catch((_acquisitionFailed: unknown) => undefined)
+ await handle?.dispose()
+ }))
+ const { agent } = await pendingHandle
await agent.whenIdle()
if (config.sessionId !== undefined) {
// The resume-time check read a snapshot; an overlay can still append a
diff --git a/packages/bundle/headless/tests/headless.spec.ts b/packages/bundle/headless/tests/headless.spec.ts
index 80326629b7..9526edc5e1 100644
--- a/packages/bundle/headless/tests/headless.spec.ts
+++ b/packages/bundle/headless/tests/headless.spec.ts
@@ -54,6 +54,8 @@ interface BenchOptions {
omitSessionQuery?: boolean
/** Leave the persistence service unmounted to exercise the fail-loud path. */
omitPersistence?: boolean
+ /** Leave the launcher cleanup registry unavailable. */
+ omitShutdown?: boolean
/** Register a live Agent under `sessionId` before the runner starts. */
prelive?: boolean
/** Header facts for that pre-registered live Agent. */
@@ -179,6 +181,13 @@ async function bench(script: Script, options: BenchOptions = {}): Promise<{
return agent
}
+ const disposers = new Set<() => Promise>()
+ if (options.omitShutdown !== true) {
+ ctx.provide('appShutdown', { addDisposer(dispose) {
+ disposers.add(dispose)
+ return () => { disposers.delete(dispose) }
+ } })
+ }
await ctx.plugin(SessionStore)
await ctx.plugin(SessionProjectionRegistry)
await ctx.plugin(AgentRegistry)
@@ -215,8 +224,11 @@ async function bench(script: Script, options: BenchOptions = {}): Promise<{
internals.stderr = { write: (chunk: string) => { err += chunk; return true } }
if (options.readStdin !== undefined) internals.readStdin = options.readStdin
const exited = new Promise((resolve) => {
- ctx.provide('appExit', (code: number, beforeDispose?: () => Promise) => {
- void Promise.resolve().then(beforeDispose).then(() => { order.push('exit'); resolve(code) })
+ ctx.provide('appExit', (code: number) => {
+ void (async () => {
+ for (const dispose of disposers) await dispose()
+ order.push('exit'); resolve(code)
+ })()
})
})
if (options.prelive === true || options.preliveMeta !== undefined) {
@@ -1133,6 +1145,7 @@ describe('headless runner', () => {
internals.stderr = { write: (chunk: string) => { err += chunk; return true } }
const exited = new Promise((resolve) => {
ctx.provide('appExit', resolve)
+ ctx.provide('appShutdown', { addDisposer: () => () => {} })
})
ctx.provide('agentDefaultModel', { currentSelection: () => ({ provider: 'p', model: 'm' }) } as never)
ctx.provide('sessions', { flush: () => Promise.resolve(true) } as never)
@@ -1150,6 +1163,7 @@ describe('headless runner', () => {
internals.stderr = { write: (chunk: string) => { err += chunk; return true } }
const exited = new Promise((resolve) => {
ctx.provide('appExit', resolve)
+ ctx.provide('appShutdown', { addDisposer: () => () => {} })
})
ctx.provide('agentDefaultModel', { currentSelection: () => ({ provider: 'p', model: 'm' }) } as never)
ctx.provide('sessions', { flush: () => Promise.resolve(true) } as never)
@@ -1193,6 +1207,17 @@ describe('headless runner', () => {
expect(() => { apply(ctx, { task: 't' }) }).toThrow('must provide ctx.appExit')
})
+ it('fails before creating an Agent when the launcher has no ordered cleanup registry', async () => {
+ const test = await bench({ afterPrompt() { throw new Error('must not prompt without shutdown') } }, { omitShutdown: true })
+ try {
+ await expect(test.run()).resolves.toMatchObject({
+ code: 1, out: '', err: 'dsh: headless-runner: the launcher must provide ctx.appShutdown\n',
+ })
+ } finally {
+ await test.ctx.fiber.dispose()
+ }
+ })
+
it('validates config: the task and run options are optional', () => {
expect(new Config({})).toEqual({})
expect(new Config({ task: 'x', sessionId: 'session-x', json: true }))
diff --git a/packages/bundle/headless/tests/terminal-failures.spec.ts b/packages/bundle/headless/tests/terminal-failures.spec.ts
index 5c20881ea9..05a0584546 100644
--- a/packages/bundle/headless/tests/terminal-failures.spec.ts
+++ b/packages/bundle/headless/tests/terminal-failures.spec.ts
@@ -37,6 +37,7 @@ async function harness() {
internals.stderr = { write: (chunk: string) => { err += chunk; return true } }
const exited = Promise.withResolvers()
context.provide('appExit', exited.resolve)
+ context.provide('appShutdown', { addDisposer: () => () => {} })
return {
ctx: context,
async run(json = false) {
diff --git a/packages/extensions/tool-cordis/src/api-catalog.ts b/packages/extensions/tool-cordis/src/api-catalog.ts
index ebcc33e8df..927e8eebf6 100644
--- a/packages/extensions/tool-cordis/src/api-catalog.ts
+++ b/packages/extensions/tool-cordis/src/api-catalog.ts
@@ -1348,7 +1348,7 @@ export const SERVICE_API: readonly ServiceApiEntry[] = [
signature: 'abstract start(spec: JobSpec): JobId',
description: 'Preflight access, validation, owner cleanup, and implementation-owned admission before starting and atomically registering work. Any preflight rejection leaves no job id or execution resource. A throwing starter leaves nothing registered; after it returns, registration cannot fail.',
parameters: [{ name: 'spec', description: 'job identity, owner, output sources, and synchronous starter.' }],
- returns: 'the registry-issued `-N` id.',
+ returns: 'the registry-issued `-` id.',
},
{
signature: 'abstract list(caller?: SessionId): JobView[]',
diff --git a/packages/jobs/jobs-local/README.i18n.yaml b/packages/jobs/jobs-local/README.i18n.yaml
index b3d602a89d..c6efb32f9f 100644
--- a/packages/jobs/jobs-local/README.i18n.yaml
+++ b/packages/jobs/jobs-local/README.i18n.yaml
@@ -30,8 +30,8 @@
en: 91dd372f742dcdb8
zh: 58eeee0254d33cd1
/deepseek-ai-dsh-jobs-local/use-this-package/session-timing-records:
- en: 51d69ff00d143272
- zh: 1022edadf46bad45
+ en: c6feb8cd9f67a6f3
+ zh: 0a0700d8e675855f
/deepseek-ai-dsh-jobs-local/use-this-package/what-can-go-wrong:
en: e1f4511c458dff3b
zh: bea247c2a365a646
diff --git a/packages/jobs/jobs-local/README.md b/packages/jobs/jobs-local/README.md
index e5b1002ce8..5e0704041a 100644
--- a/packages/jobs/jobs-local/README.md
+++ b/packages/jobs/jobs-local/README.md
@@ -58,7 +58,7 @@ Jobs belong to their owner and backend, not to the producer tool, so producer or
### Session timing records
-Owned jobs record registration and settlement in their original Session, independently of model output collection. SDK consumers receive the same events through `session.event`; the [jobs reference](../../../docs/subsystems/jobs.md#session-lifecycle-records) defines identities, timestamps, and termination confirmation. These records survive through normal Session persistence; live jobs and output rings are not resumed.
+Owned jobs record registration and settlement in their original Session, independently of model output collection. SDK consumers receive the same events through `session.event`; the [jobs reference](../../../docs/subsystems/jobs.md#session-lifecycle-records) defines identities, timestamps, and producer settlement reports. These records survive through normal Session persistence; live jobs and output rings are not resumed.
Lifecycle facts are appended synchronously at the registration or settlement commit, before live hub subscribers and completion waiters react. Append failures warn without abandoning execution or blocking cleanup; consumers must retain incomplete histories as incomplete.
diff --git a/packages/jobs/jobs-local/README.zh.md b/packages/jobs/jobs-local/README.zh.md
index ecf440c592..080190912b 100644
--- a/packages/jobs/jobs-local/README.zh.md
+++ b/packages/jobs/jobs-local/README.zh.md
@@ -58,7 +58,7 @@ kind: "package-reference"
### Session 时间记录
-有所有者的 job 在原始 Session 中记录注册与结算,不依赖模型收集输出。SDK 消费方通过 `session.event` 收到同样的事件;[jobs 参考](../../../docs/subsystems/jobs.zh.md#session-lifecycle-records)定义标识、时间戳和终止确认。这些记录通过正常 Session 持久化保留;运行中的 job 和输出环不会被恢复。
+有所有者的 job 在原始 Session 中记录注册与结算,不依赖模型收集输出。SDK 消费方通过 `session.event` 收到同样的事件;[jobs 参考](../../../docs/subsystems/jobs.zh.md#session-lifecycle-records)定义标识、时间戳和生产方结算报告。这些记录通过正常 Session 持久化保留;运行中的 job 和输出环不会被恢复。
生命周期事实在注册或结算提交时同步追加,先于运行期 hub 订阅者和完成等待方的反应。追加失败会记录警告,不放弃执行或阻塞清理;消费方必须将不完整历史保留为不完整。
diff --git a/packages/jobs/jobs-local/src/index.ts b/packages/jobs/jobs-local/src/index.ts
index 93c5692c7c..214a9989ab 100644
--- a/packages/jobs/jobs-local/src/index.ts
+++ b/packages/jobs/jobs-local/src/index.ts
@@ -17,7 +17,7 @@ import type { Agent } from '@deepseek-ai/dsh-agent'
import { ScopedLayers, scopeOf } from '@deepseek-ai/dsh-scope'
import type { SessionId } from '@deepseek-ai/dsh-session'
import { deadline, timeoutOf } from '@deepseek-ai/dsh-timeout'
-import { JobRegistry, JobExecutionId, JobId } from '@deepseek-ai/dsh-jobs'
+import { JobRegistry, JobId } from '@deepseek-ai/dsh-jobs'
import type {
JobAppendOptions, JobEvent, JobEvents, JobHandle, JobKind, JobOutcome, JobOutputRead, JobOutputSource,
JobRead, JobRegisteredData, JobSettleCause, JobSettledData, JobSpec, JobStatus, JobToolCall, JobView,
@@ -76,7 +76,6 @@ interface ProducerState {
/** The registry's mutable per-job record (never handed out — see {@link LocalJobRegistry.view}). */
interface TrackedJob {
id: JobId
- executionId: JobExecutionId
toolCall: JobToolCall | undefined
kind: JobKind
label: string
@@ -161,7 +160,6 @@ export class LocalJobRegistry extends JobRegistry {
/** Schemastery-defaulted pull-source poll interval. */
private readonly pumpPollMs: number
private store = new Map()
- private counters = new Map()
/**
* Controllers and scoped subscriptions layered by the scope that registered
* them, in the tools-registry shape: a contribution files into its
@@ -226,13 +224,8 @@ export class LocalJobRegistry extends JobRegistry {
)
}
- // The id is issued before the starter runs so the producer face can carry
- // it; a throwing starter still leaves nothing registered — its ordinal is
- // simply skipped.
- const count = (this.counters.get(spec.kind) ?? 0) + 1
- this.counters.set(spec.kind, count)
- const id = JobId(`${spec.kind}-${count}`)
- const executionId = JobExecutionId(randomUUID())
+ // The producer uses the same identity as controls and durable lifecycle records.
+ const id = JobId(`${spec.kind}-${randomUUID()}`)
const toolCall = spec.toolCall === undefined ? undefined : { ...spec.toolCall }
const ring = new OutputRing()
const state: ProducerState = { progress: undefined, job: undefined }
@@ -247,7 +240,6 @@ export class LocalJobRegistry extends JobRegistry {
const settled = new Promise((resolve) => { markSettled = resolve })
const job: TrackedJob = {
id,
- executionId,
toolCall,
kind: spec.kind,
label: spec.label,
@@ -453,7 +445,7 @@ export class LocalJobRegistry extends JobRegistry {
if (owner === undefined) return
const session = owner.session
const data: JobRegisteredData = {
- executionId: job.executionId, jobId: job.id, owner: owner.id,
+ jobId: job.id, owner: owner.id,
kind: job.kind, label: job.label, startedAt: job.startedAt,
...job.toolCall !== undefined ? { toolCall: job.toolCall } : {},
}
@@ -462,7 +454,7 @@ export class LocalJobRegistry extends JobRegistry {
else session.append('job/settled', { ...data, ...settlement })
} catch (error: unknown) {
// Recording cannot orphan already-started work or prevent waiter release.
- this.selfCtx.logger.warn(`jobs: failed to record lifecycle for ${job.executionId}: ${String(error)}`)
+ this.selfCtx.logger.warn(`jobs: failed to record lifecycle for ${job.id}: ${String(error)}`)
}
}
diff --git a/packages/jobs/jobs-local/tests/jobs.spec.ts b/packages/jobs/jobs-local/tests/jobs.spec.ts
index 1713898328..eba365895b 100644
--- a/packages/jobs/jobs-local/tests/jobs.spec.ts
+++ b/packages/jobs/jobs-local/tests/jobs.spec.ts
@@ -249,7 +249,7 @@ describe('LocalJobRegistry.start', () => {
it('rejects before producer start and id allocation, then admits immediately after settlement', async () => {
const ctx = await harness({ maxConcurrentJobsPerOwner: 1 })
const first = producer()
- expect(ctx.jobs.start(first.spec)).toBe('bash-1')
+ expect(ctx.jobs.start(first.spec)).toMatch(/^bash-[0-9a-f-]{36}$/)
const blocked = producer()
const run = vi.fn((job: JobHandle) => blocked.spec.run(job))
@@ -259,7 +259,7 @@ describe('LocalJobRegistry.start', () => {
first.settle({ status: 'completed' })
await tick()
- expect(ctx.jobs.start(blocked.spec)).toBe('bash-2')
+ expect(ctx.jobs.start(blocked.spec)).toMatch(/^bash-[0-9a-f-]{36}$/)
})
it('keeps a stopping job in the bucket until producer settlement', async () => {
@@ -273,7 +273,7 @@ describe('LocalJobRegistry.start', () => {
first.settle({ status: 'killed' })
await tick()
- expect(ctx.jobs.start(replacement.spec)).toBe('bash-2')
+ expect(ctx.jobs.start(replacement.spec)).toMatch(/^bash-[0-9a-f-]{36}$/)
})
it.each(['completed', 'killed', 'failed'] as const)(
@@ -313,12 +313,12 @@ describe('LocalJobRegistry.start', () => {
await disposeAgentScope(oldOwner)
})
- it('issues kind-prefixed ids from per-kind counters', async () => {
+ it('issues unique kind-prefixed ids', async () => {
const ctx = await harness()
- expect(ctx.jobs.start(producer().spec)).toBe('bash-1')
- expect(ctx.jobs.start(producer().spec)).toBe('bash-2')
- expect(ctx.jobs.start(producer({ kind: 'test-task' }).spec)).toBe('test-task-1')
- expect(ctx.jobs.start(producer({ kind: 'workflow' }).spec)).toBe('workflow-1')
+ expect(ctx.jobs.start(producer().spec)).toMatch(/^bash-[0-9a-f-]{36}$/)
+ expect(ctx.jobs.start(producer().spec)).toMatch(/^bash-[0-9a-f-]{36}$/)
+ expect(ctx.jobs.start(producer({ kind: 'test-task' }).spec)).toMatch(/^test-task-[0-9a-f-]{36}$/)
+ expect(ctx.jobs.start(producer({ kind: 'workflow' }).spec)).toMatch(/^workflow-[0-9a-f-]{36}$/)
})
})
@@ -725,9 +725,9 @@ describe('LocalJobRegistry owner isolation', () => {
ctx.jobs.attachController('test-controller')
expect(() => ctx.jobs.start({ ...producer().spec, owner: SessionId('a') }))
.toThrow('background job ownership requires the agent registry')
- // The failed registration mutated nothing: no stored job, counter untouched.
+ // The failed registration mutated nothing: no stored job.
expect(ctx.jobs.list()).toEqual([])
- expect(ctx.jobs.start(producer().spec)).toBe('bash-1')
+ expect(ctx.jobs.start(producer().spec)).toMatch(/^bash-[0-9a-f-]{36}$/)
})
it('rejects a session without a live agent and attaches cleanup once it has one', async () => {
@@ -752,7 +752,7 @@ describe('LocalJobRegistry owner isolation', () => {
done: new Promise((res) => { settle = res }),
}),
})
- expect(id).toBe('bash-1') // the failed attempt burned no counter
+ expect(id).toMatch(/^bash-[0-9a-f-]{36}$/)
await disposeAgentScope(ghost)
expect(cancels).toEqual(['owner disposed'])
expect(ctx.jobs.list(ghost.id)).toEqual([])
@@ -807,7 +807,9 @@ describe('LocalJobRegistry owner cleanup', () => {
await disposeAgentScope(owner)
expect(cancels).toEqual(['owner disposed'])
expect(ctx.jobs.list(owner.id)).toEqual([])
- expect(seen.map(event => event.type === 'removed' ? event.job.id : undefined)).toEqual(['test-task-1', 'bash-1'])
+ expect(seen.map(event => event.type === 'removed' ? event.job.id : undefined)).toEqual([
+ expect.stringMatching(/^test-task-[0-9a-f-]{36}$/), expect.stringMatching(/^bash-[0-9a-f-]{36}$/),
+ ])
})
it('announces teardown settlements with cause teardown', async () => {
@@ -1108,8 +1110,8 @@ describe('LocalJobRegistry events', () => {
it('announces an unowned job to every session filter, since every caller can see it', async () => {
const ctx = await harness()
const seen = collect(ctx, { owner: SessionId('anyone') }, ['registered'])
- ctx.jobs.start(producer().spec)
- expect(seen).toMatchObject([{ type: 'registered', job: { id: 'bash-1' } }])
+ const id = ctx.jobs.start(producer().spec)
+ expect(seen).toMatchObject([{ type: 'registered', job: { id } }])
expect((seen[0] as { job: JobView }).job.owner).toBeUndefined()
})
@@ -1141,7 +1143,7 @@ describe('LocalJobRegistry events', () => {
const seen = collect(ctx, { owners: 'all' }, ['registered'])
const id = ctx.jobs.start(producer().spec)
- expect(id).toBe('bash-1')
+ expect(id).toMatch(/^bash-[0-9a-f-]{36}$/)
expect(seen).toHaveLength(1)
expect(warn).toHaveBeenCalledWith(expect.stringContaining('event listener threw on registered'))
})
@@ -1586,7 +1588,7 @@ describe('owned job Session lifecycle records', () => {
expect(ctx.jobs.list(owner.id)).toEqual([])
})
- it('keeps execution identity distinct when a fresh registry reuses a job id', async () => {
+ it('keeps job identity distinct across fresh registries with the same owner', async () => {
const other = await harness()
try {
const ids: JobId[] = []
@@ -1600,9 +1602,10 @@ describe('owned job Session lifecycle records', () => {
await registry.jobs.wait(id, 1000, owner.id)
const event = owner.session.snapshotEvents().find(event => event.type === 'job/registered')
if (event?.type !== 'job/registered') throw new Error('missing registration')
- executions.push(event.data.executionId)
+ executions.push(event.data.jobId)
}
- expect(ids).toEqual(['bash-1', 'bash-1'])
+ expect(new Set(ids).size).toBe(2)
+ expect(executions).toEqual(ids)
expect(new Set(executions).size).toBe(2)
} finally {
await other.fiber.dispose()
diff --git a/packages/jobs/jobs/README.i18n.yaml b/packages/jobs/jobs/README.i18n.yaml
index 69d10055e6..9f4e8c2123 100644
--- a/packages/jobs/jobs/README.i18n.yaml
+++ b/packages/jobs/jobs/README.i18n.yaml
@@ -9,8 +9,8 @@
en: 721d849fde0fbebe
zh: 9578ffb37edc5d7b
/deepseek-ai-dsh-jobs/summary:
- en: f64d24a22c76423c
- zh: 5bfd3c04ce108bcb
+ en: 64000a337f374627
+ zh: 251c13da2768bbd1
/deepseek-ai-dsh-jobs/table-of-contents:
en: d152484eb41ac6b4
zh: 09388d293f9be9cb
@@ -18,11 +18,11 @@
en: 1246c880d1c61927
zh: 157c703fd2da6b9c
/deepseek-ai-dsh-jobs/use-this-package/what-a-background-job-gives-you:
- en: 54d99a3ae1c6f20a
- zh: 0b2ffab9644abe66
+ en: 92a9b9c0b6c9e748
+ zh: fc354b68facee753
/deepseek-ai-dsh-jobs/use-this-package/the-ownership-boundary:
- en: 873494750b38dae5
- zh: 2e6142ab889f7a8b
+ en: 167ab1eab8fad704
+ zh: 7da554826fb483c1
/deepseek-ai-dsh-jobs/use-this-package/starting-background-work-needs-a-controller:
en: 68075d00cde8763d
zh: 4bc42c01d9e4dc8d
@@ -39,8 +39,8 @@
en: 2a42e1e06dfc469e
zh: 5c2c77eca52846f0
/deepseek-ai-dsh-jobs/understand-the-implementation/source-map:
- en: 19d23d01476c0562
- zh: bf5c7405044f93b3
+ en: c9a58b4340999ae4
+ zh: ef68a0b71a06d183
/deepseek-ai-dsh-jobs/understand-the-implementation/service-operations:
en: b2e4ef34ccf1e749
zh: 128b57b57ed09316
diff --git a/packages/jobs/jobs/README.md b/packages/jobs/jobs/README.md
index 70597a43d6..a0fc8af18d 100644
--- a/packages/jobs/jobs/README.md
+++ b/packages/jobs/jobs/README.md
@@ -9,7 +9,7 @@ English | [中文](README.zh.md)
## Summary
-`dsh-jobs` lets tools keep long-running work active while an agent continues. Each job receives a stable `-N` id, and its owning agent can read output, wait with a timeout, or request cancellation. Ownership is scoped to the agent session, so other agents cannot inspect or stop the job; completion arrives as an in-session notice without polling. Users can watch retained live output without consuming what the agent can read. Background jobs can start only when the deployment supplies job execution.
+`dsh-jobs` lets tools keep long-running work active while an agent continues. Each job receives a stable `-` id, and its owning agent can read output, wait with a timeout, or request cancellation. Ownership is scoped to the agent session, so other agents cannot inspect or stop the job; completion arrives as an in-session notice without polling. Users can watch retained live output without consuming what the agent can read. Background jobs can start only when the deployment supplies job execution.
## Table of Contents
@@ -29,7 +29,7 @@ Use this package when you are composing a background-job capability or writing a
### What a background job gives you
-A producer registers work with a kind and a one-line label; the registry returns a `-N` id such as `bash-1`. Anyone who owns the job can read output, list jobs, wait up to a timeout for settlement, and request cancellation — each call returns a fresh projection of the job's status, from `running` and `stopping` to the terminal `completed`, `killed`, or `failed`. When a job settles, the registry's event stream announces it and `dsh-tool-jobs` turns the settlement into an in-session notice, so no polling is needed. A producer may attach an optional byte cap so each complete model-facing read or notice stays bounded.
+A producer registers work with a kind and a one-line label; the registry returns a `-` id such as `bash-`. Anyone who owns the job can read output, list jobs, wait up to a timeout for settlement, and request cancellation — each call returns a fresh projection of the job's status, from `running` and `stopping` to the terminal `completed`, `killed`, or `failed`. When a job settles, the registry's event stream announces it and `dsh-tool-jobs` turns the settlement into an in-session notice, so no polling is needed. A producer may attach an optional byte cap so each complete model-facing read or notice stays bounded.
A producer streams output by naming pull sources on its spec — non-consuming offset readers the registry pumps at its own cadence — or by pushing chunks through the `JobHandle` its starter receives; both land in the job's bounded ring, where `stdout` and `stderr` chunks reach the model and `log` chunks reach observers only. Observers read retained chunks at absolute byte offsets and are signaled on advancement; the settlement that ends the job also ends the stream, and `updateProgress` publishes a live progress line into every projection until then. Observation is invisible to the model: `readAt` consumes nothing and never touches notice state.
@@ -37,7 +37,7 @@ The shipped provider also records owned-job lifecycle metadata in the Session. P
### The ownership boundary
-A job belongs to the agent session that started it: another agent cannot read or stop it. Ids such as `bash-1` are predictable, so this fence is authorization, not secrecy. A job started without an owner is open to any caller and lasts until the service is disposed.
+A job belongs to the agent session that started it: another agent cannot read or stop it. An id may appear in tool output; the registry authorizes every control operation using the owner. A job started without an owner is open to any caller and lasts until the service is disposed.
### Starting background work needs a controller
@@ -81,7 +81,7 @@ This section explains the design decisions behind the contract and points at the
| [`src/index.ts`](src/index.ts) | Plugin entry: the abstract `JobRegistry` service and its contract |
| [`src/types.ts`](src/types.ts) | Shared vocabulary: `JobSpec`, `JobHandle`, `JobHooks`, `JobOutcome`, `JobEvent`, and the read results |
| [`src/view.ts`](src/view.ts) | Client-safe leaf: `JobView`, `JobChunk`, `JobStatus`, and the merge-extensible `JobKindMap` |
-| [`src/brand.ts`](src/brand.ts) | `JobId` and `JobExecutionId` branded identifiers, importable without the agent dependency |
+| [`src/brand.ts`](src/brand.ts) | the `JobId` branded identifier, importable without the agent dependency |
| [`src/archive-admission.ts`](src/archive-admission.ts) | The `job` family of the Workspace registry's archive admission, installed by the seam's constructor for every implementation |
### Service operations
diff --git a/packages/jobs/jobs/README.zh.md b/packages/jobs/jobs/README.zh.md
index 1f8eb27cb4..66116fc34b 100644
--- a/packages/jobs/jobs/README.zh.md
+++ b/packages/jobs/jobs/README.zh.md
@@ -9,7 +9,7 @@ kind: "package-reference"
## 概述
-`dsh-jobs` 让工具可以在 agent(智能体)继续推进时保持长时间工作运行。每项任务都会获得稳定的 `-N` id,拥有它的 agent 可以读取输出、带超时等待或请求取消。归属范围限定在 agent 会话内,因此其他 agent 无法查看或停止任务;任务完成时会通过会话内通知送达,无需轮询。用户可以查看保留的实时输出,而不消耗 agent 可读取的内容。只有部署提供任务执行能力时,后台任务才能启动。
+`dsh-jobs` 让工具可以在 agent(智能体)继续推进时保持长时间工作运行。每项任务都会获得稳定的 `-` id,拥有它的 agent 可以读取输出、带超时等待或请求取消。归属范围限定在 agent 会话内,因此其他 agent 无法查看或停止任务;任务完成时会通过会话内通知送达,无需轮询。用户可以查看保留的实时输出,而不消耗 agent 可读取的内容。只有部署提供任务执行能力时,后台任务才能启动。
## 目录
@@ -29,7 +29,7 @@ kind: "package-reference"
### 后台任务提供什么
-生产方以 kind 和一行标签注册工作;注册表返回 `-N` id,例如 `bash-1`。拥有任务的任何一方都可以读取输出、列出任务、带超时等待结算或请求取消——每次调用都返回任务状态的全新投影,从 `running`、`stopping` 到终止态的 `completed`、`killed` 或 `failed`。任务结算时,注册表的事件流宣布它,`dsh-tool-jobs` 把结算变成会话内通知,因此无需轮询。生产方可以附加一个可选的字节上限,让每次完整的模型侧读取或通知保持有界。
+生产方以 kind 和一行标签注册工作;注册表返回 `-` id,例如 `bash-`。拥有任务的任何一方都可以读取输出、列出任务、带超时等待结算或请求取消——每次调用都返回任务状态的全新投影,从 `running`、`stopping` 到终止态的 `completed`、`killed` 或 `failed`。任务结算时,注册表的事件流宣布它,`dsh-tool-jobs` 把结算变成会话内通知,因此无需轮询。生产方可以附加一个可选的字节上限,让每次完整的模型侧读取或通知保持有界。
生产方通过在 spec 上声明拉取源——由注册表按自己的节奏泵送的非消耗偏移读取器——或经 starter 收到的 `JobHandle` 推送块来流式输出;二者都落入该 job 的有界环,其中 `stdout` 与 `stderr` 块到达模型,`log` 块只到达观察者。观察者按绝对字节偏移读取保留块并在推进时收到信号;结束 job 的结算同时封流,在此之前 `updateProgress` 把实时进度行发布进每个投影。观测对模型不可见:`readAt` 不消耗任何东西,也从不触碰通知状态。
@@ -37,7 +37,7 @@ kind: "package-reference"
### 归属边界
-任务属于启动它的 agent 会话:其他 agent 无法读取或停止它。`bash-1` 这样的 id 可预测,因此这道隔离是授权,而非保密。没有所有者启动的任务对任何调用方开放,并持续到服务被释放为止。
+任务属于启动它的 agent 会话:其他 agent 无法读取或停止它。id 可能出现在工具输出中;注册表按所有者校验每次控制操作。没有所有者启动的任务对任何调用方开放,并持续到服务被释放为止。
### 启动后台工作需要一个控制器
@@ -81,7 +81,7 @@ kind: "package-reference"
| [`src/index.ts`](src/index.ts) | 插件入口:抽象 `JobRegistry` 服务及其约定 |
| [`src/types.ts`](src/types.ts) | 共享词汇:`JobSpec`、`JobHandle`、`JobHooks`、`JobOutcome`、`JobEvent` 与读取结果 |
| [`src/view.ts`](src/view.ts) | 客户端安全叶子:`JobView`、`JobChunk`、`JobStatus` 与可合并扩展的 `JobKindMap` |
-| [`src/brand.ts`](src/brand.ts) | `JobId` 与 `JobExecutionId` 带类型标记的标识符,无需 agent 依赖即可导入 |
+| [`src/brand.ts`](src/brand.ts) | 带类型标记的 `JobId` 标识符,无需 agent 依赖即可导入 |
| [`src/archive-admission.ts`](src/archive-admission.ts) | Workspace 注册表归档准入中的 `job` 族,由接缝构造函数为每个实现安装 |
### 服务操作
diff --git a/packages/jobs/jobs/src/brand.ts b/packages/jobs/jobs/src/brand.ts
index 435123812c..0fe1986b4c 100644
--- a/packages/jobs/jobs/src/brand.ts
+++ b/packages/jobs/jobs/src/brand.ts
@@ -13,28 +13,16 @@
import type { Branded } from '@deepseek-ai/dsh-brand'
/**
- * Identifies a background job. The registry generates `-N`; predictable
- * ids rely on owner authorization rather than secrecy.
+ * Identifies one job across registry lifetimes. The registry generates `-`;
+ * access still requires owner authorization.
*/
export type JobId = Branded<'JobId'>
/**
* Brand a string as a {@link JobId}.
- * @param id - the raw job-id string (the registry generates `-N`).
+ * @param id - the raw job-id string (the registry generates `-`).
* @returns the same string, branded; no validation is performed.
*/
export function JobId(id: string): JobId {
return id as JobId
}
-
-/** Identifies one job execution across registry restarts and copied Session history. */
-export type JobExecutionId = Branded<'JobExecutionId'>
-
-/**
- * Brand the registry-issued UUID of one execution.
- * @param id - the execution UUID.
- * @returns the same string, branded; no validation is performed.
- */
-export function JobExecutionId(id: string): JobExecutionId {
- return id as JobExecutionId
-}
diff --git a/packages/jobs/jobs/src/index.ts b/packages/jobs/jobs/src/index.ts
index 7a03bd0a95..513f01f346 100644
--- a/packages/jobs/jobs/src/index.ts
+++ b/packages/jobs/jobs/src/index.ts
@@ -15,7 +15,6 @@ import { installJobArchiveAdmission } from './archive-admission.ts'
import type { JobEvents, JobId, JobOutputRead, JobRead, JobSpec, JobView } from './types.ts'
export { JobId } from './types.ts'
-export { JobExecutionId } from './brand.ts'
export type {
JobAppendOptions,
JobChannel,
@@ -109,7 +108,7 @@ export abstract class JobRegistry extends Service {
* rejection leaves no job id or execution resource. A throwing starter
* leaves nothing registered; after it returns, registration cannot fail.
* @param spec - job identity, owner, output sources, and synchronous starter.
- * @returns the registry-issued `-N` id.
+ * @returns the registry-issued `-` id.
*/
abstract start(spec: JobSpec): JobId
diff --git a/packages/jobs/jobs/src/types.ts b/packages/jobs/jobs/src/types.ts
index 91ae4af366..9fd2294956 100644
--- a/packages/jobs/jobs/src/types.ts
+++ b/packages/jobs/jobs/src/types.ts
@@ -6,7 +6,7 @@
*/
import type { SessionEventMap, SessionId } from '@deepseek-ai/dsh-session'
-import type { JobExecutionId, JobId } from './brand.ts'
+import type { JobId } from './brand.ts'
import type { JobChannel, JobChunk, JobKind, JobView } from './view.ts'
export { JobId } from './brand.ts'
@@ -22,9 +22,7 @@ export interface JobToolCall {
/** Durable identity and registration time of one owned job execution. */
export interface JobRegisteredData {
- /** UUID unique to this execution, even when the registry reuses a display id. */
- readonly executionId: JobExecutionId
- /** Runtime-local id accepted by job controls. */
+ /** Unique execution id accepted by job controls and retained in Session history. */
readonly jobId: JobId
/** Original owner; copied history does not transfer execution ownership. */
readonly owner: SessionId
@@ -60,7 +58,7 @@ declare module '@deepseek-ai/dsh-session/types' {
*/
'job/registered': JobRegisteredData
/**
- * Log-only terminal record, paired by executionId rather than a reusable jobId.
+ * Log-only terminal record, paired by the same unique jobId used for live control.
* @param data - Registration facts plus settlement time, outcome, and producer acknowledgement.
*/
'job/settled': JobSettledData
@@ -139,7 +137,7 @@ export interface JobAppendOptions {
* throwing, so a producer's trailing flush cannot break its own teardown path.
*/
export interface JobHandle {
- /** The registry-issued id (`-N`). */
+ /** The registry-issued id (`-`). */
readonly id: JobId
/**
* Append one chunk to the output ring. Offsets advance by the chunk's UTF-8
diff --git a/packages/jobs/jobs/src/view.ts b/packages/jobs/jobs/src/view.ts
index e5c3d05bc6..ad999ef418 100644
--- a/packages/jobs/jobs/src/view.ts
+++ b/packages/jobs/jobs/src/view.ts
@@ -62,7 +62,7 @@ export interface JobChunk {
* stream all consume this one shape.
*/
export interface JobView {
- /** The registry-issued id (`-N`). */
+ /** The registry-issued id (`-`). */
readonly id: JobId
/**
* The producer kind the job was registered with: a Host-registered
diff --git a/packages/jobs/tool-jobs/README.i18n.yaml b/packages/jobs/tool-jobs/README.i18n.yaml
index ef5ea0c69a..6499336f5e 100644
--- a/packages/jobs/tool-jobs/README.i18n.yaml
+++ b/packages/jobs/tool-jobs/README.i18n.yaml
@@ -39,8 +39,8 @@
en: 6f006ff2d901bc92
zh: 779d18ba7bec4202
/deepseek-ai-dsh-tool-jobs/understand-the-implementation/output-capping:
- en: b47638fb0e80287a
- zh: c47fa4b99b6df4c3
+ en: ca18e4bdfb12a1e2
+ zh: 6592697c561c6e6d
/deepseek-ai-dsh-tool-jobs/understand-the-implementation/notice-delivery-lanes:
en: 2540b7abbb3c9710
zh: 4fb355bceab2f865
diff --git a/packages/jobs/tool-jobs/README.md b/packages/jobs/tool-jobs/README.md
index 8dbbb669fa..681010d71d 100644
--- a/packages/jobs/tool-jobs/README.md
+++ b/packages/jobs/tool-jobs/README.md
@@ -87,7 +87,7 @@ This section explains the design decisions behind the tools and points at the co
### Output capping
-`job_output` and `job_kill` capture the caller-visible job in a prepended pre-execute listener before policy runs, so the producer's cap applies to the complete rendered result. `job_output` preserves its output/status split when policy left the default rendering intact, bounding the output tail and the `[status: ...]` suffix; other single-text results — denials, short-circuits, normalized tool or pipeline failures, replacements, and blocks — are bounded as one text, while structured multi-block policy results keep their shape. A bounded completion notice reserves the stable `background job ` prefix and the `job_output` collection instruction before spending remaining bytes on variable kind, label, status, detail, and truncation marker, so the notice stays actionable at the PTY's supported 64-byte minimum; an existing producer truncation marker is reused rather than duplicated.
+`job_output` and `job_kill` capture the caller-visible job in a prepended pre-execute listener before policy runs, so the producer's cap applies to the complete rendered result. `job_output` preserves its output/status split when policy left the default rendering intact, bounding the output tail and the `[status: ...]` suffix; other single-text results — denials, short-circuits, normalized tool or pipeline failures, replacements, and blocks — are bounded as one text, while structured multi-block policy results keep their shape. A bounded completion notice retains the full job id and `job_output` action when both fit. Otherwise it directs the model to `job_list` when at least eight bytes fit; a smaller cap can carry only a prefix. The PTY's supported 64-byte minimum fits the complete `job_list` instruction. Existing producer truncation markers are reused.
### Notice delivery lanes
diff --git a/packages/jobs/tool-jobs/README.zh.md b/packages/jobs/tool-jobs/README.zh.md
index e44f2a9fed..9c82715665 100644
--- a/packages/jobs/tool-jobs/README.zh.md
+++ b/packages/jobs/tool-jobs/README.zh.md
@@ -87,7 +87,7 @@ kind: "package-reference"
### 输出上限
-`job_output` 与 `job_kill` 会在策略运行前于前置的 pre-execute 监听器中捕获调用方可见任务,因此生产方上限适用于完整渲染结果。`job_output` 在策略保留默认渲染时保持其输出/状态拆分,对输出尾部与 `[status: ...]` 后缀设界;其他单文本结果——拒绝、短路、规范化工具或流水线失败、替换与阻止——按单个文本设界,而结构化多块策略结果保持自身形状。有界完成通知先为稳定的 `background job ` 前缀与 `job_output` 收集指令预留空间,再把剩余字节用于可变的 kind、label、status、detail 与截断标记,因此在 PTY 支持的 64 字节下限下通知仍可操作;已有的生产方截断标记会被复用,不会重复添加。
+`job_output` 与 `job_kill` 会在策略运行前于前置的 pre-execute 监听器中捕获调用方可见任务,因此生产方上限适用于完整渲染结果。`job_output` 在策略保留默认渲染时保持其输出/状态拆分,对输出尾部与 `[status: ...]` 后缀设界;其他单文本结果——拒绝、短路、规范化工具或流水线失败、替换与阻止——按单个文本设界,而结构化多块策略结果保持自身形状。有界完成通知在完整 job id 与 `job_output` 指令都能容纳时保留两者。否则当上限至少有八字节时,它提示模型使用 `job_list`;更小的上限只能容纳指令前缀。PTY 支持的 64 字节下限能容纳完整的 `job_list` 指令。已有生产方截断标记会被复用。
### 通知投递通道
diff --git a/packages/jobs/tool-jobs/src/index.ts b/packages/jobs/tool-jobs/src/index.ts
index e0b961e2ae..3cba9475a6 100644
--- a/packages/jobs/tool-jobs/src/index.ts
+++ b/packages/jobs/tool-jobs/src/index.ts
@@ -137,9 +137,10 @@ function fitCompletionNotice(job: JobView): string {
const compact = `${prefix}${action}`
const compactBytes = encoder.encode(compact).byteLength
if (compactBytes <= maxBytes) return compact
- const actionBytes = encoder.encode(action).byteLength
- if (actionBytes >= maxBytes) return retainTail(action, maxBytes)
- return `${retainHead(prefix, maxBytes - actionBytes)}${action}`
+ // A partial UUID cannot identify the job. Point the model at the listing instead.
+ const recovery = 'background job ended; use job_list for id.'
+ if (encoder.encode(recovery).byteLength <= maxBytes) return recovery
+ return retainHead('job_list', maxBytes)
}
function rawSingleText(content: readonly ContentBlock[]): string | undefined {
diff --git a/packages/jobs/tool-jobs/tests/tool-jobs.spec.ts b/packages/jobs/tool-jobs/tests/tool-jobs.spec.ts
index 73e58cfe7a..4bf5ba59ed 100644
--- a/packages/jobs/tool-jobs/tests/tool-jobs.spec.ts
+++ b/packages/jobs/tool-jobs/tests/tool-jobs.spec.ts
@@ -30,6 +30,17 @@ interface RegistryConfig {
retainBytes?: number
}
+/** First-seen ids are test-local ordinals; the runtime id is opaque. */
+const issuedIds = new WeakMap>()
+
+function startedId(ctx: Context, legacy: string): JobId {
+ const match = /^(.*)-([1-9]\d*)$/.exec(legacy)
+ if (match === null) throw new Error(`invalid test job ordinal ${legacy}`)
+ const id = issuedIds.get(ctx)?.get(match[1]!)?.[Number(match[2]) - 1]
+ if (id === undefined) throw new Error(`job ${legacy} was not registered`)
+ return id
+}
+
async function setup(config: ToolJobs.Config = {}, registry: RegistryConfig = {}) {
const ctx = new Context()
await ctx.plugin(SystemPrompt)
@@ -37,6 +48,14 @@ async function setup(config: ToolJobs.Config = {}, registry: RegistryConfig = {}
const agentsFiber = await ctx.plugin(AgentRegistry)
await ctx.plugin(LocalJobRegistry, registry)
const toolsFiber = await ctx.plugin(ToolJobs, config)
+ const byKind = new Map()
+ issuedIds.set(ctx, byKind)
+ ctx.effect(() => ctx.jobs.events.subscribe({ owners: 'all' }, (event) => {
+ if (event.type !== 'registered') return
+ const ids = byKind.get(event.job.kind) ?? []
+ ids.push(event.job.id)
+ byKind.set(event.job.kind, ids)
+ }))
return { ctx, agentsFiber, toolsFiber }
}
@@ -151,12 +170,12 @@ describe('tool-jobs setup', () => {
await ctx.plugin(LocalJobRegistry)
await ctx.plugin(ToolJobs)
const p = producer()
- ctx.jobs.start(p.spec)
+ const id = ctx.jobs.start(p.spec)
p.append('open\n')
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('open\n[status: running]')
+ expect(text(await call(ctx, 'job_output', { job_id: id }))).toBe('open\n[status: running]')
p.settle({ status: 'completed' })
await tick()
- expect(text(await call(ctx, 'job_list', {}))).toBe('bash-1 [bash] completed — sleep 60')
+ expect(text(await call(ctx, 'job_list', {}))).toBe(`${id} [bash] completed — sleep 60`)
})
it('rejects a config whose default wait exceeds the cap', async () => {
@@ -260,7 +279,7 @@ describe('tool-jobs setup', () => {
? { text: 'x'.repeat(32), nextOffset: 32, lossy: false, spillPath: '/spill/out.log' }
: { text: '', nextOffset: from, lossy: false, spillPath: '/spill/out.log' },
}
- ctx.jobs.start({
+ const id = ctx.jobs.start({
kind: 'bash',
label: 'noisy',
owner: owner.id,
@@ -268,7 +287,7 @@ describe('tool-jobs setup', () => {
run: () => ({ cancel() {}, done: new Promise((resolve) => { settle = resolve }) }),
})
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }, owner)))
+ expect(text(await call(ctx, 'job_output', { job_id: id }, owner)))
.toBe(`${'x'.repeat(8)}\n[some output was dropped from memory; full output: /spill/out.log]\n[status: running]`)
settle({ status: 'completed' })
await tick()
@@ -296,17 +315,17 @@ describe('job_output', () => {
p.append('line one\n')
// A body already ending in a newline gets no doubled separator.
- const first = await call(ctx, 'job_output', { job_id: 'bash-1' })
+ const first = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })
if (first.isError) throw new Error('expected job_output success')
const firstValue = first.value as { text: string; job: Record }
expect(firstValue).toMatchObject({
text: 'line one\n',
- job: { id: 'bash-1', kind: 'bash', label: 'sleep 60', status: 'running' },
+ job: { id: startedId(ctx, 'bash-1'), kind: 'bash', label: 'sleep 60', status: 'running' },
})
expect(firstValue.job).not.toHaveProperty('owner')
expect(firstValue.job).not.toHaveProperty('output')
expect(text(first)).toBe('line one\n[status: running]')
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('(no new output)\n[status: running]')
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))).toBe('(no new output)\n[status: running]')
})
it('renders stderr in a trailing section and never shows log narration', async () => {
@@ -316,8 +335,8 @@ describe('job_output', () => {
p.append('out\n')
p.append('warn\n', { channel: 'stderr' })
p.append('observer-only narration', { channel: 'log' })
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('out\n[stderr]\nwarn\n[status: running]')
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('(no new output)\n[status: running]')
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))).toBe('out\n[stderr]\nwarn\n[status: running]')
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))).toBe('(no new output)\n[status: running]')
})
it('shows live progress beside the status until settlement replaces it with the terminal detail', async () => {
@@ -325,36 +344,36 @@ describe('job_output', () => {
const p = producer()
ctx.jobs.start(p.spec)
p.progress('3/10 files')
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('(no new output)\n[status: running, 3/10 files]')
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))).toBe('(no new output)\n[status: running, 3/10 files]')
p.settle({ status: 'completed', detail: 'exit code: 0' })
await tick()
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('(no new output)\n[status: completed, exit code: 0]')
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))).toBe('(no new output)\n[status: completed, exit code: 0]')
})
it('flags a read whose cursor fell behind the ring', async () => {
const { ctx } = await setup({}, { retainBytes: 16 })
const p = producer()
- ctx.jobs.start(p.spec)
+ const id = ctx.jobs.start(p.spec)
p.append('x'.repeat(40))
- const first = text(await call(ctx, 'job_output', { job_id: 'bash-1' }))
+ const first = text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))
expect(first).toContain('[some output was dropped from memory; full output: (unavailable)]')
expect(first).toContain('[status: running]')
expect(first).not.toContain('x'.repeat(17))
// The cursor now sits at the ring's end: the next read is clean.
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('(no new output)\n[status: running]')
+ expect(text(await call(ctx, 'job_output', { job_id: id }))).toBe('(no new output)\n[status: running]')
})
it('returns the result of a settled job exactly once, after the pending delta', async () => {
const { ctx } = await setup()
const p = producer({ kind: 'test-task', label: 'research' })
ctx.jobs.start(p.spec)
- expect(text(await call(ctx, 'job_output', { job_id: 'test-task-1' }))).toBe('(no new output)\n[status: running]')
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'test-task-1') }))).toBe('(no new output)\n[status: running]')
p.append('partial')
p.settle({ status: 'completed', detail: 'completed', result: 'the answer' })
await tick()
- expect(text(await call(ctx, 'job_output', { job_id: 'test-task-1' }))).toBe('partial\nthe answer\n[status: completed, completed]')
- expect(text(await call(ctx, 'job_output', { job_id: 'test-task-1' }))).toBe('(no new output)\n[status: completed, completed]')
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'test-task-1') }))).toBe('partial\nthe answer\n[status: completed, completed]')
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'test-task-1') }))).toBe('(no new output)\n[status: completed, completed]')
})
it('keeps a newline-terminated delta and the result on separate lines', async () => {
@@ -364,7 +383,7 @@ describe('job_output', () => {
p.append('progress log\n')
p.settle({ status: 'completed', result: 'the answer' })
await tick()
- expect(text(await call(ctx, 'job_output', { job_id: 'test-task-1' }))).toBe('progress log\nthe answer\n[status: completed]')
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'test-task-1') }))).toBe('progress log\nthe answer\n[status: completed]')
})
it('applies a producer limit to the complete body and status result', async () => {
@@ -373,7 +392,7 @@ describe('job_output', () => {
ctx.jobs.start(p.spec)
p.append('界'.repeat(100))
- const output = text(await call(ctx, 'job_output', { job_id: 'bash-1' }))
+ const output = text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))
expect(Buffer.byteLength(output)).toBeLessThanOrEqual(48)
expect(output).toContain('[status: running]')
})
@@ -383,10 +402,10 @@ describe('job_output', () => {
const p = producer({ outputLimitBytes: 64 })
ctx.jobs.start(p.spec)
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' })))
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })))
.toBe('(no new output)\n[status: running]')
p.append('line\n')
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' })))
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })))
.toBe('line\n[status: running]')
})
@@ -400,7 +419,7 @@ describe('job_output', () => {
return Promise.resolve({ kind: 'accept', content: [{ type: 'text', text: 'p'.repeat(1_000) }] })
})
- const result = await call(ctx, 'job_output', { job_id: 'bash-1' })
+ const result = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })
expect(Buffer.byteLength(text(result))).toBeLessThanOrEqual(64)
expect(text(result)).toContain('[result truncated]')
expect(text(result)).not.toContain('[status: running]')
@@ -414,7 +433,7 @@ describe('job_output', () => {
return Promise.resolve({ kind: 'accept', content: [{ type: 'text', text: `${'z'.repeat(1_000)}\n[result truncated]` }] })
})
- const result = await call(ctx, 'job_output', { job_id: 'bash-1' })
+ const result = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })
expect(Buffer.byteLength(text(result))).toBeLessThanOrEqual(64)
expect(text(result).match(/\[result truncated\]/g)).toHaveLength(1)
})
@@ -425,11 +444,11 @@ describe('job_output', () => {
ctx.jobs.start(producer({ owner: alice.id }).spec)
const bob = await fakeAgent(ctx, 'sess-bob')
- const result = await call(ctx, 'job_output', { job_id: 'bash-1' }, bob)
+ const result = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }, bob)
expect(result.isError).toBe(true)
expect(text(result)).toContain('belongs to another session')
- expect((await call(ctx, 'job_output', { job_id: 'bash-1' })).isError).toBe(true)
- expect((await call(ctx, 'job_output', { job_id: 'bash-1' }, alice)).isError).toBe(false)
+ expect((await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })).isError).toBe(true)
+ expect((await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }, alice)).isError).toBe(false)
})
it('bounds pre-, around-, and post-execute policy outcomes and failures', async () => {
@@ -439,47 +458,47 @@ describe('job_output', () => {
}
ctx.on('tools/pre-execute', async (exec, next) => {
const jobId = (exec.arguments as { job_id?: unknown }).job_id
- if (jobId === 'bash-1') return { kind: 'deny', reason: 'd'.repeat(1_000) }
- if (jobId === 'bash-3') throw new Error(`pre failed: ${'p'.repeat(1_000)}`)
+ if (jobId === startedId(ctx, 'bash-1')) return { kind: 'deny', reason: 'd'.repeat(1_000) }
+ if (jobId === startedId(ctx, 'bash-3')) throw new Error(`pre failed: ${'p'.repeat(1_000)}`)
return next()
})
ctx.on('tools/execute', async (exec, next) => {
const jobId = (exec.arguments as { job_id?: unknown }).job_id
- if (jobId === 'bash-2') {
+ if (jobId === startedId(ctx, 'bash-2')) {
return {
content: [],
isError: false,
value: {
text: 'a'.repeat(1_000),
job: {
- id: 'bash-2', kind: 'bash', label: 'sleep 60', status: 'running', startedAt: 0,
+ id: startedId(ctx, 'bash-2'), kind: 'bash', label: 'sleep 60', status: 'running', startedAt: 0,
},
},
}
}
- if (jobId === 'bash-4') throw new Error(`around failed: ${'e'.repeat(1_000)}`)
+ if (jobId === startedId(ctx, 'bash-4')) throw new Error(`around failed: ${'e'.repeat(1_000)}`)
return next()
})
ctx.on('tools/post-execute', async (exec, _result, next) => {
const jobId = (exec.arguments as { job_id?: unknown }).job_id
- if (jobId === 'bash-5') throw new Error(`post failed: ${'o'.repeat(1_000)}`)
+ if (jobId === startedId(ctx, 'bash-5')) throw new Error(`post failed: ${'o'.repeat(1_000)}`)
return next()
})
- const denied = await call(ctx, 'job_output', { job_id: 'bash-1' })
+ const denied = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') })
expect(denied.isError).toBe(true)
expect(Buffer.byteLength(text(denied))).toBeLessThanOrEqual(64)
expect(text(denied)).toContain('[result truncated]')
- const shortCircuited = await call(ctx, 'job_output', { job_id: 'bash-2' })
+ const shortCircuited = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-2') })
expect(shortCircuited.isError).toBe(false)
expect(Buffer.byteLength(text(shortCircuited))).toBeLessThanOrEqual(64)
expect(text(shortCircuited)).toContain('[output truncated]')
const failures = [
- await call(ctx, 'job_output', { job_id: 'bash-3' }),
- await call(ctx, 'job_output', { job_id: 'bash-4' }),
- await call(ctx, 'job_output', { job_id: 'bash-5' }),
+ await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-3') }),
+ await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-4') }),
+ await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-5') }),
]
for (const failure of failures) {
expect(failure.isError).toBe(true)
@@ -493,7 +512,7 @@ describe('job_output', () => {
const p = producer({ kind: 'test-task', label: 'research' })
ctx.jobs.start(p.spec)
- const pending = call(ctx, 'job_output', { job_id: 'test-task-1', wait: true })
+ const pending = call(ctx, 'job_output', { job_id: startedId(ctx, 'test-task-1'), wait: true })
p.settle({ status: 'completed', result: 'done deal' })
expect(text(await pending)).toBe('done deal\n[status: completed]')
})
@@ -504,7 +523,7 @@ describe('job_output', () => {
// A model-supplied timeout far above the cap is clamped: this returns
// promptly (≤ the 20ms cap), not after ten minutes.
- const result = await call(ctx, 'job_output', { job_id: 'bash-1', wait: true, timeout_ms: 600_000 })
+ const result = await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1'), wait: true, timeout_ms: 600_000 })
expect(text(result)).toBe('(no new output)\n[status: running]')
})
@@ -534,20 +553,20 @@ describe('job_list', () => {
if (listed.isError) throw new Error('expected job_list success')
const listedValue = listed.value as Array>
expect(listedValue).toHaveLength(3)
- expect(listedValue[0]).toMatchObject({ id: 'bash-1', kind: 'bash', label: 'pnpm test', status: 'running' })
- expect(listedValue[2]).toMatchObject({ id: 'bash-2', kind: 'bash', label: 'build', status: 'completed', detail: 'exit code: 0' })
+ expect(listedValue[0]).toMatchObject({ id: startedId(ctx, 'bash-1'), kind: 'bash', label: 'pnpm test', status: 'running' })
+ expect(listedValue[2]).toMatchObject({ id: startedId(ctx, 'bash-2'), kind: 'bash', label: 'build', status: 'completed', detail: 'exit code: 0' })
for (const job of listedValue) {
expect(job).not.toHaveProperty('owner')
expect(job).not.toHaveProperty('output')
}
expect(text(listed)).toBe([
- 'bash-1 [bash] running — pnpm test',
- 'test-task-1 [test-task] running — open research',
- 'bash-2 [bash] completed — build',
+ `${startedId(ctx, 'bash-1')} [bash] running — pnpm test`,
+ `${startedId(ctx, 'test-task-1')} [test-task] running — open research`,
+ `${startedId(ctx, 'bash-2')} [bash] completed — build`,
].join('\n'))
// A different caller sees only the unowned job.
const bob = await fakeAgent(ctx, 'sess-bob')
- expect(text(await call(ctx, 'job_list', {}, bob))).toBe('test-task-1 [test-task] running — open research')
+ expect(text(await call(ctx, 'job_list', {}, bob))).toBe(`${startedId(ctx, 'test-task-1')} [test-task] running — open research`)
})
})
@@ -557,16 +576,16 @@ describe('job_kill', () => {
const p = producer()
ctx.jobs.start(p.spec)
- const result = await call(ctx, 'job_kill', { job_id: 'bash-1', reason: 'superseded' })
+ const result = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1'), reason: 'superseded' })
if (result.isError) throw new Error('expected job_kill success')
const killValue = result.value as { outcome: string; job: Record }
expect(killValue).toMatchObject({
outcome: 'cancellation-requested',
- job: { id: 'bash-1', kind: 'bash', label: 'sleep 60', status: 'stopping' },
+ job: { id: startedId(ctx, 'bash-1'), kind: 'bash', label: 'sleep 60', status: 'stopping' },
})
expect(killValue.job).not.toHaveProperty('owner')
expect(killValue.job).not.toHaveProperty('output')
- expect(text(result)).toBe('requested cancellation of job bash-1')
+ expect(text(result)).toBe(`requested cancellation of job ${startedId(ctx, 'bash-1')}`)
expect(p.cancels).toEqual(['superseded'])
})
@@ -575,7 +594,7 @@ describe('job_kill', () => {
const p = producer({ outputLimitBytes: 8 })
ctx.jobs.start(p.spec)
- const result = await call(ctx, 'job_kill', { job_id: 'bash-1' })
+ const result = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1') })
expect(Buffer.byteLength(text(result))).toBeLessThanOrEqual(8)
expect(p.cancels).toEqual([undefined])
})
@@ -587,11 +606,11 @@ describe('job_kill', () => {
cancel: () => { throw new Error('cancel failed: '.repeat(100)) },
}).spec)
- const result = await call(ctx, 'job_kill', { job_id: 'bash-1' })
+ const result = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1') })
expect(result.isError).toBe(true)
expect(Buffer.byteLength(text(result))).toBeLessThanOrEqual(64)
expect(text(result)).toContain('[result truncated]')
- expect(ctx.jobs.get(JobId('bash-1')).status).toBe('running')
+ expect(ctx.jobs.get(JobId(startedId(ctx, 'bash-1'))).status).toBe('running')
})
it('bounds single-text post policy while preserving structured policy results', async () => {
@@ -620,20 +639,20 @@ describe('job_kill', () => {
ctx.jobs.start(producer({ outputLimitBytes: 64 }).spec)
}
- const replaced = await call(ctx, 'job_kill', { job_id: 'bash-1', reason: 'replace' })
+ const replaced = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1'), reason: 'replace' })
expect(replaced.isError).toBe(false)
expect(Buffer.byteLength(text(replaced))).toBeLessThanOrEqual(64)
expect(text(replaced)).toContain('[result truncated]')
- const blocked = await call(ctx, 'job_kill', { job_id: 'bash-2', reason: 'block' })
+ const blocked = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-2'), reason: 'block' })
expect(blocked.isError).toBe(true)
expect(Buffer.byteLength(text(blocked))).toBeLessThanOrEqual(64)
expect(text(blocked)).toContain('[result truncated]')
- const multi = await call(ctx, 'job_kill', { job_id: 'bash-3', reason: 'multi' })
+ const multi = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-3'), reason: 'multi' })
expect(multi.content).toEqual([{ type: 'text', text: 'first' }, { type: 'text', text: 'second' }])
- const reasoning = await call(ctx, 'job_kill', { job_id: 'bash-4', reason: 'reasoning' })
+ const reasoning = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-4'), reason: 'reasoning' })
expect(reasoning.content).toEqual([{ type: 'reasoning', text: 'policy detail' }])
})
@@ -645,15 +664,15 @@ describe('job_kill', () => {
p.settle({ status: 'completed', detail: 'exit code: 0' })
await tick()
- const killed = await call(ctx, 'job_kill', { job_id: 'bash-1' })
+ const killed = await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1') })
if (killed.isError) throw new Error('expected job_kill success')
expect(killed.value).toMatchObject({
outcome: 'already-finished',
- job: { id: 'bash-1', kind: 'bash', label: 'sleep 60', status: 'completed', detail: 'exit code: 0' },
+ job: { id: startedId(ctx, 'bash-1'), kind: 'bash', label: 'sleep 60', status: 'completed', detail: 'exit code: 0' },
})
- expect(text(killed)).toBe('job bash-1 had already finished [status: completed, exit code: 0]')
+ expect(text(killed)).toBe(`job ${startedId(ctx, 'bash-1')} had already finished [status: completed, exit code: 0]`)
// The kill described the job via a non-consuming projection: the delta is intact.
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }))).toBe('unread tail\n[status: completed, exit code: 0]')
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }))).toBe('unread tail\n[status: completed, exit code: 0]')
})
it('rejects an empty job id as an errored result', async () => {
@@ -750,7 +769,7 @@ describe('completion notice delivery', () => {
expect(inject).toHaveBeenCalledTimes(1)
const message = inject.mock.calls[0]![0] as { content: readonly { type: string; text: string }[] }
expect(message.content[0]!.text).toBe(
- 'background job bash-1 (bash: pnpm run watch) finished '
+ `background job ${startedId(ctx, 'bash-1')} (bash: pnpm run watch) finished `
+ '[status: killed, signal: SIGTERM; cancelled by the user]. Read its output with job_output.',
)
})
@@ -894,7 +913,7 @@ describe('completion notices', () => {
expect(inject).toHaveBeenCalledWith({
id: expect.any(String) as unknown,
role: 'user',
- content: [{ type: 'text', text: 'background job bash-1 (bash: pnpm test) finished [status: completed, exit code: 0]. Read its output with job_output.' }],
+ content: [{ type: 'text', text: `background job ${startedId(ctx, 'bash-1')} (bash: pnpm test) finished [status: completed, exit code: 0]. Read its output with job_output.` }],
source: {
kind: 'tool-jobs',
form: 'notice',
@@ -903,7 +922,7 @@ describe('completion notices', () => {
})
})
- it('preserves job ids and collection guidance in bounded completion notices', async () => {
+ it('keeps collection guidance when a bounded notice cannot carry the id', async () => {
const { ctx } = await setup()
const inject = vi.fn()
const owner = await fakeAgent(ctx, 'sess-1', { inject })
@@ -922,7 +941,7 @@ describe('completion notices', () => {
{
id: expect.any(String) as unknown,
role: 'user',
- content: [{ type: 'text', text: 'background job test-task-1\nDone; job_output.' }],
+ content: [{ type: 'text', text: 'background job ended; use job_list for id.' }],
// The label and status detail are unbounded caller text, so the durable
// one-line account caps itself rather than committing their full length.
source: {
@@ -946,11 +965,10 @@ describe('completion notices', () => {
const content = (inject.mock.calls[1]?.[0] as { content?: Array<{ type: string; text?: string }> } | undefined)?.content
const notice = content?.[0]?.text ?? ''
expect(Buffer.byteLength(notice)).toBeLessThanOrEqual(80)
- expect(notice).toContain('background job test-task-2 (test-task: xxxx')
- expect(notice).toContain('[notice truncated]\nDone; job_output.')
+ expect(notice).toBe(`background job ${startedId(ctx, 'test-task-2')}\nDone; job_output.`)
})
- it('keeps the complete PTY job id and collection action at the minimum PTY limit', async () => {
+ it('guides collection through job_list when the PTY id exceeds its output limit', async () => {
const { ctx } = await setup()
for (let index = 0; index < 99; index += 1) {
const prior = producer({ kind: 'pty-send' })
@@ -974,28 +992,50 @@ describe('completion notices', () => {
const content = (inject.mock.calls[0]?.[0] as { content?: Array<{ type: string; text?: string }> } | undefined)?.content
const notice = content?.[0]?.text ?? ''
expect(Buffer.byteLength(notice)).toBeLessThanOrEqual(64)
- expect(notice).toBe('background job pty-send-100\n[notice truncated]\nDone; job_output.')
+ expect(notice).toBe('background job ended; use job_list for id.')
})
- it('reserves the collection-action tail when a producer supplies a smaller budget', async () => {
+ it('keeps a complete job id at the exact notice boundary and uses spare bytes for context', async () => {
+ const { ctx } = await setup()
+ const inject = vi.fn()
+ const owner = await fakeAgent(ctx, 'notice-boundary', { inject })
+ const fixedBytes = Buffer.byteLength(`background job test-task-${'0'.repeat(36)}\n[notice truncated]\nDone; job_output.`)
+ for (const [index, extra] of ['', ' (t'].entries()) {
+ const p = producer({ owner: owner.id, kind: 'test-task', label: 'x'.repeat(1_000), outputLimitBytes: fixedBytes + extra.length })
+ const id = ctx.jobs.start(p.spec)
+ expect(id).toMatch(/^test-task-[0-9a-f-]{36}$/)
+ p.settle({ status: 'completed' })
+ await tick()
+ const notice = (inject.mock.calls[index]?.[0] as { content?: Array<{ text?: string }> } | undefined)?.content?.[0]?.text
+ expect(notice).toBe(`background job ${id}${extra}\n[notice truncated]\nDone; job_output.`)
+ expect(Buffer.byteLength(notice ?? '')).toBe(fixedBytes + extra.length)
+ }
+ })
+
+ it('keeps a complete job_list action when the id exceeds a smaller budget', async () => {
const { ctx } = await setup()
const inject = vi.fn()
const owner = await fakeAgent(ctx, 'sess-1', { inject })
const tiny = producer({ owner: owner.id, kind: 'pty-send', label: 'x'.repeat(100), outputLimitBytes: 8 })
const short = producer({ owner: owner.id, kind: 'pty-send', label: 'x'.repeat(100), outputLimitBytes: 32 })
+ const minimal = producer({ owner: owner.id, kind: 'pty-send', label: 'x'.repeat(100), outputLimitBytes: 3 })
ctx.jobs.start(tiny.spec)
ctx.jobs.start(short.spec)
+ ctx.jobs.start(minimal.spec)
tiny.settle({ status: 'completed' })
short.settle({ status: 'completed' })
+ minimal.settle({ status: 'completed' })
await tick()
const tinyNotice = (inject.mock.calls[0]?.[0] as { content?: Array<{ text?: string }> } | undefined)?.content?.[0]?.text ?? ''
const shortNotice = (inject.mock.calls[1]?.[0] as { content?: Array<{ text?: string }> } | undefined)?.content?.[0]?.text ?? ''
expect(Buffer.byteLength(tinyNotice)).toBeLessThanOrEqual(8)
- expect(tinyNotice).toBe('_output.')
+ expect(tinyNotice).toBe('job_list')
expect(Buffer.byteLength(shortNotice)).toBeLessThanOrEqual(32)
- expect(shortNotice).toBe('background job\nDone; job_output.')
+ expect(shortNotice).toBe('job_list')
+ const minimalNotice = (inject.mock.calls[2]?.[0] as { content?: Array<{ text?: string }> } | undefined)?.content?.[0]?.text ?? ''
+ expect(minimalNotice).toBe('job')
})
it('suppresses the notice for a job the model already killed', async () => {
@@ -1005,7 +1045,7 @@ describe('completion notices', () => {
const p = producer({ owner: owner.id })
ctx.jobs.start(p.spec)
- await call(ctx, 'job_kill', { job_id: 'bash-1' }, owner)
+ await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1') }, owner)
p.settle({ status: 'killed' })
await tick()
expect(inject).not.toHaveBeenCalled()
@@ -1018,7 +1058,7 @@ describe('completion notices', () => {
const p = producer({ owner: owner.id, kind: 'test-task' })
ctx.jobs.start(p.spec)
- const pending = call(ctx, 'job_output', { job_id: 'test-task-1', wait: true }, owner)
+ const pending = call(ctx, 'job_output', { job_id: startedId(ctx, 'test-task-1'), wait: true }, owner)
p.settle({ status: 'completed', result: 'answer' })
expect(text(await pending)).toContain('answer')
await tick()
@@ -1032,8 +1072,8 @@ describe('completion notices', () => {
const p = producer({ owner: owner.id })
ctx.jobs.start(p.spec)
- const patient = call(ctx, 'job_output', { job_id: 'bash-1', wait: true, timeout_ms: 1000 }, owner)
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1', wait: true, timeout_ms: 10 }, owner))).toBe('(no new output)\n[status: running]')
+ const patient = call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1'), wait: true, timeout_ms: 1000 }, owner)
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1'), wait: true, timeout_ms: 10 }, owner))).toBe('(no new output)\n[status: running]')
p.settle({ status: 'completed' })
expect(text(await patient)).toBe('(no new output)\n[status: completed]')
await tick()
@@ -1047,7 +1087,7 @@ describe('completion notices', () => {
const p = producer({ owner: owner.id })
ctx.jobs.start(p.spec)
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1', wait: true }, owner))).toBe('(no new output)\n[status: running]')
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1'), wait: true }, owner))).toBe('(no new output)\n[status: running]')
p.settle({ status: 'completed' })
await tick()
expect(inject).toHaveBeenCalledTimes(1)
@@ -1065,7 +1105,7 @@ describe('completion notices', () => {
signal: aborter.signal,
callId: ToolCallId('call-aborted-wait'),
name: 'job_output',
- arguments: { job_id: 'bash-1', wait: true },
+ arguments: { job_id: startedId(ctx, 'bash-1'), wait: true },
agent: owner,
})
await tick()
@@ -1091,7 +1131,7 @@ describe('completion notices', () => {
signal: aborter.signal,
callId: ToolCallId('call-abort-then-settle'),
name: 'job_output',
- arguments: { job_id: 'test-task-1', wait: true },
+ arguments: { job_id: startedId(ctx, 'test-task-1'), wait: true },
agent: owner,
})
await tick()
@@ -1117,7 +1157,7 @@ describe('completion notices', () => {
ctx.jobs.start(killed.spec)
ctx.jobs.start(other.spec)
- await call(ctx, 'job_kill', { job_id: 'bash-1' }, owner)
+ await call(ctx, 'job_kill', { job_id: startedId(ctx, 'bash-1') }, owner)
other.settle({ status: 'completed' })
await tick()
expect(inject).toHaveBeenCalledTimes(1)
@@ -1153,7 +1193,7 @@ describe('completion notices', () => {
expect(oldInject).not.toHaveBeenCalled()
expect(replacementInject).toHaveBeenCalledTimes(1)
- expect(text(await call(ctx, 'job_output', { job_id: 'bash-1' }, replacement))).toBe('(no new output)\n[status: completed]')
+ expect(text(await call(ctx, 'job_output', { job_id: startedId(ctx, 'bash-1') }, replacement))).toBe('(no new output)\n[status: completed]')
})
it('drops the notice when the agent registry left before settlement', async () => {
diff --git a/packages/shell/tool-bash/tests/background.spec.ts b/packages/shell/tool-bash/tests/background.spec.ts
index 3a1fbff9bb..9801a6dc1b 100644
--- a/packages/shell/tool-bash/tests/background.spec.ts
+++ b/packages/shell/tool-bash/tests/background.spec.ts
@@ -122,7 +122,7 @@ describe('background bash output', () => {
workdir: '/nonexistent-dsh',
run_in_background: true,
})
- expect(text(started)).toMatch(/^started background job bash-\d+$/)
+ expect(text(started)).toMatch(/^started background job bash-[0-9a-f-]{36}$/)
const jobs = ctx.jobs
const job = jobs.list()[0]
await until(() => jobs.get(job!.id).status === 'killed' ? true : undefined)
@@ -217,19 +217,21 @@ describe('foreground commands as jobs', () => {
})
await until(() => execution?.observed.stdout.readFrom(0).text.includes('early-output') ? true : undefined, task.timeout)
// The job exists from the start: the command is listed while the call still waits.
- expect(ctx.jobs.list()[0]).toMatchObject({ id: 'bash-1', kind: 'bash', status: 'running', label: 'printf "early-output\\n"; sleep 30' })
+ const jobId = ctx.jobs.list()[0]?.id
+ expect(jobId).toMatch(/^bash-[0-9a-f-]{36}$/)
+ expect(ctx.jobs.list()[0]).toMatchObject({ id: jobId, kind: 'bash', status: 'running', label: 'printf "early-output\\n"; sleep 30' })
await vi.advanceTimersByTimeAsync(250)
const result = await pending
vi.useRealTimers()
const body = text(result)
expect(body).toContain('early-output')
- expect(body).toContain('[still running after 250ms; moved to background job bash-1]')
+ expect(body).toContain(`[still running after 250ms; moved to background job ${jobId}]`)
expect(body).toContain('read newer output with job_output, stop it with job_kill')
expect(body.indexOf('early-output')).toBeLessThan(body.indexOf('[still running'))
const jobs = ctx.jobs
const job = jobs.list()[0]
- expect(job).toMatchObject({ id: 'bash-1', status: 'running' })
+ expect(job).toMatchObject({ id: jobId, status: 'running' })
// The hand-over was one consuming read: the next read repeats nothing.
expect(jobs.read(job!.id).chunks).toEqual([])
expect(jobs.kill(job!.id, undefined, 'test cleanup')).toBe('requested')
diff --git a/packages/shell/tool-bash/tests/integration.spec.ts b/packages/shell/tool-bash/tests/integration.spec.ts
index 645525ff54..c62436c099 100644
--- a/packages/shell/tool-bash/tests/integration.spec.ts
+++ b/packages/shell/tool-bash/tests/integration.spec.ts
@@ -218,8 +218,7 @@ describe('bash tool through the agent loop', () => {
const dir = mkdtempSync(join(tmpdir(), 'dsh-bg-'))
dirs.push(dir)
const sentinel = join(dir, 'release')
- // The job id is deterministic (a fresh LocalJobRegistry counts per kind from 1),
- // so the script can name `bash-1` without threading a generated id.
+ // The next model call reads the id from the prior tool result.
const adapter = new MockAdapter([
toolCallResponse('call-1', 'bash', {
command: `while [ ! -f ${JSON.stringify(sentinel)} ]; do sleep 0.02; done; echo bg-ok`,
@@ -227,7 +226,11 @@ describe('bash tool through the agent loop', () => {
run_in_background: true,
}),
textResponse('Started it in the background.'),
- toolCallResponse('call-2', 'job_output', { job_id: 'bash-1' }),
+ (options) => {
+ const jobId = /started background job (bash-[0-9a-f-]{36})/.exec(JSON.stringify(options.messages))?.[1]
+ if (jobId === undefined) throw new Error('model history lacks the started job id')
+ return toolCallResponse('call-2', 'job_output', { job_id: jobId })
+ },
textResponse('Background job finished.'),
])
const ctx = await harness(adapter)
@@ -238,7 +241,8 @@ describe('bash tool through the agent loop', () => {
const firstResult = findEvent(events(agent), 'tool/result')
expect(firstResult.data.message.isError).toBe(false)
- expect(resultText(firstResult)).toBe('started background job bash-1')
+ const jobId = /^started background job (bash-[0-9a-f-]{36})$/.exec(resultText(firstResult))?.[1]
+ if (jobId === undefined) throw new Error('first tool result lacks a job id')
// The turn closed with the task still running, so the notice cannot exist yet.
const isNotice = (e: SessionEvent): e is SessionEvent<'user/message'> =>
e.type === 'user/message' && e.data.source.kind === 'tool-jobs'
@@ -261,7 +265,7 @@ describe('bash tool through the agent loop', () => {
const notice = events(agent).find(isNotice)!
const noticeText = notice.data.content
.filter(block => block.type === 'text').map(block => block.text).join('')
- expect(noticeText).toContain('background job bash-1 (bash: ')
+ expect(noticeText).toContain(`background job ${jobId} (bash: `)
expect(noticeText).toContain('finished [status: completed, exit code: 0]')
expect(notice.data.source).toMatchObject({
kind: 'tool-jobs',
diff --git a/packages/shell/tool-bash/tests/tools.spec.ts b/packages/shell/tool-bash/tests/tools.spec.ts
index 1d01640cd0..0b01d7c81b 100644
--- a/packages/shell/tool-bash/tests/tools.spec.ts
+++ b/packages/shell/tool-bash/tests/tools.spec.ts
@@ -27,6 +27,13 @@ import * as BashEnvPlugin from '@deepseek-ai/dsh-shell-env'
import { processOutcome } from '../src/background.ts'
import { renderJobRead, renderResult } from '../src/render.ts'
+/** Use the newest id returned by the real registry, including settled jobs. */
+function currentId(ctx: Context, owner?: Agent): string {
+ const id = ctx.jobs.list(owner?.id).at(-1)?.id
+ if (id === undefined) throw new Error('background job was not registered')
+ return id
+}
+
/** Empty offset readers for fakes that never produce output. */
const silentReader = { readFrom: (fromByte: number) => ({ text: '', nextOffset: fromByte, lossy: false }) }
@@ -539,8 +546,8 @@ describe('the background surface follows the job registry', () => {
await new Promise(resolve => setTimeout(resolve, 0))
expect(backgroundAdvertised(ctx)).toBe(true)
const started = await call(ctx, 'bash', { command: 'echo swapped', description: 'test command', run_in_background: true })
- expect(text(started)).toBe('started background job bash-1')
- await callUntilText(ctx, 'job_output', { job_id: 'bash-1' }, '[status: completed')
+ expect(text(started)).toBe(`started background job ${currentId(ctx)}`)
+ await callUntilText(ctx, 'job_output', { job_id: currentId(ctx) }, '[status: completed')
// The registry leaves; the foreground-only variant returns in its place,
// and the tool never disappears in between.
@@ -572,13 +579,13 @@ describe('background execution through the job runtime', () => {
const started = await call(ctx, 'bash', { command: 'echo bg-ok', description: 'test command', run_in_background: true })
expect(started.isError).toBe(false)
if (started.isError) throw new Error('expected background bash success')
- expect(started.value).toEqual({ kind: 'background', jobId: 'bash-1', cwd: process.cwd() })
- expect(text(started)).toBe('started background job bash-1')
+ expect(started.value).toEqual({ kind: 'background', jobId: currentId(ctx), cwd: process.cwd() })
+ expect(text(started)).toBe(`started background job ${currentId(ctx)}`)
- const read = await callUntilText(ctx, 'job_output', { job_id: 'bash-1' }, 'bg-ok')
+ const read = await callUntilText(ctx, 'job_output', { job_id: currentId(ctx) }, 'bg-ok')
expect(text(read)).toContain('bg-ok')
// A later read reports the terminal outcome in the generic status line.
- const final = await callUntilText(ctx, 'job_output', { job_id: 'bash-1' }, '[status: completed, exit code: 0]')
+ const final = await callUntilText(ctx, 'job_output', { job_id: currentId(ctx) }, '[status: completed, exit code: 0]')
expect(final.isError).toBe(false)
})
@@ -586,11 +593,11 @@ describe('background execution through the job runtime', () => {
const ctx = await setupWithJobs()
await call(ctx, 'bash', { command: 'sleep 60', description: 'test command', run_in_background: true })
- const killed = await call(ctx, 'job_kill', { job_id: 'bash-1' })
- expect(text(killed)).toBe('requested cancellation of job bash-1')
+ const killed = await call(ctx, 'job_kill', { job_id: currentId(ctx) })
+ expect(text(killed)).toBe(`requested cancellation of job ${currentId(ctx)}`)
// The cancel reached the process handle; the task settles as killed with
// the signal detail mapped by processOutcome.
- const final = await call(ctx, 'job_output', { job_id: 'bash-1', wait: true })
+ const final = await call(ctx, 'job_output', { job_id: currentId(ctx), wait: true })
expect(text(final)).toContain('[status: killed, signal: SIGTERM]')
})
@@ -598,7 +605,7 @@ describe('background execution through the job runtime', () => {
const ctx = await setupWithJobs()
await call(ctx, 'bash', { command: 'kill -TERM $$', description: 'test command', run_in_background: true })
- const final = await call(ctx, 'job_output', { job_id: 'bash-1', wait: true })
+ const final = await call(ctx, 'job_output', { job_id: currentId(ctx), wait: true })
expect(text(final)).toContain('[status: killed, signal: SIGTERM]')
})
@@ -607,15 +614,15 @@ describe('background execution through the job runtime', () => {
const ctx = await setupWithJobs()
const agent = await registerFakeAgent(ctx, 'sess-owner')
const started = await call(ctx, 'bash', { command: 'sleep 60', description: 'test command', run_in_background: true }, agent)
- expect(text(started)).toBe('started background job bash-1')
+ expect(text(started)).toBe(`started background job ${currentId(ctx, agent)}`)
- const anon = await call(ctx, 'job_output', { job_id: 'bash-1' })
+ const anon = await call(ctx, 'job_output', { job_id: currentId(ctx, agent) })
expect(anon.isError).toBe(true)
expect(text(anon)).toMatch(/belongs to another session/)
- const killed = await call(ctx, 'job_kill', { job_id: 'bash-1' }, agent)
+ const killed = await call(ctx, 'job_kill', { job_id: currentId(ctx, agent) }, agent)
expect(killed.isError).toBe(false)
- await call(ctx, 'job_output', { job_id: 'bash-1', wait: true }, agent) // await settlement — no orphan
+ await call(ctx, 'job_output', { job_id: currentId(ctx, agent), wait: true }, agent) // await settlement — no orphan
})
it('fails loud when the job runtime is not loaded', async () => {
@@ -849,7 +856,7 @@ describe('sandbox escalation through the generic task producer', () => {
// with the result, and the ordinal is never reused.
expect(ctx.jobs.list(agent.id)).toEqual([])
const background = await call(ctx, 'bash', { ...escalate, run_in_background: true }, agent)
- expect(text(background)).toBe('started background job bash-2')
+ expect(text(background)).toBe(`started background job ${currentId(ctx, agent)}`)
expect(bash.modes).toEqual(['workspace-write', 'workspace-write'])
})
@@ -1463,7 +1470,7 @@ describe('the model-facing bash tool builds its request from named args only (no
// The call really went down the background path (the recorder sees the real
// request the consumer built, so the absent env/stdin below is a real
// negative, not a recorder that drops everything).
- expect(text(result)).toBe('started background job bash-1')
+ expect(text(result)).toBe(`started background job ${currentId(ctx)}`)
expect(bash.requests).toHaveLength(1)
const request = bash.requests[0]!
expect(request.command).toBe('sleep 1')
@@ -1501,8 +1508,8 @@ describe('resolved launch directory metadata', () => {
expect(resolving).toHaveBeenCalledOnce()
expect(resolving).toHaveBeenCalledWith(expect.objectContaining({ workdir: workdir === undefined ? current : join(current, workdir) }))
expect(launch).toHaveBeenCalledWith(expect.objectContaining({ workdir: directory }))
- expect(text(result)).toBe(background ? 'started background job bash-1' : 'metadata-ok')
- if (background) await call(ctx, 'job_output', { job_id: 'bash-1', wait: true }, owner)
+ expect(text(result)).toBe(background ? `started background job ${currentId(ctx, owner)}` : 'metadata-ok')
+ if (background) await call(ctx, 'job_output', { job_id: currentId(ctx, owner), wait: true }, owner)
} finally {
ensure.mockRestore()
resolving.mockRestore()
diff --git a/packages/shell/tool-pwsh/tests/background.spec.ts b/packages/shell/tool-pwsh/tests/background.spec.ts
index 8a1d46addc..8c6582be5b 100644
--- a/packages/shell/tool-pwsh/tests/background.spec.ts
+++ b/packages/shell/tool-pwsh/tests/background.spec.ts
@@ -288,12 +288,14 @@ describe('foreground commands as jobs (pwsh)', () => {
const result = await call(ctx, { command: 'Get-Slow', description: 'test command', timeoutMs: 250 })
const body = (result.content[0] as { text: string }).text
expect(body).toContain('early-output')
- expect(body).toContain('moved to background job pwsh-1]')
+ const jobId = ctx.jobs.list()[0]?.id
+ expect(jobId).toMatch(/^pwsh-[0-9a-f-]{36}$/)
+ expect(body).toContain(`moved to background job ${jobId}]`)
expect(body).toContain('read newer output with job_output, stop it with job_kill')
const jobs = ctx.jobs
const job = jobs.list()[0]
- expect(job).toMatchObject({ id: 'pwsh-1', kind: 'pwsh', status: 'running' })
+ expect(job).toMatchObject({ id: jobId, kind: 'pwsh', status: 'running' })
// The hand-over was one consuming read: the next read repeats nothing.
expect(jobs.read(job!.id).chunks).toEqual([])
scripted.finish()
diff --git a/packages/shell/tool-pwsh/tests/tools.spec.ts b/packages/shell/tool-pwsh/tests/tools.spec.ts
index d033c52535..66920cc9e9 100644
--- a/packages/shell/tool-pwsh/tests/tools.spec.ts
+++ b/packages/shell/tool-pwsh/tests/tools.spec.ts
@@ -40,6 +40,13 @@ import { renderPwshJobRead, renderPwshResult } from '../src/render.ts'
/** Empty offset readers for fakes that never produce output. */
const silentReader = { readFrom: (fromByte: number) => ({ text: '', nextOffset: fromByte, lossy: false }) }
+/** Use the newest id returned by the real registry, including settled jobs. */
+function currentId(ctx: Context, owner?: Agent): string {
+ const id = ctx.jobs.list(owner?.id).at(-1)?.id
+ if (id === undefined) throw new Error('background job was not registered')
+ return id
+}
+
const testToolSignal = new AbortController().signal
/** Per-test temp dirs (session cwd/home fixtures), removed after each test. */
@@ -720,7 +727,7 @@ describe('sandbox escalation through ctx.approval', () => {
// with the result, and the ordinal is never reused.
expect(ctx.jobs.list(agent.id)).toEqual([])
const background = await call(ctx, 'pwsh', { ...escalate, run_in_background: true }, agent)
- expect(text(background)).toBe('started background job pwsh-2')
+ expect(text(background)).toBe(`started background job ${currentId(ctx, agent)}`)
expect(bash.modes).toEqual(['workspace-write', 'workspace-write'])
})
@@ -803,13 +810,13 @@ describe('background execution through the job runtime', () => {
const started = await call(ctx, 'pwsh', { command: 'Write-Output bg-ok', description: 'test command', run_in_background: true })
expect(started.isError).toBe(false)
if (started.isError) throw new Error('expected background pwsh success')
- expect(started.value).toEqual({ kind: 'background', jobId: 'pwsh-1', cwd: process.cwd() })
- expect(text(started)).toBe('started background job pwsh-1')
+ expect(started.value).toEqual({ kind: 'background', jobId: currentId(ctx), cwd: process.cwd() })
+ expect(text(started)).toBe(`started background job ${currentId(ctx)}`)
- const read = await callUntilText(ctx, 'job_output', { job_id: 'pwsh-1' }, 'bg-ok')
+ const read = await callUntilText(ctx, 'job_output', { job_id: currentId(ctx) }, 'bg-ok')
expect(text(read)).toContain('bg-ok')
// A later read reports the terminal outcome in the generic status line.
- const final = await callUntilText(ctx, 'job_output', { job_id: 'pwsh-1' }, '[status: completed, exit code: 0]')
+ const final = await callUntilText(ctx, 'job_output', { job_id: currentId(ctx) }, '[status: completed, exit code: 0]')
expect(final.isError).toBe(false)
})
@@ -818,11 +825,11 @@ describe('background execution through the job runtime', () => {
bash.backgroundHandler = () => killableProcess()
await call(ctx, 'pwsh', { command: 'Start-Sleep -Seconds 60', description: 'test command', run_in_background: true })
- const killed = await call(ctx, 'job_kill', { job_id: 'pwsh-1' })
- expect(text(killed)).toBe('requested cancellation of job pwsh-1')
+ const killed = await call(ctx, 'job_kill', { job_id: currentId(ctx) })
+ expect(text(killed)).toBe(`requested cancellation of job ${currentId(ctx)}`)
// The cancel reached the process handle; the task settles as killed with
// the signal detail mapped by processOutcome.
- const final = await call(ctx, 'job_output', { job_id: 'pwsh-1', wait: true })
+ const final = await call(ctx, 'job_output', { job_id: currentId(ctx), wait: true })
expect(text(final)).toContain('[status: killed, signal: SIGTERM]')
})
@@ -830,15 +837,15 @@ describe('background execution through the job runtime', () => {
const { ctx } = await setupWithJobs()
const agent = await registerFakeAgent(ctx, 'sess-owner')
const started = await call(ctx, 'pwsh', { command: 'Start-Sleep -Seconds 60', description: 'test command', run_in_background: true }, agent)
- expect(text(started)).toBe('started background job pwsh-1')
+ expect(text(started)).toBe(`started background job ${currentId(ctx, agent)}`)
- const anon = await call(ctx, 'job_output', { job_id: 'pwsh-1' })
+ const anon = await call(ctx, 'job_output', { job_id: currentId(ctx, agent) })
expect(anon.isError).toBe(true)
expect(text(anon)).toMatch(/belongs to another session/)
- const killed = await call(ctx, 'job_kill', { job_id: 'pwsh-1' }, agent)
+ const killed = await call(ctx, 'job_kill', { job_id: currentId(ctx, agent) }, agent)
expect(killed.isError).toBe(false)
- await call(ctx, 'job_output', { job_id: 'pwsh-1', wait: true }, agent) // await settlement — no orphan
+ await call(ctx, 'job_output', { job_id: currentId(ctx, agent), wait: true }, agent) // await settlement — no orphan
})
it('fails loud when the job runtime is not loaded', async () => {
@@ -944,7 +951,7 @@ describe('the background surface follows the job registry', () => {
await new Promise(resolve => setTimeout(resolve, 0))
expect(backgroundAdvertised(ctx)).toBe(true)
const started = await call(ctx, 'pwsh', { command: 'Write-Output swapped', description: 'test command', run_in_background: true })
- expect(text(started)).toBe('started background job pwsh-1')
+ expect(text(started)).toBe(`started background job ${currentId(ctx)}`)
// The registry leaves; the foreground-only variant returns in its place,
// and the tool never disappears in between.
@@ -1247,8 +1254,8 @@ describe('resolved launch directory metadata', () => {
expect(resolving).toHaveBeenCalledOnce()
expect(resolving).toHaveBeenCalledWith(expect.objectContaining({ workdir: workdir === undefined ? current : join(current, workdir) }))
expect(launch).toHaveBeenCalledWith(expect.objectContaining({ workdir: directory }))
- expect(text(result)).toBe(background ? 'started background job pwsh-1' : 'metadata-ok')
- if (background) await call(ctx, 'job_output', { job_id: 'pwsh-1', wait: true }, owner)
+ expect(text(result)).toBe(background ? `started background job ${currentId(ctx, owner)}` : 'metadata-ok')
+ if (background) await call(ctx, 'job_output', { job_id: currentId(ctx, owner), wait: true }, owner)
} finally {
ensure.mockRestore()
resolving.mockRestore()
diff --git a/packages/test-support/session-snapshot/src/normalize.ts b/packages/test-support/session-snapshot/src/normalize.ts
index 609d3066d2..c4fcaaa762 100644
--- a/packages/test-support/session-snapshot/src/normalize.ts
+++ b/packages/test-support/session-snapshot/src/normalize.ts
@@ -354,24 +354,35 @@ export function normalizeSessionLog(
const cwdPathMode = options.cwdPathMode ?? 'canonical'
const identityMode = options.identityMode ?? 'legacy'
const lines = rawLog.split('\n').filter(line => line.trim().length > 0)
- const executionTokens = new Map()
+ const parsed = lines.map(line => JSON.parse(line) as Record)
+ const jobTokens = new Map()
const reservedTokens = new Set(rawLog.match(/\{\{id:[1-9]\d*\}\}/g) ?? [])
- let nextExecutionToken = 1
- const records = lines.map((line) => {
- const record = JSON.parse(line) as Record
- if (identityMode === 'legacy' && (record.type === 'job/registered' || record.type === 'job/settled')
- && record.data !== null && typeof record.data === 'object') {
- const data = record.data as Record
- const id = data.executionId
- if (typeof id === 'string' && id.length === 36 && id.search(UUID_RE) === 0) {
- let token = executionTokens.get(id)
- if (token === undefined) {
- do { token = `{{id:${nextExecutionToken++}}}` } while (reservedTokens.has(token))
- executionTokens.set(id, token)
- }
- data.executionId = token
- }
+ let nextJobToken = 1
+ if (identityMode === 'legacy') {
+ for (const record of parsed) {
+ if ((record.type !== 'job/registered' && record.type !== 'job/settled')
+ || record.data === null || typeof record.data !== 'object') continue
+ const id = (record.data as Record).jobId
+ if (typeof id !== 'string' || id.search(UUID_RE) < 0 || jobTokens.has(id)) continue
+ let token: string
+ do { token = `{{id:${nextJobToken++}}}` } while (reservedTokens.has(token))
+ jobTokens.set(id, token)
}
+ }
+ const replaceJobs = (value: unknown): unknown => {
+ if (typeof value === 'string') {
+ let output = value
+ for (const [id, token] of jobTokens) output = output.split(id).join(token)
+ return output
+ }
+ if (Array.isArray(value)) return value.map(replaceJobs)
+ if (value !== null && typeof value === 'object') {
+ return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, replaceJobs(item)]))
+ }
+ return value
+ }
+ const records = parsed.map((raw) => {
+ const record = jobTokens.size === 0 ? raw : replaceJobs(raw) as Record
if (record.type === 'session') {
if ('createdAt' in record) record.createdAt = 0
} else if (isPackedFixtureRow(record)) {
diff --git a/packages/test-support/session-snapshot/tests/normalize.spec.ts b/packages/test-support/session-snapshot/tests/normalize.spec.ts
index b2796689f5..62a6d03e7a 100644
--- a/packages/test-support/session-snapshot/tests/normalize.spec.ts
+++ b/packages/test-support/session-snapshot/tests/normalize.spec.ts
@@ -257,20 +257,26 @@ Additional instructions from: nested\AGENTS.md`,
describe('normalizeSessionLog', () => {
it('keeps job execution identities distinct in single-log and single-snapshot paths', () => {
- const ids = ['aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb']
+ const ids = ['bash-aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa', 'bash-bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb']
const raw = [
{ type: 'session', version: 0, id: ctx.sessionIds[0], cwd: ctx.cwd, createdAt: 100 },
{ type: 'user/message', data: { text: 'Keep {{id:1}} literal.' } },
- ...ids.flatMap(executionId => [
- { type: 'job/registered', data: { executionId, startedAt: 100 } },
- { type: 'job/settled', data: { executionId, startedAt: 100, finishedAt: 200 } },
+ ...ids.flatMap(jobId => [
+ { type: 'job/registered', data: { jobId, startedAt: 100 } },
+ { type: 'job/settled', data: { jobId, startedAt: 100, finishedAt: 200 } },
]),
+ { type: 'tool/call', data: { arguments: JSON.stringify({ job_id: ids[0] }) } },
+ { type: 'tool/result', data: { message: { content: [{ type: 'text', text: `read ${ids[0]}` }] } } },
].map(record => JSON.stringify(record)).join('\n') + '\n'
for (const normalize of [normalizeSessionLog, normalizeSessionSnapshot]) {
const output = normalize(raw, ctx)
- const jobs = output.split('\n').filter(Boolean).map(line => JSON.parse(line) as { type: string; data: { executionId: string } })
+ const jobs = output.split('\n').filter(Boolean).map(line => JSON.parse(line) as { type: string; data: { jobId: string } })
.filter(record => record.type.startsWith('job/'))
- expect(jobs.map(record => record.data.executionId)).toEqual(['{{id:2}}', '{{id:2}}', '{{id:3}}', '{{id:3}}'])
+ expect(jobs.map(record => record.data.jobId)).toEqual(['{{id:2}}', '{{id:2}}', '{{id:3}}', '{{id:3}}'])
+ const call = output.split('\n').filter(Boolean).map(line => JSON.parse(line) as { type: string; data: { arguments: string } })
+ .find(record => record.type === 'tool/call')
+ expect(JSON.parse(call!.data.arguments)).toEqual({ job_id: '{{id:2}}' })
+ expect(output).toContain('read {{id:2}}')
expect(output).toContain('Keep {{id:1}} literal.')
expect(normalize(output, ctx)).toBe(output)
}
diff --git a/packages/workflow/tool-workflow/tests/tool-workflow.spec.ts b/packages/workflow/tool-workflow/tests/tool-workflow.spec.ts
index 29dd42a6c6..204ef0a709 100644
--- a/packages/workflow/tool-workflow/tests/tool-workflow.spec.ts
+++ b/packages/workflow/tool-workflow/tests/tool-workflow.spec.ts
@@ -475,14 +475,15 @@ describe('dsh-tool-workflow', () => {
const result = await execute(ctx, { script: SCRIPT, meta: META, run_in_background: true }, { agent: parent })
expect(result.isError).toBe(false)
if (result.isError) throw new Error('expected background acceptance')
- expect(result.value).toEqual({ kind: 'background', jobId: 'workflow-1', runId: 'run-1' })
+ const jobId = (result.value as { jobId: string }).jobId as JobId
+ expect(result.value).toEqual({ kind: 'background', jobId, runId: 'run-1' })
expect((result.content[0] as { text: string }).text)
- .toContain('workflow "audit" started in the background as job workflow-1')
+ .toContain(`workflow "audit" started in the background as job ${jobId}`)
// The engine run carries no tool-step signal: the job owns cancellation.
expect(engine.requests[0]!.signal).toBeUndefined()
const jobs = ctx.jobs
- const job = jobs.get('workflow-1' as never, parent.id)
+ const job = jobs.get(jobId, parent.id)
expect(job).toMatchObject({ kind: 'workflow', label: 'audit', status: 'running', owner: parent.id })
expect(job.output.total).toBe(0)
@@ -500,7 +501,7 @@ describe('dsh-tool-workflow', () => {
signal: testToolSignal,
callId: ToolCallId('call-early-read'),
name: 'job_output',
- arguments: { job_id: 'workflow-1' },
+ arguments: { job_id: jobId },
agent: parent,
})
expect((early.content[0] as { text: string }).text).toBe('(no new output)\n[status: running, Scan]')
@@ -549,18 +550,20 @@ describe('dsh-tool-workflow', () => {
const { ctx, engine, parent } = await setupBackground()
const result = await execute(ctx, { script: SCRIPT, meta: META, args: { files: ['a.ts'] }, run_in_background: true }, { agent: parent })
if (result.isError) throw new Error('expected background acceptance')
+ const firstId = (result.value as { jobId: string }).jobId as JobId
expect(engine.requests[0]).toMatchObject({ args: { files: ['a.ts'] } })
engine.settleRun(WorkflowRunId('run-1'), { value: null, stopReason: 'error', error: 'script exploded', agentsStarted: 2 })
const jobs = ctx.jobs
- await vi.waitFor(() => { expect(jobs.get('workflow-1' as never, parent.id).status).toBe('failed') })
- expect(jobs.get('workflow-1' as never, parent.id).detail).toBe('script exploded')
+ await vi.waitFor(() => { expect(jobs.get(firstId, parent.id).status).toBe('failed') })
+ expect(jobs.get(firstId, parent.id).detail).toBe('script exploded')
// A failure without a message falls back to the unknown-error detail.
const second = await execute(ctx, { script: SCRIPT, meta: META, run_in_background: true }, { agent: parent })
if (second.isError) throw new Error('expected background acceptance')
+ const secondId = (second.value as { jobId: string }).jobId as JobId
engine.settleRun(WorkflowRunId('run-2'), { value: null, stopReason: 'error', agentsStarted: 0 })
- await vi.waitFor(() => { expect(jobs.get('workflow-2' as never, parent.id).status).toBe('failed') })
- expect(jobs.get('workflow-2' as never, parent.id).detail).toBe('unknown error')
+ await vi.waitFor(() => { expect(jobs.get(secondId, parent.id).status).toBe('failed') })
+ expect(jobs.get(secondId, parent.id).detail).toBe('unknown error')
})
it('a nested transport call records job lifecycle but no workflow events, and a dispose failure still settles', async () => {
@@ -573,10 +576,11 @@ describe('dsh-tool-workflow', () => {
{ agent: parent, parent: {} as ToolExecutionToken },
)
if (result.isError) throw new Error('expected background acceptance')
+ const jobId = (result.value as { jobId: string }).jobId as JobId
engine.settleRun(WorkflowRunId('run-1'), { value: 'ok', stopReason: 'completed', agentsStarted: 1 })
const jobs = ctx.jobs
- await vi.waitFor(() => { expect(jobs.get('workflow-1' as never, parent.id).status).toBe('completed') })
- expect(jobs.get('workflow-1' as never, parent.id).detail).toBe('1 agent')
+ await vi.waitFor(() => { expect(jobs.get(jobId, parent.id).status).toBe('completed') })
+ expect(jobs.get(jobId, parent.id).detail).toBe('1 agent')
expect(warn.mock.calls.map(args => String(args[0])).join('\n')).toContain('dispose failed')
expect(session.snapshotEvents().map(event => event.type)).toEqual(['job/registered', 'job/settled'])
expect(session.snapshotEvents().at(-1)?.data).toMatchObject({ producerReported: true })
diff --git a/python/sdk/tests/test_smoke_model.py b/python/sdk/tests/test_smoke_model.py
index 086a75d567..3f760d53e2 100644
--- a/python/sdk/tests/test_smoke_model.py
+++ b/python/sdk/tests/test_smoke_model.py
@@ -771,14 +771,14 @@ def test_live_turn_rejects_missing_standalone_acknowledgement(answer: str) -> No
def test_job_snapshot_normalization_preserves_pairing_and_uncertainty():
normalize = SMOKE["normalize_snapshot_value"]
replacements = []
- first = {"type": "job/registered", "data": {"executionId": "job-a", "startedAt": 10}}
- last = {"type": "job/settled", "data": {"executionId": "job-a", "startedAt": 10,
+ first = {"type": "job/registered", "data": {"jobId": "job-a", "startedAt": 10}}
+ last = {"type": "job/settled", "data": {"jobId": "job-a", "startedAt": 10,
"finishedAt": 20, "producerReported": False}}
- other = {"type": "job/registered", "data": {"executionId": "job-b", "startedAt": 30}}
+ other = {"type": "job/registered", "data": {"jobId": "job-b", "startedAt": 30}}
a, b, c = normalize([first, last, other], replacements)
- assert a["data"] == {"executionId": "{{id:1}}", "startedAt": 0}
- assert b["data"] == {"executionId": "{{id:1}}", "startedAt": 0, "finishedAt": 0, "producerReported": False}
- assert c["data"]["executionId"] == "{{id:2}}"
+ assert a["data"] == {"jobId": "{{id:1}}", "startedAt": 0}
+ assert b["data"] == {"jobId": "{{id:1}}", "startedAt": 0, "finishedAt": 0, "producerReported": False}
+ assert c["data"]["jobId"] == "{{id:2}}"
assert normalize([a, b, c], []) == [a, b, c]
assert normalize({"type": "tool/result", "data": {"startedAt": 100, "finishedAt": 200}}, []) == {
"type": "tool/result", "data": {"startedAt": 100, "finishedAt": 200},
diff --git a/scripts/ci-workflow.spec.ts b/scripts/ci-workflow.spec.ts
index 991d41901b..bcc572762f 100644
--- a/scripts/ci-workflow.spec.ts
+++ b/scripts/ci-workflow.spec.ts
@@ -187,20 +187,6 @@ describe('CI workflow', () => {
expect(wine.steps).toContainEqual(expect.objectContaining({ name: 'Shut down wineserver', if: 'always()' }))
})
- it('runs Windows coverage with the released Realm cleanup fix while retaining Node 24 native checks', () => {
- const workflow = loadWorkflow('.github/workflows/ci.yml')
- const coverage = workflowJob(workflow, 'windows-coverage')
- expect(coverage.name).toBe('windows node 26 / coverage')
- expect(coverage.steps).toContainEqual(expect.objectContaining({
- uses: 'actions/setup-node@v7.0.0', with: { 'node-version': '26.10.0' },
- }))
- for (const name of ['windows-build', 'windows-native-tests']) {
- expect(workflowJob(workflow, name).steps).toContainEqual(expect.objectContaining({
- uses: 'actions/setup-node@v7.0.0', with: { 'node-version': '${{ env.PRIMARY_NODE_VERSION }}' },
- }))
- }
- })
-
it.each([
['node-24-coverage', 'Linux', 'LINUX', 'pnpm run check:ci:coverage'],
['windows-coverage', 'Windows', 'WINDOWS', 'pnpm run check:ci:coverage'],
@@ -397,7 +383,7 @@ describe('CI workflow', () => {
expect(install!.run).not.toContain('$cloneFlag')
}
- expect(windowsCoverage.name).toBe('windows node 26 / coverage')
+ expect(windowsCoverage.name).toBe('windows node 24 / coverage')
expect(windowsCoverage.env).toMatchObject({ DSH_COVERAGE_PARTITIONS: "${{ vars.DSH_CI_FAILOVER_WINDOWS == 'selfhosted' && github.event.pull_request.user.login != 'dependabot[bot]' && '4' || '' }}" })
const coverageSteps = windowsCoverage.steps as unknown[]
const coverageCommands = coverageSteps.filter((step): step is Record & { run: string } => (
diff --git a/scripts/gen-cordis-catalog.ts b/scripts/gen-cordis-catalog.ts
index 2c012bde12..6103d93a6f 100644
--- a/scripts/gen-cordis-catalog.ts
+++ b/scripts/gen-cordis-catalog.ts
@@ -171,6 +171,7 @@ export const SERVICE_WALK_EXEMPTIONS: Record = {
webTerminals: 'client-side terminal view models — packages/api/terminal-controller/README.md owns the API',
appReady: 'not a service: launcher-provided successful-startup signal — packages/boot/cmdline/README.md owns the launcher contract',
appExit: 'not a service: launcher-provided bounded process-exit callback — packages/boot/cmdline/README.md owns the launcher contract',
+ appShutdown: 'not a service: launcher-provided ordered cleanup registry — packages/boot/cmdline/README.md owns the launcher contract',
cmdlineArgs: 'not a service: launcher-provided immutable app argument accessor — packages/boot/cmdline/README.md owns the launcher contract',
configuredAgentIdentities: 'not a service: launcher-provided boot-context value (ConfiguredAgentIdentities | undefined) — packages/core/agent-loop/README.md owns this launcher contract',
launcherSessionQueryPath: 'not a service: launcher-provided boot-context value (string | undefined) — packages/session-query/session-query-sqlite/README.md owns this launcher contract',
diff --git a/scripts/smoke-python-runtime.py b/scripts/smoke-python-runtime.py
index cb0661b8cd..ce80e26031 100644
--- a/scripts/smoke-python-runtime.py
+++ b/scripts/smoke-python-runtime.py
@@ -1610,7 +1610,7 @@ def smoke_sdk_snapshot_mode(
jobs = [event for event in result.events if event.get("type") in ("job/registered", "job/settled")]
assert [event["type"] for event in jobs] == ["job/registered", "job/settled"], jobs
registered, settled = (event["data"] for event in jobs)
- assert registered["executionId"] == settled["executionId"]
+ assert registered["jobId"] == settled["jobId"]
assert registered["startedAt"] == settled["startedAt"] <= settled["finishedAt"]
assert settled["producerReported"] is True and settled["status"] == "completed"
root_call_id = "advanced-code" if mode == "ptc" else "advanced-native"
@@ -2608,11 +2608,11 @@ def normalize_snapshot_value(
if value.get("type") in ("job/registered", "job/settled"):
data = value.get("data")
- execution_id = data.get("executionId") if isinstance(data, dict) else None
- if (isinstance(execution_id, str) and not re.fullmatch(r"\{\{id:[1-9]\d*\}\}", execution_id)
- and not any(actual == execution_id for actual, _ in replacements)):
+ job_id = data.get("jobId") if isinstance(data, dict) else None
+ if (isinstance(job_id, str) and not re.fullmatch(r"\{\{id:[1-9]\d*\}\}", job_id)
+ and not any(actual == job_id for actual, _ in replacements)):
index = 1 + sum(token.startswith("{{id:") for _, token in replacements)
- replacements.append((execution_id, "{{id:" + str(index) + "}}"))
+ replacements.append((job_id, "{{id:" + str(index) + "}}"))
normalized = {
key: normalize_snapshot_value(item, replacements)
diff --git a/scripts/snapshots/python-sdk-single-exe/advanced-ptc/result.json b/scripts/snapshots/python-sdk-single-exe/advanced-ptc/result.json
index aec1f7cc03..4b8af914f2 100644
--- a/scripts/snapshots/python-sdk-single-exe/advanced-ptc/result.json
+++ b/scripts/snapshots/python-sdk-single-exe/advanced-ptc/result.json
@@ -461,8 +461,7 @@
"seq": 24,
"time": 0,
"data": {
- "executionId": "{{id:1}}",
- "jobId": "snapshot-1",
+ "jobId": "{{id:1}}",
"owner": "{{parent}}",
"kind": "snapshot",
"label": "snapshot_double",
@@ -478,8 +477,7 @@
"seq": 25,
"time": 0,
"data": {
- "executionId": "{{id:1}}",
- "jobId": "snapshot-1",
+ "jobId": "{{id:1}}",
"owner": "{{parent}}",
"kind": "snapshot",
"label": "snapshot_double",
@@ -1605,8 +1603,7 @@
"seq": 24,
"time": 0,
"data": {
- "executionId": "{{id:1}}",
- "jobId": "snapshot-1",
+ "jobId": "{{id:1}}",
"owner": "{{parent}}",
"kind": "snapshot",
"label": "snapshot_double",
@@ -1628,8 +1625,7 @@
"seq": 25,
"time": 0,
"data": {
- "executionId": "{{id:1}}",
- "jobId": "snapshot-1",
+ "jobId": "{{id:1}}",
"owner": "{{parent}}",
"kind": "snapshot",
"label": "snapshot_double",
diff --git a/scripts/snapshots/python-sdk-single-exe/advanced-ptc/session.v4.jsonl b/scripts/snapshots/python-sdk-single-exe/advanced-ptc/session.v4.jsonl
index d2cd919f12..33fda38ea2 100644
--- a/scripts/snapshots/python-sdk-single-exe/advanced-ptc/session.v4.jsonl
+++ b/scripts/snapshots/python-sdk-single-exe/advanced-ptc/session.v4.jsonl
@@ -23,8 +23,8 @@
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Call the configured Plugin tool\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"tool-call"}]}},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"advanced-code","name":"run_code","args":[""]},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"advanced-code","args":["{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Call the configured Plugin tool\"}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Call the configured Plugin tool\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"tool-call"}]}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"advanced-code","name":"run_code","arguments":"{\"code\": \"return await tools.snapshot_double({ value: 21 })\", \"description\": \"Call the configured Plugin tool\"}"}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:ptc:1","name":"snapshot_double","arguments":{"value":21}}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"snapshot-1","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-code:ptc:1","rootCallId":"advanced-code"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"snapshot-1","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-code:ptc:1","rootCallId":"advanced-code"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-code:ptc:1","rootCallId":"advanced-code"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-code:ptc:1","rootCallId":"advanced-code"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"advanced-code","parentCallId":"advanced-code","subCallId":"advanced-code:ptc:1","name":"snapshot_double","arguments":{"value":21},"isError":false,"content":[{"type":"text","text":"42"}]}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"advanced-code"},"toolCallId":"advanced-code","content":[{"type":"text","text":"42"}],"isError":false,"id":"{{messageId}}"}},"sourceEventSeqs":[22],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
diff --git a/scripts/snapshots/python-sdk-single-exe/advanced/result.json b/scripts/snapshots/python-sdk-single-exe/advanced/result.json
index 740e09249b..0cb043baf0 100644
--- a/scripts/snapshots/python-sdk-single-exe/advanced/result.json
+++ b/scripts/snapshots/python-sdk-single-exe/advanced/result.json
@@ -463,8 +463,7 @@
"seq": 23,
"time": 0,
"data": {
- "executionId": "{{id:1}}",
- "jobId": "snapshot-1",
+ "jobId": "{{id:1}}",
"owner": "{{parent}}",
"kind": "snapshot",
"label": "snapshot_double",
@@ -480,8 +479,7 @@
"seq": 24,
"time": 0,
"data": {
- "executionId": "{{id:1}}",
- "jobId": "snapshot-1",
+ "jobId": "{{id:1}}",
"owner": "{{parent}}",
"kind": "snapshot",
"label": "snapshot_double",
@@ -2073,8 +2071,7 @@
"seq": 23,
"time": 0,
"data": {
- "executionId": "{{id:1}}",
- "jobId": "snapshot-1",
+ "jobId": "{{id:1}}",
"owner": "{{parent}}",
"kind": "snapshot",
"label": "snapshot_double",
@@ -2096,8 +2093,7 @@
"seq": 24,
"time": 0,
"data": {
- "executionId": "{{id:1}}",
- "jobId": "snapshot-1",
+ "jobId": "{{id:1}}",
"owner": "{{parent}}",
"kind": "snapshot",
"label": "snapshot_double",
diff --git a/scripts/snapshots/python-sdk-single-exe/advanced/session.v4.jsonl b/scripts/snapshots/python-sdk-single-exe/advanced/session.v4.jsonl
index cffc870a72..dcd09c277c 100644
--- a/scripts/snapshots/python-sdk-single-exe/advanced/session.v4.jsonl
+++ b/scripts/snapshots/python-sdk-single-exe/advanced/session.v4.jsonl
@@ -22,8 +22,8 @@
{"type":"session-log-deepseek/delivery-accepted","data":{"sessionId":"{{parent}}","sessionFormatVersion":4,"throughSeq":19}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"advanced-native","name":"snapshot_double","arguments":"{\"value\": 21}"}],"source":{"kind":"model","provider":"deepseek-official","model":"smoke-model","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"tool-call"}]}},"id":"{{messageId}}"},"usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"advanced-native","name":"snapshot_double","args":[""]},{"type":"tool-call-chunks","time0":0,"index":0,"dt":[],"id":"advanced-native","args":["{\"value\": 21}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"advanced-native","name":"snapshot_double","arguments":"{\"value\": 21}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3,"totalTokens":6}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"smoke-model"},"blocks":[{"type":"tool-call"}]}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"advanced-native","name":"snapshot_double","arguments":"{\"value\": 21}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"snapshot-1","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-native","rootCallId":"advanced-native"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"snapshot-1","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-native","rootCallId":"advanced-native"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-native","rootCallId":"advanced-native"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{parent}}","kind":"snapshot","label":"snapshot_double","startedAt":0,"toolCall":{"callId":"advanced-native","rootCallId":"advanced-native"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"advanced-native"},"toolCallId":"advanced-native","content":[{"type":"text","text":"42"}],"isError":false,"id":"{{messageId}}"}},"sourceEventSeqs":[22],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/acp/cancel-tool-calls/session.v4.jsonl b/snapshots/acp/cancel-tool-calls/session.v4.jsonl
index a3caca2862..a48490676b 100644
--- a/snapshots/acp/cancel-tool-calls/session.v4.jsonl
+++ b/snapshots/acp/cancel-tool-calls/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Run two shell commands: wait","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_wait","name":"bash","arguments":"{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"},{"type":"tool-call","id":"call_skipped","name":"bash","arguments":"{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":10},"stream":[{"type":"chunk","time":1791427932073,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427932073,"index":0,"dt":[],"id":"call_wait","name":"bash","args":["{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"]},{"type":"chunk","time":1791427932073,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_wait","name":"bash","arguments":"{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"}}},{"type":"chunk","time":1791427932073,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427932073,"index":1,"dt":[],"id":"call_skipped","name":"bash","args":["{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"]},{"type":"chunk","time":1791427932073,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_skipped","name":"bash","arguments":"{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"}}},{"type":"chunk","time":1791427932073,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":10}}},{"type":"chunk","time":1791427932073,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_wait","name":"bash","arguments":"{\"command\":\"node -e \\\"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\\\"\",\"description\":\"Wait until cancellation\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"node -e \"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\"","startedAt":1791427932086,"toolCall":{"callId":"call_wait","rootCallId":"call_wait"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"node -e \"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\"","startedAt":1791427932086,"toolCall":{"callId":"call_wait","rootCallId":"call_wait"},"finishedAt":1791427932134,"status":"killed","cause":"kill","producerReported":true,"detail":"signal: SIGTERM; tool call aborted"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"node -e \"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\"","startedAt":1791427932086,"toolCall":{"callId":"call_wait","rootCallId":"call_wait"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"node -e \"const fs=require('node:fs'); fs.writeFileSync('started.tmp', 'started'); fs.renameSync('started.tmp', 'started.txt'); setInterval(() => {}, 1000)\"","startedAt":1791427932086,"toolCall":{"callId":"call_wait","rootCallId":"call_wait"},"finishedAt":1791427932134,"status":"killed","cause":"kill","producerReported":true,"detail":"signal: SIGTERM; tool call aborted"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_wait"},"toolCallId":"call_wait","content":[{"type":"text","text":"Error: tool call aborted"}],"isError":true,"id":"{{message:5}}"},"error":{"name":"AbortError","code":"ABORTED"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_skipped","name":"bash","arguments":"{\"command\":\"printf skipped > skipped.txt\",\"description\":\"Write skipped marker\"}"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_skipped"},"toolCallId":"call_skipped","content":[{"type":"text","text":"Error: tool call aborted before dispatch"}],"isError":true,"id":"{{message:6}}"},"error":{"name":"AbortError","code":"ABORTED_BEFORE_DISPATCH"}},"sourceEventSeqs":[18],"surfaceOp":"append"}
diff --git a/snapshots/acp/escalation-approved/session.v4.jsonl b/snapshots/acp/escalation-approved/session.v4.jsonl
index 17c3e9f12c..4a8b4d056b 100644
--- a/snapshots/acp/escalation-approved/session.v4.jsonl
+++ b/snapshots/acp/escalation-approved/session.v4.jsonl
@@ -16,8 +16,8 @@
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","name":"bash","arguments":"{\"command\": \"printf 'escalated\\\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt\", \"description\": \"Write file outside workspace and verify\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"the user asked to write a file outside the workspace\"}"}}
{"type":"approval/asked","data":{"id":"{{approval:1}}","toolName":"bash","callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","reason":"escalate sandbox to danger-full-access: the user asked to write a file outside the workspace"}}
{"type":"approval/decided","data":{"id":"{{approval:1}}","outcome":"allowed-once"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'escalated\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt","startedAt":1791427933141,"toolCall":{"callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","rootCallId":"call_00_d0sAHpJ9mYOJi0z7KNy30441"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'escalated\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt","startedAt":1791427933141,"toolCall":{"callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","rootCallId":"call_00_d0sAHpJ9mYOJi0z7KNy30441"},"finishedAt":1791427933148,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'escalated\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt","startedAt":1791427933141,"toolCall":{"callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","rootCallId":"call_00_d0sAHpJ9mYOJi0z7KNy30441"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'escalated\\n' > /tmp/dsh-escalated.txt && cat /tmp/dsh-escalated.txt && rm /tmp/dsh-escalated.txt","startedAt":1791427933141,"toolCall":{"callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","rootCallId":"call_00_d0sAHpJ9mYOJi0z7KNy30441"},"finishedAt":1791427933148,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_d0sAHpJ9mYOJi0z7KNy30441"},"toolCallId":"call_00_d0sAHpJ9mYOJi0z7KNy30441","content":[{"type":"text","text":"escalated\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/sdk/bash-tool/notifications.expected.jsonl b/snapshots/sdk/bash-tool/notifications.expected.jsonl
index 8d62cfd218..9a64ca16b8 100644
--- a/snapshots/sdk/bash-tool/notifications.expected.jsonl
+++ b/snapshots/sdk/bash-tool/notifications.expected.jsonl
@@ -11,8 +11,8 @@
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"session/title","seq":12,"time":0,"data":{"title":"Run this exact command with","messageSeqs":[8],"source":{"kind":"fallback"}}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a specific bash command and reply with its stdout only."},{"type":"tool-call","id":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","arguments":"{\"command\": \"echo dsh-sdk-proof-7391\", \"description\": \"Run the echo command as requested\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":123,"outputTokens":89,"cacheReadTokens":1664,"reasoningTokens":17},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":0,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," specific"," bash"," command"," and"," reply"," with"," its"," stdout"," only","."]},{"type":"chunk","time":0,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," d","sh","-s","dk","-proof","-","739","1","\"",", ","\"","description","\"",": ","\"","Run"," the"," echo"," command"," as"," requested","\"","}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a specific bash command and reply with its stdout only."}}},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","arguments":"{\"command\": \"echo dsh-sdk-proof-7391\", \"description\": \"Run the echo command as requested\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":123,"outputTokens":89,"cacheReadTokens":1664,"reasoningTokens":17}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/call","seq":14,"time":0,"data":{"turn":1,"step":1,"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","arguments":"{\"command\": \"echo dsh-sdk-proof-7391\", \"description\": \"Run the echo command as requested\"}"}}}}
-{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/registered","seq":15,"time":0,"data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":0,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"}}}}}
-{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/settled","seq":16,"time":0,"data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":0,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}}}
+{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/registered","seq":15,"time":0,"data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":0,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"}}}}}
+{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/settled","seq":16,"time":0,"data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":0,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/result","seq":17,"time":0,"data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_Ry17evSfTr0uJnHhg3X93070"},"toolCallId":"call_00_Ry17evSfTr0uJnHhg3X93070","content":[{"type":"text","text":"dsh-sdk-proof-7391\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"step/end","seq":18,"time":0,"data":{"turn":1,"step":1}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"step/start","seq":19,"time":0,"data":{"turn":1,"step":2}}}}
diff --git a/snapshots/sdk/bash-tool/session.v4.jsonl b/snapshots/sdk/bash-tool/session.v4.jsonl
index 6e75947390..cf9bafc3fb 100644
--- a/snapshots/sdk/bash-tool/session.v4.jsonl
+++ b/snapshots/sdk/bash-tool/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Run this exact command with","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a specific bash command and reply with its stdout only."},{"type":"tool-call","id":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","arguments":"{\"command\": \"echo dsh-sdk-proof-7391\", \"description\": \"Run the echo command as requested\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":123,"outputTokens":89,"cacheReadTokens":1664,"reasoningTokens":17},"stream":[{"type":"chunk","time":1791427912016,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427912016,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," specific"," bash"," command"," and"," reply"," with"," its"," stdout"," only","."]},{"type":"chunk","time":1791427912016,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427912016,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," d","sh","-s","dk","-proof","-","739","1","\"",", ","\"","description","\"",": ","\"","Run"," the"," echo"," command"," as"," requested","\"","}"]},{"type":"chunk","time":1791427912016,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a specific bash command and reply with its stdout only."}}},{"type":"chunk","time":1791427912016,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","arguments":"{\"command\": \"echo dsh-sdk-proof-7391\", \"description\": \"Run the echo command as requested\"}"}}},{"type":"chunk","time":1791427912016,"chunk":{"type":"usage","usage":{"inputTokens":123,"outputTokens":89,"cacheReadTokens":1664,"reasoningTokens":17}}},{"type":"chunk","time":1791427912016,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","name":"bash","arguments":"{\"command\": \"echo dsh-sdk-proof-7391\", \"description\": \"Run the echo command as requested\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":1791427912022,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":1791427912022,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"},"finishedAt":1791427912033,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":1791427912022,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo dsh-sdk-proof-7391","startedAt":1791427912022,"toolCall":{"callId":"call_00_Ry17evSfTr0uJnHhg3X93070","rootCallId":"call_00_Ry17evSfTr0uJnHhg3X93070"},"finishedAt":1791427912033,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_Ry17evSfTr0uJnHhg3X93070"},"toolCallId":"call_00_Ry17evSfTr0uJnHhg3X93070","content":[{"type":"text","text":"dsh-sdk-proof-7391\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/sdk/multi-turn/writer.expected.jsonl b/snapshots/sdk/multi-turn/writer.expected.jsonl
index de08d3e7c5..fc36064687 100644
--- a/snapshots/sdk/multi-turn/writer.expected.jsonl
+++ b/snapshots/sdk/multi-turn/writer.expected.jsonl
@@ -12,7 +12,7 @@
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Reply with exactly the word:","messageSeqs":[8],"source":{"kind":"fallback"}}}
-{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ONE\" and use no tools."},{"type":"text","text":"ONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2864,"outputTokens":20,"cacheReadTokens":0,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791434053910,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791434053910,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","ONE","\""," and"," use"," no"," tools","."]},{"type":"chunk","time":1791434053910,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791434053910,"index":1,"dt":[],"texts":["ONE"]},{"type":"chunk","time":1791434053910,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ONE\" and use no tools."}}},{"type":"chunk","time":1791434053910,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"ONE"}}},{"type":"chunk","time":1791434053910,"chunk":{"type":"usage","usage":{"inputTokens":2864,"outputTokens":20,"cacheReadTokens":0,"reasoningTokens":18}}},{"type":"chunk","time":1791434053910,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ONE\" and use no tools."},{"type":"text","text":"ONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2864,"outputTokens":20,"cacheReadTokens":0,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791444991204,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791444991204,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","ONE","\""," and"," use"," no"," tools","."]},{"type":"chunk","time":1791444991204,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791444991204,"index":1,"dt":[],"texts":["ONE"]},{"type":"chunk","time":1791444991204,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ONE\" and use no tools."}}},{"type":"chunk","time":1791444991204,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"ONE"}}},{"type":"chunk","time":1791444991204,"chunk":{"type":"usage","usage":{"inputTokens":2864,"outputTokens":20,"cacheReadTokens":0,"reasoningTokens":18}}},{"type":"chunk","time":1791444991204,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"inserted":[{"content":[{"type":"text","text":"Reply with exactly the word: TWO. No tools."}],"source":{"kind":"user"},"role":"user","id":"{{message:5}}"}]}}
@@ -20,6 +20,6 @@
{"type":"agent/inbox/spliced","data":{"target":"next-turn","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","data":{"turn":2,"step":1}}
{"type":"user/message","data":{"content":[{"type":"text","text":"Reply with exactly the word: TWO. No tools."}],"source":{"kind":"user"},"role":"user","id":"{{message:5}}"},"surfaceOp":"append"}
-{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"TWO\" and no tools."},{"type":"text","text":"TWO"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":64,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791434053927,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791434053927,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","T","WO","\""," and"," no"," tools","."]},{"type":"chunk","time":1791434053927,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791434053927,"index":1,"dt":[0],"texts":["T","WO"]},{"type":"chunk","time":1791434053927,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"TWO\" and no tools."}}},{"type":"chunk","time":1791434053927,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"TWO"}}},{"type":"chunk","time":1791434053927,"chunk":{"type":"usage","usage":{"inputTokens":64,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18}}},{"type":"chunk","time":1791434053927,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"TWO\" and no tools."},{"type":"text","text":"TWO"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":64,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791444991222,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791444991222,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","T","WO","\""," and"," no"," tools","."]},{"type":"chunk","time":1791444991222,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791444991222,"index":1,"dt":[0],"texts":["T","WO"]},{"type":"chunk","time":1791444991222,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"TWO\" and no tools."}}},{"type":"chunk","time":1791444991222,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"TWO"}}},{"type":"chunk","time":1791444991222,"chunk":{"type":"usage","usage":{"inputTokens":64,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18}}},{"type":"chunk","time":1791444991222,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
{"type":"step/end","data":{"turn":2,"step":1}}
{"type":"turn/end","data":{"turn":2,"reason":{"kind":"completed"}}}
diff --git a/snapshots/sdk/ptc-turn/notifications.expected.jsonl b/snapshots/sdk/ptc-turn/notifications.expected.jsonl
index 4616733e6a..e905194b25 100644
--- a/snapshots/sdk/ptc-turn/notifications.expected.jsonl
+++ b/snapshots/sdk/ptc-turn/notifications.expected.jsonl
@@ -12,12 +12,12 @@
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"assistant/message","seq":13,"time":0,"data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to write a single run_code program that:\n1. Calls bash tool twice: `echo CODE_ONE` and `echo CODE_TWO`\n2. console.log exactly `captured output`\n3. Return the two outputs joined with a plus sign\n\nLet me write this."},{"type":"tool-call","id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":6152,"outputTokens":214,"cacheReadTokens":0,"reasoningTokens":60},"stream":[{"type":"chunk","time":0,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":0,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," write"," a"," single"," run","_code"," program"," that",":\n","1","."," Calls"," bash"," tool"," twice",":"," `","echo"," CODE","_","ONE","`"," and"," `","echo"," CODE","_T","WO","`\n","2","."," console",".log"," exactly"," `","capt","ured"," output","`\n","3","."," Return"," the"," two"," outputs"," joined"," with"," a"," plus"," sign","\n\n","Let"," me"," write"," this","."]},{"type":"chunk","time":0,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":0,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","args":["","{","\"","code","\"",": ","\"","\\n","const"," out","1"," ="," await"," tools",".b","ash","({","command",":"," \\\"","echo"," CODE","_","ONE","\\\","," description",":"," \\\"","Print"," CODE","_","ONE","\\\"","});\\n","const"," out","2"," ="," await"," tools",".b","ash","({","command",":"," \\\"","echo"," CODE","_T","WO","\\\","," description",":"," \\\"","Print"," CODE","_T","WO","\\\"","});\\n","console",".log","(\\\"","capt","ured"," output","\\\");\\n","const"," text","1"," ="," out","1",".stdout",".text",".trim","();\\n","const"," text","2"," ="," out","2",".stdout",".text",".trim","();\\n","return"," text","1"," +"," \\\"+","\\\""," +"," text","2",";\\n","\"",", ","\"","description","\"",": ","\"","Run"," two"," echo"," commands"," and"," join"," outputs","\"","}"]},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to write a single run_code program that:\n1. Calls bash tool twice: `echo CODE_ONE` and `echo CODE_TWO`\n2. console.log exactly `captured output`\n3. Return the two outputs joined with a plus sign\n\nLet me write this."}}},{"type":"chunk","time":0,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}}},{"type":"chunk","time":0,"chunk":{"type":"usage","usage":{"inputTokens":6152,"outputTokens":214,"cacheReadTokens":0,"reasoningTokens":60}}},{"type":"chunk","time":0,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/call","seq":14,"time":0,"data":{"turn":1,"step":1,"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/ptc-dispatch-start","seq":15,"time":0,"data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Print CODE_ONE"}}}}}
-{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/registered","seq":16,"time":0,"data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}}}
-{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/settled","seq":17,"time":0,"data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}}}
+{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/registered","seq":16,"time":0,"data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}}}
+{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/settled","seq":17,"time":0,"data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/ptc-dispatch","seq":18,"time":0,"data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Print CODE_ONE"},"isError":false,"content":[{"type":"text","text":"CODE_ONE\n"}],"meta":{"cwd":"{{cwd}}"}}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/ptc-dispatch-start","seq":19,"time":0,"data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Print CODE_TWO"}}}}}
-{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/registered","seq":20,"time":0,"data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}}}
-{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/settled","seq":21,"time":0,"data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}}}
+{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/registered","seq":20,"time":0,"data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}}}
+{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"job/settled","seq":21,"time":0,"data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":0,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":0,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/ptc-dispatch","seq":22,"time":0,"data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Print CODE_TWO"},"isError":false,"content":[{"type":"text","text":"CODE_TWO\n"}],"meta":{"cwd":"{{cwd}}"}}}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"tool/result","seq":23,"time":0,"data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"toolCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","content":[{"type":"text","text":"captured output\nCODE_ONE+CODE_TWO"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}}}
{"method":"session.event","params":{"sessionId":"{{session:1}}","event":{"type":"step/end","seq":24,"time":0,"data":{"turn":1,"step":1}}}}
diff --git a/snapshots/sdk/ptc-turn/session.v4.jsonl b/snapshots/sdk/ptc-turn/session.v4.jsonl
index d388ec61ab..1d377a58a9 100644
--- a/snapshots/sdk/ptc-turn/session.v4.jsonl
+++ b/snapshots/sdk/ptc-turn/session.v4.jsonl
@@ -15,12 +15,12 @@
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to write a single run_code program that:\n1. Calls bash tool twice: `echo CODE_ONE` and `echo CODE_TWO`\n2. console.log exactly `captured output`\n3. Return the two outputs joined with a plus sign\n\nLet me write this."},{"type":"tool-call","id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":6152,"outputTokens":214,"cacheReadTokens":0,"reasoningTokens":60},"stream":[{"type":"chunk","time":1791427916811,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427916811,"index":0,"dt":[0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," write"," a"," single"," run","_code"," program"," that",":\n","1","."," Calls"," bash"," tool"," twice",":"," `","echo"," CODE","_","ONE","`"," and"," `","echo"," CODE","_T","WO","`\n","2","."," console",".log"," exactly"," `","capt","ured"," output","`\n","3","."," Return"," the"," two"," outputs"," joined"," with"," a"," plus"," sign","\n\n","Let"," me"," write"," this","."]},{"type":"chunk","time":1791427916812,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427916812,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","args":["","{","\"","code","\"",": ","\"","\\n","const"," out","1"," ="," await"," tools",".b","ash","({","command",":"," \\\"","echo"," CODE","_","ONE","\\\","," description",":"," \\\"","Print"," CODE","_","ONE","\\\"","});\\n","const"," out","2"," ="," await"," tools",".b","ash","({","command",":"," \\\"","echo"," CODE","_T","WO","\\\","," description",":"," \\\"","Print"," CODE","_T","WO","\\\"","});\\n","console",".log","(\\\"","capt","ured"," output","\\\");\\n","const"," text","1"," ="," out","1",".stdout",".text",".trim","();\\n","const"," text","2"," ="," out","2",".stdout",".text",".trim","();\\n","return"," text","1"," +"," \\\"+","\\\""," +"," text","2",";\\n","\"",", ","\"","description","\"",": ","\"","Run"," two"," echo"," commands"," and"," join"," outputs","\"","}"]},{"type":"chunk","time":1791427916812,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to write a single run_code program that:\n1. Calls bash tool twice: `echo CODE_ONE` and `echo CODE_TWO`\n2. console.log exactly `captured output`\n3. Return the two outputs joined with a plus sign\n\nLet me write this."}}},{"type":"chunk","time":1791427916812,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}}},{"type":"chunk","time":1791427916812,"chunk":{"type":"usage","usage":{"inputTokens":6152,"outputTokens":214,"cacheReadTokens":0,"reasoningTokens":60}}},{"type":"chunk","time":1791427916812,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Print CODE_ONE"}}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427916854,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427916854,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427916857,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427916854,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427916854,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427916857,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Print CODE_ONE"},"isError":false,"content":[{"type":"text","text":"CODE_ONE\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Print CODE_TWO"}}}
-{"type":"job/registered","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427916860,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
-{"type":"job/settled","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427916860,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427916862,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427916860,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
+{"type":"job/settled","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427916860,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427916862,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Print CODE_TWO"},"isError":false,"content":[{"type":"text","text":"CODE_TWO\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"toolCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","content":[{"type":"text","text":"captured output\nCODE_ONE+CODE_TWO"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
diff --git a/snapshots/session/background-confinement-failure/replay.override.json b/snapshots/session/background-confinement-failure/replay.override.json
index 1e9cabe8a8..74a53d0a36 100644
--- a/snapshots/session/background-confinement-failure/replay.override.json
+++ b/snapshots/session/background-confinement-failure/replay.override.json
@@ -52,7 +52,7 @@
"index": 0,
"id": "async-confinement-result",
"name": "job_output",
- "argumentsDelta": "{\"job_id\":\"bash-1\",\"wait\":true}"
+ "argumentsDelta": "{\"job_id\":\"{{fromRequest:(bash-[0-9a-f-]{36})}}\",\"wait\":true}"
},
{
"type": "block-end",
@@ -61,7 +61,7 @@
"type": "tool-call",
"id": "async-confinement-result",
"name": "job_output",
- "arguments": "{\"job_id\":\"bash-1\",\"wait\":true}"
+ "arguments": "{\"job_id\":\"{{fromRequest:(bash-[0-9a-f-]{36})}}\",\"wait\":true}"
}
},
{
diff --git a/snapshots/session/background-confinement-failure/session.v4.jsonl b/snapshots/session/background-confinement-failure/session.v4.jsonl
index 725e65c4c3..88dd6b0f1c 100644
--- a/snapshots/session/background-confinement-failure/session.v4.jsonl
+++ b/snapshots/session/background-confinement-failure/session.v4.jsonl
@@ -14,16 +14,16 @@
{"type":"session/title","data":{"title":"Start printf SHOULD_NOT_RUN as one","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"async-confinement-start","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"description\":\"Start a background command under confinement\",\"run_in_background\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791434082496,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791434082496,"index":0,"dt":[],"id":"async-confinement-start","name":"bash","args":["{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"description\":\"Start a background command under confinement\",\"run_in_background\":true}"]},{"type":"chunk","time":1791434082496,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"async-confinement-start","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"description\":\"Start a background command under confinement\",\"run_in_background\":true}"}}},{"type":"chunk","time":1791434082496,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791434082496,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"async-confinement-start","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"description\":\"Start a background command under confinement\",\"run_in_background\":true}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791434082502,"toolCall":{"callId":"async-confinement-start","rootCallId":"async-confinement-start"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791434082502,"toolCall":{"callId":"async-confinement-start","rootCallId":"async-confinement-start"},"finishedAt":1791434082504,"status":"failed","cause":"producer","producerReported":true,"detail":"sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused"}}
-{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"background job bash-1 (bash: printf SHOULD_NOT_RUN > unexpected-command.txt) finished [status: failed, sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused]. Read its output with job_output."}],"source":{"kind":"tool-jobs","form":"notice","summary":"bash printf SHOULD_NOT_RUN > unexpected-command.txt [status: failed, sandbox mode \"read-only\" is requested but no sandb…"},"role":"user","id":"{{message:5}}"}]}}
-{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"async-confinement-start"},"toolCallId":"async-confinement-start","content":[{"type":"text","text":"started background job bash-1"}],"isError":false,"id":"{{message:6}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791434082502,"toolCall":{"callId":"async-confinement-start","rootCallId":"async-confinement-start"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791434082502,"toolCall":{"callId":"async-confinement-start","rootCallId":"async-confinement-start"},"finishedAt":1791434082504,"status":"failed","cause":"producer","producerReported":true,"detail":"sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused"}}
+{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"background job {{id:1}} (bash: printf SHOULD_NOT_RUN > unexpected-command.txt) finished [status: failed, sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused]. Read its output with job_output."}],"source":{"kind":"tool-jobs","form":"notice","summary":"bash printf SHOULD_NOT_RUN > unexpected-command.txt [status: failed, sandbox mode \"read-only\" is requested but no sandb…"},"role":"user","id":"{{message:5}}"}]}}
+{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"async-confinement-start"},"toolCallId":"async-confinement-start","content":[{"type":"text","text":"started background job {{id:1}}"}],"isError":false,"id":"{{message:6}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","data":{"turn":1,"step":2}}
-{"type":"user/message","data":{"content":[{"type":"text","text":"background job bash-1 (bash: printf SHOULD_NOT_RUN > unexpected-command.txt) finished [status: failed, sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused]. Read its output with job_output."}],"source":{"kind":"tool-jobs","form":"notice","summary":"bash printf SHOULD_NOT_RUN > unexpected-command.txt [status: failed, sandbox mode \"read-only\" is requested but no sandb…"},"role":"user","id":"{{message:5}}"},"surfaceOp":"append"}
-{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"async-confinement-result","name":"job_output","arguments":"{\"job_id\":\"bash-1\",\"wait\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791434082516,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791434082516,"index":0,"dt":[],"id":"async-confinement-result","name":"job_output","args":["{\"job_id\":\"bash-1\",\"wait\":true}"]},{"type":"chunk","time":1791434082516,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"async-confinement-result","name":"job_output","arguments":"{\"job_id\":\"bash-1\",\"wait\":true}"}}},{"type":"chunk","time":1791434082516,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791434082516,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
-{"type":"tool/call","data":{"turn":1,"step":2,"callId":"async-confinement-result","name":"job_output","arguments":"{\"job_id\":\"bash-1\",\"wait\":true}"}}
+{"type":"user/message","data":{"content":[{"type":"text","text":"background job {{id:1}} (bash: printf SHOULD_NOT_RUN > unexpected-command.txt) finished [status: failed, sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused]. Read its output with job_output."}],"source":{"kind":"tool-jobs","form":"notice","summary":"bash printf SHOULD_NOT_RUN > unexpected-command.txt [status: failed, sandbox mode \"read-only\" is requested but no sandb…"},"role":"user","id":"{{message:5}}"},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"async-confinement-result","name":"job_output","arguments":"{\"job_id\":\"{{id:1}}\",\"wait\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791434082516,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791434082516,"index":0,"dt":[],"id":"async-confinement-result","name":"job_output","args":["{\"job_id\":\"{{id:1}}\",\"wait\":true}"]},{"type":"chunk","time":1791434082516,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"async-confinement-result","name":"job_output","arguments":"{\"job_id\":\"{{id:1}}\",\"wait\":true}"}}},{"type":"chunk","time":1791434082516,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791434082516,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
+{"type":"tool/call","data":{"turn":1,"step":2,"callId":"async-confinement-result","name":"job_output","arguments":"{\"job_id\":\"{{id:1}}\",\"wait\":true}"}}
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"async-confinement-result"},"toolCallId":"async-confinement-result","content":[{"type":"text","text":"(no new output)\n[status: failed, sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: fixture asynchronous confinement refused]"}],"isError":false,"id":"{{message:8}}"}},"sourceEventSeqs":[24],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":2}}
{"type":"step/start","data":{"turn":1,"step":3}}
diff --git a/snapshots/session/background-job-admission/replay.override.json b/snapshots/session/background-job-admission/replay.override.json
index 91b15e86ba..c454fbdaa1 100644
--- a/snapshots/session/background-job-admission/replay.override.json
+++ b/snapshots/session/background-job-admission/replay.override.json
@@ -23,8 +23,8 @@
"kind": "chunks",
"chunks": [
{ "type": "block-start", "index": 0, "blockType": "tool-call" },
- { "type": "tool-call-delta", "index": 0, "id": "bounded-task-kill", "name": "job_kill", "argumentsDelta": "{\"job_id\":\"bash-1\",\"reason\":\"free the bounded task slot\"}" },
- { "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "bounded-task-kill", "name": "job_kill", "arguments": "{\"job_id\":\"bash-1\",\"reason\":\"free the bounded task slot\"}" } },
+ { "type": "tool-call-delta", "index": 0, "id": "bounded-task-kill", "name": "job_kill", "argumentsDelta": "{\"job_id\":\"{{fromRequest:(bash-[0-9a-f-]{36})}}\",\"reason\":\"free the bounded task slot\"}" },
+ { "type": "block-end", "index": 0, "block": { "type": "tool-call", "id": "bounded-task-kill", "name": "job_kill", "arguments": "{\"job_id\":\"{{fromRequest:(bash-[0-9a-f-]{36})}}\",\"reason\":\"free the bounded task slot\"}" } },
{ "type": "usage", "usage": { "inputTokens": 10, "outputTokens": 5 } },
{ "type": "finish", "reason": { "kind": "tool-calls" } }
]
diff --git a/snapshots/session/background-job-admission/session.v4.jsonl b/snapshots/session/background-job-admission/session.v4.jsonl
index d7a68359ae..e01bb3bc0e 100644
--- a/snapshots/session/background-job-admission/session.v4.jsonl
+++ b/snapshots/session/background-job-admission/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Start one background Bash task","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bounded-task-first","name":"bash","arguments":"{\"command\":\"while :; do sleep 60; done\",\"description\":\"Hold the only background job slot\",\"run_in_background\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427794134,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427794134,"index":0,"dt":[],"id":"bounded-task-first","name":"bash","args":["{\"command\":\"while :; do sleep 60; done\",\"description\":\"Hold the only background job slot\",\"run_in_background\":true}"]},{"type":"chunk","time":1791427794135,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bounded-task-first","name":"bash","arguments":"{\"command\":\"while :; do sleep 60; done\",\"description\":\"Hold the only background job slot\",\"run_in_background\":true}"}}},{"type":"chunk","time":1791427794135,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427794135,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"bounded-task-first","name":"bash","arguments":"{\"command\":\"while :; do sleep 60; done\",\"description\":\"Hold the only background job slot\",\"run_in_background\":true}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"while :; do sleep 60; done","startedAt":1791427794143,"toolCall":{"callId":"bounded-task-first","rootCallId":"bounded-task-first"}}}
-{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"bounded-task-first"},"toolCallId":"bounded-task-first","content":[{"type":"text","text":"started background job bash-1"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"while :; do sleep 60; done","startedAt":1791427794143,"toolCall":{"callId":"bounded-task-first","rootCallId":"bounded-task-first"}}}
+{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"bounded-task-first"},"toolCallId":"bounded-task-first","content":[{"type":"text","text":"started background job {{id:1}}"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bounded-task-second","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > second-task-ran.txt; while :; do sleep 60; done\",\"description\":\"Attempt a second background job\",\"run_in_background\":true}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427794157,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427794157,"index":0,"dt":[],"id":"bounded-task-second","name":"bash","args":["{\"command\":\"printf SHOULD_NOT_RUN > second-task-ran.txt; while :; do sleep 60; done\",\"description\":\"Attempt a second background job\",\"run_in_background\":true}"]},{"type":"chunk","time":1791427794157,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bounded-task-second","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > second-task-ran.txt; while :; do sleep 60; done\",\"description\":\"Attempt a second background job\",\"run_in_background\":true}"}}},{"type":"chunk","time":1791427794157,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427794157,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
@@ -23,16 +23,16 @@
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"bounded-task-second"},"toolCallId":"bounded-task-second","content":[{"type":"text","text":"Error: background job limit reached for this owner (limit: 1); use job_kill to stop an unneeded job, wait for it to finish, then retry"}],"isError":true,"id":"{{message:7}}"}},"sourceEventSeqs":[20],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":2}}
{"type":"step/start","data":{"turn":1,"step":3}}
-{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bounded-task-kill","name":"job_kill","arguments":"{\"job_id\":\"bash-1\",\"reason\":\"free the bounded task slot\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:8}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427794174,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427794174,"index":0,"dt":[],"id":"bounded-task-kill","name":"job_kill","args":["{\"job_id\":\"bash-1\",\"reason\":\"free the bounded task slot\"}"]},{"type":"chunk","time":1791427794174,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bounded-task-kill","name":"job_kill","arguments":"{\"job_id\":\"bash-1\",\"reason\":\"free the bounded task slot\"}"}}},{"type":"chunk","time":1791427794174,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427794174,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
-{"type":"tool/call","data":{"turn":1,"step":3,"callId":"bounded-task-kill","name":"job_kill","arguments":"{\"job_id\":\"bash-1\",\"reason\":\"free the bounded task slot\"}"}}
-{"type":"tool/result","data":{"turn":1,"step":3,"message":{"role":"tool","source":{"kind":"tool","callId":"bounded-task-kill"},"toolCallId":"bounded-task-kill","content":[{"type":"text","text":"requested cancellation of job bash-1"}],"isError":false,"id":"{{message:9}}"}},"sourceEventSeqs":[25],"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bounded-task-kill","name":"job_kill","arguments":"{\"job_id\":\"{{id:1}}\",\"reason\":\"free the bounded task slot\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:8}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427794174,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427794174,"index":0,"dt":[],"id":"bounded-task-kill","name":"job_kill","args":["{\"job_id\":\"{{id:1}}\",\"reason\":\"free the bounded task slot\"}"]},{"type":"chunk","time":1791427794174,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bounded-task-kill","name":"job_kill","arguments":"{\"job_id\":\"{{id:1}}\",\"reason\":\"free the bounded task slot\"}"}}},{"type":"chunk","time":1791427794174,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427794174,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
+{"type":"tool/call","data":{"turn":1,"step":3,"callId":"bounded-task-kill","name":"job_kill","arguments":"{\"job_id\":\"{{id:1}}\",\"reason\":\"free the bounded task slot\"}"}}
+{"type":"tool/result","data":{"turn":1,"step":3,"message":{"role":"tool","source":{"kind":"tool","callId":"bounded-task-kill"},"toolCallId":"bounded-task-kill","content":[{"type":"text","text":"requested cancellation of job {{id:1}}"}],"isError":false,"id":"{{message:9}}"}},"sourceEventSeqs":[25],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":3}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"while :; do sleep 60; done","startedAt":1791427794143,"toolCall":{"callId":"bounded-task-first","rootCallId":"bounded-task-first"},"finishedAt":1791427794181,"status":"killed","cause":"kill","producerReported":true,"detail":"signal: SIGTERM; free the bounded task slot"}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"while :; do sleep 60; done","startedAt":1791427794143,"toolCall":{"callId":"bounded-task-first","rootCallId":"bounded-task-first"},"finishedAt":1791427794181,"status":"killed","cause":"kill","producerReported":true,"detail":"signal: SIGTERM; free the bounded task slot"}}
{"type":"step/start","data":{"turn":1,"step":4}}
{"type":"assistant/message","data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bounded-task-side-effect-check","name":"bash","arguments":"{\"command\":\"test ! -e second-task-ran.txt\",\"description\":\"Verify the rejected producer did not run\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:10}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427794192,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427794192,"index":0,"dt":[],"id":"bounded-task-side-effect-check","name":"bash","args":["{\"command\":\"test ! -e second-task-ran.txt\",\"description\":\"Verify the rejected producer did not run\"}"]},{"type":"chunk","time":1791427794192,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bounded-task-side-effect-check","name":"bash","arguments":"{\"command\":\"test ! -e second-task-ran.txt\",\"description\":\"Verify the rejected producer did not run\"}"}}},{"type":"chunk","time":1791427794192,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427794192,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":4,"callId":"bounded-task-side-effect-check","name":"bash","arguments":"{\"command\":\"test ! -e second-task-ran.txt\",\"description\":\"Verify the rejected producer did not run\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"test ! -e second-task-ran.txt","startedAt":1791427794197,"toolCall":{"callId":"bounded-task-side-effect-check","rootCallId":"bounded-task-side-effect-check"}}}
-{"type":"job/settled","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"test ! -e second-task-ran.txt","startedAt":1791427794197,"toolCall":{"callId":"bounded-task-side-effect-check","rootCallId":"bounded-task-side-effect-check"},"finishedAt":1791427794199,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"test ! -e second-task-ran.txt","startedAt":1791427794197,"toolCall":{"callId":"bounded-task-side-effect-check","rootCallId":"bounded-task-side-effect-check"}}}
+{"type":"job/settled","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"test ! -e second-task-ran.txt","startedAt":1791427794197,"toolCall":{"callId":"bounded-task-side-effect-check","rootCallId":"bounded-task-side-effect-check"},"finishedAt":1791427794199,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":4,"message":{"role":"tool","source":{"kind":"tool","callId":"bounded-task-side-effect-check"},"toolCallId":"bounded-task-side-effect-check","content":[{"type":"text","text":"(no output)"}],"isError":false,"id":"{{message:11}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[31],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":4}}
{"type":"step/start","data":{"turn":1,"step":5}}
diff --git a/snapshots/session/bash-same-mode-empty-justification/session.v4.jsonl b/snapshots/session/bash-same-mode-empty-justification/session.v4.jsonl
index c2e965b24d..988e91a3da 100644
--- a/snapshots/session/bash-same-mode-empty-justification/session.v4.jsonl
+++ b/snapshots/session/bash-same-mode-empty-justification/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."},{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791427795076,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427795076,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," simple"," bash"," command"," and"," then"," reply"," with"," \"","D","ONE","\"."]},{"type":"chunk","time":1791427795076,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427795076,"index":1,"dt":[],"id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","args":["{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"\"}"]},{"type":"chunk","time":1791427795076,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."}}},{"type":"chunk","time":1791427795076,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"\"}"}}},{"type":"chunk","time":1791427795076,"chunk":{"type":"usage","usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"reasoningTokens":18}}},{"type":"chunk","time":1791427795076,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"sandbox_permissions\": \"danger-full-access\", \"justification\": \"\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427795085,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427795085,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427795087,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427795085,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427795085,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427795087,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"toolCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","content":[{"type":"text","text":"TERMINAL_OK\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/session/bash-spill/session.v4.jsonl b/snapshots/session/bash-spill/session.v4.jsonl
index 99c090f8b5..0d682ea29a 100644
--- a/snapshots/session/bash-spill/session.v4.jsonl
+++ b/snapshots/session/bash-spill/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_spill","name":"bash","arguments":"{\"command\":\"node -e \\\"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\\\"\",\"description\":\"Print large deterministic output\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427795962,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427795962,"index":0,"dt":[],"id":"call_spill","name":"bash","args":["{\"command\":\"node -e \\\"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\\\"\",\"description\":\"Print large deterministic output\"}"]},{"type":"chunk","time":1791427795962,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_spill","name":"bash","arguments":"{\"command\":\"node -e \\\"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\\\"\",\"description\":\"Print large deterministic output\"}"}}},{"type":"chunk","time":1791427795962,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427795962,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_spill","name":"bash","arguments":"{\"command\":\"node -e \\\"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\\\"\",\"description\":\"Print large deterministic output\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"node -e \"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\"","startedAt":1791427795971,"toolCall":{"callId":"call_spill","rootCallId":"call_spill"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"node -e \"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\"","startedAt":1791427795971,"toolCall":{"callId":"call_spill","rootCallId":"call_spill"},"finishedAt":1791427796001,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"node -e \"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\"","startedAt":1791427795971,"toolCall":{"callId":"call_spill","rootCallId":"call_spill"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"node -e \"process.stdout.write('SPILL_START-' + 'x'.repeat(2000) + '-SPILL_END')\"","startedAt":1791427795971,"toolCall":{"callId":"call_spill","rootCallId":"call_spill"},"finishedAt":1791427796001,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_spill"},"toolCallId":"call_spill","content":[{"type":"text","text":"SPILL_START-xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\n\n[...]\n\nxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx-SPILL_END\n\n(Omitted 1494 bytes. Full formatted result stored at: /tmp/dsh-acp-snap-ee77dff02/session-5e53dc8acfe4/2ce9d7a31a38-bash.txt. Use read with offset/limit, or grep this path to search within it.)"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/session/bash-startup-timeout/session.v4.jsonl b/snapshots/session/bash-startup-timeout/session.v4.jsonl
index cf301c05ca..8ab5d00102 100644
--- a/snapshots/session/bash-startup-timeout/session.v4.jsonl
+++ b/snapshots/session/bash-startup-timeout/session.v4.jsonl
@@ -14,11 +14,11 @@
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"bash-startup-timeout","name":"bash","arguments":"{\"command\":\"sleep 30\",\"timeoutMs\":1,\"description\":\"Cancel a command during startup\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1791433936298,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1791433936298,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"bash-startup-timeout","name":"bash","arguments":"{\"command\":\"sleep 30\",\"timeoutMs\":1,\"description\":\"Cancel a command during startup\"}"}}},{"type":"chunk","time":1791433936298,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1791433936298,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"bash-startup-timeout","name":"bash","arguments":"{\"command\":\"sleep 30\",\"timeoutMs\":1,\"description\":\"Cancel a command during startup\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"sleep 30","startedAt":1791433936310,"toolCall":{"callId":"bash-startup-timeout","rootCallId":"bash-startup-timeout"}}}
-{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"bash-startup-timeout"},"toolCallId":"bash-startup-timeout","content":[{"type":"text","text":"[still running after 1ms; moved to background job bash-1]\nThe command keeps running in the background. You will be notified when it finishes; read newer output with job_output, stop it with job_kill."}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"sleep 30","startedAt":1791433936310,"toolCall":{"callId":"bash-startup-timeout","rootCallId":"bash-startup-timeout"}}}
+{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"bash-startup-timeout"},"toolCallId":"bash-startup-timeout","content":[{"type":"text","text":"[still running after 1ms; moved to background job {{id:1}}]\nThe command keeps running in the background. You will be notified when it finishes; read newer output with job_output, stop it with job_kill."}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"text","text":"DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1791433936330,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"chunk","time":1791433936330,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"DONE"}}},{"type":"chunk","time":1791433936330,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1791433936330,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":2}}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"sleep 30","startedAt":1791433936310,"toolCall":{"callId":"bash-startup-timeout","rootCallId":"bash-startup-timeout"},"finishedAt":1791433936339,"status":"killed","cause":"teardown","producerReported":true,"detail":"signal: SIGTERM"}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"sleep 30","startedAt":1791433936310,"toolCall":{"callId":"bash-startup-timeout","rootCallId":"bash-startup-timeout"},"finishedAt":1791433936339,"status":"killed","cause":"teardown","producerReported":true,"detail":"signal: SIGTERM"}}
diff --git a/snapshots/session/bash-tool-turn/session.v4.jsonl b/snapshots/session/bash-tool-turn/session.v4.jsonl
index bc1949cfba..d7e23def59 100644
--- a/snapshots/session/bash-tool-turn/session.v4.jsonl
+++ b/snapshots/session/bash-tool-turn/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."},{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"justification\": \" \\t\\n \"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791427797855,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427797856,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," simple"," bash"," command"," and"," then"," reply"," with"," \"","D","ONE","\"."]},{"type":"chunk","time":1791427797856,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427797856,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," TER","MIN","AL","_OK","\"",", ","\"","description","\"",": ","\"","E","cho"," TER","MIN","AL","_OK"," to"," verify"," terminal"," access","\"",", \"justification\": \" \\t\\n \"}"]},{"type":"chunk","time":1791427797856,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."}}},{"type":"chunk","time":1791427797856,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"justification\": \" \\t\\n \"}"}}},{"type":"chunk","time":1791427797856,"chunk":{"type":"usage","usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"reasoningTokens":18}}},{"type":"chunk","time":1791427797856,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\", \"justification\": \" \\t\\n \"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427797866,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427797866,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427797868,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427797866,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427797866,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427797868,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"toolCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","content":[{"type":"text","text":"TERMINAL_OK\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/session/both-mode-turn/session.v4.jsonl b/snapshots/session/both-mode-turn/session.v4.jsonl
index 3b44c73035..de63ca3da9 100644
--- a/snapshots/session/both-mode-turn/session.v4.jsonl
+++ b/snapshots/session/both-mode-turn/session.v4.jsonl
@@ -15,8 +15,8 @@
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to call the run_code tool with a TypeScript program that runs `echo BOTH_OK` via `tools.bash` and returns its output."},{"type":"tool-call","id":"call_00_Era4M5eh79bvNOIey5q90401","name":"run_code","arguments":"{\"code\": \"const result = await tools.bash({ command: \\\"echo BOTH_OK\\\", description: \\\"Print BOTH_OK\\\" });\\nreturn result.stdout.text;\", \"description\": \"Run echo BOTH_OK via tools.bash\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10400,"outputTokens":130,"cacheReadTokens":0,"reasoningTokens":34},"stream":[{"type":"chunk","time":1791427798733,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427798733,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," call"," the"," run","_code"," tool"," with"," a"," Type","Script"," program"," that"," runs"," `","echo"," B","OTH","_OK","`"," via"," `","tools",".b","ash","`"," and"," returns"," its"," output","."]},{"type":"chunk","time":1791427798733,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427798733,"index":1,"dt":[0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_Era4M5eh79bvNOIey5q90401","name":"run_code","args":["","{","\"","code","\"",": ","\"","const"," result"," ="," await"," tools",".b","ash","({"," command",":"," \\\"","echo"," B","OTH","_OK","\\\","," description",":"," \\\"","Print"," B","OTH","_OK","\\\""," });\\n","return"," result",".stdout",".text",";","\"",", ","\"","description","\"",": ","\"","Run"," echo"," B","OTH","_OK"," via"," tools",".b","ash","\"","}"]},{"type":"chunk","time":1791427798734,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to call the run_code tool with a TypeScript program that runs `echo BOTH_OK` via `tools.bash` and returns its output."}}},{"type":"chunk","time":1791427798734,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_Era4M5eh79bvNOIey5q90401","name":"run_code","arguments":"{\"code\": \"const result = await tools.bash({ command: \\\"echo BOTH_OK\\\", description: \\\"Print BOTH_OK\\\" });\\nreturn result.stdout.text;\", \"description\": \"Run echo BOTH_OK via tools.bash\"}"}}},{"type":"chunk","time":1791427798734,"chunk":{"type":"usage","usage":{"inputTokens":10400,"outputTokens":130,"cacheReadTokens":0,"reasoningTokens":34}}},{"type":"chunk","time":1791427798734,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_Era4M5eh79bvNOIey5q90401","name":"run_code","arguments":"{\"code\": \"const result = await tools.bash({ command: \\\"echo BOTH_OK\\\", description: \\\"Print BOTH_OK\\\" });\\nreturn result.stdout.text;\", \"description\": \"Run echo BOTH_OK via tools.bash\"}"}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_Era4M5eh79bvNOIey5q90401","parentCallId":"call_00_Era4M5eh79bvNOIey5q90401","subCallId":"call_00_Era4M5eh79bvNOIey5q90401:ptc:1","name":"bash","arguments":{"command":"echo BOTH_OK","description":"Print BOTH_OK"}}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo BOTH_OK","startedAt":1791427798824,"toolCall":{"callId":"call_00_Era4M5eh79bvNOIey5q90401:ptc:1","rootCallId":"call_00_Era4M5eh79bvNOIey5q90401"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo BOTH_OK","startedAt":1791427798824,"toolCall":{"callId":"call_00_Era4M5eh79bvNOIey5q90401:ptc:1","rootCallId":"call_00_Era4M5eh79bvNOIey5q90401"},"finishedAt":1791427798827,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo BOTH_OK","startedAt":1791427798824,"toolCall":{"callId":"call_00_Era4M5eh79bvNOIey5q90401:ptc:1","rootCallId":"call_00_Era4M5eh79bvNOIey5q90401"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo BOTH_OK","startedAt":1791427798824,"toolCall":{"callId":"call_00_Era4M5eh79bvNOIey5q90401:ptc:1","rootCallId":"call_00_Era4M5eh79bvNOIey5q90401"},"finishedAt":1791427798827,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_Era4M5eh79bvNOIey5q90401","parentCallId":"call_00_Era4M5eh79bvNOIey5q90401","subCallId":"call_00_Era4M5eh79bvNOIey5q90401:ptc:1","name":"bash","arguments":{"command":"echo BOTH_OK","description":"Print BOTH_OK"},"isError":false,"content":[{"type":"text","text":"BOTH_OK\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_Era4M5eh79bvNOIey5q90401"},"toolCallId":"call_00_Era4M5eh79bvNOIey5q90401","content":[{"type":"text","text":"BOTH_OK\n"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
diff --git a/snapshots/session/claude-code-mods/session.v4.jsonl b/snapshots/session/claude-code-mods/session.v4.jsonl
index 5877a4facd..37a5282e6b 100644
--- a/snapshots/session/claude-code-mods/session.v4.jsonl
+++ b/snapshots/session/claude-code-mods/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user asks to run exactly two commands. The second one is a destructive git push --force. But it's a demo checkout. The instruction says to run exactly those commands, no other command. Let me consider whether it's acceptable. Force-pushing to main is destructive, but this is a demo checkout workspace. Still, I should be cautious. However the user explicitly requested it. Let me just run them and report.\n\nLet me run ls -1 . first."},{"type":"tool-call","id":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","name":"bash","arguments":"{\"description\": \"List files in current directory\", \"command\": \"ls -1 .\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"deepseek-v4-flash"},"blocks":[{"type":"reasoning","signature":"bc245c9d-d596-4709-ba98-eb7e663a01d8"},{"type":"tool-call"}]}},"id":"{{message:4}}"},"usage":{"inputTokens":5586,"outputTokens":155,"cacheReadTokens":0,"cacheWriteTokens":0,"totalTokens":5741},"stream":[{"type":"chunk","time":1791427804365,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427804365,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," asks"," to"," run"," exactly"," two"," commands","."," The"," second"," one"," is"," a"," destructive"," git"," push"," --","force","."," But"," it","'s"," a"," demo"," checkout","."," The"," instruction"," says"," to"," run"," exactly"," those"," commands",","," no"," other"," command","."," Let"," me"," consider"," whether"," it","'s"," acceptable","."," Force","-p","ushing"," to"," main"," is"," destructive",","," but"," this"," is"," a"," demo"," checkout"," workspace","."," Still",","," I"," should"," be"," cautious","."," However"," the"," user"," explicitly"," requested"," it","."," Let"," me"," just"," run"," them"," and"," report",".\n\n","Let"," me"," run"," ls"," -","1"," ."," first","."]},{"type":"chunk","time":1791427804366,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user asks to run exactly two commands. The second one is a destructive git push --force. But it's a demo checkout. The instruction says to run exactly those commands, no other command. Let me consider whether it's acceptable. Force-pushing to main is destructive, but this is a demo checkout workspace. Still, I should be cautious. However the user explicitly requested it. Let me just run them and report.\n\nLet me run ls -1 . first."}}},{"type":"chunk","time":1791427804366,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427804366,"index":1,"dt":[],"id":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","name":"bash","args":[""]},{"type":"tool-call-chunks","time0":1791427804366,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","args":["{","\"","description","\"",": ","\"","List"," files"," in"," current"," directory","\"",", ","\"","command","\"",": ","\"","ls"," -","1"," .","\"","}"]},{"type":"chunk","time":1791427804366,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","name":"bash","arguments":"{\"description\": \"List files in current directory\", \"command\": \"ls -1 .\"}"}}},{"type":"chunk","time":1791427804366,"chunk":{"type":"usage","usage":{"inputTokens":5586,"outputTokens":155,"cacheReadTokens":0,"cacheWriteTokens":0,"totalTokens":5741}}},{"type":"chunk","time":1791427804366,"chunk":{"type":"finish","reason":{"kind":"tool-calls"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"deepseek-v4-flash"},"blocks":[{"type":"reasoning","signature":"bc245c9d-d596-4709-ba98-eb7e663a01d8"},{"type":"tool-call"}]}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","name":"bash","arguments":"{\"description\": \"List files in current directory\", \"command\": \"ls -1 .\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"ls -1 .","startedAt":1791427804374,"toolCall":{"callId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","rootCallId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"ls -1 .","startedAt":1791427804374,"toolCall":{"callId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","rootCallId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652"},"finishedAt":1791427804379,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"ls -1 .","startedAt":1791427804374,"toolCall":{"callId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","rootCallId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"ls -1 .","startedAt":1791427804374,"toolCall":{"callId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","rootCallId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652"},"finishedAt":1791427804379,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652"},"toolCallId":"call_00_nCs2WqPExU5Z0coTJ8Yh3652","content":[{"type":"text","text":"about.txt\nnotes.txt\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/session/compaction-output-reserve/session.v4.jsonl b/snapshots/session/compaction-output-reserve/session.v4.jsonl
index f6bfd3123a..6b7fb48868 100644
--- a/snapshots/session/compaction-output-reserve/session.v4.jsonl
+++ b/snapshots/session/compaction-output-reserve/session.v4.jsonl
@@ -15,8 +15,8 @@
{"type":"session/title","data":{"title":"Establish a durable compaction premise","messageSeqs":[9],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":24,"outputTokens":6},"stream":[{"type":"chunk","time":1791427805327,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427805327,"index":0,"dt":[],"id":"call_compaction_marker","name":"bash","args":["{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"]},{"type":"chunk","time":1791427805327,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}}},{"type":"chunk","time":1791427805327,"chunk":{"type":"usage","usage":{"inputTokens":24,"outputTokens":6}}},{"type":"chunk","time":1791427805327,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427805336,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427805336,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"},"finishedAt":1791427805338,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427805336,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427805336,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"},"finishedAt":1791427805338,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_compaction_marker"},"toolCallId":"call_compaction_marker","content":[{"type":"text","text":"alpha\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"/tmp"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"compaction/start","data":{"compactionId":"{{id:2}}","turn":1}}
diff --git a/snapshots/session/compaction-recovery/session.v4.jsonl b/snapshots/session/compaction-recovery/session.v4.jsonl
index 4df91b732a..079716c502 100644
--- a/snapshots/session/compaction-recovery/session.v4.jsonl
+++ b/snapshots/session/compaction-recovery/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Establish a durable compaction premise","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":24,"outputTokens":6},"stream":[{"type":"chunk","time":1791427806185,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427806185,"index":0,"dt":[],"id":"call_compaction_marker","name":"bash","args":["{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"]},{"type":"chunk","time":1791427806185,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}}},{"type":"chunk","time":1791427806185,"chunk":{"type":"usage","usage":{"inputTokens":24,"outputTokens":6}}},{"type":"chunk","time":1791427806185,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427806194,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427806194,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"},"finishedAt":1791427806196,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427806194,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427806194,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"},"finishedAt":1791427806196,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_compaction_marker"},"toolCallId":"call_compaction_marker","content":[{"type":"text","text":"alpha\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/session/compaction-summary-headroom/session.v4.jsonl b/snapshots/session/compaction-summary-headroom/session.v4.jsonl
index 14c0ca1125..b3ba4ad911 100644
--- a/snapshots/session/compaction-summary-headroom/session.v4.jsonl
+++ b/snapshots/session/compaction-summary-headroom/session.v4.jsonl
@@ -15,8 +15,8 @@
{"type":"session/title","data":{"title":"Establish a durable compaction premise","messageSeqs":[9],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":24,"outputTokens":6},"stream":[{"type":"chunk","time":1791427807038,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427807038,"index":0,"dt":[],"id":"call_compaction_marker","name":"bash","args":["{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"]},{"type":"chunk","time":1791427807038,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}}},{"type":"chunk","time":1791427807038,"chunk":{"type":"usage","usage":{"inputTokens":24,"outputTokens":6}}},{"type":"chunk","time":1791427807038,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_compaction_marker","name":"bash","arguments":"{\"command\":\"printf 'alpha\\n'\",\"description\":\"Emit compaction premise marker\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427807048,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427807048,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"},"finishedAt":1791427807051,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427807048,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'alpha\n'","startedAt":1791427807048,"toolCall":{"callId":"call_compaction_marker","rootCallId":"call_compaction_marker"},"finishedAt":1791427807051,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_compaction_marker"},"toolCallId":"call_compaction_marker","content":[{"type":"text","text":"alpha\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"/tmp"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"compaction/start","data":{"compactionId":"{{id:2}}","turn":1}}
diff --git a/snapshots/session/deepseek-messages-degraded-replay/session.v4.jsonl b/snapshots/session/deepseek-messages-degraded-replay/session.v4.jsonl
index b9f49421d3..416623f7d1 100644
--- a/snapshots/session/deepseek-messages-degraded-replay/session.v4.jsonl
+++ b/snapshots/session/deepseek-messages-degraded-replay/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."},{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash","replayState":{"response":{"kind":"deepseek-messages","version":2,"model":"deepseek-v4-flash"},"blocks":[{"type":"reasoning","signature":"snapshot-signature"},{"type":"tool-call"}]}},"id":"{{message:4}}"},"usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"totalTokens":2967},"stream":[{"type":"chunk","time":1791427812803,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427812804,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," simple"," bash"," command"," and"," then"," reply"," with"," \"","D","ONE","\"."]},{"type":"chunk","time":1791427812804,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427812804,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," TER","MIN","AL","_OK","\"",", ","\"","description","\"",": ","\"","E","cho"," TER","MIN","AL","_OK"," to"," verify"," terminal"," access","\"","}"]},{"type":"chunk","time":1791427812804,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."}}},{"type":"chunk","time":1791427812804,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\"}"}}},{"type":"chunk","time":1791427812804,"chunk":{"type":"usage","usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"totalTokens":2967}}},{"type":"chunk","time":1791427812804,"chunk":{"type":"finish","reason":{"kind":"tool-calls"},"replayState":{"response":{"kind":"deepseek-messages","version":2,"model":"deepseek-v4-flash"},"blocks":[{"type":"reasoning","signature":"snapshot-signature"},{"type":"tool-call"}]}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427812813,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427812813,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427812816,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427812813,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427812813,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427812816,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"toolCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","content":[{"type":"text","text":"TERMINAL_OK\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/session/deepseek-messages-replay/session.v4.jsonl b/snapshots/session/deepseek-messages-replay/session.v4.jsonl
index ec48a1472f..e00fb0d821 100644
--- a/snapshots/session/deepseek-messages-replay/session.v4.jsonl
+++ b/snapshots/session/deepseek-messages-replay/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."},{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash","replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"deepseek-v4-flash"},"blocks":[{"type":"reasoning","signature":"snapshot-signature"},{"type":"tool-call"}]}},"id":"{{message:4}}"},"usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"totalTokens":2967},"stream":[{"type":"chunk","time":1791427815257,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427815257,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," simple"," bash"," command"," and"," then"," reply"," with"," \"","D","ONE","\"."]},{"type":"chunk","time":1791427815257,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427815257,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0],"id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," TER","MIN","AL","_OK","\"",", ","\"","description","\"",": ","\"","E","cho"," TER","MIN","AL","_OK"," to"," verify"," terminal"," access","\"","}"]},{"type":"chunk","time":1791427815258,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a simple bash command and then reply with \"DONE\"."}}},{"type":"chunk","time":1791427815258,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\"}"}}},{"type":"chunk","time":1791427815258,"chunk":{"type":"usage","usage":{"inputTokens":2877,"outputTokens":90,"cacheReadTokens":0,"totalTokens":2967}}},{"type":"chunk","time":1791427815258,"chunk":{"type":"finish","reason":{"kind":"tool-calls"},"replayState":{"response":{"kind":"deepseek-messages","version":1,"model":"deepseek-v4-flash"},"blocks":[{"type":"reasoning","signature":"snapshot-signature"},{"type":"tool-call"}]}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","name":"bash","arguments":"{\"command\": \"echo TERMINAL_OK\", \"description\": \"Echo TERMINAL_OK to verify terminal access\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427815268,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427815268,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427815271,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427815268,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo TERMINAL_OK","startedAt":1791427815268,"toolCall":{"callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","rootCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"finishedAt":1791427815271,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233"},"toolCallId":"call_00_fkbBRJsUrGKd1pWVc4Gn8233","content":[{"type":"text","text":"TERMINAL_OK\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/session/empty-response-retry/writer.expected.jsonl b/snapshots/session/empty-response-retry/writer.expected.jsonl
index 1dbc34d967..30c479ce67 100644
--- a/snapshots/session/empty-response-retry/writer.expected.jsonl
+++ b/snapshots/session/empty-response-retry/writer.expected.jsonl
@@ -12,9 +12,9 @@
{"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":"This prompt first receives an","messageSeqs":[8],"source":{"kind":"fallback"}}}
-{"type":"assistant/attempt","data":{"turn":1,"step":1,"stream":[{"type":"chunk","time":1791434106211,"chunk":{"type":"usage","usage":{"inputTokens":0,"outputTokens":0}}},{"type":"chunk","time":1791434106211,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"model returned a completed response with no content","code":"EMPTY_RESPONSE"}}}}]}}
+{"type":"assistant/attempt","data":{"turn":1,"step":1,"stream":[{"type":"chunk","time":1791444890557,"chunk":{"type":"usage","usage":{"inputTokens":0,"outputTokens":0}}},{"type":"chunk","time":1791444890557,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"model returned a completed response with no content","code":"EMPTY_RESPONSE"}}}}]}}
{"type":"llm/retry","data":{"retryId":"{{retry:1}}","turn":1,"step":1,"provider":"deepseek-official","mode":"normal","policyKey":"[\"normal\",2,[\"EMPTY_RESPONSE\",\"RATE_LIMIT\",\"SERVER\",\"TIMEOUT\",\"TRANSPORT\"],1,1,0]","retry":1,"maxRetries":2,"delayMs":1,"failure":{"message":"model returned a completed response with no content","code":"EMPTY_RESPONSE"}}}
{"type":"llm/retry-started","data":{"retryId":"{{retry:1}}","turn":1,"step":1,"retry":1}}
-{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"Recovered."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":12,"outputTokens":3},"stream":[{"type":"chunk","time":1791434106218,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1791434106218,"index":0,"dt":[],"texts":["Recovered."]},{"type":"chunk","time":1791434106218,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"Recovered."}}},{"type":"chunk","time":1791434106218,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":3}}},{"type":"chunk","time":1791434106218,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"text","text":"Recovered."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":12,"outputTokens":3},"stream":[{"type":"chunk","time":1791444890563,"chunk":{"type":"block-start","index":0,"blockType":"text"}},{"type":"text-chunks","time0":1791444890564,"index":0,"dt":[],"texts":["Recovered."]},{"type":"chunk","time":1791444890564,"chunk":{"type":"block-end","index":0,"block":{"type":"text","text":"Recovered."}}},{"type":"chunk","time":1791444890564,"chunk":{"type":"usage","usage":{"inputTokens":12,"outputTokens":3}}},{"type":"chunk","time":1791444890564,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
diff --git a/snapshots/session/error-finish/writer.expected.jsonl b/snapshots/session/error-finish/writer.expected.jsonl
index 35d3fab8c6..7a944ad039 100644
--- a/snapshots/session/error-finish/writer.expected.jsonl
+++ b/snapshots/session/error-finish/writer.expected.jsonl
@@ -12,6 +12,6 @@
{"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":"This prompt triggers a recorded","messageSeqs":[8],"source":{"kind":"fallback"}}}
-{"type":"assistant/attempt","data":{"turn":1,"step":1,"stream":[{"type":"chunk","time":1791434107899,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"simulated provider error (HTTP 401)","code":"AUTH"}}}}]}}
+{"type":"assistant/attempt","data":{"turn":1,"step":1,"stream":[{"type":"chunk","time":1791444892025,"chunk":{"type":"finish","reason":{"kind":"error","failure":{"message":"simulated provider error (HTTP 401)","code":"AUTH"}}}}]}}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"error","error":{"message":"simulated provider error (HTTP 401)","code":"AUTH"}}}}
diff --git a/snapshots/session/foreground-confinement-timeout/session.v4.jsonl b/snapshots/session/foreground-confinement-timeout/session.v4.jsonl
index 2c14b8314d..2f67c3866f 100644
--- a/snapshots/session/foreground-confinement-timeout/session.v4.jsonl
+++ b/snapshots/session/foreground-confinement-timeout/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Run printf SHOULD_NOT_RUN through the","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"foreground-confinement-timeout","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"timeoutMs\":10,\"description\":\"Bound confinement preparation before process launch\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1791427822119,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1791427822119,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"foreground-confinement-timeout","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"timeoutMs\":10,\"description\":\"Bound confinement preparation before process launch\"}"}}},{"type":"chunk","time":1791427822119,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1791427822119,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"foreground-confinement-timeout","name":"bash","arguments":"{\"command\":\"printf SHOULD_NOT_RUN > unexpected-command.txt\",\"timeoutMs\":10,\"description\":\"Bound confinement preparation before process launch\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791427822125,"toolCall":{"callId":"foreground-confinement-timeout","rootCallId":"foreground-confinement-timeout"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791427822125,"toolCall":{"callId":"foreground-confinement-timeout","rootCallId":"foreground-confinement-timeout"},"finishedAt":1791427822137,"status":"killed","cause":"kill","producerReported":true,"detail":"timed out during preparation; timed out during preparation"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791427822125,"toolCall":{"callId":"foreground-confinement-timeout","rootCallId":"foreground-confinement-timeout"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf SHOULD_NOT_RUN > unexpected-command.txt","startedAt":1791427822125,"toolCall":{"callId":"foreground-confinement-timeout","rootCallId":"foreground-confinement-timeout"},"finishedAt":1791427822137,"status":"killed","cause":"kill","producerReported":true,"detail":"timed out during preparation; timed out during preparation"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"foreground-confinement-timeout"},"toolCallId":"foreground-confinement-timeout","content":[{"type":"text","text":"(no output)\n[timed out after 10ms]\n[exit code: null]"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/session/fs-delete-recreate/session.v4.jsonl b/snapshots/session/fs-delete-recreate/session.v4.jsonl
index 345f6cfcc2..e955f40d50 100644
--- a/snapshots/session/fs-delete-recreate/session.v4.jsonl
+++ b/snapshots/session/fs-delete-recreate/session.v4.jsonl
@@ -19,8 +19,8 @@
{"type":"step/start","data":{"turn":1,"step":2}}
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"fs-delete-remove","name":"bash","arguments":"{\"command\":\"rm deleted.txt\",\"description\":\"Delete deleted.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427823079,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427823079,"index":0,"dt":[],"id":"fs-delete-remove","name":"bash","args":["{\"command\":\"rm deleted.txt\",\"description\":\"Delete deleted.txt\"}"]},{"type":"chunk","time":1791427823079,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"fs-delete-remove","name":"bash","arguments":"{\"command\":\"rm deleted.txt\",\"description\":\"Delete deleted.txt\"}"}}},{"type":"chunk","time":1791427823079,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427823079,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"fs-delete-remove","name":"bash","arguments":"{\"command\":\"rm deleted.txt\",\"description\":\"Delete deleted.txt\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"rm deleted.txt","startedAt":1791427823087,"toolCall":{"callId":"fs-delete-remove","rootCallId":"fs-delete-remove"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"rm deleted.txt","startedAt":1791427823087,"toolCall":{"callId":"fs-delete-remove","rootCallId":"fs-delete-remove"},"finishedAt":1791427823094,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"rm deleted.txt","startedAt":1791427823087,"toolCall":{"callId":"fs-delete-remove","rootCallId":"fs-delete-remove"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"rm deleted.txt","startedAt":1791427823087,"toolCall":{"callId":"fs-delete-remove","rootCallId":"fs-delete-remove"},"finishedAt":1791427823094,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"fs-delete-remove"},"toolCallId":"fs-delete-remove","content":[{"type":"text","text":"(no output)"}],"isError":false,"id":"{{message:7}}"},"meta":{"cwd":"{{cwd}}"}},"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/headless.snapshot.ts b/snapshots/session/headless.snapshot.ts
index ebc1e5206e..23c77b0377 100644
--- a/snapshots/session/headless.snapshot.ts
+++ b/snapshots/session/headless.snapshot.ts
@@ -740,10 +740,16 @@ function verifyNoMcpServers(log: string, ptc: boolean): void {
/** Require an admitted failed job and zero process allocations before updating its recorded oracle. */
async function verifyBackgroundConfinementFailure(log: string, cwd: string): Promise {
- const results = parseSessionLog(log).flatMap(event => event.type === 'tool/result' ? [event.data.message] : [])
+ const events = parseSessionLog(log)
+ const results = events.flatMap(event => event.type === 'tool/result' ? [event.data.message] : [])
const started = results.find(result => result.toolCallId === 'async-confinement-start')
const inspected = results.find(result => result.toolCallId === 'async-confinement-result')
- expect(started).toMatchObject({ isError: false, content: [{ type: 'text', text: 'started background job bash-1' }] })
+ const registered = events.find(event => event.type === 'job/registered')
+ if (registered?.type !== 'job/registered') throw new Error('missing registered job')
+ expect(registered.data.jobId).toMatch(/^bash-[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/)
+ expect(started).toMatchObject({
+ isError: false, content: [{ type: 'text', text: `started background job ${registered.data.jobId}` }],
+ })
expect(inspected?.isError).toBe(false)
const text = inspected?.content.flatMap(block => block.type === 'text' ? [block.text] : []).join('')
expect(text).toContain('[status: failed,')
diff --git a/snapshots/session/hook-cc-posttool-block/session.v4.jsonl b/snapshots/session/hook-cc-posttool-block/session.v4.jsonl
index ab9f49f8cf..807c6be53f 100644
--- a/snapshots/session/hook-cc-posttool-block/session.v4.jsonl
+++ b/snapshots/session/hook-cc-posttool-block/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Call the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run the bash tool with the command \"echo HELLO\". If it's rejected, retry once. Then quote the final result verbatim."},{"type":"tool-call","id":"call_00_VAByyMjsct4c7P6k1ysX9256","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Echo HELLO to stdout\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":3263,"outputTokens":103,"cacheReadTokens":0,"reasoningTokens":35},"stream":[{"type":"chunk","time":1791427832176,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427832176,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," the"," bash"," tool"," with"," the"," command"," \"","echo"," HE","LL","O","\"."," If"," it","'s"," rejected",","," ret","ry"," once","."," Then"," quote"," the"," final"," result"," verb","atim","."]},{"type":"chunk","time":1791427832176,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427832176,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0],"id":"call_00_VAByyMjsct4c7P6k1ysX9256","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," HE","LL","O","\"",", ","\"","description","\"",": ","\"","E","cho"," HE","LL","O"," to"," stdout","\"","}"]},{"type":"chunk","time":1791427832177,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run the bash tool with the command \"echo HELLO\". If it's rejected, retry once. Then quote the final result verbatim."}}},{"type":"chunk","time":1791427832177,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_VAByyMjsct4c7P6k1ysX9256","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Echo HELLO to stdout\"}"}}},{"type":"chunk","time":1791427832177,"chunk":{"type":"usage","usage":{"inputTokens":3263,"outputTokens":103,"cacheReadTokens":0,"reasoningTokens":35}}},{"type":"chunk","time":1791427832177,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_VAByyMjsct4c7P6k1ysX9256","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Echo HELLO to stdout\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427832187,"toolCall":{"callId":"call_00_VAByyMjsct4c7P6k1ysX9256","rootCallId":"call_00_VAByyMjsct4c7P6k1ysX9256"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427832187,"toolCall":{"callId":"call_00_VAByyMjsct4c7P6k1ysX9256","rootCallId":"call_00_VAByyMjsct4c7P6k1ysX9256"},"finishedAt":1791427832190,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427832187,"toolCall":{"callId":"call_00_VAByyMjsct4c7P6k1ysX9256","rootCallId":"call_00_VAByyMjsct4c7P6k1ysX9256"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427832187,"toolCall":{"callId":"call_00_VAByyMjsct4c7P6k1ysX9256","rootCallId":"call_00_VAByyMjsct4c7P6k1ysX9256"},"finishedAt":1791427832190,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"hook/invoked","data":{"turn":1,"point":"PostToolUse","dialect":"claude-code","handlerId":"claude-code:PostToolUse:1","matcher":"bash"}}
{"type":"hook/result","data":{"turn":1,"point":"PostToolUse","handlerId":"claude-code:PostToolUse:1","decision":"block","exitCode":2,"stderrSummary":"tool output rejected by policy: retry once","durationMs":7.767333000000008}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_VAByyMjsct4c7P6k1ysX9256"},"toolCallId":"call_00_VAByyMjsct4c7P6k1ysX9256","content":[{"type":"text","text":"tool output rejected by policy: retry once"}],"isError":true,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
@@ -23,8 +23,8 @@
{"type":"step/start","data":{"turn":1,"step":2}}
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The first call was rejected by policy. The user said to retry once. Let me retry."},{"type":"tool-call","id":"call_00_tDV4j1p5eAeHTtQhXOfn6856","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Echo HELLO to stdout\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":59,"outputTokens":89,"cacheReadTokens":3328,"reasoningTokens":21},"stream":[{"type":"chunk","time":1791427832215,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427832215,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," first"," call"," was"," rejected"," by"," policy","."," The"," user"," said"," to"," ret","ry"," once","."," Let"," me"," ret","ry","."]},{"type":"chunk","time":1791427832215,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427832215,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_tDV4j1p5eAeHTtQhXOfn6856","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," HE","LL","O","\"",", ","\"","description","\"",": ","\"","E","cho"," HE","LL","O"," to"," stdout","\"","}"]},{"type":"chunk","time":1791427832215,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The first call was rejected by policy. The user said to retry once. Let me retry."}}},{"type":"chunk","time":1791427832215,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_tDV4j1p5eAeHTtQhXOfn6856","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Echo HELLO to stdout\"}"}}},{"type":"chunk","time":1791427832215,"chunk":{"type":"usage","usage":{"inputTokens":59,"outputTokens":89,"cacheReadTokens":3328,"reasoningTokens":21}}},{"type":"chunk","time":1791427832215,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_00_tDV4j1p5eAeHTtQhXOfn6856","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Echo HELLO to stdout\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427832222,"toolCall":{"callId":"call_00_tDV4j1p5eAeHTtQhXOfn6856","rootCallId":"call_00_tDV4j1p5eAeHTtQhXOfn6856"}}}
-{"type":"job/settled","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427832222,"toolCall":{"callId":"call_00_tDV4j1p5eAeHTtQhXOfn6856","rootCallId":"call_00_tDV4j1p5eAeHTtQhXOfn6856"},"finishedAt":1791427832225,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427832222,"toolCall":{"callId":"call_00_tDV4j1p5eAeHTtQhXOfn6856","rootCallId":"call_00_tDV4j1p5eAeHTtQhXOfn6856"}}}
+{"type":"job/settled","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427832222,"toolCall":{"callId":"call_00_tDV4j1p5eAeHTtQhXOfn6856","rootCallId":"call_00_tDV4j1p5eAeHTtQhXOfn6856"},"finishedAt":1791427832225,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"hook/invoked","data":{"turn":1,"point":"PostToolUse","dialect":"claude-code","handlerId":"claude-code:PostToolUse:2","matcher":"bash"}}
{"type":"hook/result","data":{"turn":1,"point":"PostToolUse","handlerId":"claude-code:PostToolUse:2","decision":"pass","exitCode":0,"durationMs":7.7251249999999345}}
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_tDV4j1p5eAeHTtQhXOfn6856"},"toolCallId":"call_00_tDV4j1p5eAeHTtQhXOfn6856","content":[{"type":"text","text":"HELLO\n"}],"isError":false,"id":"{{message:7}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[23],"surfaceOp":"append"}
diff --git a/snapshots/session/hook-cc-posttool-context/session.v4.jsonl b/snapshots/session/hook-cc-posttool-context/session.v4.jsonl
index 796e55dc05..4c03087b4f 100644
--- a/snapshots/session/hook-cc-posttool-context/session.v4.jsonl
+++ b/snapshots/session/hook-cc-posttool-context/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run `echo HELLO` using the bash tool and report the result verbatim."},{"type":"tool-call","id":"call_00_HbCMzTslWBZTSphWN0z97382","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Run echo HELLO\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2878,"outputTokens":89,"cacheReadTokens":0,"reasoningTokens":23},"stream":[{"type":"chunk","time":1791427833073,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427833074,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," `","echo"," HE","LL","O","`"," using"," the"," bash"," tool"," and"," report"," the"," result"," verb","atim","."]},{"type":"chunk","time":1791427833074,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427833074,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_HbCMzTslWBZTSphWN0z97382","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," HE","LL","O","\"",", ","\"","description","\"",": ","\"","Run"," echo"," HE","LL","O","\"","}"]},{"type":"chunk","time":1791427833074,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run `echo HELLO` using the bash tool and report the result verbatim."}}},{"type":"chunk","time":1791427833074,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_HbCMzTslWBZTSphWN0z97382","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Run echo HELLO\"}"}}},{"type":"chunk","time":1791427833074,"chunk":{"type":"usage","usage":{"inputTokens":2878,"outputTokens":89,"cacheReadTokens":0,"reasoningTokens":23}}},{"type":"chunk","time":1791427833074,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_HbCMzTslWBZTSphWN0z97382","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Run echo HELLO\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427833082,"toolCall":{"callId":"call_00_HbCMzTslWBZTSphWN0z97382","rootCallId":"call_00_HbCMzTslWBZTSphWN0z97382"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427833082,"toolCall":{"callId":"call_00_HbCMzTslWBZTSphWN0z97382","rootCallId":"call_00_HbCMzTslWBZTSphWN0z97382"},"finishedAt":1791427833084,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427833082,"toolCall":{"callId":"call_00_HbCMzTslWBZTSphWN0z97382","rootCallId":"call_00_HbCMzTslWBZTSphWN0z97382"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427833082,"toolCall":{"callId":"call_00_HbCMzTslWBZTSphWN0z97382","rootCallId":"call_00_HbCMzTslWBZTSphWN0z97382"},"finishedAt":1791427833084,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"hook/invoked","data":{"turn":1,"point":"PostToolUse","dialect":"claude-code","handlerId":"claude-code:PostToolUse:1","matcher":"bash"}}
{"type":"hook/result","data":{"turn":1,"point":"PostToolUse","handlerId":"claude-code:PostToolUse:1","decision":"pass","exitCode":0,"durationMs":2.9534590000000662}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_HbCMzTslWBZTSphWN0z97382"},"toolCallId":"call_00_HbCMzTslWBZTSphWN0z97382","content":[{"type":"text","text":"HELLO\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
diff --git a/snapshots/session/hook-codex-posttool-block/session.v4.jsonl b/snapshots/session/hook-codex-posttool-block/session.v4.jsonl
index 4ad6de4a52..dddc7df41c 100644
--- a/snapshots/session/hook-codex-posttool-block/session.v4.jsonl
+++ b/snapshots/session/hook-codex-posttool-block/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Call the bash tool exactly","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to call the bash tool once with `echo HELLO`, then quote the result verbatim and stop."},{"type":"tool-call","id":"call_00_1rmSWHhVchVg7PDTmegT0421","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Echo HELLO to stdout\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":3256,"outputTokens":94,"cacheReadTokens":0,"reasoningTokens":26},"stream":[{"type":"chunk","time":1791427839138,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427839139,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," call"," the"," bash"," tool"," once"," with"," `","echo"," HE","LL","O","`,"," then"," quote"," the"," result"," verb","atim"," and"," stop","."]},{"type":"chunk","time":1791427839139,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427839139,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_1rmSWHhVchVg7PDTmegT0421","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," HE","LL","O","\"",", ","\"","description","\"",": ","\"","E","cho"," HE","LL","O"," to"," stdout","\"","}"]},{"type":"chunk","time":1791427839139,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to call the bash tool once with `echo HELLO`, then quote the result verbatim and stop."}}},{"type":"chunk","time":1791427839139,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_1rmSWHhVchVg7PDTmegT0421","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Echo HELLO to stdout\"}"}}},{"type":"chunk","time":1791427839139,"chunk":{"type":"usage","usage":{"inputTokens":3256,"outputTokens":94,"cacheReadTokens":0,"reasoningTokens":26}}},{"type":"chunk","time":1791427839139,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_1rmSWHhVchVg7PDTmegT0421","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Echo HELLO to stdout\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427839150,"toolCall":{"callId":"call_00_1rmSWHhVchVg7PDTmegT0421","rootCallId":"call_00_1rmSWHhVchVg7PDTmegT0421"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427839150,"toolCall":{"callId":"call_00_1rmSWHhVchVg7PDTmegT0421","rootCallId":"call_00_1rmSWHhVchVg7PDTmegT0421"},"finishedAt":1791427839153,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427839150,"toolCall":{"callId":"call_00_1rmSWHhVchVg7PDTmegT0421","rootCallId":"call_00_1rmSWHhVchVg7PDTmegT0421"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427839150,"toolCall":{"callId":"call_00_1rmSWHhVchVg7PDTmegT0421","rootCallId":"call_00_1rmSWHhVchVg7PDTmegT0421"},"finishedAt":1791427839153,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"hook/invoked","data":{"turn":1,"point":"PostToolUse","dialect":"codex","handlerId":"codex:PostToolUse:1","matcher":"bash"}}
{"type":"hook/result","data":{"turn":1,"point":"PostToolUse","handlerId":"codex:PostToolUse:1","decision":"block","exitCode":2,"stderrSummary":"tool output rejected by codex policy: summarize instead","durationMs":3.2890840000000026}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_1rmSWHhVchVg7PDTmegT0421"},"toolCallId":"call_00_1rmSWHhVchVg7PDTmegT0421","content":[{"type":"text","text":"tool output rejected by codex policy: summarize instead"}],"isError":true,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
diff --git a/snapshots/session/hook-codex-posttool-context/session.v4.jsonl b/snapshots/session/hook-codex-posttool-context/session.v4.jsonl
index d80235ce6c..ac06813b93 100644
--- a/snapshots/session/hook-codex-posttool-context/session.v4.jsonl
+++ b/snapshots/session/hook-codex-posttool-context/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run `echo HELLO` using the bash tool and report the result verbatim."},{"type":"tool-call","id":"call_00_Q6wHtakaip2QNfIXaVJY5458","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Run echo HELLO\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2878,"outputTokens":89,"cacheReadTokens":0,"reasoningTokens":23},"stream":[{"type":"chunk","time":1791427839972,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427839972,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," `","echo"," HE","LL","O","`"," using"," the"," bash"," tool"," and"," report"," the"," result"," verb","atim","."]},{"type":"chunk","time":1791427839972,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427839972,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0],"id":"call_00_Q6wHtakaip2QNfIXaVJY5458","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," HE","LL","O","\"",", ","\"","description","\"",": ","\"","Run"," echo"," HE","LL","O","\"","}"]},{"type":"chunk","time":1791427839973,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run `echo HELLO` using the bash tool and report the result verbatim."}}},{"type":"chunk","time":1791427839973,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_Q6wHtakaip2QNfIXaVJY5458","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Run echo HELLO\"}"}}},{"type":"chunk","time":1791427839973,"chunk":{"type":"usage","usage":{"inputTokens":2878,"outputTokens":89,"cacheReadTokens":0,"reasoningTokens":23}}},{"type":"chunk","time":1791427839973,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_Q6wHtakaip2QNfIXaVJY5458","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Run echo HELLO\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427839982,"toolCall":{"callId":"call_00_Q6wHtakaip2QNfIXaVJY5458","rootCallId":"call_00_Q6wHtakaip2QNfIXaVJY5458"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427839982,"toolCall":{"callId":"call_00_Q6wHtakaip2QNfIXaVJY5458","rootCallId":"call_00_Q6wHtakaip2QNfIXaVJY5458"},"finishedAt":1791427839985,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427839982,"toolCall":{"callId":"call_00_Q6wHtakaip2QNfIXaVJY5458","rootCallId":"call_00_Q6wHtakaip2QNfIXaVJY5458"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo HELLO","startedAt":1791427839982,"toolCall":{"callId":"call_00_Q6wHtakaip2QNfIXaVJY5458","rootCallId":"call_00_Q6wHtakaip2QNfIXaVJY5458"},"finishedAt":1791427839985,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"hook/invoked","data":{"turn":1,"point":"PostToolUse","dialect":"codex","handlerId":"codex:PostToolUse:1","matcher":"bash"}}
{"type":"hook/result","data":{"turn":1,"point":"PostToolUse","handlerId":"codex:PostToolUse:1","decision":"pass","exitCode":0,"durationMs":2.8519999999999754}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_Q6wHtakaip2QNfIXaVJY5458"},"toolCallId":"call_00_Q6wHtakaip2QNfIXaVJY5458","content":[{"type":"text","text":"HELLO\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
diff --git a/snapshots/session/mcp-empty-ptc/session.v4.jsonl b/snapshots/session/mcp-empty-ptc/session.v4.jsonl
index bba7a5f2c9..708ce74dd6 100644
--- a/snapshots/session/mcp-empty-ptc/session.v4.jsonl
+++ b/snapshots/session/mcp-empty-ptc/session.v4.jsonl
@@ -15,12 +15,12 @@
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_00_ET_nCgG71pOO3KB4MYELUyr5350","name":"run_code","arguments":"{\"code\": \"const a = await tools.bash({ command: 'echo CODE_ONE', description: 'Echo CODE_ONE' });\\nconst b = await tools.bash({ command: 'echo CODE_TWO', description: 'Echo CODE_TWO' });\\nconst out1 = a.kind === 'foreground' ? a.stdout.text.trim() : '';\\nconst out2 = b.kind === 'foreground' ? b.stdout.text.trim() : '';\\nconsole.log('captured output');\\nreturn out1 + '+' + out2;\", \"description\": \"Run two bash echoes and join outputs\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":8689,"outputTokens":166,"totalTokens":8855,"cacheReadTokens":0,"reasoningTokens":0},"stream":[{"type":"chunk","time":1791427845652,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427845652,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_ET_nCgG71pOO3KB4MYELUyr5350","name":"run_code","args":["","{","\"","code","\"",": ","\"","const"," a"," ="," await"," tools",".b","ash","({"," command",":"," '","echo"," CODE","_","ONE","',"," description",":"," '","E","cho"," CODE","_","ONE","'"," });\\n","const"," b"," ="," await"," tools",".b","ash","({"," command",":"," '","echo"," CODE","_T","WO","',"," description",":"," '","E","cho"," CODE","_T","WO","'"," });\\n","const"," out","1"," ="," a",".k","ind"," ==="," '","fore","ground","'"," ?"," a",".stdout",".text",".trim","()"," :"," '';\\n","const"," out","2"," ="," b",".k","ind"," ==="," '","fore","ground","'"," ?"," b",".stdout",".text",".trim","()"," :"," '';\\n","console",".log","('","capt","ured"," output","');\\n","return"," out","1"," +"," '+'"," +"," out","2",";","\"",", ","\"","description","\"",": ","\"","Run"," two"," bash"," echoes"," and"," join"," outputs","\"","}"]},{"type":"chunk","time":1791427845652,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_00_ET_nCgG71pOO3KB4MYELUyr5350","name":"run_code","arguments":"{\"code\": \"const a = await tools.bash({ command: 'echo CODE_ONE', description: 'Echo CODE_ONE' });\\nconst b = await tools.bash({ command: 'echo CODE_TWO', description: 'Echo CODE_TWO' });\\nconst out1 = a.kind === 'foreground' ? a.stdout.text.trim() : '';\\nconst out2 = b.kind === 'foreground' ? b.stdout.text.trim() : '';\\nconsole.log('captured output');\\nreturn out1 + '+' + out2;\", \"description\": \"Run two bash echoes and join outputs\"}"}}},{"type":"chunk","time":1791427845652,"chunk":{"type":"usage","usage":{"inputTokens":8689,"outputTokens":166,"totalTokens":8855,"cacheReadTokens":0,"reasoningTokens":0}}},{"type":"chunk","time":1791427845652,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350","name":"run_code","arguments":"{\"code\": \"const a = await tools.bash({ command: 'echo CODE_ONE', description: 'Echo CODE_ONE' });\\nconst b = await tools.bash({ command: 'echo CODE_TWO', description: 'Echo CODE_TWO' });\\nconst out1 = a.kind === 'foreground' ? a.stdout.text.trim() : '';\\nconst out2 = b.kind === 'foreground' ? b.stdout.text.trim() : '';\\nconsole.log('captured output');\\nreturn out1 + '+' + out2;\", \"description\": \"Run two bash echoes and join outputs\"}"}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350","parentCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350","subCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Echo CODE_ONE"}}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427845736,"toolCall":{"callId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350:ptc:1","rootCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427845736,"toolCall":{"callId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350:ptc:1","rootCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350"},"finishedAt":1791427845738,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427845736,"toolCall":{"callId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350:ptc:1","rootCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427845736,"toolCall":{"callId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350:ptc:1","rootCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350"},"finishedAt":1791427845738,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350","parentCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350","subCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Echo CODE_ONE"},"isError":false,"content":[{"type":"text","text":"CODE_ONE\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350","parentCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350","subCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Echo CODE_TWO"}}}
-{"type":"job/registered","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427845741,"toolCall":{"callId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350:ptc:2","rootCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350"}}}
-{"type":"job/settled","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427845741,"toolCall":{"callId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350:ptc:2","rootCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350"},"finishedAt":1791427845743,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427845741,"toolCall":{"callId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350:ptc:2","rootCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350"}}}
+{"type":"job/settled","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427845741,"toolCall":{"callId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350:ptc:2","rootCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350"},"finishedAt":1791427845743,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350","parentCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350","subCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Echo CODE_TWO"},"isError":false,"content":[{"type":"text","text":"CODE_TWO\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350"},"toolCallId":"call_00_ET_nCgG71pOO3KB4MYELUyr5350","content":[{"type":"text","text":"captured output\nCODE_ONE+CODE_TWO"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
diff --git a/snapshots/session/mcp-empty/session.v4.jsonl b/snapshots/session/mcp-empty/session.v4.jsonl
index ce69d9aa9b..bfc49f1d50 100644
--- a/snapshots/session/mcp-empty/session.v4.jsonl
+++ b/snapshots/session/mcp-empty/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196","name":"bash","arguments":"{\"command\": \"echo SNAPSHOT_OK\", \"description\": \"Echo SNAPSHOT_OK marker\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":7146,"outputTokens":67,"totalTokens":7213,"cacheReadTokens":0,"reasoningTokens":0},"stream":[{"type":"chunk","time":1791427844862,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427844862,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," S","NA","PS","H","OT","_OK","\"",", ","\"","description","\"",": ","\"","E","cho"," S","NA","PS","H","OT","_OK"," marker","\"","}"]},{"type":"chunk","time":1791427844862,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196","name":"bash","arguments":"{\"command\": \"echo SNAPSHOT_OK\", \"description\": \"Echo SNAPSHOT_OK marker\"}"}}},{"type":"chunk","time":1791427844862,"chunk":{"type":"usage","usage":{"inputTokens":7146,"outputTokens":67,"totalTokens":7213,"cacheReadTokens":0,"reasoningTokens":0}}},{"type":"chunk","time":1791427844862,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196","name":"bash","arguments":"{\"command\": \"echo SNAPSHOT_OK\", \"description\": \"Echo SNAPSHOT_OK marker\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo SNAPSHOT_OK","startedAt":1791427844872,"toolCall":{"callId":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196","rootCallId":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo SNAPSHOT_OK","startedAt":1791427844872,"toolCall":{"callId":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196","rootCallId":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196"},"finishedAt":1791427844874,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo SNAPSHOT_OK","startedAt":1791427844872,"toolCall":{"callId":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196","rootCallId":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo SNAPSHOT_OK","startedAt":1791427844872,"toolCall":{"callId":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196","rootCallId":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196"},"finishedAt":1791427844874,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196"},"toolCallId":"call_00_ET_HoegbDQPmrv1Ejxsq0wk2196","content":[{"type":"text","text":"SNAPSHOT_OK\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/session/missing-sandbox-runner/session.v4.jsonl b/snapshots/session/missing-sandbox-runner/session.v4.jsonl
index 3ac7288fac..d228ffa00d 100644
--- a/snapshots/session/missing-sandbox-runner/session.v4.jsonl
+++ b/snapshots/session/missing-sandbox-runner/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Run true once with bash","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"missing-runner-foreground","name":"bash","arguments":"{\"command\":\"true\",\"description\":\"Exercise missing sandbox runner\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1791427848720,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427848720,"index":0,"dt":[],"id":"missing-runner-foreground","name":"bash","args":["{\"command\":\"true\",\"description\":\"Exercise missing sandbox runner\"}"]},{"type":"chunk","time":1791427848720,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"missing-runner-foreground","name":"bash","arguments":"{\"command\":\"true\",\"description\":\"Exercise missing sandbox runner\"}"}}},{"type":"chunk","time":1791427848720,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1791427848720,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"missing-runner-foreground","name":"bash","arguments":"{\"command\":\"true\",\"description\":\"Exercise missing sandbox runner\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"true","startedAt":1791427848727,"toolCall":{"callId":"missing-runner-foreground","rootCallId":"missing-runner-foreground"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"true","startedAt":1791427848727,"toolCall":{"callId":"missing-runner-foreground","rootCallId":"missing-runner-foreground"},"finishedAt":1791427848730,"status":"killed","cause":"producer","producerReported":true,"detail":"killed before exit; [sandbox: the sandbox runner itself failed under read-only mode — the command did not run; this is a sandbox problem, not a command failure]"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"true","startedAt":1791427848727,"toolCall":{"callId":"missing-runner-foreground","rootCallId":"missing-runner-foreground"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"true","startedAt":1791427848727,"toolCall":{"callId":"missing-runner-foreground","rootCallId":"missing-runner-foreground"},"finishedAt":1791427848730,"status":"killed","cause":"producer","producerReported":true,"detail":"killed before exit; [sandbox: the sandbox runner itself failed under read-only mode — the command did not run; this is a sandbox problem, not a command failure]"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"missing-runner-foreground"},"toolCallId":"missing-runner-foreground","content":[{"type":"text","text":"Error: sandbox mode \"read-only\" is requested but no sandbox backend is usable on this host; refusing to run the command unconfined. Install bubblewrap or run a Landlock-enforcing kernel (Linux), ensure sandbox-exec is usable (macOS), or ensure the ACL restricted-token runner can start (Windows) — otherwise switch the consumer to danger-full-access. Runner failure: Error: spawn {{cwd}}/.dsh-missing-sandbox-runner ENOENT"}],"isError":true,"id":"{{message:5}}"},"error":{"name":"SandboxUnavailableError","code":"SANDBOX_UNAVAILABLE"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/session/packed-chunks/writer.expected.jsonl b/snapshots/session/packed-chunks/writer.expected.jsonl
index 73b15703ea..36e45ee89c 100644
--- a/snapshots/session/packed-chunks/writer.expected.jsonl
+++ b/snapshots/session/packed-chunks/writer.expected.jsonl
@@ -12,13 +12,13 @@
{"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":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
-{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a simple bash command and report the result verbatim."},{"type":"tool-call","id":"call_00_JliP571Bh0QQ8QExbSPk0080","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Run echo HELLO\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2878,"outputTokens":83,"cacheReadTokens":0,"reasoningTokens":17},"stream":[{"type":"chunk","time":1791434137334,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791434137334,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," simple"," bash"," command"," and"," report"," the"," result"," verb","atim","."]},{"type":"chunk","time":1791434137334,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791434137334,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_JliP571Bh0QQ8QExbSPk0080","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," HE","LL","O","\"",", ","\"","description","\"",": ","\"","Run"," echo"," HE","LL","O","\"","}"]},{"type":"chunk","time":1791434137334,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a simple bash command and report the result verbatim."}}},{"type":"chunk","time":1791434137334,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_JliP571Bh0QQ8QExbSPk0080","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Run echo HELLO\"}"}}},{"type":"chunk","time":1791434137334,"chunk":{"type":"usage","usage":{"inputTokens":2878,"outputTokens":83,"cacheReadTokens":0,"reasoningTokens":17}}},{"type":"chunk","time":1791434137334,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a simple bash command and report the result verbatim."},{"type":"tool-call","id":"call_00_JliP571Bh0QQ8QExbSPk0080","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Run echo HELLO\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2878,"outputTokens":83,"cacheReadTokens":0,"reasoningTokens":17},"stream":[{"type":"chunk","time":1791444921353,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791444921353,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," simple"," bash"," command"," and"," report"," the"," result"," verb","atim","."]},{"type":"chunk","time":1791444921353,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791444921353,"index":1,"dt":[0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_JliP571Bh0QQ8QExbSPk0080","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," HE","LL","O","\"",", ","\"","description","\"",": ","\"","Run"," echo"," HE","LL","O","\"","}"]},{"type":"chunk","time":1791444921354,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a simple bash command and report the result verbatim."}}},{"type":"chunk","time":1791444921354,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_JliP571Bh0QQ8QExbSPk0080","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Run echo HELLO\"}"}}},{"type":"chunk","time":1791444921354,"chunk":{"type":"usage","usage":{"inputTokens":2878,"outputTokens":83,"cacheReadTokens":0,"reasoningTokens":17}}},{"type":"chunk","time":1791444921354,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_JliP571Bh0QQ8QExbSPk0080","name":"bash","arguments":"{\"command\": \"echo HELLO\", \"description\": \"Run echo HELLO\"}"}}
{"type":"hook/invoked","data":{"turn":1,"point":"PreToolUse","dialect":"claude-code","handlerId":"claude-code:PreToolUse:1","matcher":"bash"}}
-{"type":"hook/result","data":{"turn":1,"point":"PreToolUse","handlerId":"claude-code:PreToolUse:1","decision":"block","exitCode":2,"stderrSummary":"bash is disabled by policy in this session","durationMs":4.5844999999999345}}
+{"type":"hook/result","data":{"turn":1,"point":"PreToolUse","handlerId":"claude-code:PreToolUse:1","decision":"block","exitCode":2,"stderrSummary":"bash is disabled by policy in this session","durationMs":4.37179100000003}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_JliP571Bh0QQ8QExbSPk0080"},"toolCallId":"call_00_JliP571Bh0QQ8QExbSPk0080","content":[{"type":"text","text":"Error: bash is disabled by policy in this session"}],"isError":true,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
-{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The bash tool is disabled by policy. I need to report this error verbatim back to the user."},{"type":"text","text":"The tool returned:\n\n> Error: bash is disabled by policy in this session\n\nI cannot run the command because the bash tool is disabled by policy."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":167,"outputTokens":52,"cacheReadTokens":2816,"reasoningTokens":21},"stream":[{"type":"chunk","time":1791434137354,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791434137354,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," bash"," tool"," is"," disabled"," by"," policy","."," I"," need"," to"," report"," this"," error"," verb","atim"," back"," to"," the"," user","."]},{"type":"chunk","time":1791434137354,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791434137354,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," tool"," returned",":\n\n",">"," Error",":"," bash"," is"," disabled"," by"," policy"," in"," this"," session","\n\n","I"," cannot"," run"," the"," command"," because"," the"," bash"," tool"," is"," disabled"," by"," policy","."]},{"type":"chunk","time":1791434137354,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The bash tool is disabled by policy. I need to report this error verbatim back to the user."}}},{"type":"chunk","time":1791434137354,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"The tool returned:\n\n> Error: bash is disabled by policy in this session\n\nI cannot run the command because the bash tool is disabled by policy."}}},{"type":"chunk","time":1791434137354,"chunk":{"type":"usage","usage":{"inputTokens":167,"outputTokens":52,"cacheReadTokens":2816,"reasoningTokens":21}}},{"type":"chunk","time":1791434137354,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The bash tool is disabled by policy. I need to report this error verbatim back to the user."},{"type":"text","text":"The tool returned:\n\n> Error: bash is disabled by policy in this session\n\nI cannot run the command because the bash tool is disabled by policy."}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":167,"outputTokens":52,"cacheReadTokens":2816,"reasoningTokens":21},"stream":[{"type":"chunk","time":1791444921374,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791444921374,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," bash"," tool"," is"," disabled"," by"," policy","."," I"," need"," to"," report"," this"," error"," verb","atim"," back"," to"," the"," user","."]},{"type":"chunk","time":1791444921374,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791444921374,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0],"texts":["The"," tool"," returned",":\n\n",">"," Error",":"," bash"," is"," disabled"," by"," policy"," in"," this"," session","\n\n","I"," cannot"," run"," the"," command"," because"," the"," bash"," tool"," is"," disabled"," by"," policy","."]},{"type":"chunk","time":1791444921375,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The bash tool is disabled by policy. I need to report this error verbatim back to the user."}}},{"type":"chunk","time":1791444921375,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"The tool returned:\n\n> Error: bash is disabled by policy in this session\n\nI cannot run the command because the bash tool is disabled by policy."}}},{"type":"chunk","time":1791444921375,"chunk":{"type":"usage","usage":{"inputTokens":167,"outputTokens":52,"cacheReadTokens":2816,"reasoningTokens":21}}},{"type":"chunk","time":1791444921375,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":2}}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
diff --git a/snapshots/session/partial-landlock-child-failure/session.v4.jsonl b/snapshots/session/partial-landlock-child-failure/session.v4.jsonl
index 1a8d326788..818e64c1a7 100644
--- a/snapshots/session/partial-landlock-child-failure/session.v4.jsonl
+++ b/snapshots/session/partial-landlock-child-failure/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"partial-landlock-call","name":"bash","arguments":"{\"command\":\"false\",\"description\":\"Exit with status one\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":1,"outputTokens":1},"stream":[{"type":"chunk","time":1791427855459,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427855459,"index":0,"dt":[],"id":"partial-landlock-call","name":"bash","args":["{\"command\":\"false\",\"description\":\"Exit with status one\"}"]},{"type":"chunk","time":1791427855459,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"partial-landlock-call","name":"bash","arguments":"{\"command\":\"false\",\"description\":\"Exit with status one\"}"}}},{"type":"chunk","time":1791427855459,"chunk":{"type":"usage","usage":{"inputTokens":1,"outputTokens":1}}},{"type":"chunk","time":1791427855459,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"partial-landlock-call","name":"bash","arguments":"{\"command\":\"false\",\"description\":\"Exit with status one\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"false","startedAt":1791427855465,"toolCall":{"callId":"partial-landlock-call","rootCallId":"partial-landlock-call"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"false","startedAt":1791427855465,"toolCall":{"callId":"partial-landlock-call","rootCallId":"partial-landlock-call"},"finishedAt":1791427855472,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 1"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"false","startedAt":1791427855465,"toolCall":{"callId":"partial-landlock-call","rootCallId":"partial-landlock-call"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"false","startedAt":1791427855465,"toolCall":{"callId":"partial-landlock-call","rootCallId":"partial-landlock-call"},"finishedAt":1791427855472,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 1"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"partial-landlock-call"},"toolCallId":"partial-landlock-call","content":[{"type":"text","text":"[stderr]\nlandlock-run: partial enforcement (older Landlock ABI)\n[exit code: 1]"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/session/ptc-node-read-only/session.v4.jsonl b/snapshots/session/ptc-node-read-only/session.v4.jsonl
index a2128cab1b..70068a6d08 100644
--- a/snapshots/session/ptc-node-read-only/session.v4.jsonl
+++ b/snapshots/session/ptc-node-read-only/session.v4.jsonl
@@ -15,8 +15,8 @@
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_node_policy","name":"run_code","arguments":"{\"code\":\"const fs = await import('node:fs/promises'); let denied = false; try { await fs.writeFile('ptc-denied.txt', 'forbidden') } catch (error) { denied = ['EPERM', 'EACCES', 'EROFS'].includes(error.code) }; const shell = await tools.bash({ command: 'pwd', description: 'Report the current workspace directory' }); return { denied, emptyEnvironment: Object.keys(process.env).length === 0, sameWorkspace: process.cwd() === shell.stdout.text.trim() };\",\"description\":\"Verify Node file policy and execution context\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427867010,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427867010,"index":0,"dt":[],"id":"call_node_policy","name":"run_code","args":["{\"code\":\"const fs = await import('node:fs/promises'); let denied = false; try { await fs.writeFile('ptc-denied.txt', 'forbidden') } catch (error) { denied = ['EPERM', 'EACCES', 'EROFS'].includes(error.code) }; const shell = await tools.bash({ command: 'pwd', description: 'Report the current workspace directory' }); return { denied, emptyEnvironment: Object.keys(process.env).length === 0, sameWorkspace: process.cwd() === shell.stdout.text.trim() };\",\"description\":\"Verify Node file policy and execution context\"}"]},{"type":"chunk","time":1791427867010,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_node_policy","name":"run_code","arguments":"{\"code\":\"const fs = await import('node:fs/promises'); let denied = false; try { await fs.writeFile('ptc-denied.txt', 'forbidden') } catch (error) { denied = ['EPERM', 'EACCES', 'EROFS'].includes(error.code) }; const shell = await tools.bash({ command: 'pwd', description: 'Report the current workspace directory' }); return { denied, emptyEnvironment: Object.keys(process.env).length === 0, sameWorkspace: process.cwd() === shell.stdout.text.trim() };\",\"description\":\"Verify Node file policy and execution context\"}"}}},{"type":"chunk","time":1791427867010,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427867010,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_node_policy","name":"run_code","arguments":"{\"code\":\"const fs = await import('node:fs/promises'); let denied = false; try { await fs.writeFile('ptc-denied.txt', 'forbidden') } catch (error) { denied = ['EPERM', 'EACCES', 'EROFS'].includes(error.code) }; const shell = await tools.bash({ command: 'pwd', description: 'Report the current workspace directory' }); return { denied, emptyEnvironment: Object.keys(process.env).length === 0, sameWorkspace: process.cwd() === shell.stdout.text.trim() };\",\"description\":\"Verify Node file policy and execution context\"}"}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_node_policy","parentCallId":"call_node_policy","subCallId":"call_node_policy:ptc:1","name":"bash","arguments":{"command":"pwd","description":"Report the current workspace directory"}}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"pwd","startedAt":1791427867102,"toolCall":{"callId":"call_node_policy:ptc:1","rootCallId":"call_node_policy"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"pwd","startedAt":1791427867102,"toolCall":{"callId":"call_node_policy:ptc:1","rootCallId":"call_node_policy"},"finishedAt":1791427867112,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"pwd","startedAt":1791427867102,"toolCall":{"callId":"call_node_policy:ptc:1","rootCallId":"call_node_policy"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"pwd","startedAt":1791427867102,"toolCall":{"callId":"call_node_policy:ptc:1","rootCallId":"call_node_policy"},"finishedAt":1791427867112,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_node_policy","parentCallId":"call_node_policy","subCallId":"call_node_policy:ptc:1","name":"bash","arguments":{"command":"pwd","description":"Report the current workspace directory"},"isError":false,"content":[{"type":"text","text":"{{cwd}}\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_node_policy"},"toolCallId":"call_node_policy","content":[{"type":"text","text":"{\n \"denied\": true,\n \"emptyEnvironment\": true,\n \"sameWorkspace\": true\n}"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
diff --git a/snapshots/session/ptc-node-workspace/session.v4.jsonl b/snapshots/session/ptc-node-workspace/session.v4.jsonl
index 9806da5e82..5c10e1a924 100644
--- a/snapshots/session/ptc-node-workspace/session.v4.jsonl
+++ b/snapshots/session/ptc-node-workspace/session.v4.jsonl
@@ -15,8 +15,8 @@
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_node_policy","name":"run_code","arguments":"{\"code\":\"const fs = await import('node:fs/promises'); await fs.writeFile('ptc-created.txt', 'created by sandboxed Node\\\\n'); const written = await fs.readFile('ptc-created.txt', 'utf8') === 'created by sandboxed Node\\\\n'; const shell = await tools.bash({ command: 'pwd', description: 'Report the current workspace directory' }); return { written, emptyEnvironment: Object.keys(process.env).length === 0, sameWorkspace: process.cwd() === shell.stdout.text.trim() };\",\"description\":\"Verify Node file policy and execution context\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427867917,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427867917,"index":0,"dt":[],"id":"call_node_policy","name":"run_code","args":["{\"code\":\"const fs = await import('node:fs/promises'); await fs.writeFile('ptc-created.txt', 'created by sandboxed Node\\\\n'); const written = await fs.readFile('ptc-created.txt', 'utf8') === 'created by sandboxed Node\\\\n'; const shell = await tools.bash({ command: 'pwd', description: 'Report the current workspace directory' }); return { written, emptyEnvironment: Object.keys(process.env).length === 0, sameWorkspace: process.cwd() === shell.stdout.text.trim() };\",\"description\":\"Verify Node file policy and execution context\"}"]},{"type":"chunk","time":1791427867917,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_node_policy","name":"run_code","arguments":"{\"code\":\"const fs = await import('node:fs/promises'); await fs.writeFile('ptc-created.txt', 'created by sandboxed Node\\\\n'); const written = await fs.readFile('ptc-created.txt', 'utf8') === 'created by sandboxed Node\\\\n'; const shell = await tools.bash({ command: 'pwd', description: 'Report the current workspace directory' }); return { written, emptyEnvironment: Object.keys(process.env).length === 0, sameWorkspace: process.cwd() === shell.stdout.text.trim() };\",\"description\":\"Verify Node file policy and execution context\"}"}}},{"type":"chunk","time":1791427867917,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427867917,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_node_policy","name":"run_code","arguments":"{\"code\":\"const fs = await import('node:fs/promises'); await fs.writeFile('ptc-created.txt', 'created by sandboxed Node\\\\n'); const written = await fs.readFile('ptc-created.txt', 'utf8') === 'created by sandboxed Node\\\\n'; const shell = await tools.bash({ command: 'pwd', description: 'Report the current workspace directory' }); return { written, emptyEnvironment: Object.keys(process.env).length === 0, sameWorkspace: process.cwd() === shell.stdout.text.trim() };\",\"description\":\"Verify Node file policy and execution context\"}"}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_node_policy","parentCallId":"call_node_policy","subCallId":"call_node_policy:ptc:1","name":"bash","arguments":{"command":"pwd","description":"Report the current workspace directory"}}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"pwd","startedAt":1791427868009,"toolCall":{"callId":"call_node_policy:ptc:1","rootCallId":"call_node_policy"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"pwd","startedAt":1791427868009,"toolCall":{"callId":"call_node_policy:ptc:1","rootCallId":"call_node_policy"},"finishedAt":1791427868019,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"pwd","startedAt":1791427868009,"toolCall":{"callId":"call_node_policy:ptc:1","rootCallId":"call_node_policy"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"pwd","startedAt":1791427868009,"toolCall":{"callId":"call_node_policy:ptc:1","rootCallId":"call_node_policy"},"finishedAt":1791427868019,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_node_policy","parentCallId":"call_node_policy","subCallId":"call_node_policy:ptc:1","name":"bash","arguments":{"command":"pwd","description":"Report the current workspace directory"},"isError":false,"content":[{"type":"text","text":"{{cwd}}\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_node_policy"},"toolCallId":"call_node_policy","content":[{"type":"text","text":"{\n \"written\": true,\n \"emptyEnvironment\": true,\n \"sameWorkspace\": true\n}"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
diff --git a/snapshots/session/ptc-python-read-only/session.v4.jsonl b/snapshots/session/ptc-python-read-only/session.v4.jsonl
index aa122eb6f9..b1e675052d 100644
--- a/snapshots/session/ptc-python-read-only/session.v4.jsonl
+++ b/snapshots/session/ptc-python-read-only/session.v4.jsonl
@@ -15,8 +15,8 @@
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_python_policy","name":"run_code","arguments":"{\"code\":\"import errno\\nimport os\\nfrom pathlib import Path\\n\\ndenied = False\\ntry:\\n Path('ptc-denied.txt').write_text('forbidden')\\nexcept OSError as error:\\n denied = error.errno in (errno.EPERM, errno.EACCES, errno.EROFS)\\nshell = await tools.bash({'command': 'pwd', 'description': 'Report the current workspace directory'})\\nreturn {'denied': denied, 'environmentScrubbed': 'DSH_PYTHON_PTC_HOST_SECRET' not in os.environ, 'sameWorkspace': os.getcwd() == shell['stdout']['text'].strip()}\",\"description\":\"Verify Python file policy and execution context\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427868882,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427868882,"index":0,"dt":[],"id":"call_python_policy","name":"run_code","args":["{\"code\":\"import errno\\nimport os\\nfrom pathlib import Path\\n\\ndenied = False\\ntry:\\n Path('ptc-denied.txt').write_text('forbidden')\\nexcept OSError as error:\\n denied = error.errno in (errno.EPERM, errno.EACCES, errno.EROFS)\\nshell = await tools.bash({'command': 'pwd', 'description': 'Report the current workspace directory'})\\nreturn {'denied': denied, 'environmentScrubbed': 'DSH_PYTHON_PTC_HOST_SECRET' not in os.environ, 'sameWorkspace': os.getcwd() == shell['stdout']['text'].strip()}\",\"description\":\"Verify Python file policy and execution context\"}"]},{"type":"chunk","time":1791427868882,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_python_policy","name":"run_code","arguments":"{\"code\":\"import errno\\nimport os\\nfrom pathlib import Path\\n\\ndenied = False\\ntry:\\n Path('ptc-denied.txt').write_text('forbidden')\\nexcept OSError as error:\\n denied = error.errno in (errno.EPERM, errno.EACCES, errno.EROFS)\\nshell = await tools.bash({'command': 'pwd', 'description': 'Report the current workspace directory'})\\nreturn {'denied': denied, 'environmentScrubbed': 'DSH_PYTHON_PTC_HOST_SECRET' not in os.environ, 'sameWorkspace': os.getcwd() == shell['stdout']['text'].strip()}\",\"description\":\"Verify Python file policy and execution context\"}"}}},{"type":"chunk","time":1791427868882,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427868882,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_python_policy","name":"run_code","arguments":"{\"code\":\"import errno\\nimport os\\nfrom pathlib import Path\\n\\ndenied = False\\ntry:\\n Path('ptc-denied.txt').write_text('forbidden')\\nexcept OSError as error:\\n denied = error.errno in (errno.EPERM, errno.EACCES, errno.EROFS)\\nshell = await tools.bash({'command': 'pwd', 'description': 'Report the current workspace directory'})\\nreturn {'denied': denied, 'environmentScrubbed': 'DSH_PYTHON_PTC_HOST_SECRET' not in os.environ, 'sameWorkspace': os.getcwd() == shell['stdout']['text'].strip()}\",\"description\":\"Verify Python file policy and execution context\"}"}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_python_policy","parentCallId":"call_python_policy","subCallId":"call_python_policy:ptc:1","name":"bash","arguments":{"command":"pwd","description":"Report the current workspace directory"}}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"pwd","startedAt":1791427869029,"toolCall":{"callId":"call_python_policy:ptc:1","rootCallId":"call_python_policy"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"pwd","startedAt":1791427869029,"toolCall":{"callId":"call_python_policy:ptc:1","rootCallId":"call_python_policy"},"finishedAt":1791427869039,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"pwd","startedAt":1791427869029,"toolCall":{"callId":"call_python_policy:ptc:1","rootCallId":"call_python_policy"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"pwd","startedAt":1791427869029,"toolCall":{"callId":"call_python_policy:ptc:1","rootCallId":"call_python_policy"},"finishedAt":1791427869039,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_python_policy","parentCallId":"call_python_policy","subCallId":"call_python_policy:ptc:1","name":"bash","arguments":{"command":"pwd","description":"Report the current workspace directory"},"isError":false,"content":[{"type":"text","text":"{{cwd}}\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_python_policy"},"toolCallId":"call_python_policy","content":[{"type":"text","text":"{\n \"denied\": true,\n \"environmentScrubbed\": true,\n \"sameWorkspace\": true\n}"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
diff --git a/snapshots/session/ptc-python-turn/session.v4.jsonl b/snapshots/session/ptc-python-turn/session.v4.jsonl
index 5a170dfd8d..c591c62ce9 100644
--- a/snapshots/session/ptc-python-turn/session.v4.jsonl
+++ b/snapshots/session/ptc-python-turn/session.v4.jsonl
@@ -15,12 +15,12 @@
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to write a single Python run_code program that:\n1. Calls bash tool twice: `echo CODE_ONE` and `echo CODE_TWO`\n2. print exactly `captured output`\n3. Return the two outputs joined with a plus sign\n\nLet me write this."},{"type":"tool-call","id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\":\"out1 = await tools.bash({\\\"command\\\": \\\"echo CODE_ONE\\\", \\\"description\\\": \\\"Print CODE_ONE\\\"})\\nout2 = await tools.bash({\\\"command\\\": \\\"echo CODE_TWO\\\", \\\"description\\\": \\\"Print CODE_TWO\\\"})\\nprint(\\\"captured output\\\")\\ntext1 = out1[\\\"stdout\\\"][\\\"text\\\"].strip()\\ntext2 = out2[\\\"stdout\\\"][\\\"text\\\"].strip()\\nreturn text1 + \\\"+\\\" + text2\",\"description\":\"Run two echo commands and join outputs\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":6152,"outputTokens":214,"cacheReadTokens":0,"reasoningTokens":60},"stream":[{"type":"chunk","time":1791427869964,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427869964,"index":0,"dt":[0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The user wants me to write a single Python run_code program that:\n1. Calls bash tool twice: `echo CODE_ONE` and `echo CODE_TWO`\n2. print exactly `captured output`\n3. Return the two outputs joined with a plus sign\n\nLet me write this.","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""]},{"type":"chunk","time":1791427869965,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427869965,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","args":["{\"code\":\"out1 = await tools.bash({\\\"command\\\": \\\"echo CODE_ONE\\\", \\\"description\\\": \\\"Print CODE_ONE\\\"})\\nout2 = await tools.bash({\\\"command\\\": \\\"echo CODE_TWO\\\", \\\"description\\\": \\\"Print CODE_TWO\\\"})\\nprint(\\\"captured output\\\")\\ntext1 = out1[\\\"stdout\\\"][\\\"text\\\"].strip()\\ntext2 = out2[\\\"stdout\\\"][\\\"text\\\"].strip()\\nreturn text1 + \\\"+\\\" + text2\",\"description\":\"Run two echo commands and join outputs\"}","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","","",""]},{"type":"chunk","time":1791427869966,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to write a single Python run_code program that:\n1. Calls bash tool twice: `echo CODE_ONE` and `echo CODE_TWO`\n2. print exactly `captured output`\n3. Return the two outputs joined with a plus sign\n\nLet me write this."}}},{"type":"chunk","time":1791427869966,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\":\"out1 = await tools.bash({\\\"command\\\": \\\"echo CODE_ONE\\\", \\\"description\\\": \\\"Print CODE_ONE\\\"})\\nout2 = await tools.bash({\\\"command\\\": \\\"echo CODE_TWO\\\", \\\"description\\\": \\\"Print CODE_TWO\\\"})\\nprint(\\\"captured output\\\")\\ntext1 = out1[\\\"stdout\\\"][\\\"text\\\"].strip()\\ntext2 = out2[\\\"stdout\\\"][\\\"text\\\"].strip()\\nreturn text1 + \\\"+\\\" + text2\",\"description\":\"Run two echo commands and join outputs\"}"}}},{"type":"chunk","time":1791427869966,"chunk":{"type":"usage","usage":{"inputTokens":6152,"outputTokens":214,"cacheReadTokens":0,"reasoningTokens":60}}},{"type":"chunk","time":1791427869966,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\":\"out1 = await tools.bash({\\\"command\\\": \\\"echo CODE_ONE\\\", \\\"description\\\": \\\"Print CODE_ONE\\\"})\\nout2 = await tools.bash({\\\"command\\\": \\\"echo CODE_TWO\\\", \\\"description\\\": \\\"Print CODE_TWO\\\"})\\nprint(\\\"captured output\\\")\\ntext1 = out1[\\\"stdout\\\"][\\\"text\\\"].strip()\\ntext2 = out2[\\\"stdout\\\"][\\\"text\\\"].strip()\\nreturn text1 + \\\"+\\\" + text2\",\"description\":\"Run two echo commands and join outputs\"}"}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Print CODE_ONE"}}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427870038,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427870038,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427870040,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427870038,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427870038,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427870040,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Print CODE_ONE"},"isError":false,"content":[{"type":"text","text":"CODE_ONE\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Print CODE_TWO"}}}
-{"type":"job/registered","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427870045,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
-{"type":"job/settled","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427870045,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427870047,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427870045,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
+{"type":"job/settled","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427870045,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427870047,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Print CODE_TWO"},"isError":false,"content":[{"type":"text","text":"CODE_TWO\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"toolCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","content":[{"type":"text","text":"captured output\nCODE_ONE+CODE_TWO"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
diff --git a/snapshots/session/ptc-read-image/session.v4.jsonl b/snapshots/session/ptc-read-image/session.v4.jsonl
index 485dcef09f..2e32c9581a 100644
--- a/snapshots/session/ptc-read-image/session.v4.jsonl
+++ b/snapshots/session/ptc-read-image/session.v4.jsonl
@@ -15,8 +15,8 @@
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"code-image-call","name":"run_code","arguments":"{\"code\":\"const bytes = [137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130];\\nawait tools.bash({ command: \\\"node -e \\\\\\\"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\\\\\\\"\\\", description: \\\"Create a one pixel PNG\\\" });\\nconst image = await tools.read_image({ file_path: \\\"red.png\\\" });\\nreturn image.path;\",\"description\":\"Create and inspect one image\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash-vision-exp"},"id":"{{message:4}}"},"usage":{"inputTokens":3,"outputTokens":3},"stream":[{"type":"chunk","time":1791427871134,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1791427871134,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"code-image-call","name":"run_code","arguments":"{\"code\":\"const bytes = [137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130];\\nawait tools.bash({ command: \\\"node -e \\\\\\\"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\\\\\\\"\\\", description: \\\"Create a one pixel PNG\\\" });\\nconst image = await tools.read_image({ file_path: \\\"red.png\\\" });\\nreturn image.path;\",\"description\":\"Create and inspect one image\"}"}}},{"type":"chunk","time":1791427871135,"chunk":{"type":"usage","usage":{"inputTokens":3,"outputTokens":3}}},{"type":"chunk","time":1791427871135,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"code-image-call","name":"run_code","arguments":"{\"code\":\"const bytes = [137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130];\\nawait tools.bash({ command: \\\"node -e \\\\\\\"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\\\\\\\"\\\", description: \\\"Create a one pixel PNG\\\" });\\nconst image = await tools.read_image({ file_path: \\\"red.png\\\" });\\nreturn image.path;\",\"description\":\"Create and inspect one image\"}"}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"code-image-call","parentCallId":"code-image-call","subCallId":"code-image-call:ptc:1","name":"bash","arguments":{"command":"node -e \"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\"","description":"Create a one pixel PNG"}}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"node -e \"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\"","startedAt":1791427871232,"toolCall":{"callId":"code-image-call:ptc:1","rootCallId":"code-image-call"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"node -e \"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\"","startedAt":1791427871232,"toolCall":{"callId":"code-image-call:ptc:1","rootCallId":"code-image-call"},"finishedAt":1791427871263,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"node -e \"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\"","startedAt":1791427871232,"toolCall":{"callId":"code-image-call:ptc:1","rootCallId":"code-image-call"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"node -e \"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\"","startedAt":1791427871232,"toolCall":{"callId":"code-image-call:ptc:1","rootCallId":"code-image-call"},"finishedAt":1791427871263,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"code-image-call","parentCallId":"code-image-call","subCallId":"code-image-call:ptc:1","name":"bash","arguments":{"command":"node -e \"require('node:fs').writeFileSync('red.png',Buffer.from([137,80,78,71,13,10,26,10,0,0,0,13,73,72,68,82,0,0,0,1,0,0,0,1,8,2,0,0,0,144,119,83,222,0,0,0,12,73,68,65,84,120,156,99,248,207,192,0,0,3,1,1,0,201,254,146,239,0,0,0,0,73,69,78,68,174,66,96,130]));\"","description":"Create a one pixel PNG"},"isError":false,"content":[{"type":"text","text":"(no output)"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"code-image-call","parentCallId":"code-image-call","subCallId":"code-image-call:ptc:2","name":"read_image","arguments":{"file_path":"red.png"}}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"code-image-call","parentCallId":"code-image-call","subCallId":"code-image-call:ptc:2","name":"read_image","arguments":{"file_path":"red.png"},"isError":false,"content":[{"type":"text","text":"{{cwd}}/red.png\nimage\n\nimage/png image, 1x1 px, 69 bytes\n"},{"type":"image","attachment":{"attachmentId":"sha256:b1ff9c8ea3a780bad09b346c423d2d0e46815926879b18e841d928376a946640","mediaType":"image/png","bytes":69,"width":1,"height":1,"name":"red.png"}}],"meta":{"path":"{{cwd}}/red.png"}}}
diff --git a/snapshots/session/ptc-turn/session.v4.jsonl b/snapshots/session/ptc-turn/session.v4.jsonl
index 1dce4c0931..8237c5baf9 100644
--- a/snapshots/session/ptc-turn/session.v4.jsonl
+++ b/snapshots/session/ptc-turn/session.v4.jsonl
@@ -15,12 +15,12 @@
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to write a single run_code program that:\n1. Calls bash tool twice: `echo CODE_ONE` and `echo CODE_TWO`\n2. console.log exactly `captured output`\n3. Return the two outputs joined with a plus sign\n\nLet me write this."},{"type":"tool-call","id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":6152,"outputTokens":214,"cacheReadTokens":0,"reasoningTokens":60},"stream":[{"type":"chunk","time":1791427873414,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427873414,"index":0,"dt":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," write"," a"," single"," run","_code"," program"," that",":\n","1","."," Calls"," bash"," tool"," twice",":"," `","echo"," CODE","_","ONE","`"," and"," `","echo"," CODE","_T","WO","`\n","2","."," console",".log"," exactly"," `","capt","ured"," output","`\n","3","."," Return"," the"," two"," outputs"," joined"," with"," a"," plus"," sign","\n\n","Let"," me"," write"," this","."]},{"type":"chunk","time":1791427873415,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427873415,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","args":["","{","\"","code","\"",": ","\"","\\n","const"," out","1"," ="," await"," tools",".b","ash","({","command",":"," \\\"","echo"," CODE","_","ONE","\\\","," description",":"," \\\"","Print"," CODE","_","ONE","\\\"","});\\n","const"," out","2"," ="," await"," tools",".b","ash","({","command",":"," \\\"","echo"," CODE","_T","WO","\\\","," description",":"," \\\"","Print"," CODE","_T","WO","\\\"","});\\n","console",".log","(\\\"","capt","ured"," output","\\\");\\n","const"," text","1"," ="," out","1",".stdout",".text",".trim","();\\n","const"," text","2"," ="," out","2",".stdout",".text",".trim","();\\n","return"," text","1"," +"," \\\"+","\\\""," +"," text","2",";\\n","\"",", ","\"","description","\"",": ","\"","Run"," two"," echo"," commands"," and"," join"," outputs","\"","}"]},{"type":"chunk","time":1791427873416,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to write a single run_code program that:\n1. Calls bash tool twice: `echo CODE_ONE` and `echo CODE_TWO`\n2. console.log exactly `captured output`\n3. Return the two outputs joined with a plus sign\n\nLet me write this."}}},{"type":"chunk","time":1791427873416,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}}},{"type":"chunk","time":1791427873416,"chunk":{"type":"usage","usage":{"inputTokens":6152,"outputTokens":214,"cacheReadTokens":0,"reasoningTokens":60}}},{"type":"chunk","time":1791427873416,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875","name":"run_code","arguments":"{\"code\": \"\\nconst out1 = await tools.bash({command: \\\"echo CODE_ONE\\\", description: \\\"Print CODE_ONE\\\"});\\nconst out2 = await tools.bash({command: \\\"echo CODE_TWO\\\", description: \\\"Print CODE_TWO\\\"});\\nconsole.log(\\\"captured output\\\");\\nconst text1 = out1.stdout.text.trim();\\nconst text2 = out2.stdout.text.trim();\\nreturn text1 + \\\"+\\\" + text2;\\n\", \"description\": \"Run two echo commands and join outputs\"}"}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Print CODE_ONE"}}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427873508,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427873508,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427873511,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427873508,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ONE","startedAt":1791427873508,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427873511,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:1","name":"bash","arguments":{"command":"echo CODE_ONE","description":"Print CODE_ONE"},"isError":false,"content":[{"type":"text","text":"CODE_ONE\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Print CODE_TWO"}}}
-{"type":"job/registered","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427873516,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
-{"type":"job/settled","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427873516,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427873518,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427873516,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"}}}
+{"type":"job/settled","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_TWO","startedAt":1791427873516,"toolCall":{"callId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"finishedAt":1791427873518,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","parentCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","subCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875:ptc:2","name":"bash","arguments":{"command":"echo CODE_TWO","description":"Print CODE_TWO"},"isError":false,"content":[{"type":"text","text":"CODE_TWO\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_UiQPVqoELyzBZCY5pm1z7875"},"toolCallId":"call_00_UiQPVqoELyzBZCY5pm1z7875","content":[{"type":"text","text":"captured output\nCODE_ONE+CODE_TWO"}],"isError":false,"id":"{{message:5}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
diff --git a/snapshots/session/pwsh-tool-turn/session.v4.jsonl b/snapshots/session/pwsh-tool-turn/session.v4.jsonl
index f8993675b2..b27fd40052 100644
--- a/snapshots/session/pwsh-tool-turn/session.v4.jsonl
+++ b/snapshots/session/pwsh-tool-turn/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Use the pwsh tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a PowerShell command and then reply with \"DONE\". Let me execute it."},{"type":"tool-call","id":"call_00_e0MSVSocL0o4UWjOdG4c2072","name":"pwsh","arguments":"{\"command\": \"[Console]::Out.Write('PWSH_OK')\", \"description\": \"Write PWSH_OK to console\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-pro"},"id":"{{message:4}}"},"usage":{"inputTokens":1255,"outputTokens":99,"cacheReadTokens":0,"reasoningTokens":22},"stream":[{"type":"chunk","time":1791429344301,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791429344302,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," PowerShell"," command"," and"," then"," reply"," with"," \"","D","ONE","\"."," Let"," me"," execute"," it","."]},{"type":"chunk","time":1791429344302,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791429344302,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_e0MSVSocL0o4UWjOdG4c2072","name":"pwsh","args":["","{","\"","command","\"",": ","\"","[","Console","]","::","Out",".Write","('","P","WS","H","_OK","')","\"",", ","\"","description","\"",": ","\"","Write"," P","WS","H","_OK"," to"," console","\"","}"]},{"type":"chunk","time":1791429344302,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a PowerShell command and then reply with \"DONE\". Let me execute it."}}},{"type":"chunk","time":1791429344302,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_e0MSVSocL0o4UWjOdG4c2072","name":"pwsh","arguments":"{\"command\": \"[Console]::Out.Write('PWSH_OK')\", \"description\": \"Write PWSH_OK to console\"}"}}},{"type":"chunk","time":1791429344302,"chunk":{"type":"usage","usage":{"inputTokens":1255,"outputTokens":99,"cacheReadTokens":0,"reasoningTokens":22}}},{"type":"chunk","time":1791429344302,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_e0MSVSocL0o4UWjOdG4c2072","name":"pwsh","arguments":"{\"command\": \"[Console]::Out.Write('PWSH_OK')\", \"description\": \"Write PWSH_OK to console\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"pwsh-1","owner":"{{session:1}}","kind":"pwsh","label":"[Console]::Out.Write('PWSH_OK')","startedAt":1791429344311,"toolCall":{"callId":"call_00_e0MSVSocL0o4UWjOdG4c2072","rootCallId":"call_00_e0MSVSocL0o4UWjOdG4c2072"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"pwsh-1","owner":"{{session:1}}","kind":"pwsh","label":"[Console]::Out.Write('PWSH_OK')","startedAt":1791429344311,"toolCall":{"callId":"call_00_e0MSVSocL0o4UWjOdG4c2072","rootCallId":"call_00_e0MSVSocL0o4UWjOdG4c2072"},"finishedAt":1791429344544,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"pwsh","label":"[Console]::Out.Write('PWSH_OK')","startedAt":1791429344311,"toolCall":{"callId":"call_00_e0MSVSocL0o4UWjOdG4c2072","rootCallId":"call_00_e0MSVSocL0o4UWjOdG4c2072"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"pwsh","label":"[Console]::Out.Write('PWSH_OK')","startedAt":1791429344311,"toolCall":{"callId":"call_00_e0MSVSocL0o4UWjOdG4c2072","rootCallId":"call_00_e0MSVSocL0o4UWjOdG4c2072"},"finishedAt":1791429344544,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_e0MSVSocL0o4UWjOdG4c2072"},"toolCallId":"call_00_e0MSVSocL0o4UWjOdG4c2072","content":[{"type":"text","text":"PWSH_OK"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/session/session-query-spill/session.v4.jsonl b/snapshots/session/session-query-spill/session.v4.jsonl
index d3fbfb81f9..1ea2f84add 100644
--- a/snapshots/session/session-query-spill/session.v4.jsonl
+++ b/snapshots/session/session-query-spill/session.v4.jsonl
@@ -19,8 +19,8 @@
{"type":"step/start","data":{"turn":1,"step":2}}
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_verify_session_query_spill","name":"bash","arguments":"{\"command\":\"file=\\\"/tmp/dsh-acp-snap-035d1d054/session-50830de2ce22/dca7b66e737f-session_event_read.txt\\\"; grep -Fq request/header \\\"$file\\\" && grep -Fq session_event_search \\\"$file\\\" && printf 'SPILL_CANONICAL_OK\\\\n'\",\"description\":\"Verify complete session query spill\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427883752,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427883752,"index":0,"dt":[],"id":"call_verify_session_query_spill","name":"bash","args":["{\"command\":\"file=\\\"/tmp/dsh-acp-snap-035d1d054/session-50830de2ce22/dca7b66e737f-session_event_read.txt\\\"; grep -Fq request/header \\\"$file\\\" && grep -Fq session_event_search \\\"$file\\\" && printf 'SPILL_CANONICAL_OK\\\\n'\",\"description\":\"Verify complete session query spill\"}"]},{"type":"chunk","time":1791427883752,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_verify_session_query_spill","name":"bash","arguments":"{\"command\":\"file=\\\"/tmp/dsh-acp-snap-035d1d054/session-50830de2ce22/dca7b66e737f-session_event_read.txt\\\"; grep -Fq request/header \\\"$file\\\" && grep -Fq session_event_search \\\"$file\\\" && printf 'SPILL_CANONICAL_OK\\\\n'\",\"description\":\"Verify complete session query spill\"}"}}},{"type":"chunk","time":1791427883752,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427883752,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_verify_session_query_spill","name":"bash","arguments":"{\"command\":\"file=\\\"/tmp/dsh-acp-snap-035d1d054/session-50830de2ce22/dca7b66e737f-session_event_read.txt\\\"; grep -Fq request/header \\\"$file\\\" && grep -Fq session_event_search \\\"$file\\\" && printf 'SPILL_CANONICAL_OK\\\\n'\",\"description\":\"Verify complete session query spill\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"file=\"/tmp/dsh-acp-snap-035d1d054/session-50830de2ce22/dca7b66e737f-session_event_read.txt\"; grep -Fq request/header \"$file\" && grep -Fq session_event_search \"$file\" && printf 'SPILL_CANONICAL_OK\\n'","startedAt":1791427883760,"toolCall":{"callId":"call_verify_session_query_spill","rootCallId":"call_verify_session_query_spill"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"file=\"/tmp/dsh-acp-snap-035d1d054/session-50830de2ce22/dca7b66e737f-session_event_read.txt\"; grep -Fq request/header \"$file\" && grep -Fq session_event_search \"$file\" && printf 'SPILL_CANONICAL_OK\\n'","startedAt":1791427883760,"toolCall":{"callId":"call_verify_session_query_spill","rootCallId":"call_verify_session_query_spill"},"finishedAt":1791427883767,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"file=\"/tmp/dsh-acp-snap-035d1d054/session-50830de2ce22/dca7b66e737f-session_event_read.txt\"; grep -Fq request/header \"$file\" && grep -Fq session_event_search \"$file\" && printf 'SPILL_CANONICAL_OK\\n'","startedAt":1791427883760,"toolCall":{"callId":"call_verify_session_query_spill","rootCallId":"call_verify_session_query_spill"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"file=\"/tmp/dsh-acp-snap-035d1d054/session-50830de2ce22/dca7b66e737f-session_event_read.txt\"; grep -Fq request/header \"$file\" && grep -Fq session_event_search \"$file\" && printf 'SPILL_CANONICAL_OK\\n'","startedAt":1791427883760,"toolCall":{"callId":"call_verify_session_query_spill","rootCallId":"call_verify_session_query_spill"},"finishedAt":1791427883767,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"call_verify_session_query_spill"},"toolCallId":"call_verify_session_query_spill","content":[{"type":"text","text":"SPILL_CANONICAL_OK\n"}],"isError":false,"id":"{{message:7}}"},"meta":{"cwd":"{{cwd}}"}},"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/subagent-multi/writer.1.expected.jsonl b/snapshots/session/subagent-multi/writer.1.expected.jsonl
index 4bd4b84061..ed47288a05 100644
--- a/snapshots/session/subagent-multi/writer.1.expected.jsonl
+++ b/snapshots/session/subagent-multi/writer.1.expected.jsonl
@@ -13,6 +13,6 @@
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Reply with exactly the word","messageSeqs":[9],"source":{"kind":"fallback"}}}
-{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ALPHA\" and nothing else."},{"type":"text","text":"ALPHA"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:14}}"},"usage":{"inputTokens":49,"outputTokens":23,"cacheReadTokens":2816,"reasoningTokens":19},"stream":[{"type":"chunk","time":1791434183818,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791434183818,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","AL","P","HA","\""," and"," nothing"," else","."]},{"type":"chunk","time":1791434183818,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791434183818,"index":1,"dt":[0,0],"texts":["AL","P","HA"]},{"type":"chunk","time":1791434183818,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ALPHA\" and nothing else."}}},{"type":"chunk","time":1791434183818,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"ALPHA"}}},{"type":"chunk","time":1791434183818,"chunk":{"type":"usage","usage":{"inputTokens":49,"outputTokens":23,"cacheReadTokens":2816,"reasoningTokens":19}}},{"type":"chunk","time":1791434183818,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ALPHA\" and nothing else."},{"type":"text","text":"ALPHA"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:14}}"},"usage":{"inputTokens":49,"outputTokens":23,"cacheReadTokens":2816,"reasoningTokens":19},"stream":[{"type":"chunk","time":1791444968849,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791444968849,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","AL","P","HA","\""," and"," nothing"," else","."]},{"type":"chunk","time":1791444968849,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791444968849,"index":1,"dt":[0,0],"texts":["AL","P","HA"]},{"type":"chunk","time":1791444968849,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"ALPHA\" and nothing else."}}},{"type":"chunk","time":1791444968849,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"ALPHA"}}},{"type":"chunk","time":1791444968849,"chunk":{"type":"usage","usage":{"inputTokens":49,"outputTokens":23,"cacheReadTokens":2816,"reasoningTokens":19}}},{"type":"chunk","time":1791444968849,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
diff --git a/snapshots/session/subagent-multi/writer.2.expected.jsonl b/snapshots/session/subagent-multi/writer.2.expected.jsonl
index 3cd9ae3c7b..ef94372fa1 100644
--- a/snapshots/session/subagent-multi/writer.2.expected.jsonl
+++ b/snapshots/session/subagent-multi/writer.2.expected.jsonl
@@ -13,6 +13,6 @@
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Reply with exactly the word","messageSeqs":[9],"source":{"kind":"fallback"}}}
-{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"BETA\" and nothing else."},{"type":"text","text":"BETA"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:18}}"},"usage":{"inputTokens":48,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791434183886,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791434183887,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","B","ETA","\""," and"," nothing"," else","."]},{"type":"chunk","time":1791434183887,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791434183887,"index":1,"dt":[0],"texts":["B","ETA"]},{"type":"chunk","time":1791434183887,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"BETA\" and nothing else."}}},{"type":"chunk","time":1791434183887,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"BETA"}}},{"type":"chunk","time":1791434183887,"chunk":{"type":"usage","usage":{"inputTokens":48,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18}}},{"type":"chunk","time":1791434183887,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"BETA\" and nothing else."},{"type":"text","text":"BETA"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:18}}"},"usage":{"inputTokens":48,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18},"stream":[{"type":"chunk","time":1791444968915,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791444968915,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","B","ETA","\""," and"," nothing"," else","."]},{"type":"chunk","time":1791444968915,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791444968915,"index":1,"dt":[0],"texts":["B","ETA"]},{"type":"chunk","time":1791444968915,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"BETA\" and nothing else."}}},{"type":"chunk","time":1791444968915,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"BETA"}}},{"type":"chunk","time":1791444968915,"chunk":{"type":"usage","usage":{"inputTokens":48,"outputTokens":21,"cacheReadTokens":2816,"reasoningTokens":18}}},{"type":"chunk","time":1791444968915,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
diff --git a/snapshots/session/subagent-multi/writer.expected.jsonl b/snapshots/session/subagent-multi/writer.expected.jsonl
index 8229f362c6..519727be11 100644
--- a/snapshots/session/subagent-multi/writer.expected.jsonl
+++ b/snapshots/session/subagent-multi/writer.expected.jsonl
@@ -12,24 +12,24 @@
{"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":"Use the subagent tool TWICE,","messageSeqs":[8],"source":{"kind":"fallback"}}}
-{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to use the subagent tool twice, sequentially (one at a time). First subagent should reply with \"ALPHA\", second with \"BETA\". After both return, I reply with \"PARENT_DONE\"."},{"type":"tool-call","id":"call_00_7zDCCjdsQgrk5LR2bAEQ1010","name":"subagent","arguments":"{\"description\": \"Return ALPHA only\", \"prompt\": \"Reply with exactly the word ALPHA and nothing else.\", \"run_in_background\": false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2938,"outputTokens":124,"cacheReadTokens":0,"reasoningTokens":49},"stream":[{"type":"chunk","time":1791434183772,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791434183772,"index":0,"dt":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," use"," the"," sub","agent"," tool"," twice",","," sequentially"," (","one"," at"," a"," time",")."," First"," sub","agent"," should"," reply"," with"," \"","AL","P","HA","\","," second"," with"," \"","B","ETA","\"."," After"," both"," return",","," I"," reply"," with"," \"","PAR","ENT","_D","ONE","\"."]},{"type":"chunk","time":1791434183774,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791434183774,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_7zDCCjdsQgrk5LR2bAEQ1010","name":"subagent","args":["","{","\"","description","\"",": ","\"","Return"," AL","P","HA"," only","\"",", ","\"","prom","pt","\"",": ","\"","Reply"," with"," exactly"," the"," word"," AL","P","HA"," and"," nothing"," else",".","\"",", \"run_in_background\": false}"]},{"type":"chunk","time":1791434183774,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to use the subagent tool twice, sequentially (one at a time). First subagent should reply with \"ALPHA\", second with \"BETA\". After both return, I reply with \"PARENT_DONE\"."}}},{"type":"chunk","time":1791434183774,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_7zDCCjdsQgrk5LR2bAEQ1010","name":"subagent","arguments":"{\"description\": \"Return ALPHA only\", \"prompt\": \"Reply with exactly the word ALPHA and nothing else.\", \"run_in_background\": false}"}}},{"type":"chunk","time":1791434183774,"chunk":{"type":"usage","usage":{"inputTokens":2938,"outputTokens":124,"cacheReadTokens":0,"reasoningTokens":49}}},{"type":"chunk","time":1791434183774,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to use the subagent tool twice, sequentially (one at a time). First subagent should reply with \"ALPHA\", second with \"BETA\". After both return, I reply with \"PARENT_DONE\"."},{"type":"tool-call","id":"call_00_7zDCCjdsQgrk5LR2bAEQ1010","name":"subagent","arguments":"{\"description\": \"Return ALPHA only\", \"prompt\": \"Reply with exactly the word ALPHA and nothing else.\", \"run_in_background\": false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2938,"outputTokens":124,"cacheReadTokens":0,"reasoningTokens":49},"stream":[{"type":"chunk","time":1791444968802,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791444968803,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," use"," the"," sub","agent"," tool"," twice",","," sequentially"," (","one"," at"," a"," time",")."," First"," sub","agent"," should"," reply"," with"," \"","AL","P","HA","\","," second"," with"," \"","B","ETA","\"."," After"," both"," return",","," I"," reply"," with"," \"","PAR","ENT","_D","ONE","\"."]},{"type":"chunk","time":1791444968804,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791444968804,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_7zDCCjdsQgrk5LR2bAEQ1010","name":"subagent","args":["","{","\"","description","\"",": ","\"","Return"," AL","P","HA"," only","\"",", ","\"","prom","pt","\"",": ","\"","Reply"," with"," exactly"," the"," word"," AL","P","HA"," and"," nothing"," else",".","\"",", \"run_in_background\": false}"]},{"type":"chunk","time":1791444968804,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to use the subagent tool twice, sequentially (one at a time). First subagent should reply with \"ALPHA\", second with \"BETA\". After both return, I reply with \"PARENT_DONE\"."}}},{"type":"chunk","time":1791444968804,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_7zDCCjdsQgrk5LR2bAEQ1010","name":"subagent","arguments":"{\"description\": \"Return ALPHA only\", \"prompt\": \"Reply with exactly the word ALPHA and nothing else.\", \"run_in_background\": false}"}}},{"type":"chunk","time":1791444968804,"chunk":{"type":"usage","usage":{"inputTokens":2938,"outputTokens":124,"cacheReadTokens":0,"reasoningTokens":49}}},{"type":"chunk","time":1791444968804,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_7zDCCjdsQgrk5LR2bAEQ1010","name":"subagent","arguments":"{\"description\": \"Return ALPHA only\", \"prompt\": \"Reply with exactly the word ALPHA and nothing else.\", \"run_in_background\": false}"}}
-{"type":"subagent/catalog","data":{"version":0,"childId":"{{session:2}}","childCreatedAt":1791434183781,"mode":"continuable","label":"Return ALPHA only"}}
+{"type":"subagent/catalog","data":{"version":0,"childId":"{{session:2}}","childCreatedAt":1791444968811,"mode":"continuable","label":"Return ALPHA only"}}
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"Background subagent {{session:2}} finished and will do no further work unless you send it more."},{"type":"text","text":"Its closing message:"},{"type":"text","text":"ALPHA"}],"source":{"kind":"subagent-settled","form":"notice","summary":"Background subagent {{session:2}} finished and will do no further work unless you send it more.","senderSessionId":"{{session:2}}"},"role":"user","id":"{{message:5}}"}]}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_7zDCCjdsQgrk5LR2bAEQ1010"},"toolCallId":"call_00_7zDCCjdsQgrk5LR2bAEQ1010","content":[{"type":"text","text":"started subagent {{session:2}}"}],"isError":false,"id":"{{message:6}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","data":{"turn":1,"step":2}}
{"type":"user/message","data":{"content":[{"type":"text","text":"Background subagent {{session:2}} finished and will do no further work unless you send it more."},{"type":"text","text":"Its closing message:"},{"type":"text","text":"ALPHA"}],"source":{"kind":"subagent-settled","form":"notice","summary":"Background subagent {{session:2}} finished and will do no further work unless you send it more.","senderSessionId":"{{session:2}}"},"role":"user","id":"{{message:5}}"},"surfaceOp":"append"}
-{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"First subagent returned \"ALPHA\". Now I'll call the second subagent to return \"BETA\"."},{"type":"tool-call","id":"call_00_FudNKuJ0fchSptGy3Scw1411","name":"subagent","arguments":"{\"description\": \"Return BETA only\", \"prompt\": \"Reply with exactly the word BETA and nothing else.\", \"run_in_background\": false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":133,"outputTokens":96,"cacheReadTokens":2944,"reasoningTokens":23},"stream":[{"type":"chunk","time":1791434183845,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791434183845,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["First"," sub","agent"," returned"," \"","AL","P","HA","\"."," Now"," I","'ll"," call"," the"," second"," sub","agent"," to"," return"," \"","B","ETA","\"."]},{"type":"chunk","time":1791434183845,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791434183845,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_FudNKuJ0fchSptGy3Scw1411","name":"subagent","args":["","{","\"","description","\"",": ","\"","Return"," B","ETA"," only","\"",", ","\"","prom","pt","\"",": ","\"","Reply"," with"," exactly"," the"," word"," B","ETA"," and"," nothing"," else",".","\"",", \"run_in_background\": false}"]},{"type":"chunk","time":1791434183845,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"First subagent returned \"ALPHA\". Now I'll call the second subagent to return \"BETA\"."}}},{"type":"chunk","time":1791434183845,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_FudNKuJ0fchSptGy3Scw1411","name":"subagent","arguments":"{\"description\": \"Return BETA only\", \"prompt\": \"Reply with exactly the word BETA and nothing else.\", \"run_in_background\": false}"}}},{"type":"chunk","time":1791434183845,"chunk":{"type":"usage","usage":{"inputTokens":133,"outputTokens":96,"cacheReadTokens":2944,"reasoningTokens":23}}},{"type":"chunk","time":1791434183845,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"First subagent returned \"ALPHA\". Now I'll call the second subagent to return \"BETA\"."},{"type":"tool-call","id":"call_00_FudNKuJ0fchSptGy3Scw1411","name":"subagent","arguments":"{\"description\": \"Return BETA only\", \"prompt\": \"Reply with exactly the word BETA and nothing else.\", \"run_in_background\": false}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:7}}"},"usage":{"inputTokens":133,"outputTokens":96,"cacheReadTokens":2944,"reasoningTokens":23},"stream":[{"type":"chunk","time":1791444968872,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791444968872,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["First"," sub","agent"," returned"," \"","AL","P","HA","\"."," Now"," I","'ll"," call"," the"," second"," sub","agent"," to"," return"," \"","B","ETA","\"."]},{"type":"chunk","time":1791444968872,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791444968872,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_FudNKuJ0fchSptGy3Scw1411","name":"subagent","args":["","{","\"","description","\"",": ","\"","Return"," B","ETA"," only","\"",", ","\"","prom","pt","\"",": ","\"","Reply"," with"," exactly"," the"," word"," B","ETA"," and"," nothing"," else",".","\"",", \"run_in_background\": false}"]},{"type":"chunk","time":1791444968872,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"First subagent returned \"ALPHA\". Now I'll call the second subagent to return \"BETA\"."}}},{"type":"chunk","time":1791444968872,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_FudNKuJ0fchSptGy3Scw1411","name":"subagent","arguments":"{\"description\": \"Return BETA only\", \"prompt\": \"Reply with exactly the word BETA and nothing else.\", \"run_in_background\": false}"}}},{"type":"chunk","time":1791444968872,"chunk":{"type":"usage","usage":{"inputTokens":133,"outputTokens":96,"cacheReadTokens":2944,"reasoningTokens":23}}},{"type":"chunk","time":1791444968872,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_00_FudNKuJ0fchSptGy3Scw1411","name":"subagent","arguments":"{\"description\": \"Return BETA only\", \"prompt\": \"Reply with exactly the word BETA and nothing else.\", \"run_in_background\": false}"}}
-{"type":"subagent/catalog","data":{"version":0,"childId":"{{session:3}}","childCreatedAt":1791434183852,"mode":"continuable","label":"Return BETA only"}}
+{"type":"subagent/catalog","data":{"version":0,"childId":"{{session:3}}","childCreatedAt":1791444968878,"mode":"continuable","label":"Return BETA only"}}
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"inserted":[{"content":[{"type":"text","text":"Background subagent {{session:3}} finished and will do no further work unless you send it more."},{"type":"text","text":"Its closing message:"},{"type":"text","text":"BETA"}],"source":{"kind":"subagent-settled","form":"notice","summary":"Background subagent {{session:3}} finished and will do no further work unless you send it more.","senderSessionId":"{{session:3}}"},"role":"user","id":"{{message:8}}"}]}}
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_FudNKuJ0fchSptGy3Scw1411"},"toolCallId":"call_00_FudNKuJ0fchSptGy3Scw1411","content":[{"type":"text","text":"started subagent {{session:3}}"}],"isError":false,"id":"{{message:9}}"}},"sourceEventSeqs":[23],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":2}}
{"type":"agent/inbox/spliced","data":{"target":"next-step","start":0,"removedCount":1,"inserted":[]}}
{"type":"step/start","data":{"turn":1,"step":3}}
{"type":"user/message","data":{"content":[{"type":"text","text":"Background subagent {{session:3}} finished and will do no further work unless you send it more."},{"type":"text","text":"Its closing message:"},{"type":"text","text":"BETA"}],"source":{"kind":"subagent-settled","form":"notice","summary":"Background subagent {{session:3}} finished and will do no further work unless you send it more.","senderSessionId":"{{session:3}}"},"role":"user","id":"{{message:8}}"},"surfaceOp":"append"}
-{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"reasoning","text":"Both subagents have returned: first with \"ALPHA\", second with \"BETA\". Now I should reply with \"PARENT_DONE\"."},{"type":"text","text":"PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:10}}"},"usage":{"inputTokens":115,"outputTokens":35,"cacheReadTokens":3072,"reasoningTokens":30},"stream":[{"type":"chunk","time":1791434183906,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791434183907,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["Both"," sub","agents"," have"," returned",":"," first"," with"," \"","AL","P","HA","\","," second"," with"," \"","B","ETA","\"."," Now"," I"," should"," reply"," with"," \"","PAR","ENT","_D","ONE","\"."]},{"type":"chunk","time":1791434183907,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791434183907,"index":1,"dt":[0,0,0],"texts":["PAR","ENT","_D","ONE"]},{"type":"chunk","time":1791434183907,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"Both subagents have returned: first with \"ALPHA\", second with \"BETA\". Now I should reply with \"PARENT_DONE\"."}}},{"type":"chunk","time":1791434183907,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"PARENT_DONE"}}},{"type":"chunk","time":1791434183907,"chunk":{"type":"usage","usage":{"inputTokens":115,"outputTokens":35,"cacheReadTokens":3072,"reasoningTokens":30}}},{"type":"chunk","time":1791434183907,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"reasoning","text":"Both subagents have returned: first with \"ALPHA\", second with \"BETA\". Now I should reply with \"PARENT_DONE\"."},{"type":"text","text":"PARENT_DONE"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:10}}"},"usage":{"inputTokens":115,"outputTokens":35,"cacheReadTokens":3072,"reasoningTokens":30},"stream":[{"type":"chunk","time":1791444968937,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791444968937,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["Both"," sub","agents"," have"," returned",":"," first"," with"," \"","AL","P","HA","\","," second"," with"," \"","B","ETA","\"."," Now"," I"," should"," reply"," with"," \"","PAR","ENT","_D","ONE","\"."]},{"type":"chunk","time":1791444968937,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791444968937,"index":1,"dt":[0,0,0],"texts":["PAR","ENT","_D","ONE"]},{"type":"chunk","time":1791444968937,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"Both subagents have returned: first with \"ALPHA\", second with \"BETA\". Now I should reply with \"PARENT_DONE\"."}}},{"type":"chunk","time":1791444968937,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"PARENT_DONE"}}},{"type":"chunk","time":1791444968937,"chunk":{"type":"usage","usage":{"inputTokens":115,"outputTokens":35,"cacheReadTokens":3072,"reasoningTokens":30}}},{"type":"chunk","time":1791444968937,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":3}}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
diff --git a/snapshots/session/text-turn/writer.expected.jsonl b/snapshots/session/text-turn/writer.expected.jsonl
index da9c7cbee9..baab2ac720 100644
--- a/snapshots/session/text-turn/writer.expected.jsonl
+++ b/snapshots/session/text-turn/writer.expected.jsonl
@@ -12,6 +12,6 @@
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"initial"}}
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash"}}
{"type":"session/title","data":{"title":"Reply with exactly the word:","messageSeqs":[8],"source":{"kind":"fallback"}}}
-{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"PONG\" and not use any tools."},{"type":"text","text":"PONG"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":3091,"outputTokens":23,"cacheReadTokens":0,"reasoningTokens":20},"stream":[{"type":"chunk","time":1791434191073,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791434191073,"index":0,"dt":[0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","P","ONG","\""," and"," not"," use"," any"," tools","."]},{"type":"chunk","time":1791434191074,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791434191074,"index":1,"dt":[0],"texts":["P","ONG"]},{"type":"chunk","time":1791434191074,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"PONG\" and not use any tools."}}},{"type":"chunk","time":1791434191074,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"PONG"}}},{"type":"chunk","time":1791434191074,"chunk":{"type":"usage","usage":{"inputTokens":3091,"outputTokens":23,"cacheReadTokens":0,"reasoningTokens":20}}},{"type":"chunk","time":1791434191074,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
+{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to reply with exactly the word \"PONG\" and not use any tools."},{"type":"text","text":"PONG"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":3091,"outputTokens":23,"cacheReadTokens":0,"reasoningTokens":20},"stream":[{"type":"chunk","time":1791444976519,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791444976519,"index":0,"dt":[1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," reply"," with"," exactly"," the"," word"," \"","P","ONG","\""," and"," not"," use"," any"," tools","."]},{"type":"chunk","time":1791444976520,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1791444976520,"index":1,"dt":[0],"texts":["P","ONG"]},{"type":"chunk","time":1791444976520,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to reply with exactly the word \"PONG\" and not use any tools."}}},{"type":"chunk","time":1791444976520,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"PONG"}}},{"type":"chunk","time":1791444976520,"chunk":{"type":"usage","usage":{"inputTokens":3091,"outputTokens":23,"cacheReadTokens":0,"reasoningTokens":20}}},{"type":"chunk","time":1791444976520,"chunk":{"type":"finish","reason":{"kind":"stop"}}}]},"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"turn/end","data":{"turn":1,"reason":{"kind":"completed"}}}
diff --git a/snapshots/session/tool-call-turn/session.v4.jsonl b/snapshots/session/tool-call-turn/session.v4.jsonl
index 36bbdd51d6..11aa5bc6f0 100644
--- a/snapshots/session/tool-call-turn/session.v4.jsonl
+++ b/snapshots/session/tool-call-turn/session.v4.jsonl
@@ -14,8 +14,8 @@
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a specific bash command and then reply with DONE."},{"type":"tool-call","id":"call_00_Rn2Mz1y8uZN62ukEXiNO2077","name":"bash","arguments":"{\"command\": \"echo SNAPSHOT_OK\", \"description\": \"Run echo SNAPSHOT_OK\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":2879,"outputTokens":89,"cacheReadTokens":0,"reasoningTokens":17},"stream":[{"type":"chunk","time":1791427900979,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791427900979,"index":0,"dt":[0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0],"texts":["The"," user"," wants"," me"," to"," run"," a"," specific"," bash"," command"," and"," then"," reply"," with"," D","ONE","."]},{"type":"chunk","time":1791427900980,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427900980,"index":1,"dt":[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],"id":"call_00_Rn2Mz1y8uZN62ukEXiNO2077","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," S","NA","PS","H","OT","_OK","\"",", ","\"","description","\"",": ","\"","Run"," echo"," S","NA","PS","H","OT","_OK","\"","}"]},{"type":"chunk","time":1791427900980,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a specific bash command and then reply with DONE."}}},{"type":"chunk","time":1791427900980,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_Rn2Mz1y8uZN62ukEXiNO2077","name":"bash","arguments":"{\"command\": \"echo SNAPSHOT_OK\", \"description\": \"Run echo SNAPSHOT_OK\"}"}}},{"type":"chunk","time":1791427900980,"chunk":{"type":"usage","usage":{"inputTokens":2879,"outputTokens":89,"cacheReadTokens":0,"reasoningTokens":17}}},{"type":"chunk","time":1791427900980,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_Rn2Mz1y8uZN62ukEXiNO2077","name":"bash","arguments":"{\"command\": \"echo SNAPSHOT_OK\", \"description\": \"Run echo SNAPSHOT_OK\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo SNAPSHOT_OK","startedAt":1791427900990,"toolCall":{"callId":"call_00_Rn2Mz1y8uZN62ukEXiNO2077","rootCallId":"call_00_Rn2Mz1y8uZN62ukEXiNO2077"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo SNAPSHOT_OK","startedAt":1791427900990,"toolCall":{"callId":"call_00_Rn2Mz1y8uZN62ukEXiNO2077","rootCallId":"call_00_Rn2Mz1y8uZN62ukEXiNO2077"},"finishedAt":1791427900991,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo SNAPSHOT_OK","startedAt":1791427900990,"toolCall":{"callId":"call_00_Rn2Mz1y8uZN62ukEXiNO2077","rootCallId":"call_00_Rn2Mz1y8uZN62ukEXiNO2077"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo SNAPSHOT_OK","startedAt":1791427900990,"toolCall":{"callId":"call_00_Rn2Mz1y8uZN62ukEXiNO2077","rootCallId":"call_00_Rn2Mz1y8uZN62ukEXiNO2077"},"finishedAt":1791427900991,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_Rn2Mz1y8uZN62ukEXiNO2077"},"toolCallId":"call_00_Rn2Mz1y8uZN62ukEXiNO2077","content":[{"type":"text","text":"SNAPSHOT_OK\n"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/session/working-directory-consumers/session.v4.jsonl b/snapshots/session/working-directory-consumers/session.v4.jsonl
index 8b3eb20729..0dc1a492e4 100644
--- a/snapshots/session/working-directory-consumers/session.v4.jsonl
+++ b/snapshots/session/working-directory-consumers/session.v4.jsonl
@@ -49,8 +49,8 @@
{"type":"step/start","data":{"turn":1,"step":6}}
{"type":"assistant/message","data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"tool-call","id":"directory-6","name":"bash","arguments":"{\"command\":\"cd .. && rm -rf second\",\"description\":\"Remove the selected directory to exercise recovery\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:18}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427907972,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1791427907972,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"directory-6","name":"bash","arguments":"{\"command\":\"cd .. && rm -rf second\",\"description\":\"Remove the selected directory to exercise recovery\"}"}}},{"type":"chunk","time":1791427907972,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427907972,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":6,"callId":"directory-6","name":"bash","arguments":"{\"command\":\"cd .. && rm -rf second\",\"description\":\"Remove the selected directory to exercise recovery\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"cd .. && rm -rf second","startedAt":1791427907979,"toolCall":{"callId":"directory-6","rootCallId":"directory-6"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"cd .. && rm -rf second","startedAt":1791427907979,"toolCall":{"callId":"directory-6","rootCallId":"directory-6"},"finishedAt":1791427907984,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"cd .. && rm -rf second","startedAt":1791427907979,"toolCall":{"callId":"directory-6","rootCallId":"directory-6"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"cd .. && rm -rf second","startedAt":1791427907979,"toolCall":{"callId":"directory-6","rootCallId":"directory-6"},"finishedAt":1791427907984,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":6,"message":{"role":"tool","source":{"kind":"tool","callId":"directory-6"},"toolCallId":"directory-6","content":[{"type":"text","text":"(no output)"}],"isError":false,"id":"{{message:19}}"},"meta":{"cwd":"{{cwd}}/second"}},"sourceEventSeqs":[49],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":6}}
{"type":"working-directory/change","data":{"cwd":"{{cwd}}"}}
diff --git a/snapshots/session/working-directory/session.v4.jsonl b/snapshots/session/working-directory/session.v4.jsonl
index 6635d11deb..1fe3bb1186 100644
--- a/snapshots/session/working-directory/session.v4.jsonl
+++ b/snapshots/session/working-directory/session.v4.jsonl
@@ -49,8 +49,8 @@
{"type":"step/start","data":{"turn":1,"step":6}}
{"type":"assistant/message","data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"tool-call","id":"directory-6","name":"bash","arguments":"{\"command\":\"cd .. && rm -rf second\",\"description\":\"Remove the selected directory to exercise recovery\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:18}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427906205,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1791427906205,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"directory-6","name":"bash","arguments":"{\"command\":\"cd .. && rm -rf second\",\"description\":\"Remove the selected directory to exercise recovery\"}"}}},{"type":"chunk","time":1791427906205,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427906205,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":6,"callId":"directory-6","name":"bash","arguments":"{\"command\":\"cd .. && rm -rf second\",\"description\":\"Remove the selected directory to exercise recovery\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"cd .. && rm -rf second","startedAt":1791427906213,"toolCall":{"callId":"directory-6","rootCallId":"directory-6"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"cd .. && rm -rf second","startedAt":1791427906213,"toolCall":{"callId":"directory-6","rootCallId":"directory-6"},"finishedAt":1791427906218,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"cd .. && rm -rf second","startedAt":1791427906213,"toolCall":{"callId":"directory-6","rootCallId":"directory-6"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"cd .. && rm -rf second","startedAt":1791427906213,"toolCall":{"callId":"directory-6","rootCallId":"directory-6"},"finishedAt":1791427906218,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":6,"message":{"role":"tool","source":{"kind":"tool","callId":"directory-6"},"toolCallId":"directory-6","content":[{"type":"text","text":"(no output)"}],"isError":false,"id":"{{message:19}}"},"meta":{"cwd":"{{cwd}}/second"}},"sourceEventSeqs":[49],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":6}}
{"type":"working-directory/change","data":{"cwd":"{{cwd}}"}}
diff --git a/snapshots/session/workspace-edit/session.v4.jsonl b/snapshots/session/workspace-edit/session.v4.jsonl
index 19cfc88ee0..a9517ec630 100644
--- a/snapshots/session/workspace-edit/session.v4.jsonl
+++ b/snapshots/session/workspace-edit/session.v4.jsonl
@@ -14,15 +14,15 @@
{"type":"session/title","data":{"title":"A file named greeting.txt in","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"tool-call","id":"workspace-append","name":"bash","arguments":"{\"command\":\"printf 'WORLD\\n' >> greeting.txt\",\"description\":\"Append WORLD to greeting.txt\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:4}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427909590,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427909590,"index":0,"dt":[],"id":"workspace-append","name":"bash","args":["{\"command\":\"printf 'WORLD\\n' >> greeting.txt\",\"description\":\"Append WORLD to greeting.txt\"}"]},{"type":"chunk","time":1791427909590,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"workspace-append","name":"bash","arguments":"{\"command\":\"printf 'WORLD\\n' >> greeting.txt\",\"description\":\"Append WORLD to greeting.txt\"}"}}},{"type":"chunk","time":1791427909590,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427909590,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"workspace-append","name":"bash","arguments":"{\"command\":\"printf 'WORLD\\n' >> greeting.txt\",\"description\":\"Append WORLD to greeting.txt\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'WORLD\n' >> greeting.txt","startedAt":1791427909597,"toolCall":{"callId":"workspace-append","rootCallId":"workspace-append"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf 'WORLD\n' >> greeting.txt","startedAt":1791427909597,"toolCall":{"callId":"workspace-append","rootCallId":"workspace-append"},"finishedAt":1791427909599,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'WORLD\n' >> greeting.txt","startedAt":1791427909597,"toolCall":{"callId":"workspace-append","rootCallId":"workspace-append"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf 'WORLD\n' >> greeting.txt","startedAt":1791427909597,"toolCall":{"callId":"workspace-append","rootCallId":"workspace-append"},"finishedAt":1791427909599,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"workspace-append"},"toolCallId":"workspace-append","content":[{"type":"text","text":"(no output)"}],"isError":false,"id":"{{message:5}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[14],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"tool-call","id":"workspace-read","name":"bash","arguments":"{\"command\":\"cat greeting.txt\",\"description\":\"Read greeting.txt to confirm\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:6}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427909613,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791427909613,"index":0,"dt":[],"id":"workspace-read","name":"bash","args":["{\"command\":\"cat greeting.txt\",\"description\":\"Read greeting.txt to confirm\"}"]},{"type":"chunk","time":1791427909613,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"workspace-read","name":"bash","arguments":"{\"command\":\"cat greeting.txt\",\"description\":\"Read greeting.txt to confirm\"}"}}},{"type":"chunk","time":1791427909613,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427909613,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"workspace-read","name":"bash","arguments":"{\"command\":\"cat greeting.txt\",\"description\":\"Read greeting.txt to confirm\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"cat greeting.txt","startedAt":1791427909618,"toolCall":{"callId":"workspace-read","rootCallId":"workspace-read"}}}
-{"type":"job/settled","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"cat greeting.txt","startedAt":1791427909618,"toolCall":{"callId":"workspace-read","rootCallId":"workspace-read"},"finishedAt":1791427909622,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"cat greeting.txt","startedAt":1791427909618,"toolCall":{"callId":"workspace-read","rootCallId":"workspace-read"}}}
+{"type":"job/settled","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"cat greeting.txt","startedAt":1791427909618,"toolCall":{"callId":"workspace-read","rootCallId":"workspace-read"},"finishedAt":1791427909622,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"workspace-read"},"toolCallId":"workspace-read","content":[{"type":"text","text":"hello\nWORLD\n"}],"isError":false,"id":"{{message:7}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[21],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":2}}
{"type":"step/start","data":{"turn":1,"step":3}}
diff --git a/snapshots/session/worktree/session.v4.jsonl b/snapshots/session/worktree/session.v4.jsonl
index c906a15b74..6346b64954 100644
--- a/snapshots/session/worktree/session.v4.jsonl
+++ b/snapshots/session/worktree/session.v4.jsonl
@@ -49,8 +49,8 @@
{"type":"step/start","data":{"turn":1,"step":6}}
{"type":"assistant/message","data":{"turn":1,"step":6,"message":{"role":"assistant","content":[{"type":"tool-call","id":"worktree-6","name":"bash","arguments":"{\"command\":\"git status --porcelain=v1\",\"description\":\"Inspect the original checkout\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:18}}"},"usage":{"inputTokens":10,"outputTokens":5},"stream":[{"type":"chunk","time":1791427910661,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"chunk","time":1791427910661,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"worktree-6","name":"bash","arguments":"{\"command\":\"git status --porcelain=v1\",\"description\":\"Inspect the original checkout\"}"}}},{"type":"chunk","time":1791427910661,"chunk":{"type":"usage","usage":{"inputTokens":10,"outputTokens":5}}},{"type":"chunk","time":1791427910661,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":6,"callId":"worktree-6","name":"bash","arguments":"{\"command\":\"git status --porcelain=v1\",\"description\":\"Inspect the original checkout\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"git status --porcelain=v1","startedAt":1791427910667,"toolCall":{"callId":"worktree-6","rootCallId":"worktree-6"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"git status --porcelain=v1","startedAt":1791427910667,"toolCall":{"callId":"worktree-6","rootCallId":"worktree-6"},"finishedAt":1791427910678,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"git status --porcelain=v1","startedAt":1791427910667,"toolCall":{"callId":"worktree-6","rootCallId":"worktree-6"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"git status --porcelain=v1","startedAt":1791427910667,"toolCall":{"callId":"worktree-6","rootCallId":"worktree-6"},"finishedAt":1791427910678,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":6,"message":{"role":"tool","source":{"kind":"tool","callId":"worktree-6"},"toolCallId":"worktree-6","content":[{"type":"text","text":" M tracked.txt\n?? local.txt\n"}],"isError":false,"id":"{{message:19}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[49],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":6}}
{"type":"step/start","data":{"turn":1,"step":7}}
diff --git a/snapshots/web/approval-composer/session.v4.jsonl b/snapshots/web/approval-composer/session.v4.jsonl
index 2aaadb0684..8a050684c7 100644
--- a/snapshots/web/approval-composer/session.v4.jsonl
+++ b/snapshots/web/approval-composer/session.v4.jsonl
@@ -21,8 +21,8 @@
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"approval-write","name":"bash","arguments":"{\"command\":\"echo 'tok63z tokc7y tokibx tokofw tokujv tok10nu tok16rt tok1cvs tok1izr tok1p3q tok1v7p tok21bo tok2a4 tok8e3 tokei2 tokkm1 tokqq0 tokwtz tok12xy tok191x tok1f5w tok1l9v tok1rdu tok1xht tok23ls tok4k8 tokao7 tokgs6 tokmw5 tokt04 tokz43 tok1582 tok1bc1 tok1hg0 tok1njz tok1tny tok1zrx tokqd tok6uc tokcyb tokj2a tokp69 tokva8 tok11e7 tok17i6 tok1dm5 tok1jq4 tok1pu3 tok1vy2 tok2221 tok30h tok94g tokf8f toklce tokrgd tokxkc tok13ob tok19sa tok1fw9 tok1m08 tok1s47 tok1y86 tok24c5 tok5al tokbek tokhij toknmi toktqh tokzug tok15yf tok1c2e tok1i6d tok1oac tok1ueb tok20ia tok1gq tok7kp tokdoo tokjsn tokpwm tokw0l tok124k tok188j tok1eci tok1kgh tok1qkg tok1wof tok22se tok3qu tok9ut tokfys tokm2r toks6q tokyap tok14eo tok1ain tok1gmm tok1mql tok1suk tok1yyj tok252i tok60y tokc4x toki8w tokocv tokugu tok10kt tok16os tok1csr tok1iwq tok1p0p tok1v4o tok218n tok273 tok8b2 tokef1 tokkj0 tokqmz tokwqy tok12ux tok18yw tok1f2v tok1l6u tok1rat tok1xes tok23ir tok4h7 tokal6 tokgp5 tokmt4 toksx3 tokz12 tok1551 tok1b90 tok1hcz tok1ngy tok1tkx tok1zow toknc tok6rb tokcva tokiz9 tokp38 tokv77 tok11b6 tok17f5 tok1dj4 tok1jn3 tok1pr2 tok1vv1 tok21z0 tok2xg tok91f tokf5e tokl9d tokrdc tokxhb tok13la tok19p9 tok1ft8 tok1lx7 tok1s16 tok1y55 tok2494 tok57k tokbbj tokhfi toknjh toktng tokzrf tok15ve tok1bzd tok1i3c tok1o7b tok1uba tok20f9 tok1dp tok7ho tokdln tokjpm tokptl tokvxk tok121j tok185i tok1e9h tok1kdg tok1qhf tok1wle tok22pd tok3nt tok9rs tokfvr toklzq toks3p toky7o tok14bn tok1afm tok1gjl tok1mnk tok1srj tok1yvi tok24zh tok5xx tokc1w toki5v toko9u tokudt tok10hs tok16lr tok1cpq tok1itp tok1oxo tok1v1n tok215m tok242 tok881 tokec0 tokkfz tokqjy tokwnx' > notes.txt\",\"description\":\"Write notes.txt with the specified text\",\"sandbox_permissions\":\"workspace-write\",\"justification\":\"Need to write the notes.txt file as requested by the user.\"}"}}
{"type":"approval/asked","data":{"id":"{{approval:1}}","toolName":"bash","callId":"approval-write","reason":"escalate sandbox to workspace-write: Need to write the notes.txt file as requested by the user."}}
{"type":"approval/decided","data":{"id":"{{approval:1}}","outcome":"allowed-once"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo 'tok63z tokc7y tokibx tokofw tokujv tok10nu tok16rt tok1cvs tok1izr tok1p3q tok1v7p tok21bo tok2a4 tok8e3 tokei2 tokkm1 tokqq0 tokwtz tok12xy tok191x tok1f5w tok1l9v tok1rdu tok1xht tok23ls tok4k8 tokao7 tokgs6 tokmw5 tokt04 tokz43 tok1582 tok1bc1 tok1hg0 tok1njz tok1tny tok1zrx tokqd tok6uc tokcyb tokj2a tokp69 tokva8 tok11e7 tok17i6 tok1dm5 tok1jq4 tok1pu3 tok1vy2 tok2221 tok30h tok94g tokf8f toklce tokrgd tokxkc tok13ob tok19sa tok1fw9 tok1m08 tok1s47 tok1y86 tok24c5 tok5al tokbek tokhij toknmi toktqh tokzug tok15yf tok1c2e tok1i6d tok1oac tok1ueb tok20ia tok1gq tok7kp tokdoo tokjsn tokpwm tokw0l tok124k tok188j tok1eci tok1kgh tok1qkg tok1wof tok22se tok3qu tok9ut tokfys tokm2r toks6q tokyap tok14eo tok1ain tok1gmm tok1mql tok1suk tok1yyj tok252i tok60y tokc4x toki8w tokocv tokugu tok10kt tok16os tok1csr tok1iwq tok1p0p tok1v4o tok218n tok273 tok8b2 tokef1 tokkj0 tokqmz tokwqy tok12ux tok18yw tok1f2v tok1l6u tok1rat tok1xes tok23ir tok4h7 tokal6 tokgp5 tokmt4 toksx3 tokz12 tok1551 tok1b90 tok1hcz tok1ngy tok1tkx tok1zow toknc tok6rb tokcva tokiz9 tokp38 tokv77 tok11b6 tok17f5 tok1dj4 tok1jn3 tok1pr2 tok1vv1 tok21z0 tok2xg tok91f tokf5e tokl9d tokrdc tokxhb tok13la tok19p9 tok1ft8 tok1lx7 tok1s16 tok1y55 tok2494 tok57k tokbbj tokhfi toknjh toktng tokzrf tok15ve tok1bzd tok1i3c tok1o7b tok1uba tok20f9 tok1dp tok7ho tokdln tokjpm tokptl tokvxk tok121j tok185i tok1e9h tok1kdg tok1qhf tok1wle tok22pd tok3nt tok9rs tokfvr toklzq toks3p toky7o tok14bn tok1afm tok1gjl tok1mnk tok1srj tok1yvi tok24zh tok5xx tokc1w toki5v toko9u tokudt tok10hs tok16lr tok1cpq tok1itp tok1oxo tok1v1n tok215m tok242 tok881 tokec0 tokkfz tokqjy tokwnx' > notes.txt","startedAt":1791430274074,"toolCall":{"callId":"approval-write","rootCallId":"approval-write"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo 'tok63z tokc7y tokibx tokofw tokujv tok10nu tok16rt tok1cvs tok1izr tok1p3q tok1v7p tok21bo tok2a4 tok8e3 tokei2 tokkm1 tokqq0 tokwtz tok12xy tok191x tok1f5w tok1l9v tok1rdu tok1xht tok23ls tok4k8 tokao7 tokgs6 tokmw5 tokt04 tokz43 tok1582 tok1bc1 tok1hg0 tok1njz tok1tny tok1zrx tokqd tok6uc tokcyb tokj2a tokp69 tokva8 tok11e7 tok17i6 tok1dm5 tok1jq4 tok1pu3 tok1vy2 tok2221 tok30h tok94g tokf8f toklce tokrgd tokxkc tok13ob tok19sa tok1fw9 tok1m08 tok1s47 tok1y86 tok24c5 tok5al tokbek tokhij toknmi toktqh tokzug tok15yf tok1c2e tok1i6d tok1oac tok1ueb tok20ia tok1gq tok7kp tokdoo tokjsn tokpwm tokw0l tok124k tok188j tok1eci tok1kgh tok1qkg tok1wof tok22se tok3qu tok9ut tokfys tokm2r toks6q tokyap tok14eo tok1ain tok1gmm tok1mql tok1suk tok1yyj tok252i tok60y tokc4x toki8w tokocv tokugu tok10kt tok16os tok1csr tok1iwq tok1p0p tok1v4o tok218n tok273 tok8b2 tokef1 tokkj0 tokqmz tokwqy tok12ux tok18yw tok1f2v tok1l6u tok1rat tok1xes tok23ir tok4h7 tokal6 tokgp5 tokmt4 toksx3 tokz12 tok1551 tok1b90 tok1hcz tok1ngy tok1tkx tok1zow toknc tok6rb tokcva tokiz9 tokp38 tokv77 tok11b6 tok17f5 tok1dj4 tok1jn3 tok1pr2 tok1vv1 tok21z0 tok2xg tok91f tokf5e tokl9d tokrdc tokxhb tok13la tok19p9 tok1ft8 tok1lx7 tok1s16 tok1y55 tok2494 tok57k tokbbj tokhfi toknjh toktng tokzrf tok15ve tok1bzd tok1i3c tok1o7b tok1uba tok20f9 tok1dp tok7ho tokdln tokjpm tokptl tokvxk tok121j tok185i tok1e9h tok1kdg tok1qhf tok1wle tok22pd tok3nt tok9rs tokfvr toklzq toks3p toky7o tok14bn tok1afm tok1gjl tok1mnk tok1srj tok1yvi tok24zh tok5xx tokc1w toki5v toko9u tokudt tok10hs tok16lr tok1cpq tok1itp tok1oxo tok1v1n tok215m tok242 tok881 tokec0 tokkfz tokqjy tokwnx' > notes.txt","startedAt":1791430274074,"toolCall":{"callId":"approval-write","rootCallId":"approval-write"},"finishedAt":1791430274084,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo 'tok63z tokc7y tokibx tokofw tokujv tok10nu tok16rt tok1cvs tok1izr tok1p3q tok1v7p tok21bo tok2a4 tok8e3 tokei2 tokkm1 tokqq0 tokwtz tok12xy tok191x tok1f5w tok1l9v tok1rdu tok1xht tok23ls tok4k8 tokao7 tokgs6 tokmw5 tokt04 tokz43 tok1582 tok1bc1 tok1hg0 tok1njz tok1tny tok1zrx tokqd tok6uc tokcyb tokj2a tokp69 tokva8 tok11e7 tok17i6 tok1dm5 tok1jq4 tok1pu3 tok1vy2 tok2221 tok30h tok94g tokf8f toklce tokrgd tokxkc tok13ob tok19sa tok1fw9 tok1m08 tok1s47 tok1y86 tok24c5 tok5al tokbek tokhij toknmi toktqh tokzug tok15yf tok1c2e tok1i6d tok1oac tok1ueb tok20ia tok1gq tok7kp tokdoo tokjsn tokpwm tokw0l tok124k tok188j tok1eci tok1kgh tok1qkg tok1wof tok22se tok3qu tok9ut tokfys tokm2r toks6q tokyap tok14eo tok1ain tok1gmm tok1mql tok1suk tok1yyj tok252i tok60y tokc4x toki8w tokocv tokugu tok10kt tok16os tok1csr tok1iwq tok1p0p tok1v4o tok218n tok273 tok8b2 tokef1 tokkj0 tokqmz tokwqy tok12ux tok18yw tok1f2v tok1l6u tok1rat tok1xes tok23ir tok4h7 tokal6 tokgp5 tokmt4 toksx3 tokz12 tok1551 tok1b90 tok1hcz tok1ngy tok1tkx tok1zow toknc tok6rb tokcva tokiz9 tokp38 tokv77 tok11b6 tok17f5 tok1dj4 tok1jn3 tok1pr2 tok1vv1 tok21z0 tok2xg tok91f tokf5e tokl9d tokrdc tokxhb tok13la tok19p9 tok1ft8 tok1lx7 tok1s16 tok1y55 tok2494 tok57k tokbbj tokhfi toknjh toktng tokzrf tok15ve tok1bzd tok1i3c tok1o7b tok1uba tok20f9 tok1dp tok7ho tokdln tokjpm tokptl tokvxk tok121j tok185i tok1e9h tok1kdg tok1qhf tok1wle tok22pd tok3nt tok9rs tokfvr toklzq toks3p toky7o tok14bn tok1afm tok1gjl tok1mnk tok1srj tok1yvi tok24zh tok5xx tokc1w toki5v toko9u tokudt tok10hs tok16lr tok1cpq tok1itp tok1oxo tok1v1n tok215m tok242 tok881 tokec0 tokkfz tokqjy tokwnx' > notes.txt","startedAt":1791430274074,"toolCall":{"callId":"approval-write","rootCallId":"approval-write"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo 'tok63z tokc7y tokibx tokofw tokujv tok10nu tok16rt tok1cvs tok1izr tok1p3q tok1v7p tok21bo tok2a4 tok8e3 tokei2 tokkm1 tokqq0 tokwtz tok12xy tok191x tok1f5w tok1l9v tok1rdu tok1xht tok23ls tok4k8 tokao7 tokgs6 tokmw5 tokt04 tokz43 tok1582 tok1bc1 tok1hg0 tok1njz tok1tny tok1zrx tokqd tok6uc tokcyb tokj2a tokp69 tokva8 tok11e7 tok17i6 tok1dm5 tok1jq4 tok1pu3 tok1vy2 tok2221 tok30h tok94g tokf8f toklce tokrgd tokxkc tok13ob tok19sa tok1fw9 tok1m08 tok1s47 tok1y86 tok24c5 tok5al tokbek tokhij toknmi toktqh tokzug tok15yf tok1c2e tok1i6d tok1oac tok1ueb tok20ia tok1gq tok7kp tokdoo tokjsn tokpwm tokw0l tok124k tok188j tok1eci tok1kgh tok1qkg tok1wof tok22se tok3qu tok9ut tokfys tokm2r toks6q tokyap tok14eo tok1ain tok1gmm tok1mql tok1suk tok1yyj tok252i tok60y tokc4x toki8w tokocv tokugu tok10kt tok16os tok1csr tok1iwq tok1p0p tok1v4o tok218n tok273 tok8b2 tokef1 tokkj0 tokqmz tokwqy tok12ux tok18yw tok1f2v tok1l6u tok1rat tok1xes tok23ir tok4h7 tokal6 tokgp5 tokmt4 toksx3 tokz12 tok1551 tok1b90 tok1hcz tok1ngy tok1tkx tok1zow toknc tok6rb tokcva tokiz9 tokp38 tokv77 tok11b6 tok17f5 tok1dj4 tok1jn3 tok1pr2 tok1vv1 tok21z0 tok2xg tok91f tokf5e tokl9d tokrdc tokxhb tok13la tok19p9 tok1ft8 tok1lx7 tok1s16 tok1y55 tok2494 tok57k tokbbj tokhfi toknjh toktng tokzrf tok15ve tok1bzd tok1i3c tok1o7b tok1uba tok20f9 tok1dp tok7ho tokdln tokjpm tokptl tokvxk tok121j tok185i tok1e9h tok1kdg tok1qhf tok1wle tok22pd tok3nt tok9rs tokfvr toklzq toks3p toky7o tok14bn tok1afm tok1gjl tok1mnk tok1srj tok1yvi tok24zh tok5xx tokc1w toki5v toko9u tokudt tok10hs tok16lr tok1cpq tok1itp tok1oxo tok1v1n tok215m tok242 tok881 tokec0 tokkfz tokqjy tokwnx' > notes.txt","startedAt":1791430274074,"toolCall":{"callId":"approval-write","rootCallId":"approval-write"},"finishedAt":1791430274084,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"approval-write"},"toolCallId":"approval-write","content":[{"type":"text","text":"(no output)"}],"isError":false,"id":"{{message:6}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[19],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/web/changed-files-turn/session.v4.jsonl b/snapshots/web/changed-files-turn/session.v4.jsonl
index afe21f0584..77a5208673 100644
--- a/snapshots/web/changed-files-turn/session.v4.jsonl
+++ b/snapshots/web/changed-files-turn/session.v4.jsonl
@@ -17,8 +17,8 @@
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_3gp2TeezOtikxb78rEPY4058","name":"read","arguments":"{\"file_path\": \"intro.md\"}"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_3gp2TeezOtikxb78rEPY4058"},"toolCallId":"call_00_3gp2TeezOtikxb78rEPY4058","content":[{"type":"text","text":"{{cwd}}/intro.md\nfile\n\n1: # 示例项目\n2: \n3: 一个用于演示的仓库。\n\n(End of file - total 3 lines)\n"}],"isError":false,"id":"{{message:6}}"},"meta":{"path":"{{cwd}}/intro.md","offset":1,"lines":[{"number":1,"text":"# 示例项目"},{"number":2,"text":""},{"number":3,"text":"一个用于演示的仓库。"}],"totalLines":3,"lang":"md"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_01_u2F0jbjbYU2FBc4xZNBK0980","name":"bash","arguments":"{\"command\": \"ls -a && echo \\\"---notes---\\\" && (cat notes.txt 2>/dev/null || echo \\\"(notes.txt 不存在)\\\")\", \"description\": \"List files and show notes.txt\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"ls -a && echo \"---notes---\" && (cat notes.txt 2>/dev/null || echo \"(notes.txt 不存在)\")","startedAt":1791430252514,"toolCall":{"callId":"call_01_u2F0jbjbYU2FBc4xZNBK0980","rootCallId":"call_01_u2F0jbjbYU2FBc4xZNBK0980"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"ls -a && echo \"---notes---\" && (cat notes.txt 2>/dev/null || echo \"(notes.txt 不存在)\")","startedAt":1791430252514,"toolCall":{"callId":"call_01_u2F0jbjbYU2FBc4xZNBK0980","rootCallId":"call_01_u2F0jbjbYU2FBc4xZNBK0980"},"finishedAt":1791430252532,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"ls -a && echo \"---notes---\" && (cat notes.txt 2>/dev/null || echo \"(notes.txt 不存在)\")","startedAt":1791430252514,"toolCall":{"callId":"call_01_u2F0jbjbYU2FBc4xZNBK0980","rootCallId":"call_01_u2F0jbjbYU2FBc4xZNBK0980"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"ls -a && echo \"---notes---\" && (cat notes.txt 2>/dev/null || echo \"(notes.txt 不存在)\")","startedAt":1791430252514,"toolCall":{"callId":"call_01_u2F0jbjbYU2FBc4xZNBK0980","rootCallId":"call_01_u2F0jbjbYU2FBc4xZNBK0980"},"finishedAt":1791430252532,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_01_u2F0jbjbYU2FBc4xZNBK0980"},"toolCallId":"call_01_u2F0jbjbYU2FBc4xZNBK0980","content":[{"type":"text","text":".\n..\n.git\n.gitignore\nintro.md\nnotes.txt\n---notes---\nstart\n"}],"isError":false,"id":"{{message:7}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[17],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
@@ -30,8 +30,8 @@
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_02_V7z2GSt4h4I0sCnJg4990423","name":"write","arguments":"{\"file_path\": \"app.local\", \"content\": \"mode=demo\\n\"}"}}
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"call_02_V7z2GSt4h4I0sCnJg4990423"},"toolCallId":"call_02_V7z2GSt4h4I0sCnJg4990423","content":[{"type":"text","text":"{{cwd}}/app.local\nfile\n\nCreated file\n"}],"isError":false,"id":"{{message:11}}"},"meta":{"operation":"create","path":"{{cwd}}/app.local","diffs":[]}},"sourceEventSeqs":[28],"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_03_M8lD8W8TAtiIT141ej8n1520","name":"bash","arguments":"{\"command\": \"echo done >> notes.txt && cat notes.txt\", \"description\": \"Append done to notes.txt\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo done >> notes.txt && cat notes.txt","startedAt":1791430252759,"toolCall":{"callId":"call_03_M8lD8W8TAtiIT141ej8n1520","rootCallId":"call_03_M8lD8W8TAtiIT141ej8n1520"}}}
-{"type":"job/settled","data":{"executionId":"{{id:2}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"echo done >> notes.txt && cat notes.txt","startedAt":1791430252759,"toolCall":{"callId":"call_03_M8lD8W8TAtiIT141ej8n1520","rootCallId":"call_03_M8lD8W8TAtiIT141ej8n1520"},"finishedAt":1791430252771,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo done >> notes.txt && cat notes.txt","startedAt":1791430252759,"toolCall":{"callId":"call_03_M8lD8W8TAtiIT141ej8n1520","rootCallId":"call_03_M8lD8W8TAtiIT141ej8n1520"}}}
+{"type":"job/settled","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"echo done >> notes.txt && cat notes.txt","startedAt":1791430252759,"toolCall":{"callId":"call_03_M8lD8W8TAtiIT141ej8n1520","rootCallId":"call_03_M8lD8W8TAtiIT141ej8n1520"},"finishedAt":1791430252771,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"call_03_M8lD8W8TAtiIT141ej8n1520"},"toolCallId":"call_03_M8lD8W8TAtiIT141ej8n1520","content":[{"type":"text","text":"start\ndone\n"}],"isError":false,"id":"{{message:12}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[30],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":2}}
{"type":"step/start","data":{"turn":1,"step":3}}
diff --git a/snapshots/web/fresh-round-trip/session.v4.jsonl b/snapshots/web/fresh-round-trip/session.v4.jsonl
index 21567fe94d..60538f7f0f 100644
--- a/snapshots/web/fresh-round-trip/session.v4.jsonl
+++ b/snapshots/web/fresh-round-trip/session.v4.jsonl
@@ -15,8 +15,8 @@
{"type":"session/title","data":{"title":"Use the bash tool to","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to run a simple bash command and reply with \"DONE\"."},{"type":"tool-call","id":"call_00_BYXlxjFaalMg95YVqEeF2495","name":"bash","arguments":"{\"command\": \"echo WEB_E2E_OK\", \"description\": \"Echo the test string\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:5}}"},"usage":{"inputTokens":122,"outputTokens":85,"cacheReadTokens":7680,"reasoningTokens":17},"stream":[{"type":"chunk","time":1791430270179,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791430270196,"index":0,"dt":[17,16,15,17,16,17,16,15,17,15,16,16,16,16,16,15],"texts":["The"," user"," wants"," me"," to"," run"," a"," simple"," bash"," command"," and"," reply"," with"," \"","D","ONE","\"."]},{"type":"chunk","time":1791430270469,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791430270484,"index":1,"dt":[17,14,17,17,15,16,16,15,16,17,16,16,16,16,16,17,16,17,15,16,38,16,16,17,16,15],"id":"call_00_BYXlxjFaalMg95YVqEeF2495","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," WEB","_E","2","E","_OK","\"",", ","\"","description","\"",": ","\"","E","cho"," the"," test"," string","\"","}"]},{"type":"chunk","time":1791430270939,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to run a simple bash command and reply with \"DONE\"."}}},{"type":"chunk","time":1791430270956,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_BYXlxjFaalMg95YVqEeF2495","name":"bash","arguments":"{\"command\": \"echo WEB_E2E_OK\", \"description\": \"Echo the test string\"}"}}},{"type":"chunk","time":1791430270971,"chunk":{"type":"usage","usage":{"inputTokens":122,"outputTokens":85,"cacheReadTokens":7680,"reasoningTokens":17}}},{"type":"chunk","time":1791430270988,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_BYXlxjFaalMg95YVqEeF2495","name":"bash","arguments":"{\"command\": \"echo WEB_E2E_OK\", \"description\": \"Echo the test string\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo WEB_E2E_OK","startedAt":1791430271002,"toolCall":{"callId":"call_00_BYXlxjFaalMg95YVqEeF2495","rootCallId":"call_00_BYXlxjFaalMg95YVqEeF2495"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo WEB_E2E_OK","startedAt":1791430271002,"toolCall":{"callId":"call_00_BYXlxjFaalMg95YVqEeF2495","rootCallId":"call_00_BYXlxjFaalMg95YVqEeF2495"},"finishedAt":1791430271015,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo WEB_E2E_OK","startedAt":1791430271002,"toolCall":{"callId":"call_00_BYXlxjFaalMg95YVqEeF2495","rootCallId":"call_00_BYXlxjFaalMg95YVqEeF2495"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo WEB_E2E_OK","startedAt":1791430271002,"toolCall":{"callId":"call_00_BYXlxjFaalMg95YVqEeF2495","rootCallId":"call_00_BYXlxjFaalMg95YVqEeF2495"},"finishedAt":1791430271015,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_BYXlxjFaalMg95YVqEeF2495"},"toolCallId":"call_00_BYXlxjFaalMg95YVqEeF2495","content":[{"type":"text","text":"WEB_E2E_OK\n"}],"isError":false,"id":"{{message:6}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}
diff --git a/snapshots/web/goal-multi-turn-actions/session.v4.jsonl b/snapshots/web/goal-multi-turn-actions/session.v4.jsonl
index c96697a9b9..886140b8a8 100644
--- a/snapshots/web/goal-multi-turn-actions/session.v4.jsonl
+++ b/snapshots/web/goal-multi-turn-actions/session.v4.jsonl
@@ -17,8 +17,8 @@
{"type":"request/context","data":{"provider":"deepseek-official","model":"deepseek-v4-flash","contextWindow":128000}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user's goal is: \"做两个turn,每个turn输出随机一个包的文件结构。注意你做完一个turn之后,直接输出内容,停止,我们的系统会帮你再开一个turn,你看着做一个类似的\" — which translates to: \"Do two turns, each turn output the file structure of a random package. Note that after you finish one turn, directly output the content and stop; our system will help you open another turn, and you just do something similar.\"\n\nSo this is round 1 of 2. I need to output the file structure of a random package. Let me check the workspace to see if there's anything relevant — maybe there are packages installed or a specific context.\n\nLet me first check the current working directory and see what's there.\n\nThe instruction says: after finishing a turn, output the content directly and stop. So for this round, I should pick a random package and output its file structure. The system will open another turn for the second one.\n\nLet me look at the workspace to understand the environment."},{"type":"tool-call","id":"call_00_IVvwEvXl7JJoIHrqvlDi1572","name":"bash","arguments":"{\"command\":\"find packages -mindepth 2 -maxdepth 2 -type d | sort\",\"description\":\"List package directories deterministically\"}"},{"type":"tool-call","id":"call_01_hlqFt1P4CYy1jb078Ke71690","name":"get_goal","arguments":"{}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:5}}"},"usage":{"inputTokens":7893,"outputTokens":297,"cacheReadTokens":0,"reasoningTokens":213},"stream":[{"type":"chunk","time":1791430277672,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791430277673,"index":0,"dt":[],"texts":["The user's goal is: \"做两个turn,每个turn输出随机一个包的文件结构。注意你做完一个turn之后,直接输出内容,停止,我们的系统会帮你再开一个turn,你看着做一个类似的\" — which translates to: \"Do two turns, each turn output the file structure of a random package. Note that after you finish one turn, directly output the content and stop; our system will help you open another turn, and you just do something similar.\"\n\nSo this is round 1 of 2. I need to output the file structure of a random package. Let me check the workspace to see if there's anything relevant — maybe there are packages installed or a specific context.\n\nLet me first check the current working directory and see what's there.\n\nThe instruction says: after finishing a turn, output the content directly and stop. So for this round, I should pick a random package and output its file structure. The system will open another turn for the second one.\n\nLet me look at the workspace to understand the environment."]},{"type":"chunk","time":1791430277673,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user's goal is: \"做两个turn,每个turn输出随机一个包的文件结构。注意你做完一个turn之后,直接输出内容,停止,我们的系统会帮你再开一个turn,你看着做一个类似的\" — which translates to: \"Do two turns, each turn output the file structure of a random package. Note that after you finish one turn, directly output the content and stop; our system will help you open another turn, and you just do something similar.\"\n\nSo this is round 1 of 2. I need to output the file structure of a random package. Let me check the workspace to see if there's anything relevant — maybe there are packages installed or a specific context.\n\nLet me first check the current working directory and see what's there.\n\nThe instruction says: after finishing a turn, output the content directly and stop. So for this round, I should pick a random package and output its file structure. The system will open another turn for the second one.\n\nLet me look at the workspace to understand the environment."}}},{"type":"chunk","time":1791430277673,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791430277673,"index":1,"dt":[],"id":"call_00_IVvwEvXl7JJoIHrqvlDi1572","name":"bash","args":["{\"command\":\"find packages -mindepth 2 -maxdepth 2 -type d | sort\",\"description\":\"List package directories deterministically\"}"]},{"type":"chunk","time":1791430277673,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_IVvwEvXl7JJoIHrqvlDi1572","name":"bash","arguments":"{\"command\":\"find packages -mindepth 2 -maxdepth 2 -type d | sort\",\"description\":\"List package directories deterministically\"}"}}},{"type":"chunk","time":1791430277673,"chunk":{"type":"block-start","index":2,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791430277673,"index":2,"dt":[],"id":"call_01_hlqFt1P4CYy1jb078Ke71690","name":"get_goal","args":["{}"]},{"type":"chunk","time":1791430277673,"chunk":{"type":"block-end","index":2,"block":{"type":"tool-call","id":"call_01_hlqFt1P4CYy1jb078Ke71690","name":"get_goal","arguments":"{}"}}},{"type":"chunk","time":1791430277673,"chunk":{"type":"usage","usage":{"inputTokens":7893,"outputTokens":297,"cacheReadTokens":0,"reasoningTokens":213}}},{"type":"chunk","time":1791430277673,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_IVvwEvXl7JJoIHrqvlDi1572","name":"bash","arguments":"{\"command\":\"find packages -mindepth 2 -maxdepth 2 -type d | sort\",\"description\":\"List package directories deterministically\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:2}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"find packages -mindepth 2 -maxdepth 2 -type d | sort","startedAt":1791430277682,"toolCall":{"callId":"call_00_IVvwEvXl7JJoIHrqvlDi1572","rootCallId":"call_00_IVvwEvXl7JJoIHrqvlDi1572"}}}
-{"type":"job/settled","data":{"executionId":"{{id:2}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"find packages -mindepth 2 -maxdepth 2 -type d | sort","startedAt":1791430277682,"toolCall":{"callId":"call_00_IVvwEvXl7JJoIHrqvlDi1572","rootCallId":"call_00_IVvwEvXl7JJoIHrqvlDi1572"},"finishedAt":1791430277698,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"find packages -mindepth 2 -maxdepth 2 -type d | sort","startedAt":1791430277682,"toolCall":{"callId":"call_00_IVvwEvXl7JJoIHrqvlDi1572","rootCallId":"call_00_IVvwEvXl7JJoIHrqvlDi1572"}}}
+{"type":"job/settled","data":{"jobId":"{{id:2}}","owner":"{{session:1}}","kind":"bash","label":"find packages -mindepth 2 -maxdepth 2 -type d | sort","startedAt":1791430277682,"toolCall":{"callId":"call_00_IVvwEvXl7JJoIHrqvlDi1572","rootCallId":"call_00_IVvwEvXl7JJoIHrqvlDi1572"},"finishedAt":1791430277698,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_IVvwEvXl7JJoIHrqvlDi1572"},"toolCallId":"call_00_IVvwEvXl7JJoIHrqvlDi1572","content":[{"type":"text","text":"packages/client/ui-chat\npackages/client/ui-conversation\npackages/context/session-reference\npackages/llm/token-meter\npackages/skill/skill-filesystem\n"}],"isError":false,"id":"{{message:6}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[17],"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_01_hlqFt1P4CYy1jb078Ke71690","name":"get_goal","arguments":"{}"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_01_hlqFt1P4CYy1jb078Ke71690"},"toolCallId":"call_01_hlqFt1P4CYy1jb078Ke71690","content":[{"type":"text","text":"{\"goal\":{\"id\":\"{{id:1}}\",\"revision\":1,\"objective\":\"做两个turn,每个turn输出随机一个包的文件结构。注意你做完一个turn之后,直接输出内容,停止,我们的系统会帮你再开一个turn,你看着做一个类似的\",\"phase\":\"active\",\"roundsStarted\":1,\"maxGoalRounds\":256},\"activation\":\"armed\"}"}],"isError":false,"id":"{{message:7}}"}},"sourceEventSeqs":[21],"surfaceOp":"append"}
@@ -26,29 +26,29 @@
{"type":"step/start","data":{"turn":1,"step":2}}
{"type":"assistant/message","data":{"turn":1,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"There's a `packages` directory. Let me look inside to see what packages are available."},{"type":"tool-call","id":"call_00_XlYaTHoKielffCybfcXb9253","name":"bash","arguments":"{\"command\":\"find packages -mindepth 1 -maxdepth 1 -type d | sed 's#^packages/##' | sort && echo '---' && find packages -maxdepth 2 -type d | sort\",\"description\":\"List packages directory contents\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:8}}"},"usage":{"inputTokens":363,"outputTokens":99,"cacheReadTokens":8064,"reasoningTokens":20},"stream":[{"type":"chunk","time":1791430277721,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791430277721,"index":0,"dt":[],"texts":["There's a `packages` directory. Let me look inside to see what packages are available."]},{"type":"chunk","time":1791430277721,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"There's a `packages` directory. Let me look inside to see what packages are available."}}},{"type":"chunk","time":1791430277721,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791430277721,"index":1,"dt":[],"id":"call_00_XlYaTHoKielffCybfcXb9253","name":"bash","args":["{\"command\":\"find packages -mindepth 1 -maxdepth 1 -type d | sed 's#^packages/##' | sort && echo '---' && find packages -maxdepth 2 -type d | sort\",\"description\":\"List packages directory contents\"}"]},{"type":"chunk","time":1791430277721,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_XlYaTHoKielffCybfcXb9253","name":"bash","arguments":"{\"command\":\"find packages -mindepth 1 -maxdepth 1 -type d | sed 's#^packages/##' | sort && echo '---' && find packages -maxdepth 2 -type d | sort\",\"description\":\"List packages directory contents\"}"}}},{"type":"chunk","time":1791430277721,"chunk":{"type":"usage","usage":{"inputTokens":363,"outputTokens":99,"cacheReadTokens":8064,"reasoningTokens":20}}},{"type":"chunk","time":1791430277721,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":2,"callId":"call_00_XlYaTHoKielffCybfcXb9253","name":"bash","arguments":"{\"command\":\"find packages -mindepth 1 -maxdepth 1 -type d | sed 's#^packages/##' | sort && echo '---' && find packages -maxdepth 2 -type d | sort\",\"description\":\"List packages directory contents\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:3}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"find packages -mindepth 1 -maxdepth 1 -type d | sed 's#^packages/##' | sort && echo '---' && find packages -maxdepth 2 -type d | sort","startedAt":1791430277727,"toolCall":{"callId":"call_00_XlYaTHoKielffCybfcXb9253","rootCallId":"call_00_XlYaTHoKielffCybfcXb9253"}}}
-{"type":"job/settled","data":{"executionId":"{{id:3}}","jobId":"bash-2","owner":"{{session:1}}","kind":"bash","label":"find packages -mindepth 1 -maxdepth 1 -type d | sed 's#^packages/##' | sort && echo '---' && find packages -maxdepth 2 -type d | sort","startedAt":1791430277727,"toolCall":{"callId":"call_00_XlYaTHoKielffCybfcXb9253","rootCallId":"call_00_XlYaTHoKielffCybfcXb9253"},"finishedAt":1791430277741,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:3}}","owner":"{{session:1}}","kind":"bash","label":"find packages -mindepth 1 -maxdepth 1 -type d | sed 's#^packages/##' | sort && echo '---' && find packages -maxdepth 2 -type d | sort","startedAt":1791430277727,"toolCall":{"callId":"call_00_XlYaTHoKielffCybfcXb9253","rootCallId":"call_00_XlYaTHoKielffCybfcXb9253"}}}
+{"type":"job/settled","data":{"jobId":"{{id:3}}","owner":"{{session:1}}","kind":"bash","label":"find packages -mindepth 1 -maxdepth 1 -type d | sed 's#^packages/##' | sort && echo '---' && find packages -maxdepth 2 -type d | sort","startedAt":1791430277727,"toolCall":{"callId":"call_00_XlYaTHoKielffCybfcXb9253","rootCallId":"call_00_XlYaTHoKielffCybfcXb9253"},"finishedAt":1791430277741,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_XlYaTHoKielffCybfcXb9253"},"toolCallId":"call_00_XlYaTHoKielffCybfcXb9253","content":[{"type":"text","text":"client\ncontext\nllm\nskill\n---\npackages\npackages/client\npackages/client/ui-chat\npackages/client/ui-conversation\npackages/context\npackages/context/session-reference\npackages/llm\npackages/llm/token-meter\npackages/skill\npackages/skill/skill-filesystem\n"}],"isError":false,"id":"{{message:9}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[26],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":2}}
{"type":"step/start","data":{"turn":1,"step":3}}
{"type":"assistant/message","data":{"turn":1,"step":3,"message":{"role":"assistant","content":[{"type":"reasoning","text":"There are 4 packages:\n- client/ui-conversation\n- context/session-reference\n- llm/token-meter\n- skill/skill-filesystem\n\nFor this turn (turn 1 of 2), I should randomly pick one package and output its file structure. Let me pick randomly — I'll use $RANDOM or just pick one. Let me use a quick random selection."},{"type":"tool-call","id":"call_00_dAM4qfVp4LLtRCiJi7d71418","name":"bash","arguments":"{\"command\":\"printf '1: packages/client/ui-conversation\\n2: packages/context/session-reference\\n3: packages/llm/token-meter\\n4: packages/skill/skill-filesystem\\n---random pick---\\n'; printf 'bash: shuf: command not found\\n' >&2; exit 127\",\"description\":\"Randomly pick one package directory\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:10}}"},"usage":{"inputTokens":156,"outputTokens":202,"cacheReadTokens":8448,"reasoningTokens":80},"stream":[{"type":"chunk","time":1791430277755,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791430277755,"index":0,"dt":[],"texts":["There are 4 packages:\n- client/ui-conversation\n- context/session-reference\n- llm/token-meter\n- skill/skill-filesystem\n\nFor this turn (turn 1 of 2), I should randomly pick one package and output its file structure. Let me pick randomly — I'll use $RANDOM or just pick one. Let me use a quick random selection."]},{"type":"chunk","time":1791430277755,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"There are 4 packages:\n- client/ui-conversation\n- context/session-reference\n- llm/token-meter\n- skill/skill-filesystem\n\nFor this turn (turn 1 of 2), I should randomly pick one package and output its file structure. Let me pick randomly — I'll use $RANDOM or just pick one. Let me use a quick random selection."}}},{"type":"chunk","time":1791430277755,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791430277755,"index":1,"dt":[],"id":"call_00_dAM4qfVp4LLtRCiJi7d71418","name":"bash","args":["{\"command\":\"printf '1: packages/client/ui-conversation\\n2: packages/context/session-reference\\n3: packages/llm/token-meter\\n4: packages/skill/skill-filesystem\\n---random pick---\\n'; printf 'bash: shuf: command not found\\n' >&2; exit 127\",\"description\":\"Randomly pick one package directory\"}"]},{"type":"chunk","time":1791430277755,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_dAM4qfVp4LLtRCiJi7d71418","name":"bash","arguments":"{\"command\":\"printf '1: packages/client/ui-conversation\\n2: packages/context/session-reference\\n3: packages/llm/token-meter\\n4: packages/skill/skill-filesystem\\n---random pick---\\n'; printf 'bash: shuf: command not found\\n' >&2; exit 127\",\"description\":\"Randomly pick one package directory\"}"}}},{"type":"chunk","time":1791430277755,"chunk":{"type":"usage","usage":{"inputTokens":156,"outputTokens":202,"cacheReadTokens":8448,"reasoningTokens":80}}},{"type":"chunk","time":1791430277755,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":3,"callId":"call_00_dAM4qfVp4LLtRCiJi7d71418","name":"bash","arguments":"{\"command\":\"printf '1: packages/client/ui-conversation\\n2: packages/context/session-reference\\n3: packages/llm/token-meter\\n4: packages/skill/skill-filesystem\\n---random pick---\\n'; printf 'bash: shuf: command not found\\n' >&2; exit 127\",\"description\":\"Randomly pick one package directory\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:4}}","jobId":"bash-3","owner":"{{session:1}}","kind":"bash","label":"printf '1: packages/client/ui-conversation\n2: packages/context/session-reference\n3: packages/llm/token-meter\n4: packages/skill/skill-filesystem\n---random pick---\n'; printf 'bash: shuf: command not found\n' >&2; exit 127","startedAt":1791430277760,"toolCall":{"callId":"call_00_dAM4qfVp4LLtRCiJi7d71418","rootCallId":"call_00_dAM4qfVp4LLtRCiJi7d71418"}}}
-{"type":"job/settled","data":{"executionId":"{{id:4}}","jobId":"bash-3","owner":"{{session:1}}","kind":"bash","label":"printf '1: packages/client/ui-conversation\n2: packages/context/session-reference\n3: packages/llm/token-meter\n4: packages/skill/skill-filesystem\n---random pick---\n'; printf 'bash: shuf: command not found\n' >&2; exit 127","startedAt":1791430277760,"toolCall":{"callId":"call_00_dAM4qfVp4LLtRCiJi7d71418","rootCallId":"call_00_dAM4qfVp4LLtRCiJi7d71418"},"finishedAt":1791430277770,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 127"}}
+{"type":"job/registered","data":{"jobId":"{{id:4}}","owner":"{{session:1}}","kind":"bash","label":"printf '1: packages/client/ui-conversation\n2: packages/context/session-reference\n3: packages/llm/token-meter\n4: packages/skill/skill-filesystem\n---random pick---\n'; printf 'bash: shuf: command not found\n' >&2; exit 127","startedAt":1791430277760,"toolCall":{"callId":"call_00_dAM4qfVp4LLtRCiJi7d71418","rootCallId":"call_00_dAM4qfVp4LLtRCiJi7d71418"}}}
+{"type":"job/settled","data":{"jobId":"{{id:4}}","owner":"{{session:1}}","kind":"bash","label":"printf '1: packages/client/ui-conversation\n2: packages/context/session-reference\n3: packages/llm/token-meter\n4: packages/skill/skill-filesystem\n---random pick---\n'; printf 'bash: shuf: command not found\n' >&2; exit 127","startedAt":1791430277760,"toolCall":{"callId":"call_00_dAM4qfVp4LLtRCiJi7d71418","rootCallId":"call_00_dAM4qfVp4LLtRCiJi7d71418"},"finishedAt":1791430277770,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 127"}}
{"type":"tool/result","data":{"turn":1,"step":3,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_dAM4qfVp4LLtRCiJi7d71418"},"toolCallId":"call_00_dAM4qfVp4LLtRCiJi7d71418","content":[{"type":"text","text":"1: packages/client/ui-conversation\n2: packages/context/session-reference\n3: packages/llm/token-meter\n4: packages/skill/skill-filesystem\n---random pick---\n[stderr]\nbash: shuf: command not found\n[exit code: 127]"}],"isError":false,"id":"{{message:11}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[33],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":3}}
{"type":"step/start","data":{"turn":1,"step":4}}
{"type":"assistant/message","data":{"turn":1,"step":4,"message":{"role":"assistant","content":[{"type":"reasoning","text":"shuf is not available on macOS. I'll use a different approach for random selection. Let me use $RANDOM in bash or use `sort -R` or Python."},{"type":"tool-call","id":"call_00_flxIP8K77cJ8bKt5YRol0386","name":"bash","arguments":"{\"command\":\"python3 -c \\\"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\\\"\",\"description\":\"Randomly select one package with Python\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:12}}"},"usage":{"inputTokens":175,"outputTokens":155,"cacheReadTokens":8704,"reasoningTokens":37},"stream":[{"type":"chunk","time":1791430277782,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791430277782,"index":0,"dt":[],"texts":["shuf is not available on macOS. I'll use a different approach for random selection. Let me use $RANDOM in bash or use `sort -R` or Python."]},{"type":"chunk","time":1791430277782,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"shuf is not available on macOS. I'll use a different approach for random selection. Let me use $RANDOM in bash or use `sort -R` or Python."}}},{"type":"chunk","time":1791430277782,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791430277782,"index":1,"dt":[],"id":"call_00_flxIP8K77cJ8bKt5YRol0386","name":"bash","args":["{\"command\":\"python3 -c \\\"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\\\"\",\"description\":\"Randomly select one package with Python\"}"]},{"type":"chunk","time":1791430277782,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_flxIP8K77cJ8bKt5YRol0386","name":"bash","arguments":"{\"command\":\"python3 -c \\\"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\\\"\",\"description\":\"Randomly select one package with Python\"}"}}},{"type":"chunk","time":1791430277782,"chunk":{"type":"usage","usage":{"inputTokens":175,"outputTokens":155,"cacheReadTokens":8704,"reasoningTokens":37}}},{"type":"chunk","time":1791430277782,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":4,"callId":"call_00_flxIP8K77cJ8bKt5YRol0386","name":"bash","arguments":"{\"command\":\"python3 -c \\\"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\\\"\",\"description\":\"Randomly select one package with Python\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:5}}","jobId":"bash-4","owner":"{{session:1}}","kind":"bash","label":"python3 -c \"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\"","startedAt":1791430277788,"toolCall":{"callId":"call_00_flxIP8K77cJ8bKt5YRol0386","rootCallId":"call_00_flxIP8K77cJ8bKt5YRol0386"}}}
-{"type":"job/settled","data":{"executionId":"{{id:5}}","jobId":"bash-4","owner":"{{session:1}}","kind":"bash","label":"python3 -c \"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\"","startedAt":1791430277788,"toolCall":{"callId":"call_00_flxIP8K77cJ8bKt5YRol0386","rootCallId":"call_00_flxIP8K77cJ8bKt5YRol0386"},"finishedAt":1791430277829,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:5}}","owner":"{{session:1}}","kind":"bash","label":"python3 -c \"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\"","startedAt":1791430277788,"toolCall":{"callId":"call_00_flxIP8K77cJ8bKt5YRol0386","rootCallId":"call_00_flxIP8K77cJ8bKt5YRol0386"}}}
+{"type":"job/settled","data":{"jobId":"{{id:5}}","owner":"{{session:1}}","kind":"bash","label":"python3 -c \"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\"","startedAt":1791430277788,"toolCall":{"callId":"call_00_flxIP8K77cJ8bKt5YRol0386","rootCallId":"call_00_flxIP8K77cJ8bKt5YRol0386"},"finishedAt":1791430277829,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":4,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_flxIP8K77cJ8bKt5YRol0386"},"toolCallId":"call_00_flxIP8K77cJ8bKt5YRol0386","content":[{"type":"text","text":"packages/context/session-reference\n"}],"isError":false,"id":"{{message:13}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[40],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":4}}
{"type":"step/start","data":{"turn":1,"step":5}}
{"type":"assistant/message","data":{"turn":1,"step":5,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The random pick for turn 1 is `packages/context/session-reference`. Now I'll output its file structure. Let me get the file tree."},{"type":"tool-call","id":"call_00_HNZzKyAOnDcNLscoKIdq3835","name":"bash","arguments":"{\"command\":\"find packages/context/session-reference -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort\",\"description\":\"List all files in session-reference package\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:14}}"},"usage":{"inputTokens":94,"outputTokens":127,"cacheReadTokens":8960,"reasoningTokens":32},"stream":[{"type":"chunk","time":1791430277842,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791430277842,"index":0,"dt":[],"texts":["The random pick for turn 1 is `packages/context/session-reference`. Now I'll output its file structure. Let me get the file tree."]},{"type":"chunk","time":1791430277842,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The random pick for turn 1 is `packages/context/session-reference`. Now I'll output its file structure. Let me get the file tree."}}},{"type":"chunk","time":1791430277842,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791430277842,"index":1,"dt":[],"id":"call_00_HNZzKyAOnDcNLscoKIdq3835","name":"bash","args":["{\"command\":\"find packages/context/session-reference -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort\",\"description\":\"List all files in session-reference package\"}"]},{"type":"chunk","time":1791430277842,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_HNZzKyAOnDcNLscoKIdq3835","name":"bash","arguments":"{\"command\":\"find packages/context/session-reference -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort\",\"description\":\"List all files in session-reference package\"}"}}},{"type":"chunk","time":1791430277842,"chunk":{"type":"usage","usage":{"inputTokens":94,"outputTokens":127,"cacheReadTokens":8960,"reasoningTokens":32}}},{"type":"chunk","time":1791430277842,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":5,"callId":"call_00_HNZzKyAOnDcNLscoKIdq3835","name":"bash","arguments":"{\"command\":\"find packages/context/session-reference -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort\",\"description\":\"List all files in session-reference package\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:6}}","jobId":"bash-5","owner":"{{session:1}}","kind":"bash","label":"find packages/context/session-reference -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort","startedAt":1791430277848,"toolCall":{"callId":"call_00_HNZzKyAOnDcNLscoKIdq3835","rootCallId":"call_00_HNZzKyAOnDcNLscoKIdq3835"}}}
-{"type":"job/settled","data":{"executionId":"{{id:6}}","jobId":"bash-5","owner":"{{session:1}}","kind":"bash","label":"find packages/context/session-reference -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort","startedAt":1791430277848,"toolCall":{"callId":"call_00_HNZzKyAOnDcNLscoKIdq3835","rootCallId":"call_00_HNZzKyAOnDcNLscoKIdq3835"},"finishedAt":1791430277859,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:6}}","owner":"{{session:1}}","kind":"bash","label":"find packages/context/session-reference -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort","startedAt":1791430277848,"toolCall":{"callId":"call_00_HNZzKyAOnDcNLscoKIdq3835","rootCallId":"call_00_HNZzKyAOnDcNLscoKIdq3835"}}}
+{"type":"job/settled","data":{"jobId":"{{id:6}}","owner":"{{session:1}}","kind":"bash","label":"find packages/context/session-reference -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort","startedAt":1791430277848,"toolCall":{"callId":"call_00_HNZzKyAOnDcNLscoKIdq3835","rootCallId":"call_00_HNZzKyAOnDcNLscoKIdq3835"},"finishedAt":1791430277859,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":5,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_HNZzKyAOnDcNLscoKIdq3835"},"toolCallId":"call_00_HNZzKyAOnDcNLscoKIdq3835","content":[{"type":"text","text":"packages/context/session-reference/README.md\npackages/context/session-reference/package.json\npackages/context/session-reference/src/index.ts\npackages/context/session-reference/src/uri.ts\npackages/context/session-reference/tests/session-reference.spec.ts\n"}],"isError":false,"id":"{{message:15}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[47],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":5}}
{"type":"step/start","data":{"turn":1,"step":6}}
@@ -63,22 +63,22 @@
{"type":"request/header","data":{"header":{"config":{"provider":"deepseek-official","model":"deepseek-v4-flash"},"tools":"{{tools}}"},"reason":"series"}}
{"type":"assistant/message","data":{"turn":2,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"This is turn 2 of 2. I need to randomly pick another package and output its file structure. In the first turn I picked `packages/context/session-reference`. Let me pick a different one randomly this time."},{"type":"tool-call","id":"call_00_wwDXszkz3z9JwKb8jUXj2737","name":"bash","arguments":"{\"command\":\"python3 -c \\\"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\\\"\",\"description\":\"Randomly select another package with Python\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:18}}"},"usage":{"inputTokens":389,"outputTokens":165,"cacheReadTokens":9216,"reasoningTokens":47},"stream":[{"type":"chunk","time":1791430277908,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791430277908,"index":0,"dt":[],"texts":["This is turn 2 of 2. I need to randomly pick another package and output its file structure. In the first turn I picked `packages/context/session-reference`. Let me pick a different one randomly this time."]},{"type":"chunk","time":1791430277908,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"This is turn 2 of 2. I need to randomly pick another package and output its file structure. In the first turn I picked `packages/context/session-reference`. Let me pick a different one randomly this time."}}},{"type":"chunk","time":1791430277908,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791430277908,"index":1,"dt":[],"id":"call_00_wwDXszkz3z9JwKb8jUXj2737","name":"bash","args":["{\"command\":\"python3 -c \\\"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\\\"\",\"description\":\"Randomly select another package with Python\"}"]},{"type":"chunk","time":1791430277908,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_wwDXszkz3z9JwKb8jUXj2737","name":"bash","arguments":"{\"command\":\"python3 -c \\\"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\\\"\",\"description\":\"Randomly select another package with Python\"}"}}},{"type":"chunk","time":1791430277908,"chunk":{"type":"usage","usage":{"inputTokens":389,"outputTokens":165,"cacheReadTokens":9216,"reasoningTokens":47}}},{"type":"chunk","time":1791430277908,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":2,"step":1,"callId":"call_00_wwDXszkz3z9JwKb8jUXj2737","name":"bash","arguments":"{\"command\":\"python3 -c \\\"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\\\"\",\"description\":\"Randomly select another package with Python\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:7}}","jobId":"bash-6","owner":"{{session:1}}","kind":"bash","label":"python3 -c \"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\"","startedAt":1791430277913,"toolCall":{"callId":"call_00_wwDXszkz3z9JwKb8jUXj2737","rootCallId":"call_00_wwDXszkz3z9JwKb8jUXj2737"}}}
-{"type":"job/settled","data":{"executionId":"{{id:7}}","jobId":"bash-6","owner":"{{session:1}}","kind":"bash","label":"python3 -c \"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\"","startedAt":1791430277913,"toolCall":{"callId":"call_00_wwDXszkz3z9JwKb8jUXj2737","rootCallId":"call_00_wwDXszkz3z9JwKb8jUXj2737"},"finishedAt":1791430277945,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:7}}","owner":"{{session:1}}","kind":"bash","label":"python3 -c \"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\"","startedAt":1791430277913,"toolCall":{"callId":"call_00_wwDXszkz3z9JwKb8jUXj2737","rootCallId":"call_00_wwDXszkz3z9JwKb8jUXj2737"}}}
+{"type":"job/settled","data":{"jobId":"{{id:7}}","owner":"{{session:1}}","kind":"bash","label":"python3 -c \"import random; from pathlib import Path; pkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json')); print(random.Random(1).choice(pkgs))\"","startedAt":1791430277913,"toolCall":{"callId":"call_00_wwDXszkz3z9JwKb8jUXj2737","rootCallId":"call_00_wwDXszkz3z9JwKb8jUXj2737"},"finishedAt":1791430277945,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":2,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_wwDXszkz3z9JwKb8jUXj2737"},"toolCallId":"call_00_wwDXszkz3z9JwKb8jUXj2737","content":[{"type":"text","text":"packages/context/session-reference\n"}],"isError":false,"id":"{{message:19}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[63],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":2,"step":1}}
{"type":"step/start","data":{"turn":2,"step":2}}
{"type":"assistant/message","data":{"turn":2,"step":2,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The random pick returned the same package as turn 1. Since turn 1 already covered `session-reference`, it would be better to pick a different one for variety. Let me re-roll, excluding the already-covered package."},{"type":"tool-call","id":"call_00_3K2lg9zGfGsTvuh19xv65220","name":"bash","arguments":"{\"command\": \"python3 -c \\\"\\nimport random\\nfrom pathlib import Path\\npkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json'))\\ncandidates = [p for p in pkgs if p != 'packages/context/session-reference']\\nprint(random.Random(0).choice(candidates))\\n\\\"\", \"description\": \"Re-roll excluding the package from turn 1\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:20}}"},"usage":{"inputTokens":62,"outputTokens":164,"cacheReadTokens":9728,"reasoningTokens":46},"stream":[{"type":"chunk","time":1791430277958,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791430277958,"index":0,"dt":[],"texts":["The random pick returned the same package as turn 1. Since turn 1 already covered `session-reference`, it would be better to pick a different one for variety. Let me re-roll, excluding the already-covered package."]},{"type":"chunk","time":1791430277958,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The random pick returned the same package as turn 1. Since turn 1 already covered `session-reference`, it would be better to pick a different one for variety. Let me re-roll, excluding the already-covered package."}}},{"type":"chunk","time":1791430277958,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791430277958,"index":1,"dt":[],"id":"call_00_3K2lg9zGfGsTvuh19xv65220","name":"bash","args":["{\"command\": \"python3 -c \\\"\\nimport random\\nfrom pathlib import Path\\npkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json'))\\ncandidates = [p for p in pkgs if p != 'packages/context/session-reference']\\nprint(random.Random(0).choice(candidates))\\n\\\"\", \"description\": \"Re-roll excluding the package from turn 1\"}"]},{"type":"chunk","time":1791430277958,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_3K2lg9zGfGsTvuh19xv65220","name":"bash","arguments":"{\"command\": \"python3 -c \\\"\\nimport random\\nfrom pathlib import Path\\npkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json'))\\ncandidates = [p for p in pkgs if p != 'packages/context/session-reference']\\nprint(random.Random(0).choice(candidates))\\n\\\"\", \"description\": \"Re-roll excluding the package from turn 1\"}"}}},{"type":"chunk","time":1791430277958,"chunk":{"type":"usage","usage":{"inputTokens":62,"outputTokens":164,"cacheReadTokens":9728,"reasoningTokens":46}}},{"type":"chunk","time":1791430277958,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":2,"step":2,"callId":"call_00_3K2lg9zGfGsTvuh19xv65220","name":"bash","arguments":"{\"command\": \"python3 -c \\\"\\nimport random\\nfrom pathlib import Path\\npkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json'))\\ncandidates = [p for p in pkgs if p != 'packages/context/session-reference']\\nprint(random.Random(0).choice(candidates))\\n\\\"\", \"description\": \"Re-roll excluding the package from turn 1\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:8}}","jobId":"bash-7","owner":"{{session:1}}","kind":"bash","label":"python3 -c \"\nimport random\nfrom pathlib import Path\npkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json'))\ncandidates = [p for p in pkgs if p != 'packages/context/session-reference']\nprint(random.Random(0).choice(candidates))\n\"","startedAt":1791430277963,"toolCall":{"callId":"call_00_3K2lg9zGfGsTvuh19xv65220","rootCallId":"call_00_3K2lg9zGfGsTvuh19xv65220"}}}
-{"type":"job/settled","data":{"executionId":"{{id:8}}","jobId":"bash-7","owner":"{{session:1}}","kind":"bash","label":"python3 -c \"\nimport random\nfrom pathlib import Path\npkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json'))\ncandidates = [p for p in pkgs if p != 'packages/context/session-reference']\nprint(random.Random(0).choice(candidates))\n\"","startedAt":1791430277963,"toolCall":{"callId":"call_00_3K2lg9zGfGsTvuh19xv65220","rootCallId":"call_00_3K2lg9zGfGsTvuh19xv65220"},"finishedAt":1791430277995,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:8}}","owner":"{{session:1}}","kind":"bash","label":"python3 -c \"\nimport random\nfrom pathlib import Path\npkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json'))\ncandidates = [p for p in pkgs if p != 'packages/context/session-reference']\nprint(random.Random(0).choice(candidates))\n\"","startedAt":1791430277963,"toolCall":{"callId":"call_00_3K2lg9zGfGsTvuh19xv65220","rootCallId":"call_00_3K2lg9zGfGsTvuh19xv65220"}}}
+{"type":"job/settled","data":{"jobId":"{{id:8}}","owner":"{{session:1}}","kind":"bash","label":"python3 -c \"\nimport random\nfrom pathlib import Path\npkgs = sorted(str(path.parent) for path in Path('packages').glob('*/*/package.json'))\ncandidates = [p for p in pkgs if p != 'packages/context/session-reference']\nprint(random.Random(0).choice(candidates))\n\"","startedAt":1791430277963,"toolCall":{"callId":"call_00_3K2lg9zGfGsTvuh19xv65220","rootCallId":"call_00_3K2lg9zGfGsTvuh19xv65220"},"finishedAt":1791430277995,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":2,"step":2,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_3K2lg9zGfGsTvuh19xv65220"},"toolCallId":"call_00_3K2lg9zGfGsTvuh19xv65220","content":[{"type":"text","text":"packages/llm/token-meter\n"}],"isError":false,"id":"{{message:21}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[70],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":2,"step":2}}
{"type":"step/start","data":{"turn":2,"step":3}}
{"type":"assistant/message","data":{"turn":2,"step":3,"message":{"role":"assistant","content":[{"type":"tool-call","id":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893","name":"bash","arguments":"{\"command\":\"find packages/llm/token-meter -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort\",\"description\":\"List all files in token-meter package\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:22}}"},"usage":{"inputTokens":119,"outputTokens":96,"cacheReadTokens":9856,"reasoningTokens":0},"stream":[{"type":"chunk","time":1791430278007,"chunk":{"type":"block-start","index":0,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791430278007,"index":0,"dt":[],"id":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893","name":"bash","args":["{\"command\":\"find packages/llm/token-meter -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort\",\"description\":\"List all files in token-meter package\"}"]},{"type":"chunk","time":1791430278007,"chunk":{"type":"block-end","index":0,"block":{"type":"tool-call","id":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893","name":"bash","arguments":"{\"command\":\"find packages/llm/token-meter -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort\",\"description\":\"List all files in token-meter package\"}"}}},{"type":"chunk","time":1791430278007,"chunk":{"type":"usage","usage":{"inputTokens":119,"outputTokens":96,"cacheReadTokens":9856,"reasoningTokens":0}}},{"type":"chunk","time":1791430278007,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":2,"step":3,"callId":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893","name":"bash","arguments":"{\"command\":\"find packages/llm/token-meter -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort\",\"description\":\"List all files in token-meter package\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:9}}","jobId":"bash-8","owner":"{{session:1}}","kind":"bash","label":"find packages/llm/token-meter -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort","startedAt":1791430278012,"toolCall":{"callId":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893","rootCallId":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893"}}}
-{"type":"job/settled","data":{"executionId":"{{id:9}}","jobId":"bash-8","owner":"{{session:1}}","kind":"bash","label":"find packages/llm/token-meter -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort","startedAt":1791430278012,"toolCall":{"callId":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893","rootCallId":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893"},"finishedAt":1791430278025,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:9}}","owner":"{{session:1}}","kind":"bash","label":"find packages/llm/token-meter -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort","startedAt":1791430278012,"toolCall":{"callId":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893","rootCallId":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893"}}}
+{"type":"job/settled","data":{"jobId":"{{id:9}}","owner":"{{session:1}}","kind":"bash","label":"find packages/llm/token-meter -type f -not -path '*/node_modules/*' -not -path '*/.git/*' | LC_ALL=C sort","startedAt":1791430278012,"toolCall":{"callId":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893","rootCallId":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893"},"finishedAt":1791430278025,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":2,"step":3,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893"},"toolCallId":"call_00_ET_mh4Jb1yklK6bS4luMCFO2893","content":[{"type":"text","text":"packages/llm/token-meter/README.md\npackages/llm/token-meter/package.json\npackages/llm/token-meter/src/index.ts\npackages/llm/token-meter/tests/token-meter.spec.ts\n"}],"isError":false,"id":"{{message:23}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[77],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":2,"step":3}}
{"type":"step/start","data":{"turn":2,"step":4}}
diff --git a/snapshots/web/present/session.v4.jsonl b/snapshots/web/present/session.v4.jsonl
index e16fb875bd..0b4afb818a 100644
--- a/snapshots/web/present/session.v4.jsonl
+++ b/snapshots/web/present/session.v4.jsonl
@@ -18,8 +18,8 @@
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_GgvYPol45qxGf6GezK3L3590","parentCallId":"call_00_GgvYPol45qxGf6GezK3L3590","subCallId":"call_00_GgvYPol45qxGf6GezK3L3590:ptc:1","name":"present","arguments":{"files":[{"path":"missing.txt","description":"must not exist"}]}}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_GgvYPol45qxGf6GezK3L3590","parentCallId":"call_00_GgvYPol45qxGf6GezK3L3590","subCallId":"call_00_GgvYPol45qxGf6GezK3L3590:ptc:1","name":"present","arguments":{"files":[{"path":"missing.txt","description":"must not exist"}]},"isError":true,"error":{"name":"FsError","code":"FS_NOT_FOUND"},"content":[{"type":"text","text":"Error: Cannot present missing.txt: file not found. Check the path, create the file if needed, and retry."}]}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_GgvYPol45qxGf6GezK3L3590","parentCallId":"call_00_GgvYPol45qxGf6GezK3L3590","subCallId":"call_00_GgvYPol45qxGf6GezK3L3590:ptc:2","name":"bash","arguments":{"command":"printf \"DELIVERED_REPORT\n\" > report.txt; printf \"DELIVERED_NOTE\n\" > 说明.txt","description":"Write DELIVERED_REPORT and DELIVERED_NOTE to files"}}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf \"DELIVERED_REPORT\n\" > report.txt; printf \"DELIVERED_NOTE\n\" > 说明.txt","startedAt":1791430240708,"toolCall":{"callId":"call_00_GgvYPol45qxGf6GezK3L3590:ptc:2","rootCallId":"call_00_GgvYPol45qxGf6GezK3L3590"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"printf \"DELIVERED_REPORT\n\" > report.txt; printf \"DELIVERED_NOTE\n\" > 说明.txt","startedAt":1791430240708,"toolCall":{"callId":"call_00_GgvYPol45qxGf6GezK3L3590:ptc:2","rootCallId":"call_00_GgvYPol45qxGf6GezK3L3590"},"finishedAt":1791430240719,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf \"DELIVERED_REPORT\n\" > report.txt; printf \"DELIVERED_NOTE\n\" > 说明.txt","startedAt":1791430240708,"toolCall":{"callId":"call_00_GgvYPol45qxGf6GezK3L3590:ptc:2","rootCallId":"call_00_GgvYPol45qxGf6GezK3L3590"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"printf \"DELIVERED_REPORT\n\" > report.txt; printf \"DELIVERED_NOTE\n\" > 说明.txt","startedAt":1791430240708,"toolCall":{"callId":"call_00_GgvYPol45qxGf6GezK3L3590:ptc:2","rootCallId":"call_00_GgvYPol45qxGf6GezK3L3590"},"finishedAt":1791430240719,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_GgvYPol45qxGf6GezK3L3590","parentCallId":"call_00_GgvYPol45qxGf6GezK3L3590","subCallId":"call_00_GgvYPol45qxGf6GezK3L3590:ptc:2","name":"bash","arguments":{"command":"printf \"DELIVERED_REPORT\n\" > report.txt; printf \"DELIVERED_NOTE\n\" > 说明.txt","description":"Write DELIVERED_REPORT and DELIVERED_NOTE to files"},"isError":false,"content":[{"type":"text","text":"(no output)"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_GgvYPol45qxGf6GezK3L3590","parentCallId":"call_00_GgvYPol45qxGf6GezK3L3590","subCallId":"call_00_GgvYPol45qxGf6GezK3L3590:ptc:3","name":"present","arguments":{"files":[{"path":"report.txt","description":"delivered report"},{"path":"说明.txt","description":"delivered note"}]}}}
{"type":"deliverables/presented","data":{"turn":1,"callId":"call_00_GgvYPol45qxGf6GezK3L3590:ptc:3","files":[{"path":"{{cwd}}/report.txt","description":"delivered report"},{"path":"{{cwd}}/说明.txt","description":"delivered note"}]}}
diff --git a/snapshots/web/ptc-round/session.v4.jsonl b/snapshots/web/ptc-round/session.v4.jsonl
index 424c7d9c61..87e8d076ba 100644
--- a/snapshots/web/ptc-round/session.v4.jsonl
+++ b/snapshots/web/ptc-round/session.v4.jsonl
@@ -16,8 +16,8 @@
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to write a single `run_code` program that:\n1. Runs bash to echo \"CODE_ROUND_OK\"\n2. Tries to read a file \"missing.txt\" and catches the error\n3. Returns an object with both outcomes\n4. They also want me to reply \"DONE\" and stop after\n\nLet me write this program."},{"type":"tool-call","id":"call_00_6VNoF1gDSerTBKoCfYSH3765","name":"run_code","arguments":"{\"description\": \"Run bash echo and catch missing file read\", \"code\": \"\\nconst bashResult = await tools.bash({\\n command: \\\"echo CODE_ROUND_OK\\\",\\n description: \\\"Echo CODE_ROUND_OK\\\"\\n});\\n\\nlet readError = null;\\ntry {\\n await tools.read({ file_path: \\\"missing.txt\\\" });\\n} catch (e) {\\n readError = {\\n toolName: e.toolName,\\n message: e.message\\n };\\n}\\n\\nreturn { bash: bashResult.stdout.text.trim(), readError };\\n\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:5}}"},"usage":{"inputTokens":8082,"outputTokens":235,"cacheReadTokens":384,"reasoningTokens":75},"stream":[{"type":"chunk","time":1791430263491,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1791430263507,"index":0,"dt":[16,16,16,16,15,16,15,15,17,16,16,17,16,16,16,15,17,15,17,16,17,15,17,15,16,17,16,16,15,16,15,15,17,14,16,16,17,15,16,17,15,17,15,17,16,16,17,17,16,16,17,15,16,16,16,17,16,16,15,16,17,14,16,17,16,15,17,16,15,21,16,16,17,16],"texts":["The"," user"," wants"," me"," to"," write"," a"," single"," `","run","_code","`"," program"," that",":\n","1","."," Runs"," bash"," to"," echo"," \"","CODE","_RO","UND","_OK","\"\n","2","."," T","ries"," to"," read"," a"," file"," \"","missing",".txt","\""," and"," catches"," the"," error","\n","3","."," Returns"," an"," object"," with"," both"," outcomes","\n","4","."," They"," also"," want"," me"," to"," reply"," \"","D","ONE","\""," and"," stop"," after","\n\n","Let"," me"," write"," this"," program","."]},{"type":"chunk","time":1791430264710,"chunk":{"type":"block-start","index":1,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1791430264726,"index":1,"dt":[42,15,16,17,16,16,15,16,16,17,16,16,16,17,16,16,16,16,17,16,17,15,17,16,16,17,16,15,16,15,16,16,17,15,15,16,15,17,16,16,16,16,14,17,15,16,15,16,16,16,16,17,15,16,17,17,15,17,15,16,16,16,17,15,17,17,15,17,15,17,16,15,16,15,17,16,17,14,17,15,17,16,16,17,16,16,16,16,17,15,16,16,16,17,15,16,16,17,16,16,17,17,15,17,16,16,16,17,16,14,16,17,16,17,16,16,15,17],"id":"call_00_6VNoF1gDSerTBKoCfYSH3765","name":"run_code","args":["","{","\"","description","\"",": ","\"","Run"," bash"," echo"," and"," catch"," missing"," file"," read","\"",", ","\"","code","\"",": ","\"","\\n","const"," bash","Result"," ="," await"," tools",".b","ash","({\\n"," "," command",":"," \\\"","echo"," CODE","_RO","UND","_OK","\\\",\\n"," "," description",":"," \\\"","E","cho"," CODE","_RO","UND","_OK","\\\"\\n","});\\n\\n","let"," read","Error"," ="," null",";\\n","try"," {\\n"," "," await"," tools",".read","({"," file","_path",":"," \\\"","missing",".txt","\\\""," });\\n","}"," catch"," (","e",")"," {\\n"," "," read","Error"," ="," {\\n"," "," tool","Name",":"," e",".t","ool","Name",",\\n"," "," message",":"," e",".message","\\n"," "," };\\n","}\\n\\n","return"," {"," bash",":"," bash","Result",".stdout",".text",".trim","(),"," read","Error"," };\\n","\"","}"]},{"type":"chunk","time":1791430266661,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to write a single `run_code` program that:\n1. Runs bash to echo \"CODE_ROUND_OK\"\n2. Tries to read a file \"missing.txt\" and catches the error\n3. Returns an object with both outcomes\n4. They also want me to reply \"DONE\" and stop after\n\nLet me write this program."}}},{"type":"chunk","time":1791430266677,"chunk":{"type":"block-end","index":1,"block":{"type":"tool-call","id":"call_00_6VNoF1gDSerTBKoCfYSH3765","name":"run_code","arguments":"{\"description\": \"Run bash echo and catch missing file read\", \"code\": \"\\nconst bashResult = await tools.bash({\\n command: \\\"echo CODE_ROUND_OK\\\",\\n description: \\\"Echo CODE_ROUND_OK\\\"\\n});\\n\\nlet readError = null;\\ntry {\\n await tools.read({ file_path: \\\"missing.txt\\\" });\\n} catch (e) {\\n readError = {\\n toolName: e.toolName,\\n message: e.message\\n };\\n}\\n\\nreturn { bash: bashResult.stdout.text.trim(), readError };\\n\"}"}}},{"type":"chunk","time":1791430266693,"chunk":{"type":"usage","usage":{"inputTokens":8082,"outputTokens":235,"cacheReadTokens":384,"reasoningTokens":75}}},{"type":"chunk","time":1791430266708,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_6VNoF1gDSerTBKoCfYSH3765","name":"run_code","arguments":"{\"description\": \"Run bash echo and catch missing file read\", \"code\": \"\\nconst bashResult = await tools.bash({\\n command: \\\"echo CODE_ROUND_OK\\\",\\n description: \\\"Echo CODE_ROUND_OK\\\"\\n});\\n\\nlet readError = null;\\ntry {\\n await tools.read({ file_path: \\\"missing.txt\\\" });\\n} catch (e) {\\n readError = {\\n toolName: e.toolName,\\n message: e.message\\n };\\n}\\n\\nreturn { bash: bashResult.stdout.text.trim(), readError };\\n\"}"}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765","parentCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765","subCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765:ptc:1","name":"bash","arguments":{"command":"echo CODE_ROUND_OK","description":"Echo CODE_ROUND_OK"}}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ROUND_OK","startedAt":1791430266798,"toolCall":{"callId":"call_00_6VNoF1gDSerTBKoCfYSH3765:ptc:1","rootCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ROUND_OK","startedAt":1791430266798,"toolCall":{"callId":"call_00_6VNoF1gDSerTBKoCfYSH3765:ptc:1","rootCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765"},"finishedAt":1791430266808,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ROUND_OK","startedAt":1791430266798,"toolCall":{"callId":"call_00_6VNoF1gDSerTBKoCfYSH3765:ptc:1","rootCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo CODE_ROUND_OK","startedAt":1791430266798,"toolCall":{"callId":"call_00_6VNoF1gDSerTBKoCfYSH3765:ptc:1","rootCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765"},"finishedAt":1791430266808,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765","parentCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765","subCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765:ptc:1","name":"bash","arguments":{"command":"echo CODE_ROUND_OK","description":"Echo CODE_ROUND_OK"},"isError":false,"content":[{"type":"text","text":"CODE_ROUND_OK\n"}],"meta":{"cwd":"{{cwd}}"}}}
{"type":"tool/ptc-dispatch-start","data":{"rootCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765","parentCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765","subCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765:ptc:2","name":"read","arguments":{"file_path":"missing.txt"}}}
{"type":"tool/ptc-dispatch","data":{"rootCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765","parentCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765","subCallId":"call_00_6VNoF1gDSerTBKoCfYSH3765:ptc:2","name":"read","arguments":{"file_path":"missing.txt"},"isError":true,"error":{"name":"FsError","code":"FS_NOT_FOUND"},"content":[{"type":"text","text":"Error: cannot read \"{{cwd}}/missing.txt\": not found"}]}}
diff --git a/snapshots/web/turn-tail-actions/session.v4.jsonl b/snapshots/web/turn-tail-actions/session.v4.jsonl
index 337f5a4cbe..cf892bea37 100644
--- a/snapshots/web/turn-tail-actions/session.v4.jsonl
+++ b/snapshots/web/turn-tail-actions/session.v4.jsonl
@@ -15,8 +15,8 @@
{"type":"session/title","data":{"title":"Begin your reply with the","messageSeqs":[8],"source":{"kind":"fallback"}}}
{"type":"assistant/message","data":{"turn":1,"step":1,"message":{"role":"assistant","content":[{"type":"reasoning","text":"The user wants me to begin with \"Reading the workspace now.\" and call bash with \"echo alpha\" in the same message. Then after the tool result, reply with the single word DONE and stop."},{"type":"text","text":"Reading the workspace now."},{"type":"tool-call","id":"call_00_1yZGg4XTqe0N5r1rnDLx5082","name":"bash","arguments":"{\"command\": \"echo alpha\", \"description\": \"Print alpha to stdout\"}"}],"source":{"kind":"model","provider":"deepseek-official","model":"deepseek-v4-flash"},"id":"{{message:5}}"},"usage":{"inputTokens":7788,"outputTokens":109,"totalTokens":7897,"cacheReadTokens":0,"reasoningTokens":42},"stream":[{"type":"chunk","time":1768449601814,"chunk":{"type":"block-start","index":0,"blockType":"reasoning"}},{"type":"reasoning-chunks","time0":1768449601814,"index":0,"dt":[2,1,2,0,2,1,1,0,2,1,1,1,1,1,2,1,1,1,1,0,2,2,0,2,0,2,0,2,1,1,1,1,1,0,2,0,2,1,1,1,1],"texts":["The"," user"," wants"," me"," to"," begin"," with"," \"","Reading"," the"," workspace"," now",".\""," and"," call"," bash"," with"," \"","echo"," alpha","\""," in"," the"," same"," message","."," Then"," after"," the"," tool"," result",","," reply"," with"," the"," single"," word"," D","ONE"," and"," stop","."]},{"type":"chunk","time":1768449601860,"chunk":{"type":"block-start","index":1,"blockType":"text"}},{"type":"text-chunks","time0":1768449601861,"index":1,"dt":[1,1,0,3],"texts":["Reading"," the"," workspace"," now","."]},{"type":"chunk","time":1768449601867,"chunk":{"type":"block-start","index":2,"blockType":"tool-call"}},{"type":"tool-call-chunks","time0":1768449601868,"index":2,"dt":[1,2,1,1,2,1,1,0,2,2,1,1,1,1,2,1,1,1,1,2,1],"id":"call_00_1yZGg4XTqe0N5r1rnDLx5082","name":"bash","args":["","{","\"","command","\"",": ","\"","echo"," alpha","\"",", ","\"","description","\"",": ","\"","Print"," alpha"," to"," stdout","\"","}"]},{"type":"chunk","time":1768449601895,"chunk":{"type":"block-end","index":0,"block":{"type":"reasoning","text":"The user wants me to begin with \"Reading the workspace now.\" and call bash with \"echo alpha\" in the same message. Then after the tool result, reply with the single word DONE and stop."}}},{"type":"chunk","time":1768449601896,"chunk":{"type":"block-end","index":1,"block":{"type":"text","text":"Reading the workspace now."}}},{"type":"chunk","time":1768449601897,"chunk":{"type":"block-end","index":2,"block":{"type":"tool-call","id":"call_00_1yZGg4XTqe0N5r1rnDLx5082","name":"bash","arguments":"{\"command\": \"echo alpha\", \"description\": \"Print alpha to stdout\"}"}}},{"type":"chunk","time":1768449601899,"chunk":{"type":"usage","usage":{"inputTokens":7788,"outputTokens":109,"totalTokens":7897,"cacheReadTokens":0,"reasoningTokens":42}}},{"type":"chunk","time":1768449601900,"chunk":{"type":"finish","reason":{"kind":"tool-calls"}}}]},"surfaceOp":"append"}
{"type":"tool/call","data":{"turn":1,"step":1,"callId":"call_00_1yZGg4XTqe0N5r1rnDLx5082","name":"bash","arguments":"{\"command\": \"echo alpha\", \"description\": \"Print alpha to stdout\"}"}}
-{"type":"job/registered","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo alpha","startedAt":1768449601908,"toolCall":{"callId":"call_00_1yZGg4XTqe0N5r1rnDLx5082","rootCallId":"call_00_1yZGg4XTqe0N5r1rnDLx5082"}}}
-{"type":"job/settled","data":{"executionId":"{{id:1}}","jobId":"bash-1","owner":"{{session:1}}","kind":"bash","label":"echo alpha","startedAt":1768449601908,"toolCall":{"callId":"call_00_1yZGg4XTqe0N5r1rnDLx5082","rootCallId":"call_00_1yZGg4XTqe0N5r1rnDLx5082"},"finishedAt":1768449601918,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
+{"type":"job/registered","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo alpha","startedAt":1768449601908,"toolCall":{"callId":"call_00_1yZGg4XTqe0N5r1rnDLx5082","rootCallId":"call_00_1yZGg4XTqe0N5r1rnDLx5082"}}}
+{"type":"job/settled","data":{"jobId":"{{id:1}}","owner":"{{session:1}}","kind":"bash","label":"echo alpha","startedAt":1768449601908,"toolCall":{"callId":"call_00_1yZGg4XTqe0N5r1rnDLx5082","rootCallId":"call_00_1yZGg4XTqe0N5r1rnDLx5082"},"finishedAt":1768449601918,"status":"completed","cause":"producer","producerReported":true,"detail":"exit code: 0"}}
{"type":"tool/result","data":{"turn":1,"step":1,"message":{"role":"tool","source":{"kind":"tool","callId":"call_00_1yZGg4XTqe0N5r1rnDLx5082"},"toolCallId":"call_00_1yZGg4XTqe0N5r1rnDLx5082","content":[{"type":"text","text":"alpha\n"}],"isError":false,"id":"{{message:6}}"},"meta":{"cwd":"{{cwd}}"}},"sourceEventSeqs":[15],"surfaceOp":"append"}
{"type":"step/end","data":{"turn":1,"step":1}}
{"type":"step/start","data":{"turn":1,"step":2}}