From 5cc64cae37b6d13d6c7e625d35916945027cc572 Mon Sep 17 00:00:00 2001 From: NineThoughts0521 Date: Sun, 16 Aug 2026 18:28:34 +0800 Subject: [PATCH 1/2] =?UTF-8?q?data:=20=E5=8F=91=E5=B8=83=20V4=20Pro=20?= =?UTF-8?q?=E7=8B=AC=E7=AB=8B=E5=A4=8D=E7=8E=B0=E5=AE=9E=E9=AA=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 增加 Minimal-Full 消融 preset 与零成本 schema gate - 发布 3+1 结果、成本、轨迹聚合和证据哈希 - 保留 OpenCode partial/replacement 与第三方统计边界 --- .gitignore | 6 + .../deepseek-v4-pro-anchoring/README.md | 12 + .../deepseek-v4-pro-anchoring/RESULTS.md | 44 + .../artifacts/balance-events.json | 90 + .../artifacts/comparison.json | 2524 +++++++++++++ .../artifacts/deepswe-gate.json | 38 + .../artifacts/environment-baseline.json | 27 + .../artifacts/evidence-manifest.json | 209 ++ .../artifacts/infrastructure-events.json | 50 + .../artifacts/metadata-corrections.json | 23 + .../artifacts/opencode-gate.json | 50 + .../price-P2-20260815-01-anchored.json | 31 + .../price-P2-20260815-02-standard.json | 31 + .../price-P2-20260815-03-minimal-full.json | 31 + .../price-P2-20260815-04-opencode.json | 31 + ...-P2-20260815-04b-opencode-replacement.json | 31 + .../artifacts/price-snapshot.json | 31 + .../runs/P2-20260815-01-anchored-balance.json | 7 + .../runs/P2-20260815-01-anchored.json | 1970 ++++++++++ .../runs/P2-20260815-02-standard-balance.json | 7 + .../runs/P2-20260815-02-standard.json | 3161 +++++++++++++++++ .../P2-20260815-03-minimal-full-balance.json | 7 + .../runs/P2-20260815-03-minimal-full.json | 3093 ++++++++++++++++ ...-20260815-04-opencode-partial-balance.json | 7 + .../runs/P2-20260815-04-opencode-partial.json | 98 + ...0815-04b-opencode-replacement-balance.json | 7 + .../P2-20260815-04b-opencode-replacement.json | 367 ++ .../artifacts/schema-gate.json | 190 + .../deepseek-v4-pro-anchoring/mock-prompt.txt | 1 + .../preregistration.json | 135 + .../deepseek-v4-pro-anchoring/run-matrix.json | 82 + .../scripts/analyze_opencode_session.py | 218 ++ .../scripts/analyze_session.py | 193 + .../scripts/build_comparison.py | 190 + .../scripts/build_evidence_manifest.py | 99 + .../scripts/calculate_balance_delta.py | 38 + .../scripts/capture_baseline.py | 76 + .../scripts/collect_balance.py | 57 + .../scripts/collect_price.py | 61 + .../scripts/dsh_session_driver.mjs | 190 + .../scripts/extract_candidate_prompt.py | 26 + .../scripts/launch_detached.py | 28 + .../scripts/mock_deepseek_server.mjs | 59 + .../scripts/run_mock_gate.ps1 | 99 + .../scripts/run_opencode_agent.py | 325 ++ .../run_opencode_replacement_detached.cmd | 8 + .../scripts/run_project2.ps1 | 69 + .../scripts/validate_presets.py | 65 + tools/deepseek-harness-presets/README.md | 10 + .../minimal-full/agent.cordis.yml | 255 ++ .../minimal-full/preset.yml | 3 + 51 files changed, 14460 insertions(+) create mode 100644 experiments/deepseek-v4-pro-anchoring/README.md create mode 100644 experiments/deepseek-v4-pro-anchoring/RESULTS.md create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/balance-events.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/comparison.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/deepswe-gate.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/environment-baseline.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/evidence-manifest.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/infrastructure-events.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/metadata-corrections.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/opencode-gate.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-01-anchored.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-02-standard.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-03-minimal-full.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-04-opencode.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-04b-opencode-replacement.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/price-snapshot.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-01-anchored-balance.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-01-anchored.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-02-standard-balance.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-02-standard.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-03-minimal-full-balance.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-03-minimal-full.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04-opencode-partial-balance.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04-opencode-partial.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04b-opencode-replacement-balance.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04b-opencode-replacement.json create mode 100644 experiments/deepseek-v4-pro-anchoring/artifacts/schema-gate.json create mode 100644 experiments/deepseek-v4-pro-anchoring/mock-prompt.txt create mode 100644 experiments/deepseek-v4-pro-anchoring/preregistration.json create mode 100644 experiments/deepseek-v4-pro-anchoring/run-matrix.json create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/analyze_opencode_session.py create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/analyze_session.py create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/build_comparison.py create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/build_evidence_manifest.py create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/calculate_balance_delta.py create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/capture_baseline.py create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/collect_balance.py create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/collect_price.py create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/dsh_session_driver.mjs create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/extract_candidate_prompt.py create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/launch_detached.py create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/mock_deepseek_server.mjs create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/run_mock_gate.ps1 create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/run_opencode_agent.py create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/run_opencode_replacement_detached.cmd create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/run_project2.ps1 create mode 100644 experiments/deepseek-v4-pro-anchoring/scripts/validate_presets.py create mode 100644 tools/deepseek-harness-presets/minimal-full/agent.cordis.yml create mode 100644 tools/deepseek-harness-presets/minimal-full/preset.yml diff --git a/.gitignore b/.gitignore index e666027..cdfa417 100644 --- a/.gitignore +++ b/.gitignore @@ -38,3 +38,9 @@ Thumbs.db # Do not commit evaluation archives (released as separate tarballs) archives/ + +# DeepSeek anchoring experiment: private sessions, credentials, and sandbox jobs +evaluator/trajectory_evidence/raw/ +experiments/deepseek-v4-pro-anchoring/private/ +experiments/deepseek-v4-pro-anchoring/jobs/ +experiments/deepseek-v4-pro-anchoring/.env diff --git a/experiments/deepseek-v4-pro-anchoring/README.md b/experiments/deepseek-v4-pro-anchoring/README.md new file mode 100644 index 0000000..6acb44b --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/README.md @@ -0,0 +1,12 @@ +# DeepSeek V4 Pro First-request Tool-schema Anchoring + +**独立复现者:** [@NineThoughts0521](https://github.com/NineThoughts0521) +**证据角色:** 面向 `xiaobright/modeltest` 的第三方独立复现;本目录的 runs 不并入维护者原有 formal `n`、排名、worst、均值或样本索引。 + +本目录保存 Project2 V4.1b 独立复现、Minimal-Full full-task 消融、OpenCode exploratory harness comparison 的预注册、运行器与可公开派生证据。结果见 [`RESULTS.md`](./RESULTS.md),机器可读汇总见 [`artifacts/comparison.json`](./artifacts/comparison.json)。 + +本阶段按固定顺序串行完成 Project2 Anchored、Project2 Standard、Project2 Minimal-Full。原 OpenCode 正式枪因外层进程生命周期中断而保留为不计分 partial;经批准后只修复 detached/background 生命周期并完成一次 replacement。DeepSWE pair 与 Terminal-Bench 均未在本阶段执行。 + +Project2 evaluator 保留 public/debug/hidden/ESP static 与 frozen scorer,但省略 optional real ESP-IDF build,因此四个有效结果的 F9 均按 frozen scorer 的 `skipped_env` 计 3/6。比较使用原始 Ability/Ship/Class,不构造调整分数。DSH 三枪属于机制消融;OpenCode replacement 仅作 post-preregistered exploratory comparison。 + +`private/` 和 `jobs/` 只保存在本机且被 Git 忽略。Git 中只保留预注册、runner、原 verifier 的可公开结果、派生统计、工具目录快照与原始证据 SHA-256;完整 reasoning、session JSONL、credential 和私人绝对路径不进入 Git。哈希索引由 `scripts/build_evidence_manifest.py` 零成本生成。 diff --git a/experiments/deepseek-v4-pro-anchoring/RESULTS.md b/experiments/deepseek-v4-pro-anchoring/RESULTS.md new file mode 100644 index 0000000..4abc8ec --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/RESULTS.md @@ -0,0 +1,44 @@ +# Project2 3+1 结果 + +**独立复现者:** [@NineThoughts0521](https://github.com/NineThoughts0521) +**统计边界:** 以下 runs 是第三方独立证据,不并入 `xiaobright/modeltest` 维护者原有 formal `n`、排名、worst、均值或样本索引。 + +本轮在同一 frozen Project2 V4.1b task `project2-v4-broken-seed`、同一 `CANDIDATE_PROMPT.md` 和同一 evaluator 下完成三枪 DSH 机制消融,并完成一枪经批准的 OpenCode exploratory replacement。模型为 DeepSeek V4 Pro,reasoning effort 为 `max`,价格按每枪运行时官方人民币单价记录,未使用峰谷价假设。 + +## 统一对比 + +| Harness/Preset | Ability | Ship | Class | Hidden | ESP static | F9 | reasoning_blocks | we | let_me | lets | visible replies | tool calls / distinct | miss/read/output/reasoning tokens | cost balance / usage | wall time | +|---|---:|---:|---|---|---|---|---:|---:|---:|---:|---:|---|---|---|---| +| DSH Anchored Standard | 96 | 96 | A | 44/45 | 9/9 | 3/6 skipped_env | 191 | 324 | 31 | 167 | 1 | 244 / 7 | 197,639 / 43,833,728 / 139,830 / 60,977 | ¥2.58 / ¥2.527740 | 35m41s | +| DSH Standard | 89 | 89 | B+ | 43/45 | 7/9 | 3/6 skipped_env | 88 | 25 | 149 | 2 | 41 | 166 / 7 | 154,935 / 24,029,312 / 115,229 / 39,156 | ¥1.64 / ¥1.756912 | 27m33s | +| DSH Minimal-Full | 85.5 | 85.5 | B+ | 42/45 | 6/9 | 3/6 skipped_env | 47 | 67 | 14 | 16 | 8 | 83 / 6 | 166,004 / 6,870,528 / 44,058 / 16,822 | ¥1.13 / ¥0.934123 | 11m45s | +| OpenCode 1.18.17 replacement | 93 | 93 | B+ | 43/45 | 8/9 | 3/6 skipped_env | 65 | 22 | 119 | 3 | 35 | 152 / 7 | 170,382 / 21,949,696 / 64,000 / 32,269 | ¥1.53 / ¥1.637502 | 21m33s | + +Token 列依次为 `cache-miss input / cache-read input / non-reasoning output / reasoning output`;DSH 和 OpenCode usage 字段保留各自原生的互斥计数语义。`let_me` 为绝对数量。措辞指纹不作为能力指标。 + +首请求工具目录证据为:Anchored 首次仅有 `2` 个工具(`pwsh`、`read`),随后恢复完整 Standard 目录;Standard 从 request 1 起有 `25` 个工具;Minimal-Full 从 request 1 起有 `25` 个工具;OpenCode 从 request 1 起静态解析出 `12` 个工具且没有 anchoring transition。OpenCode 的目录证据来自静态 agent resolution,不是原始 HTTP wire capture。 + +## 结论 + +1. 当前环境复现了 Standard 到 Anchored 的正向 Project2 差异,Ability 从 `89` 升至 `96`,差值为 `+7`。这只是同一 frozen task 的单次观测,不构成统计显著性证据。 +2. Standard 到 Minimal-Full 为 `-3.5` Ability,Minimal-Full 到 Anchored 为 `+10.5`。schema gate 证明 Anchored 与 Minimal-Full 的 system 和首请求非工具字段 hash 相等,transition 后的工具 schema 与 Standard 相等。这支持该任务上的 first-request catalog 关联,但 treatment 仍同时包含从窄目录恢复到完整目录的时序和状态转移,未单独隔离 transition 的每个实现细节。 +3. OpenCode 得分为 `93`,位于 Standard 与 Anchored 之间,但它是 post-preregistered exploratory harness comparison,不进入任何 DSH mechanism ablation 结论。它在 system scaffold、权限/工具目录和 runtime 上均有差异,因此不能解释为纯 harness 因果效应。 +4. 本阶段未运行外部 benchmark。Anchored 差异能否离开 Project2 延续,仍需后续单独通过 gate 的 DeepSWE pair 回答。Terminal-Bench 继续 deferred。 + +## OpenCode Replacement 边界 + +`P2-20260815-04-opencode` 永久登记为 `infrastructure_failed_partial_after_model_response`,不计分,账户费用 ¥0.24 计入总成本。其 export 包含 11 个 reasoning blocks 和 22 个 tool calls;event stream 在最终 pending tool 前记录了 23 个 completed tool events,因此两种计数均按各自来源语义保留。没有观察到 final stop,raw session/event evidence 继续 private。replacement `P2-20260815-04b-opencode-replacement` 在 frozen workspace reset 后只运行一次,使用 OpenCode `1.18.17` commit `02546dfc2e4515a4f90aaf9ceb3890df2ac2b479`、direct DeepSeek provider、`deepseek-v4-pro`、`--variant max` 和 `https://api.deepseek.com`;唯一主动改动是 detached/background 进程生命周期。 + +replacement 的 OpenCode exit 为 `0`,export exit 为 `0`,wall time 为 `1293.144s`,独立 balance window 为 ¥11.44 到 ¥9.91。evaluator 得到 `93/93/B+` 后未再运行,没有发生 result-based retry。 + +## 成本与时间 + +四个有效 run 的账户余额成本为 ¥6.88,token 复算成本为 ¥6.856277。保留的 partial 增加账户余额成本 ¥0.24、token 复算成本 ¥0.219027,因此总账户成本为 **¥7.12**,总 usage 复算成本为 **¥7.075304**。账户与 usage 的差异为 ¥0.044696,作为 reconciliation 信息保留,不静默归入任一 run。 + +四个 model run 合计消耗 5,791.667 秒 model wall time。计入已记录的环境准备、充值暂停、evaluator、artifact 生成和监视后,preregistered same-day wall clock 仍低于 12 小时硬上限。本阶段未运行 DeepSWE、Terminal-Bench、Docker/WSL2、optional real ESP-IDF build 或额外 paid smoke request。 + +## 证据与公开边界 + +`artifacts/comparison.json` 是 machine-readable aggregate,`artifacts/evidence-manifest.json` 记录 public artifact hash 和 private raw evidence hash。完整 DSH session JSONL、OpenCode session export/event streams、reasoning text、credentials 和私人路径只保存在已 ignore 的 `private/` 下。公开 artifact 仅保留 derived fingerprints、scores、token/time/cost aggregates、tool-catalog snapshots、evaluator result hashes 和 infrastructure events。 + +原始 evaluator output directory 继续作为本机 run product 保存。对比直接使用原 evaluator 的 Ability/Ship/Class、hidden result、ESP static result 和 F9 mode,没有修改题面、verifier 或 scoring。 diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/balance-events.json b/experiments/deepseek-v4-pro-anchoring/artifacts/balance-events.json new file mode 100644 index 0000000..3506232 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/balance-events.json @@ -0,0 +1,90 @@ +{ + "schema_version": 1, + "privacy": "Balance amounts remain in ignored private snapshots; public records contain per-run charge deltas and snapshot hashes only.", + "generations": [ + { + "generation": 0, + "starts_before_run_id": "P2-20260815-01-anchored", + "t0_retrieved_at_utc": "2026-08-15T03:48:09.996175+00:00", + "t0_raw_sha256": "ffc4b0c2733d6a629840ad2b138c324dda9f80e32b13cd37581d767471d48868", + "t1_retrieved_at_utc": "2026-08-15T04:28:24.481727+00:00", + "t1_raw_sha256": "af95816b823dbafadc771745ac1450cdad06b700070184d57abea3a21b003db7", + "run_ids": [ + "P2-20260815-01-anchored" + ], + "balance_delta_cny": 2.58, + "status": "closed_awaiting_recharge" + }, + { + "generation": 1, + "starts_before_run_id": "P2-20260815-02-standard", + "t0_retrieved_at_utc": "2026-08-15T07:59:01.286041+00:00", + "t0_raw_sha256": "c62b4e7651c599415b631211ab8a7290cf7d465d0acb9609113b81e1453ce405", + "t1_retrieved_at_utc": "2026-08-15T09:44:40.410653+00:00", + "t1_raw_sha256": "8482b1766f141e43581e9fbd0f1ae3b995693af49498ddbe0b43a5e77ff3e5cb", + "run_ids": [ + "P2-20260815-02-standard", + "P2-20260815-03-minimal-full", + "P2-20260815-04-opencode-partial", + "P2-20260815-04b-opencode-replacement" + ], + "balance_delta_cny": 4.54, + "status": "closed_after_opencode_replacement" + } + ], + "recharge_events": [ + { + "event_id": "recharge-20260815-generation1", + "operator_confirmed": true, + "recorded_at_utc": "2026-08-15T07:59:42.484417+00:00", + "generation_before": 0, + "generation_after": 1, + "pre_recharge_t1_raw_sha256": "af95816b823dbafadc771745ac1450cdad06b700070184d57abea3a21b003db7", + "post_recharge_t0_raw_sha256": "c62b4e7651c599415b631211ab8a7290cf7d465d0acb9609113b81e1453ce405", + "balance_amounts": "private", + "observed_balance_increase": true + } + ], + "run_windows": [ + { + "run_id": "P2-20260815-01-anchored", + "balance_generation": 0, + "t0_raw_sha256": "ffc4b0c2733d6a629840ad2b138c324dda9f80e32b13cd37581d767471d48868", + "t1_raw_sha256": "af95816b823dbafadc771745ac1450cdad06b700070184d57abea3a21b003db7", + "balance_delta_cny": 2.58, + "crosses_recharge_event": false + }, + { + "run_id": "P2-20260815-02-standard", + "balance_generation": 1, + "t0_raw_sha256": "c62b4e7651c599415b631211ab8a7290cf7d465d0acb9609113b81e1453ce405", + "t1_raw_sha256": "0f0794d8aeb92a150def92e8a28d420d4076edd0cfef3c0fa0e0d6f3b17b2cf2", + "balance_delta_cny": 1.64, + "crosses_recharge_event": false + }, + { + "run_id": "P2-20260815-03-minimal-full", + "balance_generation": 1, + "t0_raw_sha256": "0f0794d8aeb92a150def92e8a28d420d4076edd0cfef3c0fa0e0d6f3b17b2cf2", + "t1_raw_sha256": "8fc8ed1a6bef295b7e8337b982211e9605a469c128958e1326889dbb03f7f3c6", + "balance_delta_cny": 1.13, + "crosses_recharge_event": false + }, + { + "run_id": "P2-20260815-04-opencode-partial", + "balance_generation": 1, + "t0_raw_sha256": "8fc8ed1a6bef295b7e8337b982211e9605a469c128958e1326889dbb03f7f3c6", + "t1_raw_sha256": "8482b1766f141e43581e9fbd0f1ae3b995693af49498ddbe0b43a5e77ff3e5cb", + "balance_delta_cny": 0.24, + "crosses_recharge_event": false + }, + { + "run_id": "P2-20260815-04b-opencode-replacement", + "balance_generation": 1, + "t0_raw_sha256": "8482b1766f141e43581e9fbd0f1ae3b995693af49498ddbe0b43a5e77ff3e5cb", + "t1_raw_sha256": "e3b8089448756d4e2de419bd45668b581b922c4ac566ee67ec00027e9ce27208", + "balance_delta_cny": 1.53, + "crosses_recharge_event": false + } + ] +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/comparison.json b/experiments/deepseek-v4-pro-anchoring/artifacts/comparison.json new file mode 100644 index 0000000..fb3580b --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/comparison.json @@ -0,0 +1,2524 @@ +{ + "schema_version": 1, + "contributor": "@NineThoughts0521", + "evidence_role": "independent third-party replication; excluded from maintainer formal n", + "benchmark": "project2-v4.1b", + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "model": "deepseek-v4-pro", + "reasoning_effort": "max", + "pricing_policy": "official live CNY rates at each run; no peak/off-peak assumption", + "runs": [ + { + "run_id": "P2-20260815-01-anchored", + "label": "DSH Anchored Standard", + "preset": "anchored-standard", + "causal_role": "preregistered DSH mechanism ablation", + "benchmark": "project2-v4.1b", + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "model": "deepseek-v4-pro", + "provider": "deepseek-official", + "reasoning_effort": "max", + "resolved_endpoint": "https://api.deepseek.com", + "ability": 96.0, + "ship": 96.0, + "class": "A", + "hidden": "44/45", + "esp_static": "9/9", + "f9": 3.0, + "f9_mode": "skipped_env", + "reasoning_blocks": 191, + "we": 324, + "let_me": 31, + "lets": 167, + "visible_assistant_replies": 1, + "tool_calls": 244, + "distinct_tools": [ + "edit", + "grep", + "pwsh", + "read", + "todo_write", + "web_search", + "write" + ], + "input_tokens": 197639, + "cache_read_tokens": 43833728, + "output_tokens": 139830, + "reasoning_tokens": 60977, + "usage_cost_cny": 2.52774, + "balance_delta_cny": 2.58, + "balance_cost_difference_cny": 0.05226, + "wall_time_seconds": 2141.112, + "first_request_tool_count": 2, + "first_request_tool_names": [ + "pwsh", + "read" + ], + "catalog_transition": [ + { + "request_index": 1, + "tool_names": [ + "pwsh", + "read" + ], + "tools_sha256": "ce0194bea982c46bf3acdaf09354e966fd5be990bc72c302b98ced47971b5a4c", + "tools": [ + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + } + ] + }, + { + "request_index": 2, + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "56761b419a0089a7240f6670bb42c010fb14e80660e696246fbdb9c4e7fe0ca9", + "tools": [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "create_goal", + "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace. Must match exactly." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "exit_plan_mode", + "description": "Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.", + "parameters": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The complete plan, as markdown, starting with a # heading that names it." + } + }, + "required": [ + "plan" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of the running agent to interrupt." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children only; descendants walks the complete tree below you.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "ralph", + "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The immutable completion objective for every fresh Ralph round." + }, + "maxRounds": { + "type": "number", + "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.", + "parameters": { + "type": "object", + "properties": { + "subagent_id": { + "type": "string", + "description": "The subagent id returned when the background subagent was started." + }, + "message": { + "type": "string", + "description": "The message to deliver to the subagent." + } + }, + "required": [ + "subagent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit | pause | resume | complete | blocked", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Concrete blocking condition; required only with action blocked." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." + }, + "meta": { + "type": "object", + "description": "The workflow identity block (plain JSON — never code).", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + } + ], + "raw_evidence_sha256": "8d196aee55868ca34f4b6af17d9c14d67e77deb9c89e5f0fcce238d86188498b", + "benchmark_result_sha256": "64e6d8ddbec07c6a61812f5f9513d478b418d05f163bd656780f1f9c7a81f70e" + }, + { + "run_id": "P2-20260815-02-standard", + "label": "DSH Standard", + "preset": "standard", + "causal_role": "preregistered DSH mechanism ablation", + "benchmark": "project2-v4.1b", + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "model": "deepseek-v4-pro", + "provider": "deepseek-official", + "reasoning_effort": "max", + "resolved_endpoint": "https://api.deepseek.com", + "ability": 89.0, + "ship": 89.0, + "class": "B+", + "hidden": "43/45", + "esp_static": "7/9", + "f9": 3.0, + "f9_mode": "skipped_env", + "reasoning_blocks": 88, + "we": 25, + "let_me": 149, + "lets": 2, + "visible_assistant_replies": 41, + "tool_calls": 166, + "distinct_tools": [ + "edit", + "glob", + "grep", + "pwsh", + "read", + "todo_write", + "write" + ], + "input_tokens": 154935, + "cache_read_tokens": 24029312, + "output_tokens": 115229, + "reasoning_tokens": 39156, + "usage_cost_cny": 1.756912, + "balance_delta_cny": 1.64, + "balance_cost_difference_cny": -0.116912, + "wall_time_seconds": 1652.777, + "first_request_tool_count": 25, + "first_request_tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "catalog_transition": [ + { + "request_index": 1, + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "56761b419a0089a7240f6670bb42c010fb14e80660e696246fbdb9c4e7fe0ca9", + "tools": [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "create_goal", + "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace. Must match exactly." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "exit_plan_mode", + "description": "Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.", + "parameters": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The complete plan, as markdown, starting with a # heading that names it." + } + }, + "required": [ + "plan" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of the running agent to interrupt." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children only; descendants walks the complete tree below you.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "ralph", + "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The immutable completion objective for every fresh Ralph round." + }, + "maxRounds": { + "type": "number", + "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.", + "parameters": { + "type": "object", + "properties": { + "subagent_id": { + "type": "string", + "description": "The subagent id returned when the background subagent was started." + }, + "message": { + "type": "string", + "description": "The message to deliver to the subagent." + } + }, + "required": [ + "subagent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit | pause | resume | complete | blocked", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Concrete blocking condition; required only with action blocked." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." + }, + "meta": { + "type": "object", + "description": "The workflow identity block (plain JSON — never code).", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + } + ], + "raw_evidence_sha256": "2f65ea5f6f1f046e75fb8238a243aca395f781e2ff80fbef9577b2166798ca1a", + "benchmark_result_sha256": "1e272d23b7582324e52fa5f52dac9889c8da1bb661e2895a532381529c0a8fce" + }, + { + "run_id": "P2-20260815-03-minimal-full", + "label": "DSH Minimal-Full", + "preset": "minimal-full", + "causal_role": "preregistered DSH mechanism ablation", + "benchmark": "project2-v4.1b", + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "model": "deepseek-v4-pro", + "provider": "deepseek-official", + "reasoning_effort": "max", + "resolved_endpoint": "https://api.deepseek.com", + "ability": 85.5, + "ship": 85.5, + "class": "B+", + "hidden": "42/45", + "esp_static": "6/9", + "f9": 3.0, + "f9_mode": "skipped_env", + "reasoning_blocks": 47, + "we": 67, + "let_me": 14, + "lets": 16, + "visible_assistant_replies": 8, + "tool_calls": 83, + "distinct_tools": [ + "edit", + "glob", + "pwsh", + "read", + "todo_write", + "write" + ], + "input_tokens": 166004, + "cache_read_tokens": 6870528, + "output_tokens": 44058, + "reasoning_tokens": 16822, + "usage_cost_cny": 0.934123, + "balance_delta_cny": 1.13, + "balance_cost_difference_cny": 0.195877, + "wall_time_seconds": 704.634, + "first_request_tool_count": 25, + "first_request_tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "catalog_transition": [ + { + "request_index": 1, + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "56761b419a0089a7240f6670bb42c010fb14e80660e696246fbdb9c4e7fe0ca9", + "tools": [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "create_goal", + "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace. Must match exactly." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "exit_plan_mode", + "description": "Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.", + "parameters": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The complete plan, as markdown, starting with a # heading that names it." + } + }, + "required": [ + "plan" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of the running agent to interrupt." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children only; descendants walks the complete tree below you.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "ralph", + "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The immutable completion objective for every fresh Ralph round." + }, + "maxRounds": { + "type": "number", + "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.", + "parameters": { + "type": "object", + "properties": { + "subagent_id": { + "type": "string", + "description": "The subagent id returned when the background subagent was started." + }, + "message": { + "type": "string", + "description": "The message to deliver to the subagent." + } + }, + "required": [ + "subagent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit | pause | resume | complete | blocked", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Concrete blocking condition; required only with action blocked." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." + }, + "meta": { + "type": "object", + "description": "The workflow identity block (plain JSON — never code).", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + } + ], + "raw_evidence_sha256": "f769b58934ff862c807799a99a0593081c8cb639fe2e68b6787129cd9501fd53", + "benchmark_result_sha256": "12c04b355e062617e72ef89690f0227bd754fdd0c5d6c6add74674dbf587fb92" + }, + { + "run_id": "P2-20260815-04b-opencode-replacement", + "label": "OpenCode replacement", + "preset": "opencode-direct-deepseek", + "causal_role": "post-preregistered exploratory harness comparison", + "benchmark": "project2-v4.1b", + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "model": "deepseek-v4-pro", + "provider": "deepseek", + "reasoning_effort": "max", + "resolved_endpoint": "https://api.deepseek.com", + "ability": 93.0, + "ship": 93.0, + "class": "B+", + "hidden": "43/45", + "esp_static": "8/9", + "f9": 3.0, + "f9_mode": "skipped_env", + "reasoning_blocks": 65, + "we": 22, + "let_me": 119, + "lets": 3, + "visible_assistant_replies": 35, + "tool_calls": 152, + "distinct_tools": [ + "bash", + "edit", + "glob", + "grep", + "read", + "todowrite", + "write" + ], + "input_tokens": 170382, + "cache_read_tokens": 21949696, + "output_tokens": 64000, + "reasoning_tokens": 32269, + "usage_cost_cny": 1.637502, + "balance_delta_cny": 1.53, + "balance_cost_difference_cny": -0.107502, + "wall_time_seconds": 1293.144, + "first_request_tool_count": 12, + "first_request_tool_names": [ + "bash", + "edit", + "glob", + "grep", + "invalid", + "question", + "read", + "skill", + "task", + "todowrite", + "webfetch", + "write" + ], + "catalog_transition": "OpenCode comparison has no anchoring transition; static-resolved full catalog applies from request 1", + "raw_evidence_sha256": "178f35c19d9853cb92579c4c9458da4203a5c911021415d6c1d94720b3b73472", + "benchmark_result_sha256": "5b078eb2b647584fcf018f452621900f885a1efe22be93fb85cc66dae5b8dbce" + } + ], + "preserved_partial": { + "run_id": "P2-20260815-04-opencode-partial", + "status": "infrastructure_failed_partial_after_model_response", + "benchmark_score_status": "not_run_partial_agent_artifact_not_comparable", + "reasoning_blocks": 11, + "we": 0, + "let_me": 14, + "lets": 0, + "visible_assistant_replies": 2, + "export_tool_calls": 22, + "event_stream_completed_tool_events": 23, + "final_stop_observed": false, + "usage_cost_cny": 0.219027, + "balance_delta_cny": 0.24, + "raw_evidence_sha256": "cd35ebd8ace7be453bb159c772b5cec0b77b059d1f6ce997a1fee2b7113d5e06", + "partial_event_stream_sha256": "1f4e89c7ea3265e534108c8a3f3cf718497bda3e312cedc4dac2a8a8dd3d07e4", + "valid_for_harness_score_comparison": false + }, + "descriptive_contrasts": { + "standard_minus_minimal_full_ability": 3.5, + "anchored_minus_minimal_full_ability": 10.5, + "anchored_minus_standard_ability": 7.0, + "opencode_minus_anchored_ability": -3.0, + "opencode_minus_standard_ability": 4.0 + }, + "cost_totals_cny": { + "four_valid_runs_account_balance": 6.88, + "four_valid_runs_usage_recomputed": 6.856277, + "preserved_partial_account": 0.24, + "preserved_partial_usage_recomputed": 0.219027, + "all_account_balance_including_partial": 7.12, + "all_usage_recomputed_including_partial": 7.075304, + "all_account_minus_usage_recomputed": 0.044696 + }, + "limitations": [ + "The three DSH rows are single frozen-task observations; no significance test or new score is introduced.", + "OpenCode is exploratory and excluded from the DSH mechanism-ablation contrasts.", + "F9 is 3/6 skipped_env because the preregistered optional real ESP-IDF build was not run.", + "Trajectory wording is a fingerprint only, not a capability metric or causal evidence." + ] +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/deepswe-gate.json b/experiments/deepseek-v4-pro-anchoring/artifacts/deepswe-gate.json new file mode 100644 index 0000000..1973261 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/deepswe-gate.json @@ -0,0 +1,38 @@ +{ + "schema_version": 1, + "started_at_utc": "2026-08-15T03:42:35.0000000+00:00", + "ended_at_utc": "2026-08-15T03:46:39.5353381+00:00", + "elapsed_seconds": 244.535, + "hard_cap_seconds": 5400, + "deepswe_commit": "435ee89ec2f2e2289f33b0da4f992f0b7b7266b9", + "pier_version": "0.3.1", + "pier_commit": "df89f994623a0a6a57229103b6fe910766693c30", + "task_id": "httpx-deterministic-cookie-store", + "task_toml_sha256": "2a20d40c96714c08333c0fb4901fe3b022d845fd17a3bf5a1b8ca7a5631886fa", + "task_toml_unmodified": true, + "agent_network_mode": "no-network", + "verifier_network_mode": "no-network", + "pier_static_semantics": { + "agent_install_spec_phase": "docker image build", + "formal_agent_egress_phase": "runtime Squid filtered egress", + "install_and_inference_phases_separated": true, + "planned_formal_allowlist": [ + "api.deepseek.com" + ] + }, + "environment": { + "docker_command_present": false, + "docker_daemon_ready": false, + "wsl_binary_version": "10.0.19041.4522", + "wsl2_distribution_ready": false, + "wsl_store_update_attempt_exit_code": 1, + "wsl_store_update_requires_elevation": true + }, + "semantic_gate": "failed_environment_prerequisite", + "pair_decision": "cancelled_before_adapter_or_paid_request", + "paid_requests": 0, + "notes": [ + "Pinned Pier static network path passed structural review.", + "Docker/WSL2 execution prerequisite failed locally; the 90-minute gate ended early rather than changing task network policy." + ] +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/environment-baseline.json b/experiments/deepseek-v4-pro-anchoring/artifacts/environment-baseline.json new file mode 100644 index 0000000..c683539 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/environment-baseline.json @@ -0,0 +1,27 @@ +{ + "schema_version": 1, + "captured_at_utc": "2026-08-15T03:47:01.218464+00:00", + "modeltest_head": "04255b55f16c4439e538239fb9783070c4165081", + "modeltest_status_paths": [ + "gitignore", + "tools/deepseek-harness-presets/README.md", + "experiments/", + "tools/deepseek-harness-presets/minimal-full/" + ], + "dsh_package": "@deepseek-ai/dsh", + "dsh_version": "0.1.0-rc.6", + "dsh_source_commit_preregistered": "47f943859bef60e4160492346772ded9b24f765a", + "preset_hash_algorithm": "SHA-256 over sorted (UTF-8 relative path length/path, file length/bytes)", + "preset_hashes": { + "standard": "c189672ea0510032e76cd61c39e1d85da7e4cf84ba51b51909444bfe98a91987", + "minimal-full": "86359556bbc00623951b2b782f66b51626975e90d65a4400cfd8e85be3ab7106", + "anchored-standard": "30934f191facfd5956cb32a78a40fbbd761b32a3dfe1214e2ed45ecb8a1e74f3" + }, + "environment": { + "platform": "Windows-10-10.0.19045-SP0", + "python": "3.14.3", + "node": "v25.9.0", + "powershell": "Windows PowerShell host", + "processor_architecture": "AMD64" + } +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/evidence-manifest.json b/experiments/deepseek-v4-pro-anchoring/artifacts/evidence-manifest.json new file mode 100644 index 0000000..18a3cde --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/evidence-manifest.json @@ -0,0 +1,209 @@ +{ + "schema_version": 1, + "contributor": "@NineThoughts0521", + "evidence_role": "independent third-party replication; excluded from maintainer formal n", + "candidate_prompt_sha256": "576103f9a5a7a619c0669674cf384a975b89390bb034c368a53a148251f2df84", + "public_artifacts": [ + { + "path": "README.md", + "bytes": 1653, + "sha256": "7eeaa3afb8545b58db997f130625475fda4bb4e80da65f9c46233cdf44b66c90" + }, + { + "path": "RESULTS.md", + "bytes": 5883, + "sha256": "5b4bd969ffc0d3962199b42d1c552dbb583763d3d44e10c73a75556a022de96c" + }, + { + "path": "mock-prompt.txt", + "bytes": 88, + "sha256": "7ec5d1271502de4d8667113b51e6ca4d64337990dc30cb091d09a043d4e9484a" + }, + { + "path": "preregistration.json", + "bytes": 5417, + "sha256": "1b8b4c5a967ebf700dd189184a4579f5321146a5a00fe84aa72861e870d6cb3e" + }, + { + "path": "run-matrix.json", + "bytes": 2751, + "sha256": "dc8aeceabf104eac78e5927f0dd77d2cbfa5a96442220b6ca74ec2b029de24bf" + }, + { + "path": "artifacts/environment-baseline.json", + "bytes": 1086, + "sha256": "55881b727c5fde2dc6afe4d436194fa0308f3a24c32682243b4a75740824685b" + }, + { + "path": "artifacts/schema-gate.json", + "bytes": 5558, + "sha256": "f3fcc3c3856c3e1ecc990d825db42a2c26bc16d2f73b0345165d32e089f65475" + }, + { + "path": "artifacts/opencode-gate.json", + "bytes": 1832, + "sha256": "629b0dc4a0380e1ba4e8a5fe3fe789f025fe5deeb88f700c832c211f4bcd30fd" + }, + { + "path": "artifacts/deepswe-gate.json", + "bytes": 1530, + "sha256": "0d272fc8b467ec0a0d35bd082c1115bdee58786271c3c24fe310b59d34dbce13" + }, + { + "path": "artifacts/balance-events.json", + "bytes": 3759, + "sha256": "d6505eff000459d54d8eaaea8fabc8104342fc01c4429fb5802c6d5bac19c1bf" + }, + { + "path": "artifacts/infrastructure-events.json", + "bytes": 1972, + "sha256": "649c9c46af93d9cf2623d4dec53ee2bb4e47cd99c97158cccb6063b61bcffc4a" + }, + { + "path": "artifacts/metadata-corrections.json", + "bytes": 898, + "sha256": "01b2e7f29fe71f8c9b3e47f8f6322240786fb0bb06fb19d1edac8e1c3eecf3df" + }, + { + "path": "artifacts/comparison.json", + "bytes": 142142, + "sha256": "714d49f23495aa170245cd11c817cac69339642f3ceb00336810945181162b53" + }, + { + "path": "artifacts/price-snapshot.json", + "bytes": 885, + "sha256": "25ae1e9159be6f4beea25174fb1e5ffd3903647c43508523cacff3fde28a2e02" + }, + { + "path": "artifacts/price-P2-20260815-01-anchored.json", + "bytes": 885, + "sha256": "4e35dcb08296e71b1de2aad4acc1b7efc95ecc2449ae6ae5a83b9fea65e0c858" + }, + { + "path": "artifacts/price-P2-20260815-02-standard.json", + "bytes": 885, + "sha256": "b853238d420a34e6a5064d0d33b347bbe704d740c0250c1f5aa78c7b767ee24f" + }, + { + "path": "artifacts/price-P2-20260815-03-minimal-full.json", + "bytes": 885, + "sha256": "25ae1e9159be6f4beea25174fb1e5ffd3903647c43508523cacff3fde28a2e02" + }, + { + "path": "artifacts/price-P2-20260815-04-opencode.json", + "bytes": 885, + "sha256": "8c052abc3ac6172542045652d5c54100de5630424afd829e8ac8967e1d77885a" + }, + { + "path": "artifacts/price-P2-20260815-04b-opencode-replacement.json", + "bytes": 885, + "sha256": "e690012e343c3cdc7f818f263515de12e690aa5a80d3112aeb09a83e4533d958" + }, + { + "path": "artifacts/runs/P2-20260815-01-anchored-balance.json", + "bytes": 163, + "sha256": "75c00c9ef3487efed46cef1f3b2e668187799c72906407d6b264e0c84205de93" + }, + { + "path": "artifacts/runs/P2-20260815-01-anchored.json", + "bytes": 119124, + "sha256": "c363a928a0449d7bd5535828ec4d655c1638b9dc2a44029f887ff62cf73b71d5" + }, + { + "path": "artifacts/runs/P2-20260815-02-standard-balance.json", + "bytes": 163, + "sha256": "0c589378740dde03ae2f362fb3893e676721c6beae0228127a1157acdc335e4b" + }, + { + "path": "artifacts/runs/P2-20260815-02-standard.json", + "bytes": 182680, + "sha256": "c56a68f895db0b0f93ec1f284bc63ca327a1904d98b6b687aedb83d4dc3c41a6" + }, + { + "path": "artifacts/runs/P2-20260815-03-minimal-full-balance.json", + "bytes": 167, + "sha256": "7ac03ca524461addeceba94afe1109e5108a7a52bd677d22fa5b59ac9b781db3" + }, + { + "path": "artifacts/runs/P2-20260815-03-minimal-full.json", + "bytes": 169062, + "sha256": "debfcbbc5c33576011181b6a18a4c897692bbf3aee384f8a0c1d03ef11a736ea" + }, + { + "path": "artifacts/runs/P2-20260815-04-opencode-partial-balance.json", + "bytes": 171, + "sha256": "4bb562df23c1901bd6e9bf09b8b23aac6fdedef823b3c41cd0b9bf4d0e870a70" + }, + { + "path": "artifacts/runs/P2-20260815-04-opencode-partial.json", + "bytes": 3667, + "sha256": "6e06809f7e9638819fc9eeb260f90604523afb725cb890943af32cec66119b56" + }, + { + "path": "artifacts/runs/P2-20260815-04b-opencode-replacement-balance.json", + "bytes": 176, + "sha256": "afe5c6145c95448bd4f9692ba3632f7525d287a873dac6a8f1872e4e4538671c" + }, + { + "path": "artifacts/runs/P2-20260815-04b-opencode-replacement.json", + "bytes": 20245, + "sha256": "696be1dd077ce1038c3ebdf0aaf39b88ccc1d2f7373bbd453a6aaf4937ade311" + } + ], + "private_raw_evidence": [ + { + "run_id": "P2-20260815-01-anchored", + "evidence_kind": "dsh_session_jsonl", + "bytes": 27501965, + "sha256": "8d196aee55868ca34f4b6af17d9c14d67e77deb9c89e5f0fcce238d86188498b" + }, + { + "run_id": "P2-20260815-02-standard", + "evidence_kind": "dsh_session_jsonl", + "bytes": 23644897, + "sha256": "2f65ea5f6f1f046e75fb8238a243aca395f781e2ff80fbef9577b2166798ca1a" + }, + { + "run_id": "P2-20260815-03-minimal-full", + "evidence_kind": "dsh_session_jsonl", + "bytes": 11808103, + "sha256": "f769b58934ff862c807799a99a0593081c8cb639fe2e68b6787129cd9501fd53" + }, + { + "run_id": "P2-20260815-04-opencode-partial", + "evidence_kind": "opencode_partial_session_export", + "bytes": 475547, + "sha256": "cd35ebd8ace7be453bb159c772b5cec0b77b059d1f6ce997a1fee2b7113d5e06" + }, + { + "run_id": "P2-20260815-04-opencode-partial", + "evidence_kind": "opencode_partial_event_stream", + "bytes": 464811, + "sha256": "1f4e89c7ea3265e534108c8a3f3cf718497bda3e312cedc4dac2a8a8dd3d07e4" + }, + { + "run_id": "P2-20260815-04b-opencode-replacement", + "evidence_kind": "opencode_session_export", + "bytes": 1932490, + "sha256": "178f35c19d9853cb92579c4c9458da4203a5c911021415d6c1d94720b3b73472" + }, + { + "run_id": "P2-20260815-04b-opencode-replacement", + "evidence_kind": "opencode_event_stream", + "bytes": 1739965, + "sha256": "6cae0b03dfa162f3cfc57ec5960fbc38f536fe5c7c2cabdcec34d4d356a7a896" + }, + { + "run_id": "P2-20260815-04b-opencode-replacement", + "evidence_kind": "opencode_run_meta", + "bytes": 1304, + "sha256": "7a0b2b9c367a0c9b87a7e7e1a64b11dfd00b9f6e18981a1baa3ae2babad34c4f" + } + ], + "privacy": { + "private_paths_published": false, + "raw_reasoning_or_session_published": false, + "credentials_published": false, + "recalculation": "Recompute each listed SHA-256 from the ignored local raw file and compare it with this manifest and the corresponding public run artifact." + } +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/infrastructure-events.json b/experiments/deepseek-v4-pro-anchoring/artifacts/infrastructure-events.json new file mode 100644 index 0000000..56d705a --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/infrastructure-events.json @@ -0,0 +1,50 @@ +{ + "schema_version": 1, + "events": [ + { + "run_id": "P2-20260815-02-standard", + "phase": "pre-response", + "attempt": 1, + "event": "runner PowerShell expression parse/runtime error before reset and session creation", + "paid_requests": 0, + "model_outcome_created": false, + "action": "same-run infrastructure retry after runner fix", + "preregistered_retry_limit": 1 + }, + { + "run_id": "P2-20260815-04-opencode", + "phase": "pre-process", + "attempt": 1, + "event": "Codex unified PTY CreateProcess access denied before Python runner, reset, OpenCode session, or paid request", + "paid_requests": 0, + "model_outcome_created": false, + "action": "same-run non-PTY infrastructure retry", + "preregistered_retry_limit": 1 + }, + { + "run_id": "P2-20260815-04-opencode-partial", + "phase": "agent-after-response", + "attempt": 2, + "event": "hosting tool session was aborted while the OpenCode process was active; child process ended with one pending tool and no final stop", + "paid_requests": "yes", + "model_outcome_created": true, + "action": "preserved private partial session and public derived/hash evidence; no automatic retry or synthetic continuation", + "completed_tool_events": 23, + "reasoning_blocks": 11, + "final_stop_observed": false + }, + { + "run_id": "P2-20260815-04b-opencode-replacement", + "phase": "agent-complete", + "attempt": 1, + "event": "detached/background lifecycle wrapper completed OpenCode and private session export without external chat-session interruption", + "paid_requests": "yes", + "model_outcome_created": true, + "action": "single authorized replacement; no further retry regardless of evaluator outcome", + "opencode_exit_code": 0, + "export_exit_code": 0, + "wall_time_seconds": 1293.144, + "replacement_limit": 1 + } + ] +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/metadata-corrections.json b/experiments/deepseek-v4-pro-anchoring/artifacts/metadata-corrections.json new file mode 100644 index 0000000..264288b --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/metadata-corrections.json @@ -0,0 +1,23 @@ +{ + "schema_version": 1, + "corrections": [ + { + "run_id": "P2-20260815-01-anchored", + "evaluator_result_id": "20260815_122420", + "field": "api_cost_cny", + "captured_value": 1.934823, + "corrected_value": 2.52774, + "reason": "DSH inputTokens are already cache-miss-only disjoint counts; the initial analyzer subtracted cacheReadTokens a second time.", + "score_affected": false + }, + { + "run_id": "P2-20260815-01-anchored", + "evaluator_result_id": "20260815_122420", + "field": "resolved_api_endpoint", + "captured_value": null, + "corrected_value": "https://api.deepseek.com", + "reason": "The formal runner fixed provider=deepseek-official and rejected DEEPSEEK_BASE_URL or DSH settings endpoint overrides; the first analyzer version omitted the resolved default endpoint field.", + "score_affected": false + } + ] +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/opencode-gate.json b/experiments/deepseek-v4-pro-anchoring/artifacts/opencode-gate.json new file mode 100644 index 0000000..f72dc98 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/opencode-gate.json @@ -0,0 +1,50 @@ +{ + "schema_version": 1, + "status": "passed", + "checked_at_utc": "2026-08-15T08:55:00Z", + "opencode_version": "1.18.17", + "opencode_tag": "v1.18.17", + "opencode_commit": "02546dfc2e4515a4f90aaf9ceb3890df2ac2b479", + "provider": "deepseek", + "model": "deepseek/deepseek-v4-pro", + "model_api_id": "deepseek-v4-pro", + "resolved_endpoint": "https://api.deepseek.com", + "provider_sdk": "@ai-sdk/openai-compatible", + "reasoning_variant": "max", + "resolved_reasoning_options": { + "reasoningEffort": "max" + }, + "reasoning_export": { + "status": "statically_supported", + "evidence": "OpenCode export serializes full message parts; the pinned schema includes native reasoning parts. Raw exports remain private." + }, + "agent": "build", + "agent_mode": "primary", + "pure": true, + "system_instruction_source": "OpenCode built-in build agent/system prompt; isolated home/config; no repository AGENTS.md, CLAUDE.md, CONTEXT.md, or opencode.json discovered before the run", + "tool_names": [ + "bash", + "edit", + "glob", + "grep", + "invalid", + "question", + "read", + "skill", + "task", + "todowrite", + "webfetch", + "write" + ], + "models_catalog_sha256": "38c46f27db899736408a1371f78d79a3ade5f0d2fe5ca279e2c448ce2b247995", + "catalog_storage": "private/gitignored", + "credential_source": "existing DSH DeepSeek credential injected only as DEEPSEEK_API_KEY into the isolated OpenCode child process", + "credential_recorded": false, + "zen_used": false, + "custom_gateway_used": false, + "paid_smoke_run": false, + "limitations": [ + "Tool catalog evidence is the pinned OpenCode debug-agent resolution rather than a raw HTTP wire capture.", + "The formal session score remains valid if the provider export omits reasoning text; trajectory comparison will then be marked incomplete." + ] +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-01-anchored.json b/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-01-anchored.json new file mode 100644 index 0000000..53f9f7b --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-01-anchored.json @@ -0,0 +1,31 @@ +{ + "schema_version": 1, + "source_url": "https://api-docs.deepseek.com/zh-cn/quick_start/pricing", + "retrieved_at_utc": "2026-08-15T03:47:01.835590+00:00", + "raw_sha256": "2bab2555968333b6e0a6e9f04c5427880f36fba491d95790c3f44261e00c7d07", + "current_cny_per_million": { + "cache_hit": 0.025, + "cache_miss": 3.0, + "output": 6.0 + }, + "applicable_cny_per_million": { + "cache_hit": 0.025, + "cache_miss": 3.0, + "output": 6.0 + }, + "applicable_window": "before_2026-08-17T00:00:00+08:00", + "documented_future_effective_at": "2026-08-17T00:00:00+08:00", + "documented_future_cny_per_million": { + "off_peak": { + "cache_hit": 0.15, + "cache_miss": 4.5, + "output": 13.5 + }, + "peak": { + "cache_hit": 0.3, + "cache_miss": 9.0, + "output": 27.0 + } + }, + "documented_future_peak_offpeak_present": true +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-02-standard.json b/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-02-standard.json new file mode 100644 index 0000000..054a734 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-02-standard.json @@ -0,0 +1,31 @@ +{ + "schema_version": 1, + "source_url": "https://api-docs.deepseek.com/zh-cn/quick_start/pricing", + "retrieved_at_utc": "2026-08-15T08:00:18.895766+00:00", + "raw_sha256": "2bab2555968333b6e0a6e9f04c5427880f36fba491d95790c3f44261e00c7d07", + "current_cny_per_million": { + "cache_hit": 0.025, + "cache_miss": 3.0, + "output": 6.0 + }, + "applicable_cny_per_million": { + "cache_hit": 0.025, + "cache_miss": 3.0, + "output": 6.0 + }, + "applicable_window": "before_2026-08-17T00:00:00+08:00", + "documented_future_effective_at": "2026-08-17T00:00:00+08:00", + "documented_future_cny_per_million": { + "off_peak": { + "cache_hit": 0.15, + "cache_miss": 4.5, + "output": 13.5 + }, + "peak": { + "cache_hit": 0.3, + "cache_miss": 9.0, + "output": 27.0 + } + }, + "documented_future_peak_offpeak_present": true +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-03-minimal-full.json b/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-03-minimal-full.json new file mode 100644 index 0000000..f70253a --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-03-minimal-full.json @@ -0,0 +1,31 @@ +{ + "schema_version": 1, + "source_url": "https://api-docs.deepseek.com/zh-cn/quick_start/pricing", + "retrieved_at_utc": "2026-08-15T08:30:47.786766+00:00", + "raw_sha256": "2bab2555968333b6e0a6e9f04c5427880f36fba491d95790c3f44261e00c7d07", + "current_cny_per_million": { + "cache_hit": 0.025, + "cache_miss": 3.0, + "output": 6.0 + }, + "applicable_cny_per_million": { + "cache_hit": 0.025, + "cache_miss": 3.0, + "output": 6.0 + }, + "applicable_window": "before_2026-08-17T00:00:00+08:00", + "documented_future_effective_at": "2026-08-17T00:00:00+08:00", + "documented_future_cny_per_million": { + "off_peak": { + "cache_hit": 0.15, + "cache_miss": 4.5, + "output": 13.5 + }, + "peak": { + "cache_hit": 0.3, + "cache_miss": 9.0, + "output": 27.0 + } + }, + "documented_future_peak_offpeak_present": true +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-04-opencode.json b/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-04-opencode.json new file mode 100644 index 0000000..3f5b679 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-04-opencode.json @@ -0,0 +1,31 @@ +{ + "schema_version": 1, + "source_url": "https://api-docs.deepseek.com/zh-cn/quick_start/pricing", + "retrieved_at_utc": "2026-08-15T09:20:16.051509+00:00", + "raw_sha256": "2bab2555968333b6e0a6e9f04c5427880f36fba491d95790c3f44261e00c7d07", + "current_cny_per_million": { + "cache_hit": 0.025, + "cache_miss": 3.0, + "output": 6.0 + }, + "applicable_cny_per_million": { + "cache_hit": 0.025, + "cache_miss": 3.0, + "output": 6.0 + }, + "applicable_window": "before_2026-08-17T00:00:00+08:00", + "documented_future_effective_at": "2026-08-17T00:00:00+08:00", + "documented_future_cny_per_million": { + "off_peak": { + "cache_hit": 0.15, + "cache_miss": 4.5, + "output": 13.5 + }, + "peak": { + "cache_hit": 0.3, + "cache_miss": 9.0, + "output": 27.0 + } + }, + "documented_future_peak_offpeak_present": true +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-04b-opencode-replacement.json b/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-04b-opencode-replacement.json new file mode 100644 index 0000000..5becced --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/price-P2-20260815-04b-opencode-replacement.json @@ -0,0 +1,31 @@ +{ + "schema_version": 1, + "source_url": "https://api-docs.deepseek.com/zh-cn/quick_start/pricing", + "retrieved_at_utc": "2026-08-15T11:15:25.782319+00:00", + "raw_sha256": "2bab2555968333b6e0a6e9f04c5427880f36fba491d95790c3f44261e00c7d07", + "current_cny_per_million": { + "cache_hit": 0.025, + "cache_miss": 3.0, + "output": 6.0 + }, + "applicable_cny_per_million": { + "cache_hit": 0.025, + "cache_miss": 3.0, + "output": 6.0 + }, + "applicable_window": "before_2026-08-17T00:00:00+08:00", + "documented_future_effective_at": "2026-08-17T00:00:00+08:00", + "documented_future_cny_per_million": { + "off_peak": { + "cache_hit": 0.15, + "cache_miss": 4.5, + "output": 13.5 + }, + "peak": { + "cache_hit": 0.3, + "cache_miss": 9.0, + "output": 27.0 + } + }, + "documented_future_peak_offpeak_present": true +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/price-snapshot.json b/experiments/deepseek-v4-pro-anchoring/artifacts/price-snapshot.json new file mode 100644 index 0000000..f70253a --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/price-snapshot.json @@ -0,0 +1,31 @@ +{ + "schema_version": 1, + "source_url": "https://api-docs.deepseek.com/zh-cn/quick_start/pricing", + "retrieved_at_utc": "2026-08-15T08:30:47.786766+00:00", + "raw_sha256": "2bab2555968333b6e0a6e9f04c5427880f36fba491d95790c3f44261e00c7d07", + "current_cny_per_million": { + "cache_hit": 0.025, + "cache_miss": 3.0, + "output": 6.0 + }, + "applicable_cny_per_million": { + "cache_hit": 0.025, + "cache_miss": 3.0, + "output": 6.0 + }, + "applicable_window": "before_2026-08-17T00:00:00+08:00", + "documented_future_effective_at": "2026-08-17T00:00:00+08:00", + "documented_future_cny_per_million": { + "off_peak": { + "cache_hit": 0.15, + "cache_miss": 4.5, + "output": 13.5 + }, + "peak": { + "cache_hit": 0.3, + "cache_miss": 9.0, + "output": 27.0 + } + }, + "documented_future_peak_offpeak_present": true +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-01-anchored-balance.json b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-01-anchored-balance.json new file mode 100644 index 0000000..435d188 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-01-anchored-balance.json @@ -0,0 +1,7 @@ +{ + "schema_version": 1, + "run_id": "P2-20260815-01-anchored", + "balance_generation": 0, + "balance_delta_cny": 2.58, + "crosses_recharge_event": false +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-01-anchored.json b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-01-anchored.json new file mode 100644 index 0000000..d9a2a19 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-01-anchored.json @@ -0,0 +1,1970 @@ +{ + "schema_version": 1, + "benchmark": "project2-v4.1b", + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "run_id": "P2-20260815-01-anchored", + "model": "deepseek-v4-pro", + "provider": "deepseek-official", + "reasoning_effort": "max", + "dsh_version": "0.1.0-rc.6", + "dsh_commit": "47f943859bef60e4160492346772ded9b24f765a", + "preset": "anchored-standard", + "preset_hash": "30934f191facfd5956cb32a78a40fbbd761b32a3dfe1214e2ed45ecb8a1e74f3", + "os_environment": "Windows 10 / PowerShell / DSH native Windows", + "started_at_utc": "2026-08-15T03:48:32.126Z", + "ended_at_utc": "2026-08-15T04:24:13.238Z", + "wall_time_seconds": 2141.112, + "turn_duration_seconds": 2129.42, + "benchmark_result": { + "project": "\\project2_task", + "results_dir": "\\evaluator\\results\\20260815_122420", + "benchmark": "project2-v4.1b", + "steps": { + "public": 0, + "debug_probe": 0, + "hidden": 1, + "espidf_static": 0 + }, + "details": { + "hidden_summary": "\\evaluator\\results\\20260815_122420\\hidden_summary.json", + "espidf_static_summary": "\\evaluator\\results\\20260815_122420\\espidf_static_summary.json", + "pr_template": { + "expected": "\\project2_task\\PULL_REQUEST_TEMPLATE.md", + "found": true, + "source": "\\project2_task\\PULL_REQUEST_TEMPLATE.md", + "artifact": "\\evaluator\\results\\20260815_122420\\pull_request_template.md", + "bytes": 11641 + }, + "git": { + "baseline": "project2-v4-broken-seed", + "diff": "\\evaluator\\results\\20260815_122420\\candidate_diff.patch", + "diff_lines": 2315, + "shortstat": "18 files changed, 817 insertions(+), 666 deletions(-)", + "status": "\\evaluator\\results\\20260815_122420\\candidate_status.txt", + "changed_file_count": 29, + "log": "\\evaluator\\results\\20260815_122420\\candidate_log.txt", + "duration_source": "unknown" + }, + "score_draft": "\\evaluator\\results\\20260815_122420\\score_draft.json", + "blockers": "\\evaluator\\results\\20260815_122420\\blockers.json", + "score_draft_confidence": "\\evaluator\\results\\20260815_122420\\score_draft_confidence.json", + "dimensions": "\\evaluator\\results\\20260815_122420\\dimensions.json" + }, + "meta": { + "model": "deepseek-v4-pro", + "channel": "deepseek-official", + "harness": "dsh-anchored-standard", + "benchmark": "project2-v4.1b", + "seed_tag": "project2-v4-broken-seed", + "registry_version": "v4.1b", + "schema_version": 1, + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "run_id": "P2-20260815-01-anchored", + "provider": "deepseek-official", + "reasoning_effort": "max", + "dsh_version": "0.1.0-rc.6", + "dsh_commit": "47f943859bef60e4160492346772ded9b24f765a", + "preset": "anchored-standard", + "preset_hash": "30934f191facfd5956cb32a78a40fbbd761b32a3dfe1214e2ed45ecb8a1e74f3", + "os_environment": "Windows 10 / PowerShell / DSH native Windows", + "started_at_utc": "2026-08-15T03:48:32.126Z", + "ended_at_utc": "2026-08-15T04:24:13.238Z", + "wall_time_seconds": 2141.112, + "turn_duration_seconds": 2129.42, + "benchmark_result": null, + "benchmark_result_sha256": null, + "usage": { + "cacheReadTokens": 43833728, + "inputTokens": 197639, + "outputTokens": 139830, + "reasoningTokens": 60977 + }, + "input_tokens": 197639, + "cache_read_tokens": 43833728, + "cache_miss_tokens": 0, + "output_tokens": 139830, + "reasoning_tokens": 60977, + "api_cost_cny": 1.934823, + "tool_call_count": 244, + "distinct_tools_used": [ + "edit", + "grep", + "pwsh", + "read", + "todo_write", + "web_search", + "write" + ], + "tool_breakdown": { + "edit": 71, + "grep": 13, + "pwsh": 73, + "read": 60, + "todo_write": 2, + "web_search": 7, + "write": 18 + }, + "visible_assistant_replies": [ + "已把 Project2 修到可提测状态。最终验证:\n\n- `python tests\\run_public_tests.py project2_task`(配合工作区 `sitecustomize.py` 沙箱 tempfile 兼容,`PYTHONPATH` 指向 `project2_task`):**4 个 public 测试文件全部通过**。\n- `python tools\\run_debug_probe.py project2_task`:**8 项 `[probe:ok]` 全部通过**(缺/伪 Cookie 拒绝、有效 Cookie 通过、unknown/过期 session 拒绝、care_event 未登录拒绝、room/bed 规范化、voice 显式取会话)。\n- 额外验证:旧 `care_events` 缺列库迁移且旧行保留、混合有/无 room-bed 的 CSV 按行分床、管理员密码非明文且加盐 PBKDF2。\n\n改动摘要:\n- **鉴权/会话**:`auth.py` 盐化 PBKDF2、token 仅存 SHA-256 且严格校验;`gateway.py` 管理 API 不再对本机豁免,v3 context 强制显式 session、过期/unknown/缺 actor 一律拒绝,未授权不返回患者、记忆、护理事件和 session 对象。\n- **care_event**:补全表列迁移、CRUD、路由、倒序/limit、大小写规范化,并接入 `modalities.care_events`。\n- **睡眠 CSV**:`first` 修复为第一个床位,无归属策略按行生效,显式 room/bed 行不串床。\n- **voice**:先取 `/api/v3/session/current` 的显式 `session_id` 再请求 context,不靠 ambient session。\n- **ESP32-S3 `testpro4`**:新增 `protocol_packet / maixsense_parser / device_config / mqtt_payload / network_backhaul` 模块,恢复 Wi-Fi STA + 巴法云 MQTT,topic 小写拼接,`payload_b64` 动态 base64 编码原始 payload,USB CDC 与 MQTT 并行;CMake/idf_component 补齐 `esp_wifi/esp_netif/esp_event/lwip/mqtt/mbedtls`。\n- 同步更新 README、gateway README、RK3588 指南、ESP32 文档和已填写的 `PULL_REQUEST_TEMPLATE.md`。\n\n剩余风险:\n- 本机未安装 ESP-IDF(`ESP_IDF_ACTIVATION_SCRIPT`/`IDF_PATH` 为空),`run_espidf_build.py` 明确失败在环境检查(exit 2),Windows wrapper 也因空 activation 参数失败;**未声称编译通过**。固件只做了源码实现与 g++ 语法检查,需在装 IDF 的机器上跑 `idf.py build` 验证 MQTT 配置结构体等 API 兼容性。\n- 未做实机 flash、真实 Wi-Fi/MQTT、USB 枚举、ToF/MLX 读数验证。\n- `sitecustomize.py` 仅在 `DSH_SESSION_ID` 沙箱内启用,用于绕开 CPython 3.12.4+ Windows `TemporaryDirectory(0o700)` 的 ACL 问题;普通部署不受影响。" + ], + "first_request_tool_catalog": { + "request_index": 1, + "tool_names": [ + "pwsh", + "read" + ], + "tools_sha256": "ce0194bea982c46bf3acdaf09354e966fd5be990bc72c302b98ced47971b5a4c", + "tools": [ + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + } + ] + }, + "catalog_transition": [ + { + "request_index": 1, + "tool_names": [ + "pwsh", + "read" + ], + "tools_sha256": "ce0194bea982c46bf3acdaf09354e966fd5be990bc72c302b98ced47971b5a4c", + "tools": [ + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + } + ] + }, + { + "request_index": 2, + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "56761b419a0089a7240f6670bb42c010fb14e80660e696246fbdb9c4e7fe0ca9", + "tools": [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "create_goal", + "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace. Must match exactly." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "exit_plan_mode", + "description": "Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.", + "parameters": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The complete plan, as markdown, starting with a # heading that names it." + } + }, + "required": [ + "plan" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of the running agent to interrupt." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children only; descendants walks the complete tree below you.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "ralph", + "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The immutable completion objective for every fresh Ralph round." + }, + "maxRounds": { + "type": "number", + "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.", + "parameters": { + "type": "object", + "properties": { + "subagent_id": { + "type": "string", + "description": "The subagent id returned when the background subagent was started." + }, + "message": { + "type": "string", + "description": "The message to deliver to the subagent." + } + }, + "required": [ + "subagent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit | pause | resume | complete | blocked", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Concrete blocking condition; required only with action blocked." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." + }, + "meta": { + "type": "object", + "description": "The workflow identity block (plain JSON — never code).", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + } + ], + "system_prompt_sha256_by_request": [ + "5fab6e32f283d71510531ce850df2690b8fb77437d36bfabbe8c4ac862f19df9", + "5fab6e32f283d71510531ce850df2690b8fb77437d36bfabbe8c4ac862f19df9" + ], + "trajectory_fingerprints": { + "reasoning_blocks": 191, + "reasoning_chars": 248659, + "reasoning_words": 35111, + "we": 324, + "let_me": 31, + "lets": 167 + }, + "raw_evidence_sha256": "8d196aee55868ca34f4b6af17d9c14d67e77deb9c89e5f0fcce238d86188498b", + "run_group_id": "dsv4p-anchor-20260815-anchored-standard", + "thinking_level": "max", + "endpoint_product": "DeepSeek-API", + "billing_tier": "paygo", + "run_index": 1, + "complete": true, + "meta_partial": false + }, + "meta_complete": true, + "duration_sec": 19, + "duration_source": "eval_wall", + "ability_draft": 96.0, + "ship_draft": 96.0, + "release_class_hint": "A", + "blockers": [], + "behavior_blockers": [], + "semantic_only_codes": [ + "V4-F12-04" + ], + "f9_mode": "skipped_env", + "f11_status": "heuristic", + "family_draft": { + "F1": 8.0, + "F2": 12.0, + "F3": 16.0, + "F4": 4.0, + "F5": 12.0, + "F6": 10.0, + "F7": 8.0, + "F8": 8.0, + "F9": 3.0, + "F10": 8.0, + "F11": 4.0, + "F12": 3.0 + }, + "dimensions": { + "final_code": 10.0, + "security": 9.72, + "migration": 10.0, + "esp_deploy": 7.86, + "process_truth": 10.0, + "efficiency": null + } + }, + "benchmark_result_sha256": "64e6d8ddbec07c6a61812f5f9513d478b418d05f163bd656780f1f9c7a81f70e", + "usage": { + "cacheReadTokens": 43833728, + "inputTokens": 197639, + "outputTokens": 139830, + "reasoningTokens": 60977 + }, + "input_tokens": 197639, + "cache_read_tokens": 43833728, + "cache_miss_tokens": 197639, + "output_tokens": 139830, + "reasoning_tokens": 60977, + "api_cost_cny": 2.52774, + "api_cost_scope": "DSH completed agent assistant/message usage; official runtime rates; auxiliary title traffic excluded", + "usage_semantics": "input_tokens and cache_read_tokens are disjoint DSH counts", + "tool_call_count": 244, + "distinct_tools_used": [ + "edit", + "grep", + "pwsh", + "read", + "todo_write", + "web_search", + "write" + ], + "tool_breakdown": { + "edit": 71, + "grep": 13, + "pwsh": 73, + "read": 60, + "todo_write": 2, + "web_search": 7, + "write": 18 + }, + "visible_assistant_replies": [ + "已把 Project2 修到可提测状态。最终验证:\n\n- `python tests\\run_public_tests.py project2_task`(配合工作区 `sitecustomize.py` 沙箱 tempfile 兼容,`PYTHONPATH` 指向 `project2_task`):**4 个 public 测试文件全部通过**。\n- `python tools\\run_debug_probe.py project2_task`:**8 项 `[probe:ok]` 全部通过**(缺/伪 Cookie 拒绝、有效 Cookie 通过、unknown/过期 session 拒绝、care_event 未登录拒绝、room/bed 规范化、voice 显式取会话)。\n- 额外验证:旧 `care_events` 缺列库迁移且旧行保留、混合有/无 room-bed 的 CSV 按行分床、管理员密码非明文且加盐 PBKDF2。\n\n改动摘要:\n- **鉴权/会话**:`auth.py` 盐化 PBKDF2、token 仅存 SHA-256 且严格校验;`gateway.py` 管理 API 不再对本机豁免,v3 context 强制显式 session、过期/unknown/缺 actor 一律拒绝,未授权不返回患者、记忆、护理事件和 session 对象。\n- **care_event**:补全表列迁移、CRUD、路由、倒序/limit、大小写规范化,并接入 `modalities.care_events`。\n- **睡眠 CSV**:`first` 修复为第一个床位,无归属策略按行生效,显式 room/bed 行不串床。\n- **voice**:先取 `/api/v3/session/current` 的显式 `session_id` 再请求 context,不靠 ambient session。\n- **ESP32-S3 `testpro4`**:新增 `protocol_packet / maixsense_parser / device_config / mqtt_payload / network_backhaul` 模块,恢复 Wi-Fi STA + 巴法云 MQTT,topic 小写拼接,`payload_b64` 动态 base64 编码原始 payload,USB CDC 与 MQTT 并行;CMake/idf_component 补齐 `esp_wifi/esp_netif/esp_event/lwip/mqtt/mbedtls`。\n- 同步更新 README、gateway README、RK3588 指南、ESP32 文档和已填写的 `PULL_REQUEST_TEMPLATE.md`。\n\n剩余风险:\n- 本机未安装 ESP-IDF(`ESP_IDF_ACTIVATION_SCRIPT`/`IDF_PATH` 为空),`run_espidf_build.py` 明确失败在环境检查(exit 2),Windows wrapper 也因空 activation 参数失败;**未声称编译通过**。固件只做了源码实现与 g++ 语法检查,需在装 IDF 的机器上跑 `idf.py build` 验证 MQTT 配置结构体等 API 兼容性。\n- 未做实机 flash、真实 Wi-Fi/MQTT、USB 枚举、ToF/MLX 读数验证。\n- `sitecustomize.py` 仅在 `DSH_SESSION_ID` 沙箱内启用,用于绕开 CPython 3.12.4+ Windows `TemporaryDirectory(0o700)` 的 ACL 问题;普通部署不受影响。" + ], + "first_request_tool_catalog": { + "request_index": 1, + "tool_names": [ + "pwsh", + "read" + ], + "tools_sha256": "ce0194bea982c46bf3acdaf09354e966fd5be990bc72c302b98ced47971b5a4c", + "tools": [ + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + } + ] + }, + "catalog_transition": [ + { + "request_index": 1, + "tool_names": [ + "pwsh", + "read" + ], + "tools_sha256": "ce0194bea982c46bf3acdaf09354e966fd5be990bc72c302b98ced47971b5a4c", + "tools": [ + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + } + ] + }, + { + "request_index": 2, + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "56761b419a0089a7240f6670bb42c010fb14e80660e696246fbdb9c4e7fe0ca9", + "tools": [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "create_goal", + "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace. Must match exactly." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "exit_plan_mode", + "description": "Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.", + "parameters": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The complete plan, as markdown, starting with a # heading that names it." + } + }, + "required": [ + "plan" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of the running agent to interrupt." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children only; descendants walks the complete tree below you.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "ralph", + "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The immutable completion objective for every fresh Ralph round." + }, + "maxRounds": { + "type": "number", + "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.", + "parameters": { + "type": "object", + "properties": { + "subagent_id": { + "type": "string", + "description": "The subagent id returned when the background subagent was started." + }, + "message": { + "type": "string", + "description": "The message to deliver to the subagent." + } + }, + "required": [ + "subagent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit | pause | resume | complete | blocked", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Concrete blocking condition; required only with action blocked." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." + }, + "meta": { + "type": "object", + "description": "The workflow identity block (plain JSON — never code).", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + } + ], + "system_prompt_sha256_by_request": [ + "5fab6e32f283d71510531ce850df2690b8fb77437d36bfabbe8c4ac862f19df9", + "5fab6e32f283d71510531ce850df2690b8fb77437d36bfabbe8c4ac862f19df9" + ], + "trajectory_fingerprints": { + "reasoning_blocks": 191, + "reasoning_chars": 248659, + "reasoning_words": 35111, + "we": 324, + "let_me": 31, + "lets": 167 + }, + "raw_evidence_sha256": "8d196aee55868ca34f4b6af17d9c14d67e77deb9c89e5f0fcce238d86188498b", + "resolved_api_endpoint": "https://api.deepseek.com", + "cost_reconciliation": { + "balance_generation": 0, + "t0_raw_sha256": "ffc4b0c2733d6a629840ad2b138c324dda9f80e32b13cd37581d767471d48868", + "t1_raw_sha256": "af95816b823dbafadc771745ac1450cdad06b700070184d57abea3a21b003db7", + "account_balance_delta_cny": 2.58, + "official_rate_recomputed_agent_usage_cny": 2.52774, + "account_minus_recomputed_cny": 0.05226, + "crosses_recharge_event": false, + "notes": "Balance includes all account traffic in the isolated T0/T1 window, including DSH auxiliary requests; token recomputation covers completed agent assistant/message usage." + } +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-02-standard-balance.json b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-02-standard-balance.json new file mode 100644 index 0000000..ba950fa --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-02-standard-balance.json @@ -0,0 +1,7 @@ +{ + "schema_version": 1, + "run_id": "P2-20260815-02-standard", + "balance_generation": 1, + "balance_delta_cny": 1.64, + "crosses_recharge_event": false +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-02-standard.json b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-02-standard.json new file mode 100644 index 0000000..46d9f5e --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-02-standard.json @@ -0,0 +1,3161 @@ +{ + "schema_version": 1, + "benchmark": "project2-v4.1b", + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "run_id": "P2-20260815-02-standard", + "model": "deepseek-v4-pro", + "provider": "deepseek-official", + "reasoning_effort": "max", + "dsh_version": "0.1.0-rc.6", + "dsh_commit": "47f943859bef60e4160492346772ded9b24f765a", + "preset": "standard", + "preset_hash": "c189672ea0510032e76cd61c39e1d85da7e4cf84ba51b51909444bfe98a91987", + "os_environment": "Windows 10 / PowerShell / DSH native Windows", + "started_at_utc": "2026-08-15T08:01:01.827Z", + "ended_at_utc": "2026-08-15T08:28:34.604Z", + "wall_time_seconds": 1652.777, + "turn_duration_seconds": 1599.416, + "benchmark_result": { + "project": "\\project2_task", + "results_dir": "\\evaluator\\results\\20260815_162840", + "benchmark": "project2-v4.1b", + "steps": { + "public": 0, + "debug_probe": 0, + "hidden": 1, + "espidf_static": 1 + }, + "details": { + "hidden_summary": "\\evaluator\\results\\20260815_162840\\hidden_summary.json", + "espidf_static_summary": "\\evaluator\\results\\20260815_162840\\espidf_static_summary.json", + "pr_template": { + "expected": "\\project2_task\\PULL_REQUEST_TEMPLATE.md", + "found": true, + "source": "\\project2_task\\PULL_REQUEST_TEMPLATE.md", + "artifact": "\\evaluator\\results\\20260815_162840\\pull_request_template.md", + "bytes": 15467 + }, + "git": { + "baseline": "project2-v4-broken-seed", + "diff": "\\evaluator\\results\\20260815_162840\\candidate_diff.patch", + "diff_lines": 2231, + "shortstat": "17 files changed, 740 insertions(+), 651 deletions(-)", + "status": "\\evaluator\\results\\20260815_162840\\candidate_status.txt", + "changed_file_count": 27, + "log": "\\evaluator\\results\\20260815_162840\\candidate_log.txt", + "duration_source": "unknown" + }, + "score_draft": "\\evaluator\\results\\20260815_162840\\score_draft.json", + "blockers": "\\evaluator\\results\\20260815_162840\\blockers.json", + "score_draft_confidence": "\\evaluator\\results\\20260815_162840\\score_draft_confidence.json", + "dimensions": "\\evaluator\\results\\20260815_162840\\dimensions.json" + }, + "meta": { + "model": "deepseek-v4-pro", + "channel": "deepseek-official", + "harness": "dsh-standard", + "benchmark": "project2-v4.1b", + "seed_tag": "project2-v4-broken-seed", + "registry_version": "v4.1b", + "schema_version": 1, + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "run_id": "P2-20260815-02-standard", + "provider": "deepseek-official", + "reasoning_effort": "max", + "dsh_version": "0.1.0-rc.6", + "dsh_commit": "47f943859bef60e4160492346772ded9b24f765a", + "preset": "standard", + "preset_hash": "c189672ea0510032e76cd61c39e1d85da7e4cf84ba51b51909444bfe98a91987", + "os_environment": "Windows 10 / PowerShell / DSH native Windows", + "started_at_utc": "2026-08-15T08:01:01.827Z", + "ended_at_utc": "2026-08-15T08:28:34.604Z", + "wall_time_seconds": 1652.777, + "turn_duration_seconds": 1599.416, + "benchmark_result": null, + "benchmark_result_sha256": null, + "usage": { + "cacheReadTokens": 24029312, + "inputTokens": 154935, + "outputTokens": 115229, + "reasoningTokens": 39156 + }, + "input_tokens": 154935, + "cache_read_tokens": 24029312, + "cache_miss_tokens": 154935, + "output_tokens": 115229, + "reasoning_tokens": 39156, + "api_cost_cny": 1.756912, + "api_cost_scope": "DSH completed agent assistant/message usage; official runtime rates; auxiliary title traffic excluded", + "usage_semantics": "input_tokens and cache_read_tokens are disjoint DSH counts", + "tool_call_count": 166, + "distinct_tools_used": [ + "edit", + "glob", + "grep", + "pwsh", + "read", + "todo_write", + "write" + ], + "tool_breakdown": { + "edit": 46, + "glob": 4, + "grep": 5, + "pwsh": 39, + "read": 44, + "todo_write": 3, + "write": 25 + }, + "visible_assistant_replies": [ + "Now let me read the gateway support modules.", + "Now let me run the required initial diagnostics before making any changes.", + "The tests fail at DB creation in temp directories. Let me investigate whether this is an environment/sandbox issue or a code issue.", + "The sandbox denies writes outside the workspace, so `tempfile` targets fail. Let me redirect TEMP into the workspace when running diagnostics.", + "The sandbox locks directories created with `tempfile`'s restrictive mode. I'll build a small environment shim for running diagnostics (not touching tests/tools), then verify the full flow.", + "The sandbox blocks `tempfile`'s default restrictive-mode dirs. I'll create a transparent environment shim (outside tests/tools, not part of project code) so the mandated diagnostics can run here, then re-run them.", + "Public tests pass with the shim. Note `sleep_epoch rows=1 bed=R1203-B2` — the \"first\" policy imported to the last bed (bug confirmed). Now the debug probe:", + "Probe confirms 6 failures + voice warning. Now let me examine the voice assistant and ESP32 firmware.", + "Now let me look at the ESP32 firmware.", + "Let me read the ESP32 docs and remaining project docs.", + "Let me check which gateway endpoints the local workers actually call, to size the local-service whitelist correctly.", + "Now I have a complete picture. Let me set up the work plan and start fixing.", + "Now fixing `auth.py` — salted hashing, DB-backed existence check, token-validated sessions, and legacy plaintext migration:", + "Now `db.py` — full care_events schema plus legacy migration:", + "Now `care_events.py`:", + "Now the main `gateway.py` changes. First the imports and auth policy:", + "Now the voice module — explicit current-session fetch:", + "Let me verify the gateway-side fixes before moving to ESP32:", + "The probe expects an `ok` flag on the create result. Fix:", + "Probe passes. Run public tests and write a quick extra check for migration + end-to-end flows:", + "Public tests pass. Let me write a deeper verification script (in scratch space, not part of the project) covering migration, auth flows, care events, sleep import, and context authorization:", + "The indexes referenced `ts` before migration ran. Move care_events indexes into the migration:", + "Two real issues found: denied shape should mirror the contract (`modalities` all `{}`), and actor identity should only be echoed when authorized (prevents identity probing via a leaked session id). Fix both:", + "Fix the scratch script's broken line, then re-run:", + "Two scratch-script bugs (legacy db deleted before use; voice deps missing). Let me fix the script and debug the sleep check in isolation:", + "The importer works but TTL pruning drops the imported rows — let me check the actual CSV timestamps:", + "Example CSVs have no `ts` column (fallback = now), so pruning is by design; my scratch CSV used stale timestamps. Fix the scratch script (also legacy ordering and text-based voice check):", + "All 60 checks pass. Now the ESP32 firmware restructure:", + "Now `device_config.h/.cpp`:", + "Now rewrite `main.cpp` as glue using the new modules:", + "Now update the build files:", + "Now run the ESP-IDF build to record the exact failure point:", + "ESP-IDF is not installed here (both entries fail on the missing activation script). But `g++` exists — let me host-compile and unit-test the self-contained protocol/parser modules:", + "CRC + parser logic verified on host. Now host-test the MQTT payload builder with a reference base64 (stub mbedtls header kept in scratch only):", + "Both host tests pass. Now update documentation. Let me read the remaining docs:", + "Let me check for stale \"not implemented\" references across docs:", + "Now run the full final verification suite:", + "All green. Now write the final `PULL_REQUEST_TEMPLATE.md`:", + "One claim in the PR (logout invalidates old cookie) wasn't actually exercised in my extended checks — let me add that test and verify:", + "Logout verified. Final cleanup of scratch artifacts (keeping only a clearly-labeled environment shim) and a last full test run:", + "全部完成。简短总结:\n\n## 改了什么\n\n**Gateway 鉴权与安全**\n- `gateway/auth.py`:密码改为 PBKDF2-SHA256(200k)+随机盐,绝不存明文;`admin_account_exists()` 改为查库;会话按 token hash + 过期时间校验(伪造 Cookie 直接拒绝);历史明文账户登录后原地重哈希;logout 后旧 Cookie 立即失效。\n- `gateway/gateway.py`:管理 API(subjects/assignments/memories/credentials/sessions/care events)即使本机调用也必须带管理员 Cookie;本机无 Cookie 例外收敛为白名单(`/api/v2/*`、`/api/esp/*`、`/api/v3/context/chat`、identity/gallery、identity/match、vision/observation、session/current)。v3 上下文严格校验(actor 非空 + recognized + assurance 非空 + 未过期),拒绝时患者明细/记忆/护理事件全空、不回显 actor 身份。新增 `POST/GET /api/v3/care/events` 路由。\n\n**数据与模块**\n- `gateway/db.py`:`care_events` 全字段表 + 旧库缺列自动迁移(回填 `ts=created_ts`、规范化 room/bed、旧行保留,索引在迁移后创建)。\n- `gateway/care_events.py`:完整 CRUD、双侧 room/bed 规范化、limit/倒序、真实上下文摘要(`modalities.care_events`)。\n- `gateway/sleep_importer.py`:修复 `first` 策略误取最后一个床位;显式 room/bed 行按行规范化只写对应床位,无归属行才走策略。\n- `voice/voice_assistant_integrated.py`:先显式 GET `/api/v3/session/current` 再带 session_id 请求上下文,不再依赖隐式 ambient session。\n\n**ESP32-S3(esp32/testpro4)**\n- 恢复 Wi-Fi STA + 巴法云 MQTT 回传,按交接规范拆出 `protocol_packet` / `maixsense_parser` / `device_config` / `mqtt_payload` / `network_backhaul` 五个模块;USB CDC 契约不变,并行向 `{room}{bed}tof1/tof2/mlx1/mlx2` 发布 `{\"payload_b64\":...}`(动态缓冲);NVS 校验 `ssid/uid/room/bed` 齐全才联网;`CMakeLists.txt`/`idf_component.yml` 补齐 `esp_wifi/esp_netif/esp_event/lwip/mqtt/mbedtls` 依赖。\n\n**文档**:README、gateway/README、RK3588_TEST_GUIDE、CHANGES、esp32 各文档同步;`PULL_REQUEST_TEMPLATE.md` 已写入真实初始诊断、修改说明、验证结果与风险(无「待填写」)。\n\n## 验证了什么\n- `tests\\run_public_tests.py`:全部通过(初始 3 文件失败 → 0)。\n- `tools\\run_debug_probe.py`:初始 6 项 FAIL + voice 警告 → 全部通过。\n- 自建 60+ 项断言:旧库迁移、密码非明文/重哈希、伪造 token/logout 失效、管理 API 401、care_event 大写命中、上下文 5 种拒绝场景 + staff/patient 授权、混合 CSV 按行归属——全部通过。\n- ESP 协议模块主机单测(g++):CRC16-CCITT 校验值 0x29B1、帧解析(噪声/分块/坏尾/异常长度/半帧)、10018B payload base64 JSON 与参考实现一致。\n\n## 风险\n- **ESP-IDF 编译未验证**:本机无 IDF 工具链,`run_espidf_build.py` 明确失败在 `ESP-IDF activation script not found`(exit 2),未虚报通过;Wi-Fi/MQTT 代码按 v5.x/6.x API 编写,需在有 EIM IDF v6.0.1 的机器上实编。\n- 实机未验证:flash/monitor、真实 Wi-Fi/MQTT 连通、ToF/MLX 实机读数。\n- voice/vision 重依赖模块无法本机全量实跑(已做调用链级验证)。\n- 本机沙箱限制 tempfile 写入,诊断通过工作区外的 `.dshcompat` 环境 shim 运行(不影响项目代码,PR 中已如实说明)。" + ], + "first_request_tool_catalog": { + "request_index": 1, + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "56761b419a0089a7240f6670bb42c010fb14e80660e696246fbdb9c4e7fe0ca9", + "tools": [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "create_goal", + "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace. Must match exactly." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "exit_plan_mode", + "description": "Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.", + "parameters": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The complete plan, as markdown, starting with a # heading that names it." + } + }, + "required": [ + "plan" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of the running agent to interrupt." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children only; descendants walks the complete tree below you.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "ralph", + "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The immutable completion objective for every fresh Ralph round." + }, + "maxRounds": { + "type": "number", + "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.", + "parameters": { + "type": "object", + "properties": { + "subagent_id": { + "type": "string", + "description": "The subagent id returned when the background subagent was started." + }, + "message": { + "type": "string", + "description": "The message to deliver to the subagent." + } + }, + "required": [ + "subagent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit | pause | resume | complete | blocked", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Concrete blocking condition; required only with action blocked." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." + }, + "meta": { + "type": "object", + "description": "The workflow identity block (plain JSON — never code).", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + }, + "catalog_transition": [ + { + "request_index": 1, + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "56761b419a0089a7240f6670bb42c010fb14e80660e696246fbdb9c4e7fe0ca9", + "tools": [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "create_goal", + "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace. Must match exactly." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "exit_plan_mode", + "description": "Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.", + "parameters": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The complete plan, as markdown, starting with a # heading that names it." + } + }, + "required": [ + "plan" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of the running agent to interrupt." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children only; descendants walks the complete tree below you.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "ralph", + "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The immutable completion objective for every fresh Ralph round." + }, + "maxRounds": { + "type": "number", + "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.", + "parameters": { + "type": "object", + "properties": { + "subagent_id": { + "type": "string", + "description": "The subagent id returned when the background subagent was started." + }, + "message": { + "type": "string", + "description": "The message to deliver to the subagent." + } + }, + "required": [ + "subagent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit | pause | resume | complete | blocked", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Concrete blocking condition; required only with action blocked." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." + }, + "meta": { + "type": "object", + "description": "The workflow identity block (plain JSON — never code).", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + } + ], + "system_prompt_sha256_by_request": [ + "eb8aa5af63179f92403c1835d1b525e16ace733b3b5e52565bb68ee033a9e4e7" + ], + "trajectory_fingerprints": { + "reasoning_blocks": 88, + "reasoning_chars": 147877, + "reasoning_words": 20999, + "we": 25, + "let_me": 149, + "lets": 2 + }, + "raw_evidence_sha256": "2f65ea5f6f1f046e75fb8238a243aca395f781e2ff80fbef9577b2166798ca1a", + "run_group_id": "dsv4p-anchor-20260815-standard", + "thinking_level": "max", + "endpoint_product": "DeepSeek-API", + "billing_tier": "paygo", + "run_index": 1, + "complete": true, + "meta_partial": false + }, + "meta_complete": true, + "duration_sec": 14, + "duration_source": "eval_wall", + "ability_draft": 89.0, + "ship_draft": 89.0, + "release_class_hint": "B+", + "blockers": [ + "S-ambient", + "E-contract" + ], + "behavior_blockers": [ + "S-ambient", + "E-contract" + ], + "semantic_only_codes": [ + "V4-F12-04" + ], + "f9_mode": "skipped_env", + "f11_status": "heuristic", + "family_draft": { + "F1": 8.0, + "F2": 12.0, + "F3": 11.0, + "F4": 4.0, + "F5": 12.0, + "F6": 10.0, + "F7": 8.0, + "F8": 6.0, + "F9": 3.0, + "F10": 8.0, + "F11": 4.0, + "F12": 3.0 + }, + "dimensions": { + "final_code": 10.0, + "security": 8.33, + "migration": 10.0, + "esp_deploy": 6.43, + "process_truth": 10.0, + "efficiency": null + } + }, + "benchmark_result_sha256": "1e272d23b7582324e52fa5f52dac9889c8da1bb661e2895a532381529c0a8fce", + "usage": { + "cacheReadTokens": 24029312, + "inputTokens": 154935, + "outputTokens": 115229, + "reasoningTokens": 39156 + }, + "input_tokens": 154935, + "cache_read_tokens": 24029312, + "cache_miss_tokens": 154935, + "output_tokens": 115229, + "reasoning_tokens": 39156, + "api_cost_cny": 1.756912, + "api_cost_scope": "DSH completed agent assistant/message usage; official runtime rates; auxiliary title traffic excluded", + "usage_semantics": "input_tokens and cache_read_tokens are disjoint DSH counts", + "tool_call_count": 166, + "distinct_tools_used": [ + "edit", + "glob", + "grep", + "pwsh", + "read", + "todo_write", + "write" + ], + "tool_breakdown": { + "edit": 46, + "glob": 4, + "grep": 5, + "pwsh": 39, + "read": 44, + "todo_write": 3, + "write": 25 + }, + "visible_assistant_replies": [ + "Now let me read the gateway support modules.", + "Now let me run the required initial diagnostics before making any changes.", + "The tests fail at DB creation in temp directories. Let me investigate whether this is an environment/sandbox issue or a code issue.", + "The sandbox denies writes outside the workspace, so `tempfile` targets fail. Let me redirect TEMP into the workspace when running diagnostics.", + "The sandbox locks directories created with `tempfile`'s restrictive mode. I'll build a small environment shim for running diagnostics (not touching tests/tools), then verify the full flow.", + "The sandbox blocks `tempfile`'s default restrictive-mode dirs. I'll create a transparent environment shim (outside tests/tools, not part of project code) so the mandated diagnostics can run here, then re-run them.", + "Public tests pass with the shim. Note `sleep_epoch rows=1 bed=R1203-B2` — the \"first\" policy imported to the last bed (bug confirmed). Now the debug probe:", + "Probe confirms 6 failures + voice warning. Now let me examine the voice assistant and ESP32 firmware.", + "Now let me look at the ESP32 firmware.", + "Let me read the ESP32 docs and remaining project docs.", + "Let me check which gateway endpoints the local workers actually call, to size the local-service whitelist correctly.", + "Now I have a complete picture. Let me set up the work plan and start fixing.", + "Now fixing `auth.py` — salted hashing, DB-backed existence check, token-validated sessions, and legacy plaintext migration:", + "Now `db.py` — full care_events schema plus legacy migration:", + "Now `care_events.py`:", + "Now the main `gateway.py` changes. First the imports and auth policy:", + "Now the voice module — explicit current-session fetch:", + "Let me verify the gateway-side fixes before moving to ESP32:", + "The probe expects an `ok` flag on the create result. Fix:", + "Probe passes. Run public tests and write a quick extra check for migration + end-to-end flows:", + "Public tests pass. Let me write a deeper verification script (in scratch space, not part of the project) covering migration, auth flows, care events, sleep import, and context authorization:", + "The indexes referenced `ts` before migration ran. Move care_events indexes into the migration:", + "Two real issues found: denied shape should mirror the contract (`modalities` all `{}`), and actor identity should only be echoed when authorized (prevents identity probing via a leaked session id). Fix both:", + "Fix the scratch script's broken line, then re-run:", + "Two scratch-script bugs (legacy db deleted before use; voice deps missing). Let me fix the script and debug the sleep check in isolation:", + "The importer works but TTL pruning drops the imported rows — let me check the actual CSV timestamps:", + "Example CSVs have no `ts` column (fallback = now), so pruning is by design; my scratch CSV used stale timestamps. Fix the scratch script (also legacy ordering and text-based voice check):", + "All 60 checks pass. Now the ESP32 firmware restructure:", + "Now `device_config.h/.cpp`:", + "Now rewrite `main.cpp` as glue using the new modules:", + "Now update the build files:", + "Now run the ESP-IDF build to record the exact failure point:", + "ESP-IDF is not installed here (both entries fail on the missing activation script). But `g++` exists — let me host-compile and unit-test the self-contained protocol/parser modules:", + "CRC + parser logic verified on host. Now host-test the MQTT payload builder with a reference base64 (stub mbedtls header kept in scratch only):", + "Both host tests pass. Now update documentation. Let me read the remaining docs:", + "Let me check for stale \"not implemented\" references across docs:", + "Now run the full final verification suite:", + "All green. Now write the final `PULL_REQUEST_TEMPLATE.md`:", + "One claim in the PR (logout invalidates old cookie) wasn't actually exercised in my extended checks — let me add that test and verify:", + "Logout verified. Final cleanup of scratch artifacts (keeping only a clearly-labeled environment shim) and a last full test run:", + "全部完成。简短总结:\n\n## 改了什么\n\n**Gateway 鉴权与安全**\n- `gateway/auth.py`:密码改为 PBKDF2-SHA256(200k)+随机盐,绝不存明文;`admin_account_exists()` 改为查库;会话按 token hash + 过期时间校验(伪造 Cookie 直接拒绝);历史明文账户登录后原地重哈希;logout 后旧 Cookie 立即失效。\n- `gateway/gateway.py`:管理 API(subjects/assignments/memories/credentials/sessions/care events)即使本机调用也必须带管理员 Cookie;本机无 Cookie 例外收敛为白名单(`/api/v2/*`、`/api/esp/*`、`/api/v3/context/chat`、identity/gallery、identity/match、vision/observation、session/current)。v3 上下文严格校验(actor 非空 + recognized + assurance 非空 + 未过期),拒绝时患者明细/记忆/护理事件全空、不回显 actor 身份。新增 `POST/GET /api/v3/care/events` 路由。\n\n**数据与模块**\n- `gateway/db.py`:`care_events` 全字段表 + 旧库缺列自动迁移(回填 `ts=created_ts`、规范化 room/bed、旧行保留,索引在迁移后创建)。\n- `gateway/care_events.py`:完整 CRUD、双侧 room/bed 规范化、limit/倒序、真实上下文摘要(`modalities.care_events`)。\n- `gateway/sleep_importer.py`:修复 `first` 策略误取最后一个床位;显式 room/bed 行按行规范化只写对应床位,无归属行才走策略。\n- `voice/voice_assistant_integrated.py`:先显式 GET `/api/v3/session/current` 再带 session_id 请求上下文,不再依赖隐式 ambient session。\n\n**ESP32-S3(esp32/testpro4)**\n- 恢复 Wi-Fi STA + 巴法云 MQTT 回传,按交接规范拆出 `protocol_packet` / `maixsense_parser` / `device_config` / `mqtt_payload` / `network_backhaul` 五个模块;USB CDC 契约不变,并行向 `{room}{bed}tof1/tof2/mlx1/mlx2` 发布 `{\"payload_b64\":...}`(动态缓冲);NVS 校验 `ssid/uid/room/bed` 齐全才联网;`CMakeLists.txt`/`idf_component.yml` 补齐 `esp_wifi/esp_netif/esp_event/lwip/mqtt/mbedtls` 依赖。\n\n**文档**:README、gateway/README、RK3588_TEST_GUIDE、CHANGES、esp32 各文档同步;`PULL_REQUEST_TEMPLATE.md` 已写入真实初始诊断、修改说明、验证结果与风险(无「待填写」)。\n\n## 验证了什么\n- `tests\\run_public_tests.py`:全部通过(初始 3 文件失败 → 0)。\n- `tools\\run_debug_probe.py`:初始 6 项 FAIL + voice 警告 → 全部通过。\n- 自建 60+ 项断言:旧库迁移、密码非明文/重哈希、伪造 token/logout 失效、管理 API 401、care_event 大写命中、上下文 5 种拒绝场景 + staff/patient 授权、混合 CSV 按行归属——全部通过。\n- ESP 协议模块主机单测(g++):CRC16-CCITT 校验值 0x29B1、帧解析(噪声/分块/坏尾/异常长度/半帧)、10018B payload base64 JSON 与参考实现一致。\n\n## 风险\n- **ESP-IDF 编译未验证**:本机无 IDF 工具链,`run_espidf_build.py` 明确失败在 `ESP-IDF activation script not found`(exit 2),未虚报通过;Wi-Fi/MQTT 代码按 v5.x/6.x API 编写,需在有 EIM IDF v6.0.1 的机器上实编。\n- 实机未验证:flash/monitor、真实 Wi-Fi/MQTT 连通、ToF/MLX 实机读数。\n- voice/vision 重依赖模块无法本机全量实跑(已做调用链级验证)。\n- 本机沙箱限制 tempfile 写入,诊断通过工作区外的 `.dshcompat` 环境 shim 运行(不影响项目代码,PR 中已如实说明)。" + ], + "first_request_tool_catalog": { + "request_index": 1, + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "56761b419a0089a7240f6670bb42c010fb14e80660e696246fbdb9c4e7fe0ca9", + "tools": [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "create_goal", + "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace. Must match exactly." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "exit_plan_mode", + "description": "Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.", + "parameters": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The complete plan, as markdown, starting with a # heading that names it." + } + }, + "required": [ + "plan" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of the running agent to interrupt." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children only; descendants walks the complete tree below you.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "ralph", + "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The immutable completion objective for every fresh Ralph round." + }, + "maxRounds": { + "type": "number", + "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.", + "parameters": { + "type": "object", + "properties": { + "subagent_id": { + "type": "string", + "description": "The subagent id returned when the background subagent was started." + }, + "message": { + "type": "string", + "description": "The message to deliver to the subagent." + } + }, + "required": [ + "subagent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit | pause | resume | complete | blocked", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Concrete blocking condition; required only with action blocked." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." + }, + "meta": { + "type": "object", + "description": "The workflow identity block (plain JSON — never code).", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + }, + "catalog_transition": [ + { + "request_index": 1, + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "56761b419a0089a7240f6670bb42c010fb14e80660e696246fbdb9c4e7fe0ca9", + "tools": [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "create_goal", + "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace. Must match exactly." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "exit_plan_mode", + "description": "Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.", + "parameters": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The complete plan, as markdown, starting with a # heading that names it." + } + }, + "required": [ + "plan" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of the running agent to interrupt." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children only; descendants walks the complete tree below you.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "ralph", + "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The immutable completion objective for every fresh Ralph round." + }, + "maxRounds": { + "type": "number", + "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.", + "parameters": { + "type": "object", + "properties": { + "subagent_id": { + "type": "string", + "description": "The subagent id returned when the background subagent was started." + }, + "message": { + "type": "string", + "description": "The message to deliver to the subagent." + } + }, + "required": [ + "subagent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit | pause | resume | complete | blocked", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Concrete blocking condition; required only with action blocked." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." + }, + "meta": { + "type": "object", + "description": "The workflow identity block (plain JSON — never code).", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + } + ], + "system_prompt_sha256_by_request": [ + "eb8aa5af63179f92403c1835d1b525e16ace733b3b5e52565bb68ee033a9e4e7" + ], + "trajectory_fingerprints": { + "reasoning_blocks": 88, + "reasoning_chars": 147877, + "reasoning_words": 20999, + "we": 25, + "let_me": 149, + "lets": 2 + }, + "raw_evidence_sha256": "2f65ea5f6f1f046e75fb8238a243aca395f781e2ff80fbef9577b2166798ca1a", + "pricing_snapshot_sha256": "b853238d420a34e6a5064d0d33b347bbe704d740c0250c1f5aa78c7b767ee24f", + "resolved_api_endpoint": "https://api.deepseek.com", + "cost_reconciliation": { + "balance_generation": 1, + "t0_raw_sha256": "c62b4e7651c599415b631211ab8a7290cf7d465d0acb9609113b81e1453ce405", + "t1_raw_sha256": "0f0794d8aeb92a150def92e8a28d420d4076edd0cfef3c0fa0e0d6f3b17b2cf2", + "account_balance_delta_cny": 1.64, + "official_rate_recomputed_agent_usage_cny": 1.756912, + "account_minus_recomputed_cny": -0.116912, + "crosses_recharge_event": false, + "notes": "Stable account T0/T1 delta and DSH disjoint-token recomputation are reported independently." + } +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-03-minimal-full-balance.json b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-03-minimal-full-balance.json new file mode 100644 index 0000000..a593fd7 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-03-minimal-full-balance.json @@ -0,0 +1,7 @@ +{ + "schema_version": 1, + "run_id": "P2-20260815-03-minimal-full", + "balance_generation": 1, + "balance_delta_cny": 1.13, + "crosses_recharge_event": false +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-03-minimal-full.json b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-03-minimal-full.json new file mode 100644 index 0000000..8c51fbd --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-03-minimal-full.json @@ -0,0 +1,3093 @@ +{ + "schema_version": 1, + "benchmark": "project2-v4.1b", + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "run_id": "P2-20260815-03-minimal-full", + "model": "deepseek-v4-pro", + "provider": "deepseek-official", + "reasoning_effort": "max", + "dsh_version": "0.1.0-rc.6", + "dsh_commit": "47f943859bef60e4160492346772ded9b24f765a", + "preset": "minimal-full", + "preset_hash": "86359556bbc00623951b2b782f66b51626975e90d65a4400cfd8e85be3ab7106", + "os_environment": "Windows 10 / PowerShell / DSH native Windows", + "started_at_utc": "2026-08-15T08:30:48.568Z", + "ended_at_utc": "2026-08-15T08:42:33.202Z", + "wall_time_seconds": 704.634, + "turn_duration_seconds": 680.044, + "benchmark_result": { + "project": "\\project2_task", + "results_dir": "\\evaluator\\results\\20260815_164240", + "benchmark": "project2-v4.1b", + "steps": { + "public": 0, + "debug_probe": 0, + "hidden": 1, + "espidf_static": 1 + }, + "details": { + "hidden_summary": "\\evaluator\\results\\20260815_164240\\hidden_summary.json", + "espidf_static_summary": "\\evaluator\\results\\20260815_164240\\espidf_static_summary.json", + "pr_template": { + "expected": "\\project2_task\\PULL_REQUEST_TEMPLATE.md", + "found": true, + "source": "\\project2_task\\PULL_REQUEST_TEMPLATE.md", + "artifact": "\\evaluator\\results\\20260815_164240\\pull_request_template.md", + "bytes": 1071 + }, + "git": { + "baseline": "project2-v4-broken-seed", + "diff": "\\evaluator\\results\\20260815_164240\\candidate_diff.patch", + "diff_lines": 986, + "shortstat": "6 files changed, 485 insertions(+), 135 deletions(-)", + "status": "\\evaluator\\results\\20260815_164240\\candidate_status.txt", + "changed_file_count": 14, + "log": "\\evaluator\\results\\20260815_164240\\candidate_log.txt", + "duration_source": "unknown" + }, + "score_draft": "\\evaluator\\results\\20260815_164240\\score_draft.json", + "blockers": "\\evaluator\\results\\20260815_164240\\blockers.json", + "score_draft_confidence": "\\evaluator\\results\\20260815_164240\\score_draft_confidence.json", + "dimensions": "\\evaluator\\results\\20260815_164240\\dimensions.json" + }, + "meta": { + "model": "deepseek-v4-pro", + "channel": "deepseek-official", + "harness": "dsh-minimal-full", + "benchmark": "project2-v4.1b", + "seed_tag": "project2-v4-broken-seed", + "registry_version": "v4.1b", + "schema_version": 1, + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "run_id": "P2-20260815-03-minimal-full", + "provider": "deepseek-official", + "reasoning_effort": "max", + "dsh_version": "0.1.0-rc.6", + "dsh_commit": "47f943859bef60e4160492346772ded9b24f765a", + "preset": "minimal-full", + "preset_hash": "86359556bbc00623951b2b782f66b51626975e90d65a4400cfd8e85be3ab7106", + "os_environment": "Windows 10 / PowerShell / DSH native Windows", + "started_at_utc": "2026-08-15T08:30:48.568Z", + "ended_at_utc": "2026-08-15T08:42:33.202Z", + "wall_time_seconds": 704.634, + "turn_duration_seconds": 680.044, + "benchmark_result": null, + "benchmark_result_sha256": null, + "usage": { + "cacheReadTokens": 6870528, + "inputTokens": 166004, + "outputTokens": 44058, + "reasoningTokens": 16822 + }, + "input_tokens": 166004, + "cache_read_tokens": 6870528, + "cache_miss_tokens": 166004, + "output_tokens": 44058, + "reasoning_tokens": 16822, + "api_cost_cny": 0.934123, + "api_cost_scope": "DSH completed agent assistant/message usage; official runtime rates; auxiliary title traffic excluded", + "usage_semantics": "input_tokens and cache_read_tokens are disjoint DSH counts", + "tool_call_count": 83, + "distinct_tools_used": [ + "edit", + "glob", + "pwsh", + "read", + "todo_write", + "write" + ], + "tool_breakdown": { + "edit": 11, + "glob": 5, + "pwsh": 10, + "read": 44, + "todo_write": 1, + "write": 12 + }, + "visible_assistant_replies": [ + "Now let me run the mandatory initial diagnostics as required.", + "Initial diagnostics ran and failed early on a DB-path issue. Let me investigate the environment quickly.", + "The workspace ships a `.dshcompat` shim for the sandbox's restrictive temp dirs (documented in its own README), but it isn't on `PYTHONPATH` by default. Let me run the mandated diagnostics with the shim active so project code actually executes.", + "Baseline captured: public tests pass, debug probe fails 6 checks. Now let me survey the rest of the code.", + "I have a full picture now. Let me set up a task list and start implementing.", + "Now fix the voice bridge so it explicitly carries the current session instead of relying on the gateway's ambient session.", + "Debug probe is green. Let me run a few extra hidden-risk checks (legacy DB migration, password hashing, authorization, mixed CSV).", + "Python side looks solid. Now the ESP32-S3 firmware work — creating the recommended protocol modules." + ], + "first_request_tool_catalog": { + "request_index": 1, + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "56761b419a0089a7240f6670bb42c010fb14e80660e696246fbdb9c4e7fe0ca9", + "tools": [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "create_goal", + "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace. Must match exactly." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "exit_plan_mode", + "description": "Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.", + "parameters": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The complete plan, as markdown, starting with a # heading that names it." + } + }, + "required": [ + "plan" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of the running agent to interrupt." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children only; descendants walks the complete tree below you.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "ralph", + "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The immutable completion objective for every fresh Ralph round." + }, + "maxRounds": { + "type": "number", + "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.", + "parameters": { + "type": "object", + "properties": { + "subagent_id": { + "type": "string", + "description": "The subagent id returned when the background subagent was started." + }, + "message": { + "type": "string", + "description": "The message to deliver to the subagent." + } + }, + "required": [ + "subagent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit | pause | resume | complete | blocked", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Concrete blocking condition; required only with action blocked." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." + }, + "meta": { + "type": "object", + "description": "The workflow identity block (plain JSON — never code).", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + }, + "catalog_transition": [ + { + "request_index": 1, + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "56761b419a0089a7240f6670bb42c010fb14e80660e696246fbdb9c4e7fe0ca9", + "tools": [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "create_goal", + "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace. Must match exactly." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "exit_plan_mode", + "description": "Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.", + "parameters": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The complete plan, as markdown, starting with a # heading that names it." + } + }, + "required": [ + "plan" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of the running agent to interrupt." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children only; descendants walks the complete tree below you.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "ralph", + "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The immutable completion objective for every fresh Ralph round." + }, + "maxRounds": { + "type": "number", + "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.", + "parameters": { + "type": "object", + "properties": { + "subagent_id": { + "type": "string", + "description": "The subagent id returned when the background subagent was started." + }, + "message": { + "type": "string", + "description": "The message to deliver to the subagent." + } + }, + "required": [ + "subagent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit | pause | resume | complete | blocked", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Concrete blocking condition; required only with action blocked." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." + }, + "meta": { + "type": "object", + "description": "The workflow identity block (plain JSON — never code).", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + } + ], + "system_prompt_sha256_by_request": [ + "5fab6e32f283d71510531ce850df2690b8fb77437d36bfabbe8c4ac862f19df9" + ], + "trajectory_fingerprints": { + "reasoning_blocks": 47, + "reasoning_chars": 67743, + "reasoning_words": 9368, + "we": 67, + "let_me": 14, + "lets": 16 + }, + "raw_evidence_sha256": "f769b58934ff862c807799a99a0593081c8cb639fe2e68b6787129cd9501fd53", + "run_group_id": "dsv4p-anchor-20260815-minimal-full", + "thinking_level": "max", + "endpoint_product": "DeepSeek-API", + "billing_tier": "paygo", + "run_index": 1, + "complete": true, + "meta_partial": false + }, + "meta_complete": true, + "duration_sec": 30, + "duration_source": "eval_wall", + "ability_draft": 85.5, + "ship_draft": 85.5, + "release_class_hint": "B+", + "blockers": [ + "P-report", + "M-fidelity", + "E-contract" + ], + "behavior_blockers": [ + "P-report", + "M-fidelity", + "E-contract" + ], + "semantic_only_codes": [ + "V4-F12-04" + ], + "f9_mode": "skipped_env", + "f11_status": "placeholder", + "family_draft": { + "F1": 6.0, + "F2": 12.0, + "F3": 16.0, + "F4": 4.0, + "F5": 12.0, + "F6": 8.0, + "F7": 8.0, + "F8": 5.0, + "F9": 3.0, + "F10": 8.0, + "F11": 0.5, + "F12": 3.0 + }, + "dimensions": { + "final_code": 9.2, + "security": 9.72, + "migration": 8.0, + "esp_deploy": 5.71, + "process_truth": 7.5, + "efficiency": null + } + }, + "benchmark_result_sha256": "12c04b355e062617e72ef89690f0227bd754fdd0c5d6c6add74674dbf587fb92", + "usage": { + "cacheReadTokens": 6870528, + "inputTokens": 166004, + "outputTokens": 44058, + "reasoningTokens": 16822 + }, + "input_tokens": 166004, + "cache_read_tokens": 6870528, + "cache_miss_tokens": 166004, + "output_tokens": 44058, + "reasoning_tokens": 16822, + "api_cost_cny": 0.934123, + "api_cost_scope": "DSH completed agent assistant/message usage; official runtime rates; auxiliary title traffic excluded", + "usage_semantics": "input_tokens and cache_read_tokens are disjoint DSH counts", + "tool_call_count": 83, + "distinct_tools_used": [ + "edit", + "glob", + "pwsh", + "read", + "todo_write", + "write" + ], + "tool_breakdown": { + "edit": 11, + "glob": 5, + "pwsh": 10, + "read": 44, + "todo_write": 1, + "write": 12 + }, + "visible_assistant_replies": [ + "Now let me run the mandatory initial diagnostics as required.", + "Initial diagnostics ran and failed early on a DB-path issue. Let me investigate the environment quickly.", + "The workspace ships a `.dshcompat` shim for the sandbox's restrictive temp dirs (documented in its own README), but it isn't on `PYTHONPATH` by default. Let me run the mandated diagnostics with the shim active so project code actually executes.", + "Baseline captured: public tests pass, debug probe fails 6 checks. Now let me survey the rest of the code.", + "I have a full picture now. Let me set up a task list and start implementing.", + "Now fix the voice bridge so it explicitly carries the current session instead of relying on the gateway's ambient session.", + "Debug probe is green. Let me run a few extra hidden-risk checks (legacy DB migration, password hashing, authorization, mixed CSV).", + "Python side looks solid. Now the ESP32-S3 firmware work — creating the recommended protocol modules." + ], + "first_request_tool_catalog": { + "request_index": 1, + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "56761b419a0089a7240f6670bb42c010fb14e80660e696246fbdb9c4e7fe0ca9", + "tools": [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "create_goal", + "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace. Must match exactly." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "exit_plan_mode", + "description": "Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.", + "parameters": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The complete plan, as markdown, starting with a # heading that names it." + } + }, + "required": [ + "plan" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of the running agent to interrupt." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children only; descendants walks the complete tree below you.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "ralph", + "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The immutable completion objective for every fresh Ralph round." + }, + "maxRounds": { + "type": "number", + "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.", + "parameters": { + "type": "object", + "properties": { + "subagent_id": { + "type": "string", + "description": "The subagent id returned when the background subagent was started." + }, + "message": { + "type": "string", + "description": "The message to deliver to the subagent." + } + }, + "required": [ + "subagent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit | pause | resume | complete | blocked", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Concrete blocking condition; required only with action blocked." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." + }, + "meta": { + "type": "object", + "description": "The workflow identity block (plain JSON — never code).", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + }, + "catalog_transition": [ + { + "request_index": 1, + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "56761b419a0089a7240f6670bb42c010fb14e80660e696246fbdb9c4e7fe0ca9", + "tools": [ + { + "name": "ask_user_question", + "description": "Ask the user a concise question when you need confirmation, a choice, or missing information before proceeding. Send one or more questions, each with a stable id that will be echoed in the answer.", + "parameters": { + "type": "object", + "properties": { + "questions": { + "type": "array", + "description": "Questions to ask the user before continuing.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "id": { + "type": "string", + "description": "Stable id for this question; echoed in the answer." + }, + "question": { + "type": "string", + "description": "The specific question to ask the user." + }, + "header": { + "type": "string", + "description": "Optional short heading for the question, such as \"Confirm\" or \"Choose Mode\"." + }, + "options": { + "type": "array", + "description": "Optional choices to show the user. If you recommend one, put it first and append \"(Recommended)\" to that label.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "label": { + "type": "string", + "description": "Short user-facing option label." + }, + "description": { + "type": "string", + "description": "One sentence explaining the tradeoff or impact." + } + }, + "required": [ + "label" + ] + } + }, + "multi_select": { + "type": "boolean", + "description": "Whether the user may select more than one option. Defaults to false." + } + }, + "required": [ + "id", + "question" + ] + } + } + }, + "required": [ + "questions" + ] + } + }, + { + "name": "create_goal", + "description": "Create one persisted same-session completion goal when the current direct human request is a long-running objective that should continue across autonomous goal rounds. You may infer that intent without requiring the user to say \"create a goal\". Do not use this for trivial single-turn work. Execution rejects non-human and subagent authority.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The concrete completion objective inferred from the direct human request." + }, + "max_goal_rounds": { + "type": "number", + "description": "Optional positive safe-integer limit on automatic continuation rounds." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "edit", + "description": "Edit an existing UTF-8 text file by replacing literal text.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to edit, resolved by the filesystem backend." + }, + "old_string": { + "type": "string", + "description": "Literal text to replace. Must match exactly." + }, + "new_string": { + "type": "string", + "description": "Literal replacement text. Use an empty string to delete the match." + }, + "replace_all": { + "type": "boolean", + "description": "Replace all matches. Defaults to false; when false, old_string must appear exactly once." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "old_string", + "new_string" + ] + } + }, + { + "name": "exit_plan_mode", + "description": "Use only in plan mode. Present your plan for the user's review and, on approval, leave plan mode. Send the COMPLETE plan as markdown, starting with a # heading that names it. The user may approve (carry out the plan from your next step) or keep planning — their feedback comes back in the tool result; revise and present again.", + "parameters": { + "type": "object", + "properties": { + "plan": { + "type": "string", + "description": "The complete plan, as markdown, starting with a # heading that names it." + } + }, + "required": [ + "plan" + ] + } + }, + { + "name": "get_goal", + "description": "Read the current same-session goal, including its exact id/revision, objective, phase, completed continuation rounds, round limit, blocker reason when present, and whether another continuation is armed. Call this before updating a goal.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "glob", + "description": "Find files whose paths match a glob pattern. Returns matching file paths — never directories — including hidden and ignored files (VCS metadata directories are excluded). Up to 100 paths come back in modification-time order; a larger result returns the first 100 paths in modification-time order, says so, and reports where the complete sorted list was saved. This tool does not enumerate directory entries.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Glob pattern to match file paths against (e.g. \"**/*.ts\", \"src/**/*.test.js\"). A pattern with no \"/\" matches the basename at any depth, so \"*\" and \"*.ts\" both search the whole tree; include a separator to anchor the depth." + }, + "path": { + "type": "string", + "description": "Directory to search in. Defaults to the session workspace; a relative path resolves against it." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "grep", + "description": "Search file contents with a ripgrep regular expression. Returns matching lines with line numbers, grouped by file. Returns the first 250 matches inline; a capped result reports where the complete match list was saved. Use read on a matched file for surrounding context.", + "parameters": { + "type": "object", + "properties": { + "pattern": { + "type": "string", + "description": "Regular expression to search for (ripgrep syntax)." + }, + "path": { + "type": "string", + "description": "File or directory to search. Defaults to the session workspace; a relative path resolves against it." + }, + "include": { + "type": "string", + "description": "One glob filter for which files to search (e.g. \"*.ts\", \"*.{js,jsx}\"). Not a list; negation is not supported." + } + }, + "required": [ + "pattern" + ] + } + }, + { + "name": "interrupt_agent", + "description": "Request cancellation of a background agent's current turn by its agent id. The target may be your direct child or a deeper agent created under you. Only the current turn stops: messages already queued for the agent stay parked until a later send_message, agents it started keep running, and the agent itself stays available for follow-ups. This call returns as soon as the stop request is accepted, so the target may keep running briefly; interrupting an agent that already finished is an accepted no-op.", + "parameters": { + "type": "object", + "properties": { + "agent_id": { + "type": "string", + "description": "The agent id of the running agent to interrupt." + } + }, + "required": [ + "agent_id" + ] + } + }, + { + "name": "job_kill", + "description": "Request cancellation of a running background job by job id. Returns immediately; the job settles as killed once its work actually stops.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "reason": { + "type": "string", + "description": "Optional short reason, recorded in the log and forwarded to the job." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "job_list", + "description": "List your background jobs (running and finished) with their ids, kinds, and statuses.", + "parameters": { + "type": "object", + "properties": {} + } + }, + { + "name": "job_output", + "description": "Read a background job. Stream jobs return only output since the previous read; final-output jobs return their result after settlement. Every response ends with `[status: ...]`. Reads are non-blocking unless `wait: true`, which waits up to the configured cap.", + "parameters": { + "type": "object", + "properties": { + "job_id": { + "type": "string", + "description": "Job id returned by the tool that started the background work." + }, + "wait": { + "type": "boolean", + "description": "Block until the job reaches a terminal status or the timeout expires. A timed-out wait returns [status: running] and leaves the job alive." + }, + "timeout_ms": { + "type": "number", + "description": "Max wait in milliseconds (only meaningful with wait: true). Defaults to the configured wait timeout; capped by the configured maximum." + } + }, + "required": [ + "job_id" + ] + } + }, + { + "name": "list_agents", + "description": "List your continuable background subagents by durable id and label. Use it to recall which ones you started, not to poll for completion — you are told when one finishes. Status comes from the live registry: running means the agent is working right now, idle means it is loaded but between turns (it may be waiting on agents it started), and ready means it exists only in storage — resumable, not terminal, and not a result waiting to be collected; a `send_message` starts a new turn on the same conversation, and a direct child remains a `send_message` candidate in every status. The snapshot is not a delivery promise — `send_message` performs the authoritative check and may still fail. Children that could not be read are reported as diagnostics instead of being silently dropped. Scope `descendants` walks the whole tree below you in stable pre-order, annotating each entry with its durable direct-parent session id and depth. You may use `send_message` only for depth-1 entries; deeper entries are candidates for `interrupt_agent` only.", + "parameters": { + "type": "object", + "properties": { + "scope": { + "type": "string", + "description": "children (default) lists direct children only; descendants walks the complete tree below you.", + "enum": [ + "children", + "descendants" + ] + } + } + } + }, + { + "name": "pwsh", + "description": "Execute a PowerShell command (`pwsh -Command`) and return its stdout/stderr. Each call runs in a fresh pwsh process: no state (cwd, variables, functions) persists between calls — pass `workdir` instead of using `cd`. Paths use native Windows form (`C:\\...`); read environment variables with `$env:NAME`. Non-zero exits are reported as `[exit code: N]`. Current harness environment facts are exposed through managed `$env:DSH_*` variables; inspect them when needed. Commands may run under a file sandbox; a blocked file operation is reported as `[sandbox: file access denied under mode]` — a policy denial, not a bug in the command; do not retry another way. Long output is truncated to its tail; the full output is saved to a file whose path is reported when available. On Windows a force-killed command settles as `[exit code: 1]` without a signal marker — treat it as an interruption, not a command failure. Set `run_in_background: true` for long-running commands: the call returns a job id immediately; read its output with `job_output` and stop it with `job_kill`. Under the Windows sandbox, read-only pwsh runs in PowerShell ConstrainedLanguage mode, while workspace-write stays in FullLanguage unless host policy says otherwise. In read-only, prefer cmdlets and core types (`[string]`, `[datetime]`, `[regex]`, `[guid]`); .NET static calls (`[System.IO.*]::`, `[math]::`), `Add-Type`, COM objects, and reflection fail with \"only core types\" errors. `-f` formatting, property access, and core cmdlets work. In both confined modes, programs cannot open named pipes, so a command that captures another program's output through piped stdio (Node.js `child_process.spawn`/`exec` with the default `stdio: 'pipe'`) fails with EPERM, while `stdio: 'inherit'` and `stdio: 'ignore'` spawns work and PowerShell's own pipelines are unaffected. That EPERM is the documented boundary: do not retry the command another way — escalate the exact command once or restructure it to avoid capturing output. Attempting a command the sandbox may deny is safe and expected: run it and read the marker rather than assuming the denial. When a command is denied and a wider mode would let it succeed, escalate immediately in the same turn — the one sanctioned exception to a denial: retry the exact same command once with `sandbox_permissions` (the narrowest wider mode that suffices) plus a one-sentence `justification`. Do not detour through chat to ask permission first — the approval prompt raised by that retry is how the user consents. If the session states approval prompts are disabled, there is no exception: a denial is final — do not set `sandbox_permissions`. Never escalate speculatively: ground the request in a real denial — normally the one this command just hit; escalating up front is fine only when this session already denied the same access. A rejected escalation is final for that command — stop and explain, never work around it — but it does not forbid attempting or escalating other commands later.", + "parameters": { + "type": "object", + "properties": { + "command": { + "type": "string", + "description": "The PowerShell command to execute." + }, + "description": { + "type": "string", + "description": "Clear, concise description of what this command does in active voice, 5-10 words (shown in the UI). Examples: \"ls\" → \"List files in current directory\"; \"git status\" → \"Show working tree status\"; \"Get-Process\" → \"List running processes\"." + }, + "timeoutMs": { + "type": "number", + "description": "Timeout in milliseconds. The executor applies its configured default and cap, and kills the command on expiry." + }, + "workdir": { + "type": "string", + "description": "Working directory for this command. Defaults to the session workspace; a relative path is resolved against it." + }, + "run_in_background": { + "type": "boolean", + "description": "Run in the background and return a job id immediately (collect with job_output, stop with job_kill). No timeout applies." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this command needs. Only valid as a one-shot retry of a command the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact command needs the wider access." + } + }, + "required": [ + "command", + "description" + ] + } + }, + { + "name": "ralph", + "description": "Run a foreground fresh-agent Ralph loop toward one immutable objective. Use only when the direct human explicitly asks for Ralph or fresh-agent iteration. Each round opens a new child with no parent conversation or prior child session; the shared workspace is long-term memory, and only a bounded structured report crosses rounds. The call returns when a worker reports completion or a concrete blocker, or at the round limit. Ordinary long-running same-session work belongs to goal tools.", + "parameters": { + "type": "object", + "properties": { + "objective": { + "type": "string", + "description": "The immutable completion objective for every fresh Ralph round." + }, + "maxRounds": { + "type": "number", + "description": "Optional positive safe-integer round cap, bounded by the deployment ceiling." + } + }, + "required": [ + "objective" + ] + } + }, + { + "name": "read", + "description": "Read a UTF-8 text file and return line-numbered content.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to read, resolved by the filesystem backend." + }, + "offset": { + "type": "number", + "description": "1-based first line to return. Defaults to 1." + }, + "limit": { + "type": "number", + "description": "Maximum number of lines to return. Defaults to 2000." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "read_image", + "description": "Read a PNG/JPEG/WebP/GIF file and return the image itself. Requires the current model to accept image input.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to the image file, resolved by the filesystem backend." + } + }, + "required": [ + "file_path" + ] + } + }, + { + "name": "send_message", + "description": "Send a message to a background subagent by its subagent id, continuing the same conversation. It becomes the subagent's next turn: if it is still working, the message waits until its current turn finishes, so it cannot redirect work already underway. This call returns no answer from the subagent — only confirmation that the message was delivered — so use it to give it more work. A failure means the message was NOT delivered.", + "parameters": { + "type": "object", + "properties": { + "subagent_id": { + "type": "string", + "description": "The subagent id returned when the background subagent was started." + }, + "message": { + "type": "string", + "description": "The message to deliver to the subagent." + } + }, + "required": [ + "subagent_id", + "message" + ] + } + }, + { + "name": "skill", + "description": "Load the full instructions for an available skill. Call this with the exact skill name from the session skill catalog before acting on a task that names or clearly matches that skill.", + "parameters": { + "type": "object", + "properties": { + "name": { + "type": "string", + "description": "The exact skill name from the available skills list." + } + }, + "required": [ + "name" + ] + } + }, + { + "name": "subagent", + "description": "Delegate a self-contained task to a subagent (a separate agent that works in its own context) to offload focused, independent work — research, a scoped implementation, an analysis — so it does not consume this conversation's context. The subagent returns its result, not its intermediate steps. Give it a complete, standalone prompt: it does not see this conversation. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The complete, self-contained task for the subagent. It does not share this conversation's context, so include everything it needs." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "subagent_fork", + "description": "Delegate a task to a subagent that inherits this conversation: a child agent seeded with all completed turns so far (it does not see the current in-flight turn). Use this when the subtask builds on this conversation's context — a follow-up analysis, a review, a continuation — without consuming this conversation's context for the work itself. You receive its result, not its intermediate steps. This tool runs in the background by default, immediately returns a durable subagent id, and keeps the child conversation available for later turns. When that run settles, the runtime sends the parent a notice containing its outcome and any final assistant message; `send_message` starts a later turn in the same child conversation. Set `run_in_background: false` only when your next action depends on receiving the result.", + "parameters": { + "type": "object", + "properties": { + "description": { + "type": "string", + "description": "A short (3-5 word) description of the delegated task, for display." + }, + "prompt": { + "type": "string", + "description": "The task for the subagent. It already sees this conversation's completed turns, so build on them freely and state only what is new." + }, + "run_in_background": { + "type": "boolean", + "description": "Whether to run in the background and return a durable subagent id immediately. Defaults to true. Set false to wait for the result when your next action depends on it." + } + }, + "required": [ + "description", + "prompt" + ] + } + }, + { + "name": "todo_write", + "description": "Record and update a structured task list for the current work. Send the ENTIRE list every call — it REPLACES the previous list (there are no partial updates, no per-item edits). Use it to plan multi-step work and show progress: add one todo per concrete step before you start. Mark every todo being actively worked on `in_progress` — several at once when work genuinely runs in parallel (e.g. concurrent subagents or background commands), one for sequential work; while work remains, at least one task should be `in_progress`. Mark a todo `completed` the moment it is done (do not batch completions), and allow no `in_progress` item only once all work is complete. Skip the list for trivial single-step tasks. Statuses: `pending` (not started), `in_progress` (being worked on now), `completed` (finished).", + "parameters": { + "type": "object", + "properties": { + "todos": { + "type": "array", + "description": "The COMPLETE task list, replacing any previous list.", + "items": { + "type": "object", + "additionalProperties": false, + "properties": { + "content": { + "type": "string", + "description": "What the task is — a short imperative line." + }, + "status": { + "type": "string", + "description": "pending (not started) | in_progress (now) | completed (done).", + "enum": [ + "pending", + "in_progress", + "completed" + ] + } + }, + "required": [ + "content", + "status" + ] + } + } + }, + "required": [ + "todos" + ] + } + }, + { + "name": "update_goal", + "description": "Update the exact current goal revision. edit, pause, and resume require a direct top-level human request. During an automatic continuation of the current goal, complete and blocked are also allowed. blocked is rejected before the configured minimum round count; the model remains responsible for judging that the same condition persisted across those rounds and must explain it in blocked_reason.", + "parameters": { + "type": "object", + "properties": { + "goal_id": { + "type": "string", + "description": "Exact id returned by get_goal." + }, + "revision": { + "type": "number", + "description": "Exact positive revision returned by get_goal." + }, + "action": { + "type": "string", + "description": "edit | pause | resume | complete | blocked", + "enum": [ + "edit", + "pause", + "resume", + "complete", + "blocked" + ] + }, + "objective": { + "type": "string", + "description": "Replacement objective; valid only with action edit." + }, + "max_goal_rounds": { + "type": "number", + "description": "Replacement cap; valid only with action edit." + }, + "blocked_reason": { + "type": "string", + "description": "Concrete blocking condition; required only with action blocked." + } + }, + "required": [ + "goal_id", + "revision", + "action" + ] + } + }, + { + "name": "web_search", + "description": "Search the web for current information. Returns an optional summary answer and a list of source URLs.", + "parameters": { + "type": "object", + "properties": { + "query": { + "type": "string", + "description": "The search query." + } + }, + "required": [ + "query" + ] + } + }, + { + "name": "workflow", + "description": "Run a JavaScript workflow script that orchestrates subagents at scale. Use this for work that fans out across many independent pieces — an audit over many files, a migration, multi-angle research, adversarial verification of findings — where you write the orchestration as a script instead of delegating turn by turn.\n\nThe workflow's identity rides the `meta` parameter as JSON: required `name` (short kebab-case) and `description` strings, optional `whenToUse` string and `phases` array (`{title, detail?, provider?, model?}`). The `script` parameter is the plain JavaScript body ONLY (NOT TypeScript, and NO `export const meta` statement — meta is a parameter, not code), running with top-level await; end with `return ` — the value must be JSON-serializable and is this tool's result.\n\nScript-body hooks:\n- `agent(prompt, opts?): Promise` — run one subagent to completion. Without `opts.schema` it resolves to the child's final text; with `opts.schema` (an object-rooted JSON Schema using ONLY type/properties/required/additionalProperties/items/enum/const/oneOf — no pattern/format/numeric bounds) it resolves to the validated object. Resolves `null` when the child fails (filter with `.filter(Boolean)`). Other opts: `label` (display), `phase` (progress group), and independent `provider`/`model` LLM target overrides (either may be provided alone). Anything else (`effort`/`isolation`/`agentType`) is rejected loudly.\n- `pipeline(items, ...stages): Promise` — run each item through the stages independently with NO barrier between stages (prefer this for multi-stage work). Each stage receives `(prev, item, index)`. An ordinary stage throw drops that ITEM to `null` and skips its remaining stages.\n- `parallel(thunks): Promise` — run zero-argument functions concurrently and await ALL of them (a barrier; use only when a stage genuinely needs every prior result together). A throwing thunk resolves to `null`.\n- `phase(title)` — start a progress phase; `log(message)` — narrate progress; `args` — the tool call's `args` input, verbatim.\n\nMisused hooks (bad arguments, unknown options, unsupported schemas, tripped caps) throw errors that ALWAYS kill the script — they never dissolve into a per-item `null`.\n\nConstraints: concurrency and total-agent caps apply; no filesystem, network, timers, or Node.js APIs are provided — the agents do the work, the script only coordinates them. The run executes in the foreground: this call returns when the whole script finishes.", + "parameters": { + "type": "object", + "properties": { + "script": { + "type": "string", + "description": "The plain-JS workflow script body (top-level await allowed; NO `export const meta` statement; end with `return `)." + }, + "meta": { + "type": "object", + "description": "The workflow identity block (plain JSON — never code).", + "additionalProperties": true, + "properties": { + "name": { + "type": "string", + "description": "Short kebab-case workflow name." + }, + "description": { + "type": "string", + "description": "One-line description of what the workflow does." + }, + "whenToUse": { + "type": "string", + "description": "Optional guidance on when this workflow applies." + }, + "phases": { + "type": "array", + "description": "Optional phase declarations matched by phase() calls.", + "items": { + "type": "object", + "additionalProperties": true, + "properties": { + "title": { + "type": "string", + "description": "The phase title phase() calls match by exact string." + }, + "detail": { + "type": "string", + "description": "Optional one-line description of the phase." + }, + "provider": { + "type": "string", + "description": "Optional provider override this phase is expected to use." + }, + "model": { + "type": "string", + "description": "Optional model override this phase is expected to use." + } + }, + "required": [ + "title" + ] + } + } + }, + "required": [ + "name", + "description" + ] + }, + "args": { + "type": "object", + "description": "Optional JSON input exposed to the script as the `args` global (wrap a bare list as a field, e.g. {\"files\": [...]}).", + "additionalProperties": true + } + }, + "required": [ + "script", + "meta" + ] + } + }, + { + "name": "write", + "description": "Create or fully replace a UTF-8 text file.", + "parameters": { + "type": "object", + "properties": { + "file_path": { + "type": "string", + "description": "Path to write, resolved by the filesystem backend." + }, + "content": { + "type": "string", + "description": "Full UTF-8 text content to write." + }, + "sandbox_permissions": { + "type": "string", + "description": "The wider sandbox mode this file operation needs. Only valid as a one-shot retry of an operation the sandbox just denied; requires justification and user approval.", + "enum": [ + "workspace-write", + "danger-full-access" + ] + }, + "justification": { + "type": "string", + "description": "Required with sandbox_permissions: one sentence for the user explaining why this exact file operation needs the wider access." + } + }, + "required": [ + "file_path", + "content" + ] + } + } + ] + } + ], + "system_prompt_sha256_by_request": [ + "5fab6e32f283d71510531ce850df2690b8fb77437d36bfabbe8c4ac862f19df9" + ], + "trajectory_fingerprints": { + "reasoning_blocks": 47, + "reasoning_chars": 67743, + "reasoning_words": 9368, + "we": 67, + "let_me": 14, + "lets": 16 + }, + "raw_evidence_sha256": "f769b58934ff862c807799a99a0593081c8cb639fe2e68b6787129cd9501fd53", + "pricing_snapshot_sha256": "25ae1e9159be6f4beea25174fb1e5ffd3903647c43508523cacff3fde28a2e02", + "resolved_api_endpoint": "https://api.deepseek.com", + "cost_reconciliation": { + "balance_generation": 1, + "t0_raw_sha256": "0f0794d8aeb92a150def92e8a28d420d4076edd0cfef3c0fa0e0d6f3b17b2cf2", + "t1_raw_sha256": "8fc8ed1a6bef295b7e8337b982211e9605a469c128958e1326889dbb03f7f3c6", + "account_balance_delta_cny": 1.13, + "official_rate_recomputed_agent_usage_cny": 0.934123, + "account_minus_recomputed_cny": 0.195877, + "crosses_recharge_event": false, + "notes": "Stable account T0/T1 delta and DSH disjoint-token recomputation are reported independently." + } +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04-opencode-partial-balance.json b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04-opencode-partial-balance.json new file mode 100644 index 0000000..ecd59a9 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04-opencode-partial-balance.json @@ -0,0 +1,7 @@ +{ + "schema_version": 1, + "run_id": "P2-20260815-04-opencode-partial", + "balance_generation": 1, + "balance_delta_cny": 0.24, + "crosses_recharge_event": false +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04-opencode-partial.json b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04-opencode-partial.json new file mode 100644 index 0000000..de2b815 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04-opencode-partial.json @@ -0,0 +1,98 @@ +{ + "schema_version": 1, + "experimental_status": "post-preregistered exploratory harness comparison", + "excluded_from_dsh_mechanism_ablation": true, + "benchmark": "project2-v4.1b", + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "run_id": "P2-20260815-04-opencode-partial", + "model": "deepseek-v4-pro", + "provider": "deepseek", + "resolved_endpoint": "https://api.deepseek.com", + "reasoning_effort": "max", + "opencode_version": "1.18.17", + "opencode_commit": "02546dfc2e4515a4f90aaf9ceb3890df2ac2b479", + "agent": "build", + "agent_mode": "primary", + "models_catalog_sha256": "38c46f27db899736408a1371f78d79a3ade5f0d2fe5ca279e2c448ce2b247995", + "system_instruction_source": "OpenCode built-in build agent/system prompt; isolated home/config; no repository AGENTS.md, CLAUDE.md, CONTEXT.md, or opencode.json discovered before the run", + "os_environment": "Windows 10 / PowerShell / OpenCode native Windows", + "started_at_utc": "2026-08-15T09:20:55.957000+00:00", + "ended_at_utc": "2026-08-15T09:23:17.151000+00:00", + "wall_time_seconds": 141.194, + "opencode_exit_code": null, + "stop_reason": "external_tool_session_interruption_after_model_response", + "benchmark_result": null, + "benchmark_result_sha256": null, + "usage": { + "input": 65855, + "output": 1279, + "reasoning": 537, + "cache": { + "read": 422656, + "write": 0 + } + }, + "cache_miss_tokens": 65855, + "cache_read_tokens": 422656, + "output_tokens": 1279, + "reasoning_tokens": 537, + "billable_output_tokens": 1816, + "api_cost_cny": 0.219027, + "opencode_catalog_cost": 0.031758973, + "api_cost_scope": "OpenCode session usage recomputed at official live CNY rates", + "usage_semantics": "OpenCode v1.18.17 stores cache-miss input, cache-read input, non-reasoning output, and reasoning output as disjoint counts", + "assistant_message_count": 14, + "visible_assistant_replies": [ + "Now running the required diagnostics before making changes:", + "Initial diagnosis captured. Now reading the gateway source:" + ], + "visible_assistant_reply_count": 2, + "tool_call_count": 22, + "distinct_tools_used": [ + "bash", + "read" + ], + "tool_breakdown": { + "bash": 4, + "read": 18 + }, + "first_request_tool_catalog": { + "evidence_source": "zero-cost static resolution via `opencode debug agent build --pure`; not a wire request capture", + "tool_names": [ + "bash", + "edit", + "glob", + "grep", + "invalid", + "question", + "read", + "skill", + "task", + "todowrite", + "webfetch", + "write" + ], + "tool_count": 12 + }, + "catalog_transition": "OpenCode comparison has no anchoring transition; static-resolved full catalog applies from request 1", + "system_prompt_sha256_by_user_message": [], + "trajectory_fingerprints": { + "reasoning_blocks": 11, + "reasoning_chars": 2386, + "reasoning_words": 385, + "we": 0, + "let_me": 14, + "lets": 0 + }, + "trajectory_comparison": "available", + "raw_evidence_sha256": "cd35ebd8ace7be453bb159c772b5cec0b77b059d1f6ce997a1fee2b7113d5e06", + "run_status": "infrastructure_failed_partial_after_model_response", + "benchmark_score_status": "not_run_partial_agent_artifact_not_comparable", + "partial_event_stream_sha256": "1f4e89c7ea3265e534108c8a3f3cf718497bda3e312cedc4dac2a8a8dd3d07e4", + "account_balance_delta_cny": 0.24, + "balance_generation": 1, + "balance_cost_difference_cny": 0.020973, + "valid_for_harness_score_comparison": false, + "valid_for_dsh_mechanism_ablation": false +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04b-opencode-replacement-balance.json b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04b-opencode-replacement-balance.json new file mode 100644 index 0000000..ad8634e --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04b-opencode-replacement-balance.json @@ -0,0 +1,7 @@ +{ + "schema_version": 1, + "run_id": "P2-20260815-04b-opencode-replacement", + "balance_generation": 1, + "balance_delta_cny": 1.53, + "crosses_recharge_event": false +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04b-opencode-replacement.json b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04b-opencode-replacement.json new file mode 100644 index 0000000..f02301e --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/runs/P2-20260815-04b-opencode-replacement.json @@ -0,0 +1,367 @@ +{ + "schema_version": 1, + "experimental_status": "post-preregistered exploratory harness comparison", + "excluded_from_dsh_mechanism_ablation": true, + "benchmark": "project2-v4.1b", + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "run_id": "P2-20260815-04b-opencode-replacement", + "model": "deepseek-v4-pro", + "provider": "deepseek", + "resolved_endpoint": "https://api.deepseek.com", + "reasoning_effort": "max", + "opencode_version": "1.18.17", + "opencode_commit": "02546dfc2e4515a4f90aaf9ceb3890df2ac2b479", + "agent": "build", + "agent_mode": "primary", + "models_catalog_sha256": "38c46f27db899736408a1371f78d79a3ade5f0d2fe5ca279e2c448ce2b247995", + "system_instruction_source": "OpenCode built-in build agent/system prompt; isolated home/config; no repository AGENTS.md, CLAUDE.md, CONTEXT.md, or opencode.json discovered before the run", + "os_environment": "Windows 10 / PowerShell / OpenCode native Windows", + "started_at_utc": "2026-08-15T11:19:04.849968+00:00", + "ended_at_utc": "2026-08-15T11:40:37.993600+00:00", + "wall_time_seconds": 1293.144, + "opencode_exit_code": 0, + "stop_reason": null, + "benchmark_result": { + "project": "\\project2_task", + "results_dir": "\\evaluator\\results\\20260815_194422", + "benchmark": "project2-v4.1b", + "steps": { + "public": 0, + "debug_probe": 0, + "hidden": 1, + "espidf_static": 1 + }, + "details": { + "hidden_summary": "\\evaluator\\results\\20260815_194422\\hidden_summary.json", + "espidf_static_summary": "\\evaluator\\results\\20260815_194422\\espidf_static_summary.json", + "pr_template": { + "expected": "\\project2_task\\PULL_REQUEST_TEMPLATE.md", + "found": true, + "source": "\\project2_task\\PULL_REQUEST_TEMPLATE.md", + "artifact": "\\evaluator\\results\\20260815_194422\\pull_request_template.md", + "bytes": 16369 + }, + "git": { + "baseline": "project2-v4-broken-seed", + "diff": "\\evaluator\\results\\20260815_194422\\candidate_diff.patch", + "diff_lines": 2358, + "shortstat": "17 files changed, 735 insertions(+), 760 deletions(-)", + "status": "\\evaluator\\results\\20260815_194422\\candidate_status.txt", + "changed_file_count": 27, + "log": "\\evaluator\\results\\20260815_194422\\candidate_log.txt", + "duration_source": "unknown" + }, + "score_draft": "\\evaluator\\results\\20260815_194422\\score_draft.json", + "blockers": "\\evaluator\\results\\20260815_194422\\blockers.json", + "score_draft_confidence": "\\evaluator\\results\\20260815_194422\\score_draft_confidence.json", + "dimensions": "\\evaluator\\results\\20260815_194422\\dimensions.json" + }, + "meta": { + "model": "deepseek-v4-pro", + "channel": "deepseek-official", + "harness": "opencode-1.18.17", + "benchmark": "project2-v4.1b", + "seed_tag": "project2-v4-broken-seed", + "registry_version": "v4.1b", + "schema_version": 1, + "experimental_status": "post-preregistered exploratory harness comparison", + "excluded_from_dsh_mechanism_ablation": true, + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "run_id": "P2-20260815-04b-opencode-replacement", + "provider": "deepseek", + "resolved_endpoint": "https://api.deepseek.com", + "reasoning_effort": "max", + "opencode_version": "1.18.17", + "opencode_commit": "02546dfc2e4515a4f90aaf9ceb3890df2ac2b479", + "agent": "build", + "agent_mode": "primary", + "models_catalog_sha256": "38c46f27db899736408a1371f78d79a3ade5f0d2fe5ca279e2c448ce2b247995", + "system_instruction_source": "OpenCode built-in build agent/system prompt; isolated home/config; no repository AGENTS.md, CLAUDE.md, CONTEXT.md, or opencode.json discovered before the run", + "os_environment": "Windows 10 / PowerShell / OpenCode native Windows", + "started_at_utc": "2026-08-15T11:19:04.849968+00:00", + "ended_at_utc": "2026-08-15T11:40:37.993600+00:00", + "wall_time_seconds": 1293.144, + "opencode_exit_code": 0, + "stop_reason": null, + "benchmark_result": null, + "benchmark_result_sha256": null, + "usage": { + "input": 170382, + "output": 64000, + "reasoning": 32269, + "cache": { + "read": 21949696, + "write": 0 + } + }, + "cache_miss_tokens": 170382, + "cache_read_tokens": 21949696, + "output_tokens": 64000, + "reasoning_tokens": 32269, + "billable_output_tokens": 96269, + "api_cost_cny": 1.637502, + "opencode_catalog_cost": 0.23743784800000003, + "api_cost_scope": "OpenCode session usage recomputed at official live CNY rates", + "usage_semantics": "OpenCode v1.18.17 stores cache-miss input, cache-read input, non-reasoning output, and reasoning output as disjoint counts", + "assistant_message_count": 126, + "visible_assistant_replies": [ + "Now let me look at the remaining project files and run the initial diagnostics.", + "Initial diagnostics recorded. Now exploring ESP32 firmware and remaining docs.", + "Now let me check the remaining docs and the legacy sample DB.", + "I have a full picture now. Setting up the task list and starting fixes.", + "Now fixing `auth.py`:", + "Now `db.py` — full care_events schema plus migration for legacy tables:", + "Now rewrite `care_events.py`:", + "Now `gateway.py` — imports and auth logic:", + "Now the session/auth policy functions and v3 context builder:", + "Now add the care_event GET route:", + "Now the care_event POST route:", + "Now fix `sleep_importer.py`:", + "Now fix voice to explicitly fetch and pass the current session:", + "Now run the public tests and probe to verify the gateway fixes:", + "Sleep importer now lands on B1. Running the probe:", + "All probe checks pass. Let me write an ad-hoc verification script (temp dir) for the remaining edge cases: legacy DB migration, mixed CSV scoping, and end-to-end context/care_event flows.", + "All ad-hoc checks pass (the trailing error is just Windows tempdir cleanup of an open sqlite handle). Now an end-to-end HTTP check of context + care events:", + "E2E HTTP checks pass. Moving to the ESP32 firmware restructure.", + "Now writing the ESP32 firmware modules. First `protocol_packet`:", + "Now `device_config`:", + "Now `mqtt_payload`:", + "Now `net_backhaul` (Wi-Fi STA + MQTT):", + "Now rewrite `main.cpp` to use the modules and wire the MQTT uplink:", + "Now update the build files:", + "Let me create ESP-IDF header stubs to syntax-check the ESP-dependent modules locally:", + "Firmware syntax-checks pass. Now update the ESP32 project docs:", + "Now update the main project README:", + "Now update the RK3588 guide for the tightened context contract:", + "Now re-run the full verification suite:", + "All checks green. Now run the ESP-IDF build script to record its actual behavior:", + "Both ESP-IDF entry points fail at environment detection (no EIM install on this machine) — recorded. As compensating verification, syntax-check `usb_descriptors.c` too:", + "All firmware sources pass syntax checks. Let me review the full diff before writing the PR template:", + "Now write the PULL_REQUEST_TEMPLATE.md with the full report:", + "Final verification round:", + "修复完成。\n\n## 改了什么\n\n**Gateway 安全与鉴权**\n- `auth.py`:密码强制 PBKDF2-SHA256 加盐哈希(历史明文账户首次登录一次性升级);`admin_account_exists` 查库;`get_admin_http_session` 严格校验 token hash + 有效期,不再\"取任意活跃会话\"\n- `gateway.py`:本机例外收紧为仅指定服务接口(v2/esp/context/gallery/match/observation/session/current),管理 API 本机远程一律要登录;face/credential 模板允许已登录管理员读取;`session_is_authenticated` 四条件全查(actor/id-state/assurance/expiry);context 不再静默回退 ambient session\n- `voice`:新增 `fetch_current_session()` 显式携带 session_id 请求 context\n\n**care_event**\n- `db.py` 补全列 + 旧表 `ALTER TABLE` 迁移(旧数据保留、ts 回填);`care_events.py` 重写 CRUD(room/bed 大写规范化、limit、ts/created_ts 倒序);新增 `GET/POST /api/v3/care/events`;授权通过时 context 注入 `modalities.care_events`\n\n**睡眠 CSV**:按行策略——显式行只落自己床位,无归属行按 default→first(修复了原来落到 `beds[-1]` 的 bug)→skip→all(仅显式调试)\n\n**ESP32-S3**:模块化重构(protocol_packet/maixsense_parser/device_config/mqtt_payload/net_backhaul),恢复 Wi-Fi STA + 巴法云 MQTT 回传,USB CDC 与 MQTT 双通道并行;补 CMakeLists/idf_component.yml 依赖;文档同步更新\n\n## 验证了什么\n\n- `run_public_tests.py`:全过;`run_debug_probe.py`:从 6 FAIL → 8 项全过\n- 自写临时脚本:旧 9 列库迁移、明文账户升级、混合 CSV 逐行落床、HTTP 端到端(伪造 Cookie 401、过期/unknown session 拒绝、patient 越权拒绝、care_event 无 Cookie 401 + 大写查询命中)全部通过\n- 固件全部源文件 g++ stub 语法检查通过\n\n## 风险\n\n- 本机无 ESP-IDF EIM 环境,`run_espidf_build.py` 在激活脚本缺失处明确失败(exit 2,未进编译阶段),已如实记录;MQTT 结构体按 IDF 5/6 形态书写,需有 EIM 的机器做真实编译\n- 未做硬件实机验证(Wi-Fi/MQTT 连通、ToF/MLX 实机读数)\n- 隐藏 CI 测试未可见,边界均以临时库自测覆盖,以 CI 实际结果为准" + ], + "visible_assistant_reply_count": 35, + "tool_call_count": 152, + "distinct_tools_used": [ + "bash", + "edit", + "glob", + "grep", + "read", + "todowrite", + "write" + ], + "tool_breakdown": { + "bash": 29, + "edit": 51, + "glob": 1, + "grep": 1, + "read": 53, + "todowrite": 2, + "write": 15 + }, + "first_request_tool_catalog": { + "evidence_source": "zero-cost static resolution via `opencode debug agent build --pure`; not a wire request capture", + "tool_names": [ + "bash", + "edit", + "glob", + "grep", + "invalid", + "question", + "read", + "skill", + "task", + "todowrite", + "webfetch", + "write" + ], + "tool_count": 12 + }, + "catalog_transition": "OpenCode comparison has no anchoring transition; static-resolved full catalog applies from request 1", + "system_prompt_sha256_by_user_message": [], + "trajectory_fingerprints": { + "reasoning_blocks": 65, + "reasoning_chars": 117225, + "reasoning_words": 16133, + "we": 22, + "let_me": 119, + "lets": 3 + }, + "trajectory_comparison": "available", + "raw_evidence_sha256": "178f35c19d9853cb92579c4c9458da4203a5c911021415d6c1d94720b3b73472", + "run_group_id": "dsv4p-opencode-20260815", + "thinking_level": "max", + "endpoint_product": "DeepSeek-API", + "billing_tier": "paygo", + "run_index": 1, + "complete": true, + "meta_partial": false + }, + "meta_complete": true, + "duration_sec": 12, + "duration_source": "eval_wall", + "ability_draft": 93.0, + "ship_draft": 93.0, + "release_class_hint": "B+", + "blockers": [ + "M-fidelity", + "E-contract" + ], + "behavior_blockers": [ + "M-fidelity", + "E-contract" + ], + "semantic_only_codes": [ + "V4-F12-04" + ], + "f9_mode": "skipped_env", + "f11_status": "heuristic", + "family_draft": { + "F1": 8.0, + "F2": 12.0, + "F3": 16.0, + "F4": 4.0, + "F5": 12.0, + "F6": 8.0, + "F7": 8.0, + "F8": 7.0, + "F9": 3.0, + "F10": 8.0, + "F11": 4.0, + "F12": 3.0 + }, + "dimensions": { + "final_code": 10.0, + "security": 9.72, + "migration": 8.0, + "esp_deploy": 7.14, + "process_truth": 10.0, + "efficiency": null + } + }, + "benchmark_result_sha256": "5b078eb2b647584fcf018f452621900f885a1efe22be93fb85cc66dae5b8dbce", + "usage": { + "input": 170382, + "output": 64000, + "reasoning": 32269, + "cache": { + "read": 21949696, + "write": 0 + } + }, + "cache_miss_tokens": 170382, + "cache_read_tokens": 21949696, + "output_tokens": 64000, + "reasoning_tokens": 32269, + "billable_output_tokens": 96269, + "api_cost_cny": 1.637502, + "opencode_catalog_cost": 0.23743784800000003, + "api_cost_scope": "OpenCode session usage recomputed at official live CNY rates", + "usage_semantics": "OpenCode v1.18.17 stores cache-miss input, cache-read input, non-reasoning output, and reasoning output as disjoint counts", + "assistant_message_count": 126, + "visible_assistant_replies": [ + "Now let me look at the remaining project files and run the initial diagnostics.", + "Initial diagnostics recorded. Now exploring ESP32 firmware and remaining docs.", + "Now let me check the remaining docs and the legacy sample DB.", + "I have a full picture now. Setting up the task list and starting fixes.", + "Now fixing `auth.py`:", + "Now `db.py` — full care_events schema plus migration for legacy tables:", + "Now rewrite `care_events.py`:", + "Now `gateway.py` — imports and auth logic:", + "Now the session/auth policy functions and v3 context builder:", + "Now add the care_event GET route:", + "Now the care_event POST route:", + "Now fix `sleep_importer.py`:", + "Now fix voice to explicitly fetch and pass the current session:", + "Now run the public tests and probe to verify the gateway fixes:", + "Sleep importer now lands on B1. Running the probe:", + "All probe checks pass. Let me write an ad-hoc verification script (temp dir) for the remaining edge cases: legacy DB migration, mixed CSV scoping, and end-to-end context/care_event flows.", + "All ad-hoc checks pass (the trailing error is just Windows tempdir cleanup of an open sqlite handle). Now an end-to-end HTTP check of context + care events:", + "E2E HTTP checks pass. Moving to the ESP32 firmware restructure.", + "Now writing the ESP32 firmware modules. First `protocol_packet`:", + "Now `device_config`:", + "Now `mqtt_payload`:", + "Now `net_backhaul` (Wi-Fi STA + MQTT):", + "Now rewrite `main.cpp` to use the modules and wire the MQTT uplink:", + "Now update the build files:", + "Let me create ESP-IDF header stubs to syntax-check the ESP-dependent modules locally:", + "Firmware syntax-checks pass. Now update the ESP32 project docs:", + "Now update the main project README:", + "Now update the RK3588 guide for the tightened context contract:", + "Now re-run the full verification suite:", + "All checks green. Now run the ESP-IDF build script to record its actual behavior:", + "Both ESP-IDF entry points fail at environment detection (no EIM install on this machine) — recorded. As compensating verification, syntax-check `usb_descriptors.c` too:", + "All firmware sources pass syntax checks. Let me review the full diff before writing the PR template:", + "Now write the PULL_REQUEST_TEMPLATE.md with the full report:", + "Final verification round:", + "修复完成。\n\n## 改了什么\n\n**Gateway 安全与鉴权**\n- `auth.py`:密码强制 PBKDF2-SHA256 加盐哈希(历史明文账户首次登录一次性升级);`admin_account_exists` 查库;`get_admin_http_session` 严格校验 token hash + 有效期,不再\"取任意活跃会话\"\n- `gateway.py`:本机例外收紧为仅指定服务接口(v2/esp/context/gallery/match/observation/session/current),管理 API 本机远程一律要登录;face/credential 模板允许已登录管理员读取;`session_is_authenticated` 四条件全查(actor/id-state/assurance/expiry);context 不再静默回退 ambient session\n- `voice`:新增 `fetch_current_session()` 显式携带 session_id 请求 context\n\n**care_event**\n- `db.py` 补全列 + 旧表 `ALTER TABLE` 迁移(旧数据保留、ts 回填);`care_events.py` 重写 CRUD(room/bed 大写规范化、limit、ts/created_ts 倒序);新增 `GET/POST /api/v3/care/events`;授权通过时 context 注入 `modalities.care_events`\n\n**睡眠 CSV**:按行策略——显式行只落自己床位,无归属行按 default→first(修复了原来落到 `beds[-1]` 的 bug)→skip→all(仅显式调试)\n\n**ESP32-S3**:模块化重构(protocol_packet/maixsense_parser/device_config/mqtt_payload/net_backhaul),恢复 Wi-Fi STA + 巴法云 MQTT 回传,USB CDC 与 MQTT 双通道并行;补 CMakeLists/idf_component.yml 依赖;文档同步更新\n\n## 验证了什么\n\n- `run_public_tests.py`:全过;`run_debug_probe.py`:从 6 FAIL → 8 项全过\n- 自写临时脚本:旧 9 列库迁移、明文账户升级、混合 CSV 逐行落床、HTTP 端到端(伪造 Cookie 401、过期/unknown session 拒绝、patient 越权拒绝、care_event 无 Cookie 401 + 大写查询命中)全部通过\n- 固件全部源文件 g++ stub 语法检查通过\n\n## 风险\n\n- 本机无 ESP-IDF EIM 环境,`run_espidf_build.py` 在激活脚本缺失处明确失败(exit 2,未进编译阶段),已如实记录;MQTT 结构体按 IDF 5/6 形态书写,需有 EIM 的机器做真实编译\n- 未做硬件实机验证(Wi-Fi/MQTT 连通、ToF/MLX 实机读数)\n- 隐藏 CI 测试未可见,边界均以临时库自测覆盖,以 CI 实际结果为准" + ], + "visible_assistant_reply_count": 35, + "tool_call_count": 152, + "distinct_tools_used": [ + "bash", + "edit", + "glob", + "grep", + "read", + "todowrite", + "write" + ], + "tool_breakdown": { + "bash": 29, + "edit": 51, + "glob": 1, + "grep": 1, + "read": 53, + "todowrite": 2, + "write": 15 + }, + "first_request_tool_catalog": { + "evidence_source": "zero-cost static resolution via `opencode debug agent build --pure`; not a wire request capture", + "tool_names": [ + "bash", + "edit", + "glob", + "grep", + "invalid", + "question", + "read", + "skill", + "task", + "todowrite", + "webfetch", + "write" + ], + "tool_count": 12 + }, + "catalog_transition": "OpenCode comparison has no anchoring transition; static-resolved full catalog applies from request 1", + "system_prompt_sha256_by_user_message": [], + "trajectory_fingerprints": { + "reasoning_blocks": 65, + "reasoning_chars": 117225, + "reasoning_words": 16133, + "we": 22, + "let_me": 119, + "lets": 3 + }, + "trajectory_comparison": "available", + "raw_evidence_sha256": "178f35c19d9853cb92579c4c9458da4203a5c911021415d6c1d94720b3b73472", + "cost_reconciliation": { + "balance_generation": 1, + "t0_raw_sha256": "8482b1766f141e43581e9fbd0f1ae3b995693af49498ddbe0b43a5e77ff3e5cb", + "t1_raw_sha256": "e3b8089448756d4e2de419bd45668b581b922c4ac566ee67ec00027e9ce27208", + "account_balance_delta_cny": 1.53, + "official_rate_recomputed_agent_usage_cny": 1.637502, + "account_minus_recomputed_cny": -0.107502, + "crosses_recharge_event": false, + "notes": "Stable account T0/T1 delta and OpenCode disjoint-token recomputation are reported independently." + }, + "run_status": "completed", + "benchmark_score_status": "evaluator_result_recorded", + "valid_for_harness_score_comparison": true, + "valid_for_dsh_mechanism_ablation": false +} diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/schema-gate.json b/experiments/deepseek-v4-pro-anchoring/artifacts/schema-gate.json new file mode 100644 index 0000000..2bf3fcc --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/schema-gate.json @@ -0,0 +1,190 @@ +{ + "schema_version": 1, + "passed": true, + "assertions": { + "standard_first_is_full": true, + "minimal_first_equals_anchored_second_tools": true, + "minimal_first_equals_standard_first_tools": true, + "minimal_and_anchored_system_equal": true, + "minimal_and_anchored_first_non_tools_equal": true, + "anchored_first_is_shell_read": true, + "anchored_second_is_full": true + }, + "snapshots": { + "standard": [ + { + "system_sha256": "e29769325a5dc1ef8e4ec86dd3b18b3745c9fc0df557307800046f56b6ff00e4", + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "2d6cb989d15da25a64aa2de8c618a310c555932e40eb2ef1db5d30199b84d072", + "non_tools_sha256": "eaf508d86419f4b1bcfbd6ede279b54d9e242c8fc45e61caeca2a1ee620cc06e" + }, + { + "system_sha256": "e29769325a5dc1ef8e4ec86dd3b18b3745c9fc0df557307800046f56b6ff00e4", + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "2d6cb989d15da25a64aa2de8c618a310c555932e40eb2ef1db5d30199b84d072", + "non_tools_sha256": "3b1342cb622f2dcf458489577db3cf5d39323781f9124cf140f20489ad5113c5" + } + ], + "minimal-full": [ + { + "system_sha256": "af5d5073dc27d768af14d166cdfd5758d21365a3c1ef6c3540255380d564be75", + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "2d6cb989d15da25a64aa2de8c618a310c555932e40eb2ef1db5d30199b84d072", + "non_tools_sha256": "547aec351e816e7b76c8b9c8e2115166f365f845aaab6e526944ff1d55939956" + }, + { + "system_sha256": "af5d5073dc27d768af14d166cdfd5758d21365a3c1ef6c3540255380d564be75", + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "2d6cb989d15da25a64aa2de8c618a310c555932e40eb2ef1db5d30199b84d072", + "non_tools_sha256": "b17ec0e6a988f6b0522c823c48139ff8e6a096d1c9d6c604d8adae12618a5e70" + } + ], + "anchored-standard": [ + { + "system_sha256": "af5d5073dc27d768af14d166cdfd5758d21365a3c1ef6c3540255380d564be75", + "tool_names": [ + "pwsh", + "read" + ], + "tools_sha256": "2fceff54430b2c6d2a94d85716f44d9092d2b8ef04e427ce807f10a19ec8d3e1", + "non_tools_sha256": "547aec351e816e7b76c8b9c8e2115166f365f845aaab6e526944ff1d55939956" + }, + { + "system_sha256": "af5d5073dc27d768af14d166cdfd5758d21365a3c1ef6c3540255380d564be75", + "tool_names": [ + "ask_user_question", + "create_goal", + "edit", + "exit_plan_mode", + "get_goal", + "glob", + "grep", + "interrupt_agent", + "job_kill", + "job_list", + "job_output", + "list_agents", + "pwsh", + "ralph", + "read", + "read_image", + "send_message", + "skill", + "subagent", + "subagent_fork", + "todo_write", + "update_goal", + "web_search", + "workflow", + "write" + ], + "tools_sha256": "2d6cb989d15da25a64aa2de8c618a310c555932e40eb2ef1db5d30199b84d072", + "non_tools_sha256": "b17ec0e6a988f6b0522c823c48139ff8e6a096d1c9d6c604d8adae12618a5e70" + } + ] + } +} diff --git a/experiments/deepseek-v4-pro-anchoring/mock-prompt.txt b/experiments/deepseek-v4-pro-anchoring/mock-prompt.txt new file mode 100644 index 0000000..d7a5f9a --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/mock-prompt.txt @@ -0,0 +1 @@ +Inspect the current workspace with one durable shell tool call, then report completion. diff --git a/experiments/deepseek-v4-pro-anchoring/preregistration.json b/experiments/deepseek-v4-pro-anchoring/preregistration.json new file mode 100644 index 0000000..2b0f51b --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/preregistration.json @@ -0,0 +1,135 @@ +{ + "schema_version": 2, + "contribution": { + "contributor": "@NineThoughts0521", + "role": "independent third-party replication", + "upstream_repository": "xiaobright/modeltest", + "scoreboard_policy": "do not merge these runs into maintainer formal n, rank, worst, mean, or sample index" + }, + "status": "project2_3_plus_1_completed_external_pairs_deferred", + "approved_at_utc": "2026-08-15T03:23:28Z", + "wall_clock_started_at_utc": "2026-08-15T03:23:28Z", + "wall_clock_hard_limit_hours": 12, + "wall_clock_completed_at_utc": "2026-08-15T12:03:03.148Z", + "wall_clock_elapsed_hours": 8.659763, + "completion": { + "valid_project2_runs": 4, + "preserved_unscored_partial_runs": 1, + "account_cost_cny_including_partial": 7.12, + "usage_recomputed_cost_cny_including_partial": 7.075304, + "deepswe_pair": "deferred_to_later_phase", + "terminal_bench_pair": "deferred_to_later_phase", + "optional_real_espidf_build": "not_run" + }, + "experiment_seed": "modeltest-dsv4p-anchor-20260815", + "model": { + "provider": "deepseek-official", + "id": "deepseek-v4-pro", + "documented_version": "DeepSeek-V4-Pro-0813", + "reasoning_effort": "max" + }, + "pins": { + "modeltest_base_commit": "04255b55f16c4439e538239fb9783070c4165081", + "dsh_version": "0.1.0-rc.6", + "dsh_source_commit": "47f943859bef60e4160492346772ded9b24f765a", + "deepswe_commit": "435ee89ec2f2e2289f33b0da4f992f0b7b7266b9", + "pier_version": "0.3.1", + "pier_source_commit": "df89f994623a0a6a57229103b6fe910766693c30", + "terminal_bench_v3_git_tag_commit": "2b0442c3c583b710ca8da14c8e601b99f2f1f244" + }, + "project2": { + "include_optional_real_espidf_build": false, + "expected_f9_mode": "skipped_env", + "expected_f9_points": "3/6", + "agent_timeout_seconds": 4500, + "evaluator_timeout_seconds": 1800 + }, + "deepswe": { + "task_id": "httpx-deterministic-cookie-store", + "environment_gate_seconds": 5400, + "agent_timeout_seconds": 5400, + "verifier_timeout_seconds": 1800, + "pair_only": true, + "fallback_task_ids_before_any_paid_external_run": [ + "helm-unified-manifest-stream", + "wasmi-trap-coredumps" + ], + "network_semantics": { + "task_network_policy_immutable": true, + "required_task_mode": "no-network", + "installed_agent_network_allowlist_required": true, + "install_and_inference_egress_separated": true, + "inference_allowlist_target": "api.deepseek.com", + "public_task_network_forbidden": true + } + }, + "budget_cny": { + "target": 45, + "absolute_maximum": 50, + "deepswe_start_project2_spend_max": 27, + "deepswe_pair_reservation": 18, + "pricing_source": "https://api-docs.deepseek.com/zh-cn/quick_start/pricing", + "pricing_policy": "snapshot immediately before the first paid request and use the live CNY rates applicable to each response" + }, + "retry_policy": { + "pre_response_infrastructure_retry_limit": 1, + "model_outcome_retry": false, + "verifier_infrastructure_retry_same_artifact_only": true, + "result_based_task_switching": false + }, + "publication": { + "raw_reasoning_or_session_in_git": false, + "publish_raw_sha256": true, + "publish_original_verifier_outputs": true, + "trajectory_markers_are_capability_metrics": false + }, + "approved_amendments": [ + { + "approved_at_utc": "2026-08-15T04:21:00Z", + "reason": "Operator requested per-run balance generations around a recharge event.", + "protocol": { + "pause_after_project2_run_1": true, + "capture_t1_before_requesting_recharge": true, + "capture_fresh_t0_after_recharge_before_run_2": true, + "record_recharge_event_in_metadata": true, + "balance_delta_must_not_cross_recharge_event": true, + "token_recomputed_cost_retained_per_run": true + } + }, + { + "approved_at_utc": "2026-08-15T07:59:10Z", + "reason": "Operator added one post-preregistered exploratory OpenCode harness comparison after the DSH ablation.", + "protocol": { + "run_order_after_dsh": "OpenCode direct DeepSeek provider", + "run_id": "P2-20260815-04-opencode", + "causal_ablation_membership": false, + "api_cost_hard_cap_cny": 4, + "required_provider": "direct DeepSeek official API", + "opencode_zen": false, + "score_run_allowed_if_reasoning_export_incomplete": true, + "result_based_retry": false + } + }, + { + "approval_timestamp_status": "not independently timestamped in public artifacts", + "approved_before_t0_retrieved_at_utc": "2026-08-15T11:14:54.992009+00:00", + "reason": "Operator authorized one detached-process replacement for the preserved OpenCode infrastructure partial.", + "protocol": { + "replacement_run_id": "P2-20260815-04b-opencode-replacement", + "replaces_partial_run_id": "P2-20260815-04-opencode-partial", + "replacement_limit": 1, + "reset_frozen_project2": true, + "opencode_version": "1.18.17", + "provider": "deepseek", + "model": "deepseek-v4-pro", + "reasoning_variant": "max", + "resolved_endpoint": "https://api.deepseek.com", + "lifecycle_change_only": "detached/background process wrapper", + "tool_schema_prompt_permissions_unchanged": true, + "independent_t0_t1_window": true, + "result_based_retry": false, + "replacement_is_exploratory_only": true + } + } + ] +} diff --git a/experiments/deepseek-v4-pro-anchoring/run-matrix.json b/experiments/deepseek-v4-pro-anchoring/run-matrix.json new file mode 100644 index 0000000..1f4883f --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/run-matrix.json @@ -0,0 +1,82 @@ +{ + "schema_version": 1, + "contributor": "@NineThoughts0521", + "evidence_role": "independent third-party replication; excluded from maintainer formal n", + "seed": "modeltest-dsv4p-anchor-20260815", + "runs": [ + { + "order": 1, + "run_id": "P2-20260815-01-anchored", + "benchmark": "project2-v4.1b", + "task_id": "project2-v4-broken-seed", + "preset": "anchored-standard", + "required": true, + "evaluator_result_id": "20260815_122420", + "status": "completed" + }, + { + "order": 2, + "run_id": "P2-20260815-02-standard", + "benchmark": "project2-v4.1b", + "task_id": "project2-v4-broken-seed", + "preset": "standard", + "required": true, + "evaluator_result_id": "20260815_162840", + "status": "completed" + }, + { + "order": 3, + "run_id": "P2-20260815-03-minimal-full", + "benchmark": "project2-v4.1b", + "task_id": "project2-v4-broken-seed", + "preset": "minimal-full", + "required": true, + "evaluator_result_id": "20260815_164240", + "status": "completed" + }, + { + "order": 4, + "run_id": "P2-20260815-04-opencode", + "benchmark": "project2-v4.1b", + "task_id": "project2-v4-broken-seed", + "preset": "opencode-direct-deepseek", + "required": "conditional-budget-and-wall-clock", + "registration": "post-preregistered-exploratory-harness-comparison", + "excluded_from_dsh_mechanism_causal_ablation": true, + "status": "infrastructure-failed-partial-after-model-response", + "partial_artifact_run_id": "P2-20260815-04-opencode-partial", + "automatic_retry_allowed": false + }, + { + "order": 4, + "run_id": "P2-20260815-04b-opencode-replacement", + "benchmark": "project2-v4.1b", + "task_id": "project2-v4-broken-seed", + "preset": "opencode-direct-deepseek", + "required": "completed-post-preregistered-exploratory", + "registration": "replacement-approved-before-run", + "replacement_of": "P2-20260815-04-opencode-partial", + "evaluator_result_id": "20260815_194422", + "status": "completed_evaluator_recorded", + "automatic_retry_allowed": false + }, + { + "order": 5, + "run_id": "DSWE-20260815-01-standard", + "benchmark": "deepswe-v1.1", + "task_id": "httpx-deterministic-cookie-store", + "preset": "standard", + "required": "conditional-pair", + "status": "deferred_to_later_phase" + }, + { + "order": 6, + "run_id": "DSWE-20260815-02-anchored", + "benchmark": "deepswe-v1.1", + "task_id": "httpx-deterministic-cookie-store", + "preset": "anchored-standard", + "required": "conditional-pair", + "status": "deferred_to_later_phase" + } + ] +} diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/analyze_opencode_session.py b/experiments/deepseek-v4-pro-anchoring/scripts/analyze_opencode_session.py new file mode 100644 index 0000000..1e92f35 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/analyze_opencode_session.py @@ -0,0 +1,218 @@ +from __future__ import annotations + +import argparse +import hashlib +import json +import re +from collections import Counter +from pathlib import Path +from typing import Any + + +WORD_RE = re.compile(r"\b[\w']+\b", re.UNICODE) + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def redact_text(text: str, workspace: str | None) -> str: + values = [ + (workspace, ""), + (str(Path(workspace).parent) if workspace else None, ""), + (str(Path.home()), ""), + ] + for value, label in sorted((item for item in values if item[0]), key=lambda item: len(item[0]), reverse=True): + text = re.sub(re.escape(value), label, text, flags=re.IGNORECASE) + text = re.sub(re.escape(value.replace("\\", "/")), label, text, flags=re.IGNORECASE) + return text + + +def redact_object(value: Any, workspace: str | None) -> Any: + if isinstance(value, str): + return redact_text(value, workspace) + if isinstance(value, list): + return [redact_object(item, workspace) for item in value] + if isinstance(value, dict): + return {key: redact_object(item, workspace) for key, item in value.items()} + return value + + +def marker_stats(texts: list[str]) -> dict[str, int]: + joined = "\n".join(texts) + return { + "reasoning_blocks": len(texts), + "reasoning_chars": sum(map(len, texts)), + "reasoning_words": len(WORD_RE.findall(joined)), + "we": len(re.findall(r"\bwe\b", joined, flags=re.IGNORECASE)), + "let_me": len(re.findall(r"\blet me\b", joined, flags=re.IGNORECASE)), + "lets": len(re.findall(r"\blet's\b", joined, flags=re.IGNORECASE)), + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--session", type=Path, required=True) + parser.add_argument("--run-meta", type=Path, required=True) + parser.add_argument("--gate", type=Path, required=True) + parser.add_argument("--price", type=Path, required=True) + parser.add_argument("--balance", type=Path) + parser.add_argument("--t0-balance", type=Path) + parser.add_argument("--t1-balance", type=Path) + parser.add_argument("--result", type=Path) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--run-id", required=True) + parser.add_argument("--benchmark-commit", required=True) + args = parser.parse_args() + + root = json.loads(args.session.read_text(encoding="utf-8")) + meta = json.loads(args.run_meta.read_text(encoding="utf-8")) + gate = json.loads(args.gate.read_text(encoding="utf-8")) + price = json.loads(args.price.read_text(encoding="utf-8")) + balance = json.loads(args.balance.read_text(encoding="utf-8")) if args.balance else None + info = root.get("info", {}) + reasoning: list[str] = [] + visible: list[str] = [] + tools: Counter[str] = Counter() + assistant_messages: list[dict[str, Any]] = [] + system_hashes: list[str] = [] + + for message in root.get("messages", []): + message_info = message.get("info", {}) + if message_info.get("role") == "user" and isinstance(message_info.get("system"), str): + system_hashes.append(hashlib.sha256(message_info["system"].encode("utf-8")).hexdigest()) + if message_info.get("role") != "assistant": + continue + assistant_messages.append(message_info) + for part in message.get("parts", []): + part_type = part.get("type") + if part_type == "reasoning": + reasoning.append(str(part.get("text", ""))) + elif part_type == "text" and part.get("text"): + visible.append(str(part["text"])) + elif part_type == "tool": + tools[str(part.get("tool", ""))] += 1 + + first_assistant = assistant_messages[0] if assistant_messages else {} + tokens = info.get("tokens", {}) if isinstance(info.get("tokens"), dict) else {} + cache = tokens.get("cache", {}) if isinstance(tokens.get("cache"), dict) else {} + cache_miss = int(tokens.get("input", 0) or 0) + cache_read = int(cache.get("read", 0) or 0) + output = int(tokens.get("output", 0) or 0) + reasoning_tokens = int(tokens.get("reasoning", 0) or 0) + rates = price["applicable_cny_per_million"] + recomputed_cost = round( + ( + cache_miss * rates["cache_miss"] + + cache_read * rates["cache_hit"] + + (output + reasoning_tokens) * rates["output"] + ) + / 1_000_000, + 6, + ) + + benchmark_result = None + result_hash = None + if args.result: + result_hash = sha256_file(args.result) + benchmark_result = redact_object( + json.loads(args.result.read_text(encoding="utf-8")), + meta.get("workspace"), + ) + + cost_reconciliation = None + if balance is not None: + account_delta = float(balance["balance_delta_cny"]) + t0_hash = sha256_file(args.t0_balance) if args.t0_balance else None + t1_hash = sha256_file(args.t1_balance) if args.t1_balance else None + cost_reconciliation = { + "balance_generation": balance.get("balance_generation"), + "t0_raw_sha256": t0_hash, + "t1_raw_sha256": t1_hash, + "account_balance_delta_cny": account_delta, + "official_rate_recomputed_agent_usage_cny": recomputed_cost, + "account_minus_recomputed_cny": round(account_delta - recomputed_cost, 6), + "crosses_recharge_event": bool(balance.get("crosses_recharge_event", False)), + "notes": "Stable account T0/T1 delta and OpenCode disjoint-token recomputation are reported independently.", + } + + payload = { + "schema_version": 1, + "experimental_status": "post-preregistered exploratory harness comparison", + "excluded_from_dsh_mechanism_ablation": True, + "benchmark": "project2-v4.1b", + "benchmark_commit": args.benchmark_commit, + "task_id": "project2-v4-broken-seed", + "run_id": args.run_id, + "model": first_assistant.get("modelID", "deepseek-v4-pro"), + "provider": first_assistant.get("providerID", "deepseek"), + "resolved_endpoint": meta.get("resolved_endpoint"), + "reasoning_effort": meta.get("reasoning_variant"), + "opencode_version": gate["opencode_version"], + "opencode_commit": gate["opencode_commit"], + "agent": first_assistant.get("agent", meta.get("agent")), + "agent_mode": gate["agent_mode"], + "models_catalog_sha256": gate["models_catalog_sha256"], + "system_instruction_source": gate["system_instruction_source"], + "os_environment": "Windows 10 / PowerShell / OpenCode native Windows", + "started_at_utc": meta["started_at_utc"], + "ended_at_utc": meta["ended_at_utc"], + "wall_time_seconds": meta["wall_time_seconds"], + "opencode_exit_code": meta["opencode_exit_code"], + "stop_reason": meta.get("stop_reason"), + "benchmark_result": benchmark_result, + "benchmark_result_sha256": result_hash, + "usage": tokens, + "cache_miss_tokens": cache_miss, + "cache_read_tokens": cache_read, + "output_tokens": output, + "reasoning_tokens": reasoning_tokens, + "billable_output_tokens": output + reasoning_tokens, + "api_cost_cny": recomputed_cost, + "opencode_catalog_cost": info.get("cost"), + "api_cost_scope": "OpenCode session usage recomputed at official live CNY rates", + "usage_semantics": "OpenCode v1.18.17 stores cache-miss input, cache-read input, non-reasoning output, and reasoning output as disjoint counts", + "assistant_message_count": len(assistant_messages), + "visible_assistant_replies": [redact_text(text, meta.get("workspace")) for text in visible], + "visible_assistant_reply_count": len(visible), + "tool_call_count": sum(tools.values()), + "distinct_tools_used": sorted(tools), + "tool_breakdown": dict(sorted(tools.items())), + "first_request_tool_catalog": { + "evidence_source": "zero-cost static resolution via `opencode debug agent build --pure`; not a wire request capture", + "tool_names": gate["tool_names"], + "tool_count": len(gate["tool_names"]), + }, + "catalog_transition": "OpenCode comparison has no anchoring transition; static-resolved full catalog applies from request 1", + "system_prompt_sha256_by_user_message": system_hashes, + "trajectory_fingerprints": marker_stats(reasoning), + "trajectory_comparison": "available" if reasoning else "unavailable_or_incomplete", + "raw_evidence_sha256": sha256_file(args.session), + "cost_reconciliation": cost_reconciliation, + "run_status": "completed" if meta.get("opencode_exit_code") == 0 and meta.get("export_exit_code") == 0 else "infrastructure_failed", + "benchmark_score_status": "evaluator_result_recorded" if benchmark_result is not None else "evaluation_pending", + "valid_for_harness_score_comparison": benchmark_result is not None, + "valid_for_dsh_mechanism_ablation": False, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print( + json.dumps( + { + "run_id": args.run_id, + "reasoning_effort": payload["reasoning_effort"], + "api_cost_cny": recomputed_cost, + "raw_evidence_sha256": payload["raw_evidence_sha256"], + }, + ensure_ascii=False, + ) + ) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/analyze_session.py b/experiments/deepseek-v4-pro-anchoring/scripts/analyze_session.py new file mode 100644 index 0000000..d36edd4 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/analyze_session.py @@ -0,0 +1,193 @@ +from __future__ import annotations + +import argparse +import hashlib +import json +import re +from collections import Counter +from datetime import datetime +from pathlib import Path +from typing import Any + + +WORD_RE = re.compile(r"\b[\w']+\b", re.UNICODE) + + +def canonical(value: object) -> bytes: + return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def redact_visible(text: str, cwd: str | None, home: str | None) -> str: + repository = str(Path(cwd).parent) if cwd else None + replacements = sorted( + ((value, label) for value, label in ((cwd, ""), (repository, ""), (home, "")) if value), + key=lambda item: len(item[0]), + reverse=True, + ) + for value, label in replacements: + text = re.sub(re.escape(value), label, text, flags=re.IGNORECASE) + text = re.sub(re.escape(value.replace("\\", "/")), label, text, flags=re.IGNORECASE) + return text + + +def redact_object(value: Any, cwd: str | None, home: str | None) -> Any: + if isinstance(value, str): + return redact_visible(value, cwd, home) + if isinstance(value, list): + return [redact_object(item, cwd, home) for item in value] + if isinstance(value, dict): + return {key: redact_object(item, cwd, home) for key, item in value.items()} + return value + + +def marker_stats(texts: list[str]) -> dict[str, int]: + joined = "\n".join(texts) + return { + "reasoning_blocks": len(texts), + "reasoning_chars": sum(map(len, texts)), + "reasoning_words": len(WORD_RE.findall(joined)), + "we": len(re.findall(r"\bwe\b", joined, flags=re.IGNORECASE)), + "let_me": len(re.findall(r"\blet me\b", joined, flags=re.IGNORECASE)), + "lets": len(re.findall(r"\blet's\b", joined, flags=re.IGNORECASE)), + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--session", type=Path, required=True) + parser.add_argument("--session-meta", type=Path, required=True) + parser.add_argument("--benchmark", required=True) + parser.add_argument("--task-id", required=True) + parser.add_argument("--run-id", required=True) + parser.add_argument("--dsh-version", required=True) + parser.add_argument("--dsh-commit", required=True) + parser.add_argument("--preset-hash", required=True) + parser.add_argument("--benchmark-commit", required=True) + parser.add_argument("--price", type=Path) + parser.add_argument("--result", type=Path) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + + meta = json.loads(args.session_meta.read_text(encoding="utf-8")) + reasoning: list[str] = [] + visible: list[str] = [] + usage: Counter[str] = Counter() + tools: Counter[str] = Counter() + catalogs: list[dict[str, Any]] = [] + system_hashes: list[str] = [] + request_index = 0 + turn_start: int | None = None + turn_end: int | None = None + + for raw_line in args.session.read_text(encoding="utf-8").splitlines(): + if not raw_line.strip(): + continue + event = json.loads(raw_line) + event_type = event.get("type") + data = event.get("data", {}) + if event_type == "request/header": + request_index += 1 + header = data.get("header", {}) + catalog = header.get("tools", []) + fingerprint = hashlib.sha256(canonical(catalog)).hexdigest() + if not catalogs or catalogs[-1]["tools_sha256"] != fingerprint: + catalogs.append({ + "request_index": request_index, + "tool_names": [item.get("name") for item in catalog], + "tools_sha256": fingerprint, + "tools": catalog, + }) + system_hashes.append(hashlib.sha256(str(header.get("system", "")).encode("utf-8")).hexdigest()) + elif event_type == "turn/start": + turn_start = event.get("time") + elif event_type == "turn/end": + turn_end = event.get("time") + elif event_type == "assistant/message": + for key, value in data.get("usage", {}).items(): + if isinstance(value, int): + usage[key] += value + for block in data.get("message", {}).get("content", []): + block_type = block.get("type") + if block_type == "reasoning": + reasoning.append(str(block.get("text", ""))) + elif block_type == "text" and block.get("text"): + visible.append(str(block["text"])) + elif block_type == "tool-call": + tools[str(block.get("name", ""))] += 1 + + price_data = json.loads(args.price.read_text(encoding="utf-8")) if args.price else None + cache_read = usage.get("cacheReadTokens", 0) + input_tokens = usage.get("inputTokens", 0) + output_tokens = usage.get("outputTokens", 0) + # DSH TokenUsage is disjoint: the DeepSeek adapter already subtracts cache + # hits from wire prompt_tokens before publishing inputTokens. + miss_tokens = input_tokens + cost = None + if price_data: + rates = price_data["applicable_cny_per_million"] + cost = round((cache_read * rates["cache_hit"] + miss_tokens * rates["cache_miss"] + output_tokens * rates["output"]) / 1_000_000, 6) + + benchmark_result = None + result_sha256 = None + if args.result: + result_sha256 = sha256_file(args.result) + benchmark_result = redact_object(json.loads(args.result.read_text(encoding="utf-8")), meta.get("cwd"), str(Path.home())) + + started = datetime.fromisoformat(meta["startedAt"].replace("Z", "+00:00")) + ended = datetime.fromisoformat(meta["endedAt"].replace("Z", "+00:00")) + public_visible = [redact_visible(text, meta.get("cwd"), str(Path.home())) for text in visible] + payload = { + "schema_version": 1, + "benchmark": args.benchmark, + "benchmark_commit": args.benchmark_commit, + "task_id": args.task_id, + "run_id": args.run_id, + "model": meta.get("model"), + "provider": meta.get("provider"), + "reasoning_effort": meta.get("reasoningEffort"), + "dsh_version": args.dsh_version, + "dsh_commit": args.dsh_commit, + "preset": meta.get("preset"), + "preset_hash": args.preset_hash, + "os_environment": "Windows 10 / PowerShell / DSH native Windows", + "started_at_utc": meta["startedAt"], + "ended_at_utc": meta["endedAt"], + "wall_time_seconds": round((ended - started).total_seconds(), 3), + "turn_duration_seconds": round((turn_end - turn_start) / 1000, 3) if isinstance(turn_start, int) and isinstance(turn_end, int) else None, + "benchmark_result": benchmark_result, + "benchmark_result_sha256": result_sha256, + "usage": dict(sorted(usage.items())), + "input_tokens": input_tokens, + "cache_read_tokens": cache_read, + "cache_miss_tokens": miss_tokens, + "output_tokens": output_tokens, + "reasoning_tokens": usage.get("reasoningTokens", 0), + "api_cost_cny": cost, + "api_cost_scope": "DSH completed agent assistant/message usage; official runtime rates; auxiliary title traffic excluded", + "usage_semantics": "input_tokens and cache_read_tokens are disjoint DSH counts", + "tool_call_count": sum(tools.values()), + "distinct_tools_used": sorted(tools), + "tool_breakdown": dict(sorted(tools.items())), + "visible_assistant_replies": public_visible, + "first_request_tool_catalog": catalogs[0] if catalogs else None, + "catalog_transition": catalogs, + "system_prompt_sha256_by_request": system_hashes, + "trajectory_fingerprints": marker_stats(reasoning), + "raw_evidence_sha256": sha256_file(args.session), + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(json.dumps({"run_id": args.run_id, "api_cost_cny": cost, "raw_evidence_sha256": payload["raw_evidence_sha256"]}, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/build_comparison.py b/experiments/deepseek-v4-pro-anchoring/scripts/build_comparison.py new file mode 100644 index 0000000..103d8fd --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/build_comparison.py @@ -0,0 +1,190 @@ +from __future__ import annotations + +import argparse +import json +from pathlib import Path +from typing import Any + + +RUNS = [ + { + "run_id": "P2-20260815-01-anchored", + "label": "DSH Anchored Standard", + "preset": "anchored-standard", + "evaluator_result_id": "20260815_122420", + "causal_role": "preregistered DSH mechanism ablation", + }, + { + "run_id": "P2-20260815-02-standard", + "label": "DSH Standard", + "preset": "standard", + "evaluator_result_id": "20260815_162840", + "causal_role": "preregistered DSH mechanism ablation", + }, + { + "run_id": "P2-20260815-03-minimal-full", + "label": "DSH Minimal-Full", + "preset": "minimal-full", + "evaluator_result_id": "20260815_164240", + "causal_role": "preregistered DSH mechanism ablation", + }, + { + "run_id": "P2-20260815-04b-opencode-replacement", + "label": "OpenCode replacement", + "preset": "opencode-direct-deepseek", + "evaluator_result_id": "20260815_194422", + "causal_role": "post-preregistered exploratory harness comparison", + }, +] + + +def read_json(path: Path) -> dict[str, Any]: + return json.loads(path.read_text(encoding="utf-8")) + + +def evaluation_counts(evaluator_root: Path, result_id: str) -> dict[str, str]: + result = evaluator_root / result_id + hidden = read_json(result / "hidden_summary.json") + esp = read_json(result / "espidf_static_summary.json") + return { + "hidden": f"{hidden.get('passed', 0)}/{hidden.get('tests_run', 0)}", + "esp_static": f"{esp.get('passed', 0)}/{esp.get('tests_run', 0)}", + } + + +def build_run(experiment_root: Path, evaluator_root: Path, spec: dict[str, str]) -> dict[str, Any]: + artifact = read_json(experiment_root / "artifacts" / "runs" / f"{spec['run_id']}.json") + benchmark = artifact.get("benchmark_result") or {} + reconciliation = artifact.get("cost_reconciliation") or {} + fingerprints = artifact.get("trajectory_fingerprints") or {} + catalog = artifact.get("first_request_tool_catalog") or {} + counts = evaluation_counts(evaluator_root, spec["evaluator_result_id"]) + visible_replies = artifact.get("visible_assistant_reply_count") + if visible_replies is None: + visible_replies = len(artifact.get("visible_assistant_replies") or []) + return { + "run_id": spec["run_id"], + "label": spec["label"], + "preset": spec["preset"], + "causal_role": spec["causal_role"], + "benchmark": artifact.get("benchmark", "project2-v4.1b"), + "benchmark_commit": artifact.get("benchmark_commit"), + "task_id": artifact.get("task_id"), + "model": artifact.get("model"), + "provider": artifact.get("provider"), + "reasoning_effort": artifact.get("reasoning_effort"), + "resolved_endpoint": artifact.get("resolved_endpoint") or artifact.get("resolved_api_endpoint"), + "ability": benchmark.get("ability_draft"), + "ship": benchmark.get("ship_draft"), + "class": benchmark.get("release_class_hint"), + "hidden": counts["hidden"], + "esp_static": counts["esp_static"], + "f9": benchmark.get("family_draft", {}).get("F9"), + "f9_mode": benchmark.get("f9_mode"), + "reasoning_blocks": fingerprints.get("reasoning_blocks"), + "we": fingerprints.get("we"), + "let_me": fingerprints.get("let_me"), + "lets": fingerprints.get("lets"), + "visible_assistant_replies": visible_replies, + "tool_calls": artifact.get("tool_call_count"), + "distinct_tools": artifact.get("distinct_tools_used", []), + "input_tokens": artifact.get("cache_miss_tokens"), + "cache_read_tokens": artifact.get("cache_read_tokens"), + "output_tokens": artifact.get("output_tokens"), + "reasoning_tokens": artifact.get("reasoning_tokens"), + "usage_cost_cny": artifact.get("api_cost_cny"), + "balance_delta_cny": reconciliation.get("account_balance_delta_cny"), + "balance_cost_difference_cny": reconciliation.get("account_minus_recomputed_cny"), + "wall_time_seconds": artifact.get("wall_time_seconds"), + "first_request_tool_count": catalog.get("tool_count", len(catalog.get("tool_names", []))), + "first_request_tool_names": catalog.get("tool_names", []), + "catalog_transition": artifact.get("catalog_transition"), + "raw_evidence_sha256": artifact.get("raw_evidence_sha256"), + "benchmark_result_sha256": artifact.get("benchmark_result_sha256"), + } + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--experiment-root", type=Path, required=True) + parser.add_argument("--evaluator-root", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + + runs = [build_run(args.experiment_root, args.evaluator_root, spec) for spec in RUNS] + runs_by_id = {run["run_id"]: run for run in runs} + partial = read_json(args.experiment_root / "artifacts" / "runs" / "P2-20260815-04-opencode-partial.json") + infrastructure = read_json(args.experiment_root / "artifacts" / "infrastructure-events.json") + partial_event = next( + event + for event in infrastructure["events"] + if event["run_id"] == "P2-20260815-04-opencode-partial" + ) + valid_account_cost = round(sum(float(run["balance_delta_cny"]) for run in runs), 6) + valid_usage_cost = round(sum(float(run["usage_cost_cny"]) for run in runs), 6) + partial_account_cost = float(partial["account_balance_delta_cny"]) + partial_usage_cost = float(partial["api_cost_cny"]) + anchored = runs_by_id["P2-20260815-01-anchored"]["ability"] + standard = runs_by_id["P2-20260815-02-standard"]["ability"] + minimal_full = runs_by_id["P2-20260815-03-minimal-full"]["ability"] + opencode = runs_by_id["P2-20260815-04b-opencode-replacement"]["ability"] + payload = { + "schema_version": 1, + "contributor": "@NineThoughts0521", + "evidence_role": "independent third-party replication; excluded from maintainer formal n", + "benchmark": "project2-v4.1b", + "benchmark_commit": "04255b55f16c4439e538239fb9783070c4165081", + "task_id": "project2-v4-broken-seed", + "model": "deepseek-v4-pro", + "reasoning_effort": "max", + "pricing_policy": "official live CNY rates at each run; no peak/off-peak assumption", + "runs": runs, + "preserved_partial": { + "run_id": partial["run_id"], + "status": partial["run_status"], + "benchmark_score_status": partial["benchmark_score_status"], + "reasoning_blocks": partial["trajectory_fingerprints"]["reasoning_blocks"], + "we": partial["trajectory_fingerprints"]["we"], + "let_me": partial["trajectory_fingerprints"]["let_me"], + "lets": partial["trajectory_fingerprints"]["lets"], + "visible_assistant_replies": partial["visible_assistant_reply_count"], + "export_tool_calls": partial["tool_call_count"], + "event_stream_completed_tool_events": partial_event["completed_tool_events"], + "final_stop_observed": partial_event["final_stop_observed"], + "usage_cost_cny": partial_usage_cost, + "balance_delta_cny": partial_account_cost, + "raw_evidence_sha256": partial["raw_evidence_sha256"], + "partial_event_stream_sha256": partial["partial_event_stream_sha256"], + "valid_for_harness_score_comparison": False, + }, + "descriptive_contrasts": { + "standard_minus_minimal_full_ability": round(standard - minimal_full, 6), + "anchored_minus_minimal_full_ability": round(anchored - minimal_full, 6), + "anchored_minus_standard_ability": round(anchored - standard, 6), + "opencode_minus_anchored_ability": round(opencode - anchored, 6), + "opencode_minus_standard_ability": round(opencode - standard, 6), + }, + "cost_totals_cny": { + "four_valid_runs_account_balance": valid_account_cost, + "four_valid_runs_usage_recomputed": valid_usage_cost, + "preserved_partial_account": partial_account_cost, + "preserved_partial_usage_recomputed": partial_usage_cost, + "all_account_balance_including_partial": round(valid_account_cost + partial_account_cost, 6), + "all_usage_recomputed_including_partial": round(valid_usage_cost + partial_usage_cost, 6), + "all_account_minus_usage_recomputed": round(valid_account_cost + partial_account_cost - valid_usage_cost - partial_usage_cost, 6), + }, + "limitations": [ + "The three DSH rows are single frozen-task observations; no significance test or new score is introduced.", + "OpenCode is exploratory and excluded from the DSH mechanism-ablation contrasts.", + "F9 is 3/6 skipped_env because the preregistered optional real ESP-IDF build was not run.", + "Trajectory wording is a fingerprint only, not a capability metric or causal evidence.", + ], + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(json.dumps({"runs": len(runs), "output": str(args.output)}, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/build_evidence_manifest.py b/experiments/deepseek-v4-pro-anchoring/scripts/build_evidence_manifest.py new file mode 100644 index 0000000..ccb16ec --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/build_evidence_manifest.py @@ -0,0 +1,99 @@ +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path +from typing import Any + + +PUBLIC_ARTIFACTS = [ + "README.md", + "RESULTS.md", + "mock-prompt.txt", + "preregistration.json", + "run-matrix.json", + "artifacts/environment-baseline.json", + "artifacts/schema-gate.json", + "artifacts/opencode-gate.json", + "artifacts/deepswe-gate.json", + "artifacts/balance-events.json", + "artifacts/infrastructure-events.json", + "artifacts/metadata-corrections.json", + "artifacts/comparison.json", + "artifacts/price-snapshot.json", + "artifacts/price-P2-20260815-01-anchored.json", + "artifacts/price-P2-20260815-02-standard.json", + "artifacts/price-P2-20260815-03-minimal-full.json", + "artifacts/price-P2-20260815-04-opencode.json", + "artifacts/price-P2-20260815-04b-opencode-replacement.json", + "artifacts/runs/P2-20260815-01-anchored-balance.json", + "artifacts/runs/P2-20260815-01-anchored.json", + "artifacts/runs/P2-20260815-02-standard-balance.json", + "artifacts/runs/P2-20260815-02-standard.json", + "artifacts/runs/P2-20260815-03-minimal-full-balance.json", + "artifacts/runs/P2-20260815-03-minimal-full.json", + "artifacts/runs/P2-20260815-04-opencode-partial-balance.json", + "artifacts/runs/P2-20260815-04-opencode-partial.json", + "artifacts/runs/P2-20260815-04b-opencode-replacement-balance.json", + "artifacts/runs/P2-20260815-04b-opencode-replacement.json", +] + +PRIVATE_EVIDENCE = [ + ("P2-20260815-01-anchored", "dsh_session_jsonl", "private/project2/P2-20260815-01-anchored/session.jsonl"), + ("P2-20260815-02-standard", "dsh_session_jsonl", "private/project2/P2-20260815-02-standard/session.jsonl"), + ("P2-20260815-03-minimal-full", "dsh_session_jsonl", "private/project2/P2-20260815-03-minimal-full/session.jsonl"), + ("P2-20260815-04-opencode-partial", "opencode_partial_session_export", "private/project2/P2-20260815-04-opencode/interrupted-session-export.json"), + ("P2-20260815-04-opencode-partial", "opencode_partial_event_stream", "private/project2/P2-20260815-04-opencode/events.jsonl"), + ("P2-20260815-04b-opencode-replacement", "opencode_session_export", "private/project2/P2-20260815-04b-opencode-replacement/session-export.json"), + ("P2-20260815-04b-opencode-replacement", "opencode_event_stream", "private/project2/P2-20260815-04b-opencode-replacement/events.jsonl"), + ("P2-20260815-04b-opencode-replacement", "opencode_run_meta", "private/project2/P2-20260815-04b-opencode-replacement/run-meta.json"), +] + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as stream: + for chunk in iter(lambda: stream.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def public_entry(root: Path, relative: str) -> dict[str, Any]: + path = root / relative + return {"path": relative.replace("\\", "/"), "bytes": path.stat().st_size, "sha256": sha256_file(path)} + + +def private_entry(root: Path, run_id: str, kind: str, relative: str) -> dict[str, Any]: + path = root / relative + return {"run_id": run_id, "evidence_kind": kind, "bytes": path.stat().st_size, "sha256": sha256_file(path)} + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--experiment-root", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + root = args.experiment_root.resolve() + payload = { + "schema_version": 1, + "contributor": "@NineThoughts0521", + "evidence_role": "independent third-party replication; excluded from maintainer formal n", + "candidate_prompt_sha256": "576103f9a5a7a619c0669674cf384a975b89390bb034c368a53a148251f2df84", + "public_artifacts": [public_entry(root, relative) for relative in PUBLIC_ARTIFACTS], + "private_raw_evidence": [private_entry(root, run_id, kind, relative) for run_id, kind, relative in PRIVATE_EVIDENCE], + "privacy": { + "private_paths_published": False, + "raw_reasoning_or_session_published": False, + "credentials_published": False, + "recalculation": "Recompute each listed SHA-256 from the ignored local raw file and compare it with this manifest and the corresponding public run artifact.", + }, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(json.dumps({"public": len(payload["public_artifacts"]), "private": len(payload["private_raw_evidence"])}, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/calculate_balance_delta.py b/experiments/deepseek-v4-pro-anchoring/scripts/calculate_balance_delta.py new file mode 100644 index 0000000..7801473 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/calculate_balance_delta.py @@ -0,0 +1,38 @@ +from __future__ import annotations + +import argparse +import json +from pathlib import Path + + +def cny_total(path: Path) -> float: + payload = json.loads(path.read_text(encoding="utf-8")) + return sum(float(item["total_balance"]) for item in payload.get("balance_infos", []) if item.get("currency") == "CNY") + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--t0", type=Path, required=True) + parser.add_argument("--t1", type=Path, required=True) + parser.add_argument("--run-id", required=True) + parser.add_argument("--generation", type=int, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + delta = round(cny_total(args.t0) - cny_total(args.t1), 6) + if delta < 0: + raise RuntimeError("T1 balance 高于 T0;该区间包含充值或余额调整,不作为单枪费用差") + result = { + "schema_version": 1, + "run_id": args.run_id, + "balance_generation": args.generation, + "balance_delta_cny": delta, + "crosses_recharge_event": False, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(json.dumps(result, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/capture_baseline.py b/experiments/deepseek-v4-pro-anchoring/scripts/capture_baseline.py new file mode 100644 index 0000000..17d0b6b --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/capture_baseline.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import platform +import subprocess +from datetime import datetime, timezone +from pathlib import Path + + +def tree_hash(root: Path) -> str: + digest = hashlib.sha256() + for path in sorted(item for item in root.rglob("*") if item.is_file()): + relative = path.relative_to(root).as_posix().encode("utf-8") + digest.update(len(relative).to_bytes(8, "big")) + digest.update(relative) + data = path.read_bytes() + digest.update(len(data).to_bytes(8, "big")) + digest.update(data) + return digest.hexdigest() + + +def command(*args: str, cwd: Path | None = None) -> str: + return subprocess.check_output(args, cwd=cwd, text=True, encoding="utf-8").strip() + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--repository", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + repository = args.repository.resolve() + dsh_home = Path(os.environ.get("DSH_HOME", Path.home() / ".dsh")) + dsh_package = dsh_home / "profiles" / "node_modules" / "@deepseek-ai" / "dsh" + package = json.loads((dsh_package / "package.json").read_text(encoding="utf-8")) + preset_roots = { + "standard": dsh_package / "config" / "agent-presets" / "standard", + "minimal-full": repository / "tools" / "deepseek-harness-presets" / "minimal-full", + "anchored-standard": repository / "tools" / "deepseek-harness-presets" / "anchored-standard", + } + status_paths = [] + for line in command("git", "status", "--porcelain=v1", cwd=repository).splitlines(): + if line: + status_paths.append(line[3:].replace("\\", "/")) + payload = { + "schema_version": 1, + "captured_at_utc": datetime.now(timezone.utc).isoformat(), + "modeltest_head": command("git", "rev-parse", "HEAD", cwd=repository), + "modeltest_status_paths": status_paths, + "dsh_package": package.get("name"), + "dsh_version": package.get("version"), + "dsh_source_commit_preregistered": "47f943859bef60e4160492346772ded9b24f765a", + "preset_hash_algorithm": "SHA-256 over sorted (UTF-8 relative path length/path, file length/bytes)", + "preset_hashes": {name: tree_hash(root) for name, root in preset_roots.items()}, + "environment": { + "platform": platform.platform(), + "python": platform.python_version(), + "node": command("node", "--version"), + "powershell": os.environ.get("POWERSHELL_DISTRIBUTION_CHANNEL", "Windows PowerShell host"), + "processor_architecture": platform.machine(), + }, + } + if payload["dsh_version"] != "0.1.0-rc.6": + raise RuntimeError(f"DSH 版本漂移:{payload['dsh_version']}") + if payload["modeltest_head"] != "04255b55f16c4439e538239fb9783070c4165081": + raise RuntimeError(f"modeltest HEAD 漂移:{payload['modeltest_head']}") + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(json.dumps({"dsh_version": payload["dsh_version"], "preset_hashes": payload["preset_hashes"]}, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/collect_balance.py b/experiments/deepseek-v4-pro-anchoring/scripts/collect_balance.py new file mode 100644 index 0000000..94a0016 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/collect_balance.py @@ -0,0 +1,57 @@ +from __future__ import annotations + +import argparse +import hashlib +import json +import os +import urllib.request +from datetime import datetime, timezone +from pathlib import Path + + +URL = "https://api.deepseek.com/user/balance" + + +def credential() -> str: + value = os.environ.get("DEEPSEEK_API_KEY") + if value: + return value + path = Path(os.environ.get("DSH_HOME", Path.home() / ".dsh")) / ".credentials.yaml" + for line in path.read_text(encoding="utf-8").splitlines(): + if line.startswith("DEEPSEEK_API_KEY:"): + return line.split(":", 1)[1].strip().strip("'\"") + raise RuntimeError("未找到 DSH DeepSeek credential") + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--private-json", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + parser.add_argument("--required-cny", type=float, default=50.0) + args = parser.parse_args() + request = urllib.request.Request(URL, headers={"Authorization": f"Bearer {credential()}", "User-Agent": "modeltest-anchoring-prereg/1"}) + with urllib.request.urlopen(request, timeout=30) as response: + raw = response.read() + payload = json.loads(raw.decode("utf-8")) + cny_total = sum(float(item["total_balance"]) for item in payload.get("balance_infos", []) if item.get("currency") == "CNY") + result = { + "schema_version": 1, + "source_url": URL, + "retrieved_at_utc": datetime.now(timezone.utc).isoformat(), + "raw_sha256": hashlib.sha256(raw).hexdigest(), + "is_available": bool(payload.get("is_available")), + "cny_balance_at_least_required": cny_total >= args.required_cny, + "required_cny": args.required_cny, + } + args.private_json.parent.mkdir(parents=True, exist_ok=True) + args.private_json.write_bytes(raw) + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(json.dumps(result, ensure_ascii=False)) + if not result["is_available"] or not result["cny_balance_at_least_required"]: + return 2 + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/collect_price.py b/experiments/deepseek-v4-pro-anchoring/scripts/collect_price.py new file mode 100644 index 0000000..83b1b6f --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/collect_price.py @@ -0,0 +1,61 @@ +from __future__ import annotations + +import argparse +import hashlib +import json +import re +import urllib.request +from datetime import datetime, timezone +from pathlib import Path +from zoneinfo import ZoneInfo + + +URL = "https://api-docs.deepseek.com/zh-cn/quick_start/pricing" + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--private-html", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + request = urllib.request.Request(URL, headers={"User-Agent": "modeltest-anchoring-prereg/1"}) + with urllib.request.urlopen(request, timeout=30) as response: + raw = response.read() + text = raw.decode("utf-8") + compact = re.sub(r"\s+", " ", re.sub(r"<[^>]+>", " ", text)) + required = ["0.025", "3元", "6元"] + missing = [item for item in required if item not in compact] + if missing: + raise RuntimeError(f"官方页面缺少预期当前价格字段:{missing}") + future = all(item in compact for item in ["0.15元", "4.5元", "13.5元", "0.30元", "9.0元", "27.0元"]) + args.private_html.parent.mkdir(parents=True, exist_ok=True) + args.private_html.write_bytes(raw) + retrieved_at = datetime.now(timezone.utc) + future_effective_at = datetime(2026, 8, 17, 0, 0, tzinfo=ZoneInfo("Asia/Shanghai")) + current_rates = {"cache_hit": 0.025, "cache_miss": 3.0, "output": 6.0} + future_rates = { + "off_peak": {"cache_hit": 0.15, "cache_miss": 4.5, "output": 13.5}, + "peak": {"cache_hit": 0.30, "cache_miss": 9.0, "output": 27.0}, + } + if retrieved_at >= future_effective_at.astimezone(timezone.utc): + raise RuntimeError("官方页面的未来价格已生效;需按页面实时峰谷时段重建适用价格后再发起付费请求") + result = { + "schema_version": 1, + "source_url": URL, + "retrieved_at_utc": retrieved_at.isoformat(), + "raw_sha256": hashlib.sha256(raw).hexdigest(), + "current_cny_per_million": current_rates, + "applicable_cny_per_million": current_rates, + "applicable_window": "before_2026-08-17T00:00:00+08:00", + "documented_future_effective_at": future_effective_at.isoformat(), + "documented_future_cny_per_million": future_rates if future else None, + "documented_future_peak_offpeak_present": future, + } + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(json.dumps(result, ensure_ascii=False)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/dsh_session_driver.mjs b/experiments/deepseek-v4-pro-anchoring/scripts/dsh_session_driver.mjs new file mode 100644 index 0000000..4c2c51d --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/dsh_session_driver.mjs @@ -0,0 +1,190 @@ +import { randomUUID } from 'node:crypto'; +import { spawn } from 'node:child_process'; +import { mkdir, readFile, writeFile } from 'node:fs/promises'; +import net from 'node:net'; +import os from 'node:os'; +import path from 'node:path'; + +function parseArgs(argv) { + const result = {}; + for (let i = 0; i < argv.length; i += 2) { + const key = argv[i]; + if (!key?.startsWith('--') || argv[i + 1] === undefined) { + throw new Error(`参数格式错误:${key ?? ''}`); + } + result[key.slice(2)] = argv[i + 1]; + } + return result; +} + +function required(args, key) { + const value = args[key]; + if (!value) throw new Error(`缺少 --${key}`); + return value; +} + +async function freePort() { + const server = net.createServer(); + await new Promise((resolve, reject) => { + server.once('error', reject); + server.listen(0, '127.0.0.1', resolve); + }); + const address = server.address(); + const port = typeof address === 'object' && address ? address.port : undefined; + await new Promise((resolve) => server.close(resolve)); + if (!port) throw new Error('未取得本地空闲端口'); + return port; +} + +async function rpc(baseUrl, method, payload, timeoutMs = 30_000) { + const rpcId = randomUUID(); + const response = await fetch(`${baseUrl}/api/${method}`, { + method: 'POST', + headers: { 'content-type': 'application/json' }, + body: JSON.stringify({ type: 'client-request', rpcId, method, payload }), + signal: AbortSignal.timeout(timeoutMs), + }); + if (!response.ok) throw new Error(`${method} HTTP ${response.status}`); + const envelope = await response.json(); + if (envelope.rpcId !== rpcId) throw new Error(`${method} rpcId 不匹配`); + if (!envelope.result?.ok) { + throw new Error(`${method} 失败:${JSON.stringify(envelope.result?.error ?? envelope.result)}`); + } + return envelope.result.value; +} + +async function waitReady(baseUrl, child, timeoutMs) { + const deadline = Date.now() + timeoutMs; + let lastError; + while (Date.now() < deadline) { + if (child.exitCode !== null) throw new Error(`DSH Web 提前退出:${child.exitCode}`); + try { + await rpc(baseUrl, 'host.describe', {}, 2_000); + return; + } catch (error) { + lastError = error; + await new Promise((resolve) => setTimeout(resolve, 250)); + } + } + throw new Error(`DSH Web 启动超时:${lastError?.message ?? 'unknown'}`); +} + +async function waitIdle(baseUrl, sessionId, timeoutMs) { + const deadline = Date.now() + timeoutMs; + let observedRunning = false; + let observedNonBlank = false; + while (Date.now() < deadline) { + const { items } = await rpc(baseUrl, 'session.list', {}, 15_000); + const current = items.find((item) => item.sessionId === sessionId); + if (!current) throw new Error(`session.list 缺少 ${sessionId}`); + observedRunning ||= current.running; + observedNonBlank ||= !current.blank; + if (observedNonBlank && !current.running) return current; + await new Promise((resolve) => setTimeout(resolve, 1_000)); + } + throw new Error(`DSH session ${sessionId} 超过 ${timeoutMs / 1000} 秒仍未 idle`); +} + +async function readAllHistory(baseUrl, sessionId) { + const bySeq = new Map(); + let beforeSeq; + for (let page = 0; page < 1_000; page += 1) { + const payload = { sessionId, maxMessages: 1_000 }; + if (beforeSeq !== undefined) payload.beforeSeq = beforeSeq; + const value = await rpc(baseUrl, 'session.history', payload, 30_000); + for (const entry of value.events) bySeq.set(entry.event.seq, entry.event); + if (!value.hasMore || value.events.length === 0) break; + beforeSeq = Math.min(...value.events.map((entry) => entry.event.seq)); + } + return [...bySeq.values()].sort((a, b) => a.seq - b.seq); +} + +async function terminate(child) { + if (child.exitCode !== null) return; + child.kill('SIGTERM'); + await Promise.race([ + new Promise((resolve) => child.once('exit', resolve)), + new Promise((resolve) => setTimeout(resolve, 5_000)), + ]); + if (child.exitCode === null) child.kill('SIGKILL'); +} + +async function main() { + const args = parseArgs(process.argv.slice(2)); + const cwd = path.resolve(required(args, 'cwd')); + const preset = required(args, 'preset'); + const promptFile = path.resolve(required(args, 'prompt-file')); + const privateOutput = path.resolve(required(args, 'private-output')); + const timeoutSeconds = Number(args['timeout-seconds'] ?? '4500'); + const provider = args.provider ?? 'deepseek-official'; + const model = args.model ?? 'deepseek-v4-pro'; + const reasoningEffort = args['reasoning-effort'] ?? 'max'; + const dshHome = process.env.DSH_HOME ?? path.join(os.homedir(), '.dsh'); + const dshBin = process.env.DSH_BIN ?? path.join(dshHome, 'profiles', 'node_modules', '@deepseek-ai', 'dsh', 'lib', 'bin.js'); + const port = await freePort(); + const baseUrl = `http://127.0.0.1:${port}`; + await mkdir(privateOutput, { recursive: true }); + + const stdout = []; + const stderr = []; + const childArgs = [dshBin, '--profile', 'web']; + if (args.patch) childArgs.push('--patch', path.resolve(args.patch)); + childArgs.push('--host', '127.0.0.1', '--port', String(port)); + const child = spawn(process.execPath, childArgs, { + cwd, + env: { ...process.env, DSH_HOME: dshHome, DSH_TELEMETRY_DISABLED: '1' }, + windowsHide: true, + stdio: ['ignore', 'pipe', 'pipe'], + }); + child.stdout.on('data', (chunk) => stdout.push(chunk)); + child.stderr.on('data', (chunk) => stderr.push(chunk)); + + const startedAt = new Date().toISOString(); + let sessionId; + try { + await waitReady(baseUrl, child, 60_000); + const presets = await rpc(baseUrl, 'agentPreset.list', {}, 30_000); + if (!presets.presets.some((entry) => entry.id === preset)) { + throw new Error(`DSH 未发现 preset ${preset}`); + } + const created = await rpc(baseUrl, 'session.create', { cwd, agentPreset: preset }, 30_000); + sessionId = created.sessionId; + await rpc(baseUrl, 'session.selectModel', { + sessionId, + provider, + model, + reasoningEffort, + }, 30_000); + const prompt = await readFile(promptFile, 'utf8'); + await rpc(baseUrl, 'session.prompt', { + sessionId, + mode: 'queue', + content: [{ type: 'text', text: prompt }], + clientTimeZone: Intl.DateTimeFormat().resolvedOptions().timeZone, + }, 30_000); + const summary = await waitIdle(baseUrl, sessionId, timeoutSeconds * 1_000); + const events = await readAllHistory(baseUrl, sessionId); + await writeFile(path.join(privateOutput, 'session.jsonl'), `${events.map((event) => JSON.stringify(event)).join('\n')}\n`, 'utf8'); + await writeFile(path.join(privateOutput, 'session-meta.json'), `${JSON.stringify({ + sessionId, + preset, + provider, + model, + reasoningEffort, + cwd, + startedAt, + endedAt: new Date().toISOString(), + summary, + }, null, 2)}\n`, 'utf8'); + process.stdout.write(`${JSON.stringify({ sessionId, eventCount: events.length, privateOutput })}\n`); + } finally { + await terminate(child); + await writeFile(path.join(privateOutput, 'dsh-web.stdout.log'), Buffer.concat(stdout), 'utf8'); + await writeFile(path.join(privateOutput, 'dsh-web.stderr.log'), Buffer.concat(stderr), 'utf8'); + } +} + +main().catch((error) => { + process.stderr.write(`${error.stack ?? error.message}\n`); + process.exitCode = 1; +}); diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/extract_candidate_prompt.py b/experiments/deepseek-v4-pro-anchoring/scripts/extract_candidate_prompt.py new file mode 100644 index 0000000..5c0f1f9 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/extract_candidate_prompt.py @@ -0,0 +1,26 @@ +from __future__ import annotations + +import argparse +import hashlib +import re +from pathlib import Path + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--source", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + source = args.source.read_text(encoding="utf-8") + matches = re.findall(r"```text\s*\n(.*?)\n```", source, flags=re.DOTALL) + if len(matches) != 1: + raise RuntimeError(f"候选正文代码块数量应为 1,实际为 {len(matches)}") + prompt = matches[0].replace("\r\n", "\n").rstrip() + "\n" + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(prompt, encoding="utf-8", newline="\n") + print(hashlib.sha256(prompt.encode("utf-8")).hexdigest()) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/launch_detached.py b/experiments/deepseek-v4-pro-anchoring/scripts/launch_detached.py new file mode 100644 index 0000000..cf23e50 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/launch_detached.py @@ -0,0 +1,28 @@ +from __future__ import annotations + +import argparse +import subprocess +import sys +from pathlib import Path + + +def main() -> int: + parser = argparse.ArgumentParser(add_help=False) + parser.add_argument("--runner", type=Path, required=True) + parser.add_argument("--stdout-log", type=Path, required=True) + parser.add_argument("--stderr-log", type=Path, required=True) + args, runner_args = parser.parse_known_args() + args.stdout_log.parent.mkdir(parents=True, exist_ok=True) + args.stderr_log.parent.mkdir(parents=True, exist_ok=True) + with args.stdout_log.open("wb") as stdout, args.stderr_log.open("wb") as stderr: + process = subprocess.Popen( + [sys.executable, str(args.runner), *runner_args], + stdout=stdout, + stderr=stderr, + close_fds=True, + ) + return process.wait() + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/mock_deepseek_server.mjs b/experiments/deepseek-v4-pro-anchoring/scripts/mock_deepseek_server.mjs new file mode 100644 index 0000000..0a31ccb --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/mock_deepseek_server.mjs @@ -0,0 +1,59 @@ +import { createServer } from 'node:http'; +import { mkdir, writeFile } from 'node:fs/promises'; +import path from 'node:path'; + +const port = Number(process.env.MOCK_PORT ?? '32190'); +const output = path.resolve(process.env.MOCK_OUTPUT ?? 'mock-requests.json'); +const requests = []; + +function sse(response, chunks) { + response.writeHead(200, { + 'content-type': 'text/event-stream', + 'cache-control': 'no-cache', + connection: 'keep-alive', + }); + for (const chunk of chunks) response.write(`data: ${JSON.stringify(chunk)}\n\n`); + response.end('data: [DONE]\n\n'); +} + +const server = createServer(async (request, response) => { + if (request.method !== 'POST' || !request.url?.endsWith('/chat/completions')) { + response.writeHead(404).end(); + return; + } + const body = []; + for await (const chunk of request) body.push(chunk); + const parsed = JSON.parse(Buffer.concat(body).toString('utf8')); + requests.push(parsed); + await mkdir(path.dirname(output), { recursive: true }); + await writeFile(output, `${JSON.stringify(requests, null, 2)}\n`, 'utf8'); + const id = `mock-${requests.length}`; + const base = { id, object: 'chat.completion.chunk', created: 0, model: parsed.model }; + if (!Array.isArray(parsed.tools) || parsed.tools.length === 0) { + sse(response, [ + { ...base, choices: [{ index: 0, delta: { role: 'assistant', content: 'Mock schema gate' }, finish_reason: null }] }, + { ...base, choices: [{ index: 0, delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 5, completion_tokens: 3, prompt_cache_hit_tokens: 0, prompt_cache_miss_tokens: 5, completion_tokens_details: { reasoning_tokens: 0 } } }, + ]); + return; + } + const hasToolResult = parsed.messages.some((message) => message.role === 'tool'); + if (!hasToolResult) { + const shell = parsed.tools.find((tool) => ['pwsh', 'bash'].includes(tool.function?.name))?.function?.name; + if (!shell) throw new Error('mock agent request 缺少 shell tool'); + const command = shell === 'pwsh' ? 'Get-Location' : 'pwd'; + sse(response, [ + { ...base, choices: [{ index: 0, delta: { role: 'assistant', reasoning_content: 'Inspect the workspace first.' }, finish_reason: null }] }, + { ...base, choices: [{ index: 0, delta: { tool_calls: [{ index: 0, id: 'mock_call_1', type: 'function', function: { name: shell, arguments: JSON.stringify({ command, description: 'Inspect current working directory' }) } }] }, finish_reason: null }] }, + { ...base, choices: [{ index: 0, delta: {}, finish_reason: 'tool_calls' }], usage: { prompt_tokens: 10, completion_tokens: 10, prompt_cache_hit_tokens: 0, prompt_cache_miss_tokens: 10, completion_tokens_details: { reasoning_tokens: 4 } } }, + ]); + } else { + sse(response, [ + { ...base, choices: [{ index: 0, delta: { role: 'assistant', reasoning_content: 'The schema transition is complete.' }, finish_reason: null }] }, + { ...base, choices: [{ index: 0, delta: { content: 'Mock validation complete.' }, finish_reason: null }] }, + { ...base, choices: [{ index: 0, delta: {}, finish_reason: 'stop' }], usage: { prompt_tokens: 20, completion_tokens: 8, prompt_cache_hit_tokens: 5, prompt_cache_miss_tokens: 15, completion_tokens_details: { reasoning_tokens: 3 } } }, + ]); + } +}); + +server.listen(port, '127.0.0.1', () => process.stdout.write(`mock-listening:${port}\n`)); +for (const signal of ['SIGINT', 'SIGTERM']) process.on(signal, () => server.close(() => process.exit(0))); diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/run_mock_gate.ps1 b/experiments/deepseek-v4-pro-anchoring/scripts/run_mock_gate.ps1 new file mode 100644 index 0000000..3a43332 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/run_mock_gate.ps1 @@ -0,0 +1,99 @@ +param( + [string]$RepositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..')).Path +) + +$ErrorActionPreference = 'Stop' +$experimentRoot = Resolve-Path (Join-Path $PSScriptRoot '..') +$dshHome = if ($env:DSH_HOME) { $env:DSH_HOME } else { Join-Path $HOME '.dsh' } +$presetHome = Join-Path $dshHome '.agent-presets' +$privateRoot = Join-Path $experimentRoot 'private\mock' +$publicOutput = Join-Path $experimentRoot 'artifacts\schema-gate.json' +$promptFile = Join-Path $experimentRoot 'mock-prompt.txt' +$driver = Join-Path $PSScriptRoot 'dsh_session_driver.mjs' +$server = Join-Path $PSScriptRoot 'mock_deepseek_server.mjs' +$validator = Join-Path $PSScriptRoot 'validate_presets.py' + +function Install-Preset([string]$Name) { + $source = Join-Path $RepositoryRoot "tools\deepseek-harness-presets\$Name" + $target = Join-Path $presetHome $Name + if (Test-Path $target) { + $sourceHashes = Get-ChildItem -Recurse -File $source | ForEach-Object { + [pscustomobject]@{ Relative = [IO.Path]::GetRelativePath($source, $_.FullName); Hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash } + } + $targetHashes = Get-ChildItem -Recurse -File $target | ForEach-Object { + [pscustomobject]@{ Relative = [IO.Path]::GetRelativePath($target, $_.FullName); Hash = (Get-FileHash $_.FullName -Algorithm SHA256).Hash } + } + if (($sourceHashes | ConvertTo-Json -Compress) -ne ($targetHashes | ConvertTo-Json -Compress)) { + throw "DSH preset 目标已存在且内容不同:$target" + } + return + } + New-Item -ItemType Directory -Force -Path $target | Out-Null + Copy-Item -Path (Join-Path $source '*') -Destination $target -Recurse +} + +function Wait-Tcp([int]$Port, [Diagnostics.Process]$Process) { + $deadline = [DateTimeOffset]::UtcNow.AddSeconds(15) + while ([DateTimeOffset]::UtcNow -lt $deadline) { + if ($Process.HasExited) { throw "mock server 提前退出:$($Process.ExitCode)" } + $client = [Net.Sockets.TcpClient]::new() + try { + $client.Connect('127.0.0.1', $Port) + return + } catch { + Start-Sleep -Milliseconds 200 + } finally { + $client.Dispose() + } + } + throw "mock server 端口 $Port 启动超时" +} + +Install-Preset 'anchored-standard' +Install-Preset 'minimal-full' +New-Item -ItemType Directory -Force -Path $privateRoot,(Split-Path $publicOutput) | Out-Null + +$oldBaseUrl = $env:DEEPSEEK_BASE_URL +$oldApiKey = $env:DEEPSEEK_API_KEY +$oldMockPort = $env:MOCK_PORT +$oldMockOutput = $env:MOCK_OUTPUT +try { + $requestFiles = @{} + $runs = @( + @{ Name = 'standard'; Port = 32190 }, + @{ Name = 'minimal-full'; Port = 32191 }, + @{ Name = 'anchored-standard'; Port = 32192 } + ) + foreach ($run in $runs) { + $name = $run.Name + $port = $run.Port + $runDir = Join-Path $privateRoot $name + New-Item -ItemType Directory -Force -Path $runDir | Out-Null + $requestFile = Join-Path $runDir 'requests.json' + $env:MOCK_PORT = [string]$port + $env:MOCK_OUTPUT = $requestFile + $env:DEEPSEEK_BASE_URL = "http://127.0.0.1:$port" + $env:DEEPSEEK_API_KEY = 'mock-key' + $stdout = Join-Path $runDir 'mock.stdout.log' + $stderr = Join-Path $runDir 'mock.stderr.log' + $process = Start-Process -FilePath (Get-Command node).Source -ArgumentList @($server) -WindowStyle Hidden -RedirectStandardOutput $stdout -RedirectStandardError $stderr -PassThru + try { + Wait-Tcp -Port $port -Process $process + & node $driver --cwd $RepositoryRoot --preset $name --prompt-file $promptFile --private-output $runDir --timeout-seconds 120 + if ($LASTEXITCODE -ne 0) { throw "DSH mock run $name 失败,exit=$LASTEXITCODE" } + $requestFiles[$name] = $requestFile + } finally { + if (-not $process.HasExited) { Stop-Process -Id $process.Id -Force } + $process.WaitForExit() + } + } + & python $validator --standard $requestFiles['standard'] --minimal-full $requestFiles['minimal-full'] --anchored $requestFiles['anchored-standard'] --output $publicOutput + if ($LASTEXITCODE -ne 0) { throw "preset semantic gate 失败,exit=$LASTEXITCODE" } +} finally { + $env:DEEPSEEK_BASE_URL = $oldBaseUrl + $env:DEEPSEEK_API_KEY = $oldApiKey + $env:MOCK_PORT = $oldMockPort + $env:MOCK_OUTPUT = $oldMockOutput +} + +Write-Output "schema gate: $publicOutput" diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/run_opencode_agent.py b/experiments/deepseek-v4-pro-anchoring/scripts/run_opencode_agent.py new file mode 100644 index 0000000..63cb980 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/run_opencode_agent.py @@ -0,0 +1,325 @@ +from __future__ import annotations + +import argparse +import json +import os +import shutil +import subprocess +import time +import urllib.request +from datetime import datetime, timezone +from pathlib import Path +from typing import Any + + +BALANCE_URL = "https://api.deepseek.com/user/balance" +MODEL_ID = "deepseek/deepseek-v4-pro" +ENDPOINT = "https://api.deepseek.com" + + +def utc_now() -> str: + return datetime.now(timezone.utc).isoformat() + + +def credential() -> str: + value = os.environ.get("DEEPSEEK_API_KEY") + if value: + return value + path = Path(os.environ.get("DSH_HOME", Path.home() / ".dsh")) / ".credentials.yaml" + for line in path.read_text(encoding="utf-8").splitlines(): + if line.startswith("DEEPSEEK_API_KEY:"): + return line.split(":", 1)[1].strip().strip("'\"") + raise RuntimeError("未找到 DSH DeepSeek credential") + + +def cny_balance(api_key: str) -> tuple[float, bytes]: + request = urllib.request.Request( + BALANCE_URL, + headers={ + "Authorization": f"Bearer {api_key}", + "User-Agent": "modeltest-opencode-project2/1", + }, + ) + with urllib.request.urlopen(request, timeout=30) as response: + raw = response.read() + payload = json.loads(raw.decode("utf-8")) + if not payload.get("is_available"): + raise RuntimeError("DeepSeek balance endpoint 报告账户不可用") + total = sum( + float(item["total_balance"]) + for item in payload.get("balance_infos", []) + if item.get("currency") == "CNY" + ) + return total, raw + + +def total_from_snapshot(path: Path) -> float: + payload = json.loads(path.read_text(encoding="utf-8")) + return sum( + float(item["total_balance"]) + for item in payload.get("balance_infos", []) + if item.get("currency") == "CNY" + ) + + +def terminate_tree(process: subprocess.Popen[bytes]) -> None: + if process.poll() is not None: + return + if os.name == "nt": + subprocess.run( + ["taskkill", "/PID", str(process.pid), "/T", "/F"], + stdout=subprocess.DEVNULL, + stderr=subprocess.DEVNULL, + check=False, + ) + else: + process.terminate() + try: + process.wait(timeout=15) + except subprocess.TimeoutExpired: + process.kill() + + +def parse_session_id(events: Path) -> str: + for line in events.read_text(encoding="utf-8", errors="replace").splitlines(): + try: + event = json.loads(line) + except json.JSONDecodeError: + continue + session_id = event.get("sessionID") + if isinstance(session_id, str) and session_id: + return session_id + raise RuntimeError("OpenCode JSON event stream 中缺少 sessionID") + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--workspace", type=Path, required=True) + parser.add_argument("--prompt", type=Path, required=True) + parser.add_argument("--private-run", type=Path, required=True) + parser.add_argument("--models-catalog", type=Path, required=True) + parser.add_argument("--t0-balance", type=Path, required=True) + parser.add_argument("--run-id", required=True) + parser.add_argument("--timeout-seconds", type=int, default=4500) + parser.add_argument("--cost-stop-cny", type=float, default=3.75) + parser.add_argument("--balance-poll-seconds", type=int, default=60) + args = parser.parse_args() + + args.workspace = args.workspace.resolve() + args.prompt = args.prompt.resolve() + args.private_run = args.private_run.resolve() + args.models_catalog = args.models_catalog.resolve() + args.t0_balance = args.t0_balance.resolve() + args.private_run.mkdir(parents=True, exist_ok=False) + + api_key = credential() + t0 = total_from_snapshot(args.t0_balance) + isolation = args.private_run / "isolation" + isolation.mkdir(parents=True) + events_path = args.private_run / "events.jsonl" + stderr_path = args.private_run / "stderr.log" + export_path = args.private_run / "session-export.json" + balance_log_path = args.private_run / "balance-monitor.json" + meta_path = args.private_run / "run-meta.json" + + executable = shutil.which("opencode.exe") or shutil.which("opencode.cmd") or shutil.which("opencode") + if not executable: + raise RuntimeError("PATH 中未找到 OpenCode executable") + + env = os.environ.copy() + env.update( + { + "DEEPSEEK_API_KEY": api_key, + "OPENCODE_TEST_HOME": str(isolation / "home"), + "XDG_CONFIG_HOME": str(isolation / "config"), + "XDG_DATA_HOME": str(isolation / "data"), + "XDG_STATE_HOME": str(isolation / "state"), + "XDG_CACHE_HOME": str(isolation / "cache"), + "OPENCODE_AUTH_CONTENT": "{}", + "OPENCODE_CONFIG_CONTENT": json.dumps( + { + "share": "disabled", + "autoupdate": False, + "provider": {"deepseek": {"options": {"baseURL": ENDPOINT}}}, + }, + separators=(",", ":"), + ), + "OPENCODE_MODELS_PATH": str(args.models_catalog), + "OPENCODE_DISABLE_MODELS_FETCH": "1", + "OPENCODE_DISABLE_AUTOUPDATE": "1", + "OPENCODE_DISABLE_LSP_DOWNLOAD": "1", + "OPENCODE_DISABLE_DEFAULT_PLUGINS": "1", + "OPENCODE_DISABLE_EXTERNAL_SKILLS": "1", + "OPENCODE_DISABLE_CLAUDE_CODE": "1", + "OPENCODE_PURE": "1", + } + ) + + command = [ + executable, + "run", + "--pure", + "--model", + MODEL_ID, + "--variant", + "max", + "--thinking", + "--format", + "json", + "--dir", + str(args.workspace), + "--agent", + "build", + "--auto", + "--title", + args.run_id, + ] + started = utc_now() + started_monotonic = time.monotonic() + observations: list[dict[str, Any]] = [] + stop_reason: str | None = None + last_balance_poll = -float("inf") + last_progress = -float("inf") + + print( + json.dumps( + { + "stage": "opencode_agent_start", + "run_id": args.run_id, + "model": MODEL_ID, + "reasoning_variant": "max", + "endpoint": ENDPOINT, + "timeout_seconds": args.timeout_seconds, + "cost_stop_cny": args.cost_stop_cny, + }, + ensure_ascii=False, + ), + flush=True, + ) + + with ( + args.prompt.open("rb") as prompt_stream, + events_path.open("wb") as events_stream, + stderr_path.open("wb") as stderr_stream, + ): + process = subprocess.Popen( + command, + cwd=args.workspace, + env=env, + stdin=prompt_stream, + stdout=events_stream, + stderr=stderr_stream, + ) + while process.poll() is None: + elapsed = time.monotonic() - started_monotonic + if elapsed >= args.timeout_seconds: + stop_reason = "wall_timeout" + terminate_tree(process) + break + if elapsed - last_balance_poll >= args.balance_poll_seconds: + last_balance_poll = elapsed + try: + current, raw = cny_balance(api_key) + observation_path = args.private_run / f"balance-{len(observations):03d}.json" + observation_path.write_bytes(raw) + delta = round(t0 - current, 6) + observations.append( + { + "retrieved_at_utc": utc_now(), + "observed_delta_cny": delta, + "private_snapshot": observation_path.name, + } + ) + if delta >= args.cost_stop_cny: + stop_reason = "observed_cost_stop" + terminate_tree(process) + break + except Exception as error: + observations.append( + { + "retrieved_at_utc": utc_now(), + "balance_poll_error": type(error).__name__, + } + ) + if elapsed - last_progress >= 30: + last_progress = elapsed + recent_delta = observations[-1].get("observed_delta_cny") if observations else None + print( + json.dumps( + { + "stage": "opencode_agent_running", + "elapsed_seconds": round(elapsed, 1), + "latest_observed_delta_cny": recent_delta, + }, + ensure_ascii=False, + ), + flush=True, + ) + time.sleep(5) + exit_code = process.wait() + + ended = utc_now() + duration = round(time.monotonic() - started_monotonic, 3) + balance_log_path.write_text(json.dumps(observations, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + session_id = parse_session_id(events_path) + + export_command = [executable, "export", session_id] + with export_path.open("wb") as export_stream, stderr_path.open("ab") as stderr_stream: + export_result = subprocess.run( + export_command, + cwd=args.workspace, + env=env, + stdout=export_stream, + stderr=stderr_stream, + timeout=120, + check=False, + ) + + meta = { + "schema_version": 1, + "run_id": args.run_id, + "started_at_utc": started, + "ended_at_utc": ended, + "wall_time_seconds": duration, + "opencode_exit_code": exit_code, + "export_exit_code": export_result.returncode, + "session_id": session_id, + "model": MODEL_ID, + "provider": "deepseek", + "resolved_endpoint": ENDPOINT, + "reasoning_variant": "max", + "thinking_export_enabled": True, + "agent": "build", + "pure": True, + "auto_approve": True, + "cost_stop_cny": args.cost_stop_cny, + "stop_reason": stop_reason, + "workspace": str(args.workspace), + "models_catalog": str(args.models_catalog), + "events": str(events_path), + "session_export": str(export_path), + "balance_monitor": str(balance_log_path), + } + meta_path.write_text(json.dumps(meta, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print( + json.dumps( + { + "stage": "opencode_agent_complete", + "run_id": args.run_id, + "exit_code": exit_code, + "export_exit_code": export_result.returncode, + "wall_time_seconds": duration, + "stop_reason": stop_reason, + "session_id_recorded_private": True, + }, + ensure_ascii=False, + ), + flush=True, + ) + if export_result.returncode != 0: + return 3 + return exit_code + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/run_opencode_replacement_detached.cmd b/experiments/deepseek-v4-pro-anchoring/scripts/run_opencode_replacement_detached.cmd new file mode 100644 index 0000000..f0f2c26 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/run_opencode_replacement_detached.cmd @@ -0,0 +1,8 @@ +@echo off +setlocal +set "EXP=%~dp0.." +set "REPO=%~dp0..\..\.." +set "RUN=P2-20260815-04b-opencode-replacement" +set "LOGDIR=%EXP%\private\opencode-run" +python "%EXP%\scripts\run_opencode_agent.py" --workspace "%REPO%\workspace" --prompt "%LOGDIR%\candidate-prompt-%RUN%.txt" --private-run "%EXP%\private\project2\%RUN%" --models-catalog "%LOGDIR%\models-20260815.json" --t0-balance "%EXP%\private\pricing\balance-%RUN%-t0-b.json" --run-id "%RUN%" --timeout-seconds 4500 --cost-stop-cny 3.75 --balance-poll-seconds 60 1> "%LOGDIR%\%RUN%.runner.stdout.log" 2> "%LOGDIR%\%RUN%.runner.stderr.log" +exit /b %ERRORLEVEL% diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/run_project2.ps1 b/experiments/deepseek-v4-pro-anchoring/scripts/run_project2.ps1 new file mode 100644 index 0000000..8e783de --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/run_project2.ps1 @@ -0,0 +1,69 @@ +param( + [Parameter(Mandatory = $true)][ValidateSet('standard','minimal-full','anchored-standard')][string]$Preset, + [Parameter(Mandatory = $true)][string]$RunId, + [string]$RepositoryRoot = (Resolve-Path (Join-Path $PSScriptRoot '..\..\..')).Path +) + +$ErrorActionPreference = 'Stop' +$experimentRoot = Resolve-Path (Join-Path $PSScriptRoot '..') +$privateRun = Join-Path $experimentRoot "private\project2\$RunId" +$publicRun = Join-Path $experimentRoot "artifacts\runs\$RunId.json" +$baseline = Join-Path $experimentRoot 'artifacts\environment-baseline.json' +$price = Join-Path $experimentRoot 'artifacts\price-snapshot.json' +$prompt = Join-Path $privateRun 'candidate-prompt.txt' +$workspace = Join-Path $RepositoryRoot 'workspace' +$session = Join-Path $privateRun 'session.jsonl' +$sessionMeta = Join-Path $privateRun 'session-meta.json' +$driver = Join-Path $PSScriptRoot 'dsh_session_driver.mjs' +$analyzer = Join-Path $PSScriptRoot 'analyze_session.py' + +if (-not (Test-Path $baseline)) { throw "缺少 baseline:$baseline" } +if (-not (Test-Path $price)) { throw "缺少价格快照:$price" } +if (Test-Path $privateRun) { throw "run ID 已存在:$RunId" } +if ($env:DEEPSEEK_BASE_URL) { throw "正式 DSH run 检测到 DEEPSEEK_BASE_URL override" } +$dshHomeForSettings = if ($env:DSH_HOME) { $env:DSH_HOME } else { Join-Path $HOME '.dsh' } +$settingsPath = Join-Path $dshHomeForSettings 'settings.yaml' +if ((Test-Path $settingsPath) -and (Select-String -Path $settingsPath -Pattern 'baseURL|baseUrl' -Quiet)) { + throw "正式 DSH run 检测到 settings endpoint override" +} +New-Item -ItemType Directory -Force -Path $privateRun,(Split-Path $publicRun) | Out-Null + +& python (Join-Path $PSScriptRoot 'extract_candidate_prompt.py') --source (Join-Path $RepositoryRoot 'CANDIDATE_PROMPT.md') --output $prompt +if ($LASTEXITCODE -ne 0) { throw "候选提示提取失败,exit=$LASTEXITCODE" } + +& python (Join-Path $RepositoryRoot 'evaluator\make_broken_project.py') +if ($LASTEXITCODE -ne 0) { throw "Project2 reset 失败,exit=$LASTEXITCODE" } + +$baselineData = Get-Content $baseline -Raw | ConvertFrom-Json +$presetHash = $baselineData.preset_hashes.$Preset +if (-not $presetHash) { throw "baseline 缺少 preset hash:$Preset" } + +& node $driver --cwd $workspace --preset $Preset --prompt-file $prompt --private-output $privateRun --timeout-seconds 4500 --provider deepseek-official --model deepseek-v4-pro --reasoning-effort max +if ($LASTEXITCODE -ne 0) { throw "DSH run $RunId 失败,exit=$LASTEXITCODE" } + +$metaExtra = Join-Path $privateRun 'meta-extra.json' +& python $analyzer --session $session --session-meta $sessionMeta --benchmark project2-v4.1b --task-id project2-v4-broken-seed --run-id $RunId --dsh-version 0.1.0-rc.6 --dsh-commit 47f943859bef60e4160492346772ded9b24f765a --preset-hash $presetHash --benchmark-commit 04255b55f16c4439e538239fb9783070c4165081 --price $price --output $metaExtra +if ($LASTEXITCODE -ne 0) { throw "session 初次分析失败,exit=$LASTEXITCODE" } + +$before = @(Get-ChildItem (Join-Path $RepositoryRoot 'evaluator\results') -Directory | ForEach-Object FullName) +& python (Join-Path $RepositoryRoot 'evaluator\run_full_eval.py') (Join-Path $workspace 'project2_task') --model DeepSeek-V4-Pro --channel deepseek-official --harness "dsh-$Preset" --require-meta --run-group-id "dsv4p-anchor-20260815-$Preset" --run-index 1 --thinking-level max --provider DeepSeek --endpoint-product DeepSeek-API --billing-tier paygo --meta-extra $metaExtra +$evalExit = $LASTEXITCODE +$after = @(Get-ChildItem (Join-Path $RepositoryRoot 'evaluator\results') -Directory | Where-Object { $_.FullName -notin $before }) +if ($after.Count -ne 1) { throw "evaluator 新结果目录数量应为 1,实际为 $($after.Count)" } +$summary = Join-Path $after[0].FullName 'summary.json' +if (-not (Test-Path $summary)) { throw "evaluator 缺少 summary.json:$($after[0].Name)" } + +& python $analyzer --session $session --session-meta $sessionMeta --benchmark project2-v4.1b --task-id project2-v4-broken-seed --run-id $RunId --dsh-version 0.1.0-rc.6 --dsh-commit 47f943859bef60e4160492346772ded9b24f765a --preset-hash $presetHash --benchmark-commit 04255b55f16c4439e538239fb9783070c4165081 --price $price --result $summary --output $publicRun +if ($LASTEXITCODE -ne 0) { throw "session 最终分析失败,exit=$LASTEXITCODE" } + +$result = Get-Content $publicRun -Raw | ConvertFrom-Json +Write-Output ([pscustomobject]@{ + run_id = $RunId + preset = $Preset + evaluator_exit = $evalExit + evaluator_result_id = $after[0].Name + ability = $result.benchmark_result.ability_draft + ship = $result.benchmark_result.ship_draft + class = $result.benchmark_result.release_class_hint + cost_cny = $result.api_cost_cny +} | ConvertTo-Json -Compress) diff --git a/experiments/deepseek-v4-pro-anchoring/scripts/validate_presets.py b/experiments/deepseek-v4-pro-anchoring/scripts/validate_presets.py new file mode 100644 index 0000000..d1b73e0 --- /dev/null +++ b/experiments/deepseek-v4-pro-anchoring/scripts/validate_presets.py @@ -0,0 +1,65 @@ +from __future__ import annotations + +import argparse +import hashlib +import json +from pathlib import Path + + +def canonical(value: object) -> bytes: + return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")).encode("utf-8") + + +def request_fingerprint(request: dict) -> dict: + tools = request.get("tools", []) + return { + "system_sha256": hashlib.sha256(canonical(request.get("messages", [])[0])).hexdigest(), + "tool_names": [item.get("function", {}).get("name") for item in tools], + "tools_sha256": hashlib.sha256(canonical(tools)).hexdigest(), + "non_tools_sha256": hashlib.sha256(canonical({key: value for key, value in request.items() if key != "tools"})).hexdigest(), + } + + +def load(path: Path) -> list[dict]: + value = json.loads(path.read_text(encoding="utf-8")) + agent_requests = [item for item in value if item.get("tools")] + if not isinstance(value, list) or len(agent_requests) < 2: + raise ValueError(f"{path} 至少需要两次携带工具目录的 mock agent request") + return agent_requests + + +def main() -> int: + parser = argparse.ArgumentParser() + parser.add_argument("--standard", type=Path, required=True) + parser.add_argument("--minimal-full", type=Path, required=True) + parser.add_argument("--anchored", type=Path, required=True) + parser.add_argument("--output", type=Path, required=True) + args = parser.parse_args() + + standard = load(args.standard) + minimal = load(args.minimal_full) + anchored = load(args.anchored) + snapshots = { + "standard": [request_fingerprint(item) for item in standard], + "minimal-full": [request_fingerprint(item) for item in minimal], + "anchored-standard": [request_fingerprint(item) for item in anchored], + } + full_names = snapshots["standard"][0]["tool_names"] + assertions = { + "standard_first_is_full": len(full_names) > 2, + "minimal_first_equals_anchored_second_tools": snapshots["minimal-full"][0]["tools_sha256"] == snapshots["anchored-standard"][1]["tools_sha256"], + "minimal_first_equals_standard_first_tools": snapshots["minimal-full"][0]["tools_sha256"] == snapshots["standard"][0]["tools_sha256"], + "minimal_and_anchored_system_equal": snapshots["minimal-full"][0]["system_sha256"] == snapshots["anchored-standard"][0]["system_sha256"], + "minimal_and_anchored_first_non_tools_equal": snapshots["minimal-full"][0]["non_tools_sha256"] == snapshots["anchored-standard"][0]["non_tools_sha256"], + "anchored_first_is_shell_read": set(snapshots["anchored-standard"][0]["tool_names"]) in ({"pwsh", "read"}, {"bash", "read"}), + "anchored_second_is_full": snapshots["anchored-standard"][1]["tool_names"] == full_names, + } + result = {"schema_version": 1, "passed": all(assertions.values()), "assertions": assertions, "snapshots": snapshots} + args.output.parent.mkdir(parents=True, exist_ok=True) + args.output.write_text(json.dumps(result, ensure_ascii=False, indent=2) + "\n", encoding="utf-8") + print(json.dumps({"passed": result["passed"], "assertions": assertions}, ensure_ascii=False)) + return 0 if result["passed"] else 1 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/deepseek-harness-presets/README.md b/tools/deepseek-harness-presets/README.md index 62c6aa9..077eef0 100644 --- a/tools/deepseek-harness-presets/README.md +++ b/tools/deepseek-harness-presets/README.md @@ -38,3 +38,13 @@ C:\Users\<用户名>\.dsh\.agent-presets\anchored-standard `agent.cordis.yml` 基于 DeepSeek Harness 的 Standard preset 修改,`tool-bootstrap.mjs` 为本项目新增。DeepSeek Harness 使用 MIT License,许可文本见 [`LICENSE.deepseek-harness`](./LICENSE.deepseek-harness)。 + +## minimal-full + +`minimal-full/` 是本轮 full-task 消融 preset。它与 `anchored-standard` 保持相同的 +Minimal complete persona、runtime-context suppression 和 Standard capability roster,唯一的 +模型可见目标差异是移除 `tool-bootstrap`:从第一次模型请求起即暴露同平台完整 Standard +工具目录。正式运行前必须通过 request/header 快照验证;若首请求除工具目录外还有差异, +该消融不进入付费阶段。 + +该 preset 由 [@NineThoughts0521](https://github.com/NineThoughts0521) 用于 2026-08-15 独立复现;实现、schema gate 与 full-task 结果见 [`../../experiments/deepseek-v4-pro-anchoring/`](../../experiments/deepseek-v4-pro-anchoring/README.md)。这些 runs 不并入维护者原有 formal `n`。 diff --git a/tools/deepseek-harness-presets/minimal-full/agent.cordis.yml b/tools/deepseek-harness-presets/minimal-full/agent.cordis.yml new file mode 100644 index 0000000..86b8df2 --- /dev/null +++ b/tools/deepseek-harness-presets/minimal-full/agent.cordis.yml @@ -0,0 +1,255 @@ +# The `minimal-full` experimental preset: Standard capabilities with the +# Minimal mode system-prompt condition used by the V4 trajectory evaluation. +# +# This file is an AGENT-PLANE composition. The roster mounts it ONCE under a +# standing scope; every session naming it joins by scope parentage, so the +# tools and prompt sections registered here cover each joined agent while a +# session's own state stays keyed per Session/Agent inside the plugins. The +# host composition (`base.cordis.yml` + `web.cordis.yml`) keeps everything a +# preset must not own: the registries themselves, the sandbox and approval +# stack, persistence, and the model route. +# +# A service row here MUST sit inside a group carrying an `isolate` realm. +# Without one it publishes into the root realm, where it is process-global — +# another preset publishing the same name collides, and a host reader would +# resolve one preset's instance for every session; `dsh-agent-presets` rejects +# that at mount. `true` means an entry-local realm: this standing mount's own +# private instance, apart from every other preset's. (A shared label does NOT +# pool instances — `provide()` throws on the second registration under the +# same realm symbol; labels join REALMS, and are not what this file needs.) + +# ── identity ──────────────────────────────────────────────────────────────── + +# Keep this text byte-identical to the Minimal preset. `complete` prevents the +# Harness identity and per-tool guidance from changing the system prompt, while +# runtime-context suppression leaves task and repository rules to user messages +# and explicit file reads. Tool schemas and their runtime enforcement remain. +- id: persona + name: '@deepseek-ai/dsh-persona' + config: + text: You are a helpful software engineer assistant. + complete: true + includeRuntimeContext: false + +- id: agent-instructions + name: '@deepseek-ai/dsh-agent-instructions' + config: + maxBytes: 65536 + +# ── shell ─────────────────────────────────────────────────────────────────── + +# `shell-env` stays in the HOST composition: `apps/cli/src/web.ts` injects it to +# publish `DSH_WEB_URL`/`DSH_WEB_MODE`, and a host row that injects a service is +# the criterion for host-plane ownership — injection resolves before any session +# exists, so there is no agent to key by. Behind a preset realm those variables +# never reached the model's shell at all. Both shell tools consume the host +# registry from here; their executors (`bash-sandbox`/`pwsh-sandbox`) are +# host-plane too. +- id: tool-bash + name: '@deepseek-ai/dsh-tool-bash' + disabled: !!js process.platform === 'win32' + +- id: tool-pwsh + name: '@deepseek-ai/dsh-tool-pwsh' + disabled: !!js process.platform !== 'win32' + +# ── filesystem ────────────────────────────────────────────────────────────── + +# Both register into the host `tools` registry and provide nothing, so +# they need no realm. The `fs` service and its policy stay in the host. +- id: tool-fs + name: '@deepseek-ai/dsh-tool-fs' + +- id: tool-fs-search + name: '@deepseek-ai/dsh-tool-fs-search' + config: + sampleOverCapGlobResults: false + +# ── background jobs ──────────────────────────────────────────────────────── + +# Only the model-facing controls. The task REGISTRY stays on the host plane: +# its producers sit outside any realm this file could put it in — `tool-bash` +# above resolves it with `ctx.get`, and an entry-local realm here is invisible +# to every sibling row, so `run_in_background` would answer "background jobs +# unavailable" while these controls sat in the catalog. The registry is keyed by +# owning agent anyway, so one host instance serves every session. What a preset +# chooses is whether its agent can collect and stop background work at all. +- id: tool-jobs + name: '@deepseek-ai/dsh-tool-jobs' + +# ── skills ────────────────────────────────────────────────────────────────── + +# The skill REGISTRY lives in the host composition and is layered per scope: +# these rows register into THIS preset's layer of it, so they need no realm. +# `skill-filesystem` contributes local-root discovery for agents on this preset, and +# `tool-skill` gives them the catalog and loader; the merged catalog also +# carries whatever the deployment registered globally (repository plugins). +- id: skill-filesystem + name: '@deepseek-ai/dsh-skill-filesystem' + +- id: tool-skill + name: '@deepseek-ai/dsh-tool-skill' + +# ── goals ─────────────────────────────────────────────────────────────────── + +# Only the model-facing tool. The goal SERVICE, its session driver, and the +# `/goal` command stay on the host plane: the Gateway serves the goal domain as +# Remote endpoints whose receiver comes from a generated descriptor, so it +# resolves `goals` on the host and an entry-local realm here would hide it. The +# registry is keyed by session anyway, so one host instance serves every +# session. What a preset chooses is whether its agent can call the goal tool. +- id: tool-goal + name: '@deepseek-ai/dsh-tool-goal' + +# ── plan mode ─────────────────────────────────────────────────────────────── + +# Plan state is per-agent by nature, so an entry-local realm is not a +# workaround here — it is the correct lifetime. +- id: planning + name: cordis:group + group: true + isolate: + planMode: true + config: + - id: plan-mode + name: '@deepseek-ai/dsh-plan-mode' + config: + section: | + You are in plan mode. Stay in plan mode until exit_plan_mode succeeds or the user switches the session mode. Imperative language to implement changes means plan the implementation, not execute it. A user's conversational agreement — including an answer confirming something you asked — approves nothing and does not end plan mode; fold the confirmed decision into the plan and submit it through exit_plan_mode. + + Explore first. Use non-mutating reads, searches, static analysis, and checks to ground the plan in the actual repository. Do not edit or write files, change configuration, run formatters or code generation that rewrites tracked files, commit, or otherwise carry out the plan. Prefer existing functions and patterns over new machinery. + + The tool catalog stays the same across modes for request-cache stability. These plan-mode rules override any later tool description or guidance that suggests using mutation tools; those tools remain listed to keep the tool catalog unchanged. Do not use todo_write to track this planning phase: it tracks implementation after an approved plan, while the plan itself belongs in exit_plan_mode. + + Resolve discoverable facts by inspection. Use ask_user_question only for user-owned choices or material ambiguity that inspection cannot answer. Do not ask the user where code lives or how current behavior works when you can find out. + + Make the plan decision-complete: state the goal and success criteria; group implementation changes by subsystem; identify public API, schema, and data-flow changes; cover edge cases, failure modes, tests, acceptance criteria, and explicit assumptions. Keep it concise enough to review but detailed enough that another engineer can implement it without making design decisions. + + When ready, call exit_plan_mode with the complete plan markdown, starting with a # title. Make exit_plan_mode the only and final tool call in that assistant response: it presents the plan for approval, and implementation begins only in a later step after approval. Do not paste the final plan as a plain reply or ask "should I proceed?" through prose or ask_user_question. If review rejects it, incorporate the feedback and present again. If the review channel is unavailable or aborted, stay in plan mode and ask the user to switch modes manually; do not proceed with implementation. + +# ── compaction ────────────────────────────────────────────────────────────── + +# `compaction-basic` reads `toolResultPrune` through `ctx.get`, so the pruner must +# share this realm rather than sit outside it. +# +# `tokenMeter` is deliberately NOT in this realm: the meter stays on the HOST +# plane, and the rows here resolve that one instance. It takes no configuration, +# keys every fold by Session, and owns the context-meter projection units the +# browser reads for every session — behind a realm those units would come and go +# with whichever presets happen to be mounted. What a preset chooses is whether +# its agent compacts at all, which is `compaction-basic` below. +- id: compaction + name: cordis:group + group: true + isolate: + compaction: true + toolResultPruner: true + config: + - id: compaction-basic + name: '@deepseek-ai/dsh-compaction-basic' + + - id: command-compact + name: '@deepseek-ai/dsh-command-compact' + + - id: tool-result-pruner + name: '@deepseek-ai/dsh-compaction-tool-result-pruner' + config: + thresholdChars: 8192 + headChars: 4096 + tailChars: 1024 + +# ── delegation and workflows ──────────────────────────────────────────────── + +# The `subagents` registry and its spawn/fork backends live in the HOST +# composition: the registry is a process singleton whose cross-session queries +# the api-proxy serves to the browser, and a provider name may only be +# registered once. This preset contributes the delegation TOOLS, which resolve +# that host registry. +# +# `workflows` is different — nothing outside an agent reads it — so every row +# that reaches it shares one entry-local realm here, and a consumer left +# outside would resolve a host registry this preset does not populate. +# +# `tool-subagent-report` is host-plane for the same reason as the registry, +# not because a preset may not want it: it registers a CONTINUABLE SETUP on +# that singleton rather than a tool this agent calls, and the setup list is +# not scope-aware — one copy per mounted preset means every child gets +# `report` registered once per live session, which throws on the second. +- id: delegation + name: cordis:group + group: true + isolate: + workflowEngine: true + config: + - id: tool-subagent-control + name: '@deepseek-ai/dsh-tool-subagent-control' + + - id: tool-subagent-list-agents + name: '@deepseek-ai/dsh-tool-subagent-control/list-agents' + + - id: tool-subagent + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: spawn + toolName: subagent + backgroundMode: continuable + + - id: tool-subagent-fork + name: '@deepseek-ai/dsh-tool-subagent' + config: + provider: fork + toolName: subagent_fork + backgroundMode: continuable + + # Product providers are host-plane singletons. Copy this preset, then + # remove `disabled` from either ordinary tool row to expose that product + # only to agents composed from the copy. + - id: tool-subagent-codex + name: '@deepseek-ai/dsh-tool-subagent' + disabled: true + config: + provider: codex + toolName: subagent_codex + enableRunInBackground: false + maxDepth: provider-managed + + - id: tool-subagent-claude-code + name: '@deepseek-ai/dsh-tool-subagent' + disabled: true + config: + provider: claude-code + toolName: subagent_claude_code + enableRunInBackground: false + maxDepth: provider-managed + + - id: workflow-worker-thread + name: '@deepseek-ai/dsh-workflow-worker-thread' + config: + provider: spawn + + - id: tool-workflow + name: '@deepseek-ai/dsh-tool-workflow' + + - id: tool-ralph + name: '@deepseek-ai/dsh-tool-ralph' + config: + subagentProvider: spawn + maxRounds: 64 + +# ── remaining model-facing rows ───────────────────────────────────────────── + +- id: tool-ask-user + name: '@deepseek-ai/dsh-tool-ask-user' + +- id: tool-todo + name: '@deepseek-ai/dsh-tool-todo' + config: + allowParallelInProgress: true + +# The `web` service and its search provider stay in the host composition; only +# the model-facing tool is per-session. +- id: tool-web + name: '@deepseek-ai/dsh-tool-web' + config: + fetch: false + searchTimeoutMs: 60000 diff --git a/tools/deepseek-harness-presets/minimal-full/preset.yml b/tools/deepseek-harness-presets/minimal-full/preset.yml new file mode 100644 index 0000000..552038c --- /dev/null +++ b/tools/deepseek-harness-presets/minimal-full/preset.yml @@ -0,0 +1,3 @@ +name: 极简完整目录模式(实验消融) +description: 保持 Minimal complete system condition 与 Standard capabilities,从第一次请求起暴露完整 Standard 工具目录。 +order: 6 From 2680ce1e8bdc8be9789811e3c7653b10f1cb6a45 Mon Sep 17 00:00:00 2001 From: NineThoughts0521 Date: Sun, 16 Aug 2026 18:28:34 +0800 Subject: [PATCH 2/2] =?UTF-8?q?docs:=20=E8=A1=A5=E5=85=85=20V4=20Pro=20?= =?UTF-8?q?=E7=AC=AC=E4=B8=89=E6=96=B9=E5=A4=8D=E7=8E=B0=E6=8A=A5=E5=91=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 区分维护者正式统计与 NineThoughts0521 独立结果 - 接入 Minimal-Full、trajectory、成本与证据限制 - 如实披露 OpenCode partial 和 replacement --- README.md | 12 ++- ...EPSEEK_V4_PRO_HARNESS_ANALYSIS_20260814.md | 8 ++ ...V4_PRO_INDEPENDENT_REPLICATION_20260815.md | 101 ++++++++++++++++++ ...EEPSEEK_V4_TRAJECTORY_ANALYSIS_20260814.md | 13 +++ ..._TRIGGER_MECHANISM_EXPERIMENTS_20260814.md | 6 ++ docs/v4.1/README.md | 1 + evaluator/reports/README.md | 1 + evaluator/reports/v4.1b_scoreboard.md | 13 +++ evaluator/trajectory_evidence/README.md | 3 + .../deepseek-v4-pro-anchoring/README.md | 3 +- .../deepseek-v4-pro-anchoring/RESULTS.md | 3 +- .../artifacts/evidence-manifest.json | 8 +- 12 files changed, 162 insertions(+), 10 deletions(-) create mode 100644 docs/v4.1/DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md diff --git a/README.md b/README.md index bb8d2e0..0894c92 100644 --- a/README.md +++ b/README.md @@ -12,8 +12,7 @@ PR 一致性预审。 - **当前正式稳定基线:V4.1b**,已于 2026-07-23 正式冻结。不再迭代、不开发 V5。 详见 [`PROJECT_FROZEN.md`](./PROJECT_FROZEN.md)。 -- 冻结的是题面、测试与计分规则;模型、渠道和 harness 的实测台账仍会追加。 - 最新一轮记录截至 2026-08-14。 +- 冻结的是题面、测试与计分规则;模型、渠道和 harness 的实测台账仍会追加。维护者原始记录截至 2026-08-14;2026-08-15 新增 [@NineThoughts0521](https://github.com/NineThoughts0521) 的独立复现补充,不并入维护者 formal `n`。 - 这是一个**个人项目**,不是面向社区的公开 benchmark;Ability 阈值与结论只对本 题面、本工具环境有效,**不构成跨项目通用认证**。 - V5 两次尝试均失败,工作区与归档见独立 repo **`modeltest-v5`**。 @@ -38,6 +37,13 @@ PR 一致性预审。 | DeepSeek V4 Pro / 正式 DSH minimal | **99, 96** | 正式版在 RL 对齐 scaffold 下复现 | | DeepSeek V4 Pro / DSH anchored-standard | **98, 99** | Windows 两阶段目录;完整 Standard 工具可用 | +### 独立第三方复现(2026-08-15,不并入 formal n) + +[@NineThoughts0521](https://github.com/NineThoughts0521) 在同一 frozen Project2 V4.1b、DeepSeek V4 Pro、reasoning `max` 下完成 DSH Anchored Standard / Standard / Minimal-Full 三枪和一枪 OpenCode exploratory replacement。独立 Ability 为 **96 / 89 / 85.5 / 93**;其中 Anchored 相对同批 Standard 为 `+7`,相对 Minimal-Full 为 `+10.5`。该结果 supports 首请求工具目录具有额外贡献,并与维护者原 Anchored 高于 Standard 的方向 consistent with;它不追加到上表 `n`,也不证明跨任务普适因果。 + +- **独立复现报告:** [`DeepSeek V4 Pro 首请求工具目录锚定:独立复现与 full-task 消融`](./docs/v4.1/DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md) +- **预注册、runner 与公开证据:** [`experiments/deepseek-v4-pro-anchoring/`](./experiments/deepseek-v4-pro-anchoring/README.md) + 因此可以说,**V4 Pro 的已观测能力上限在本题上确实进入了 Fable 5、Opus 5 和 Sol 的同一顶端分数带**;这不是跨任务通用等价证明,也不能证明灰测实际代理了任何 Claude 后端。 @@ -200,6 +206,8 @@ python evaluator\prepare_candidate_handoff.py [`docs/v4.1/DEEPSEEK_V4_TRAJECTORY_ANALYSIS_20260814.md`](./docs/v4.1/DEEPSEEK_V4_TRAJECTORY_ANALYSIS_20260814.md) - DeepSeek V4 Pro / Flash 触发机制实验: [`docs/v4.1/DEEPSEEK_V4_TRIGGER_MECHANISM_EXPERIMENTS_20260814.md`](./docs/v4.1/DEEPSEEK_V4_TRIGGER_MECHANISM_EXPERIMENTS_20260814.md) +- DeepSeek V4 Pro 独立复现与 Minimal-Full full-task 消融(不并入维护者 formal `n`): + [`docs/v4.1/DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md`](./docs/v4.1/DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md) - 最终评估与使用阈值:[`docs/v4.1/FINAL_ASSESSMENT_20260719.md`](./docs/v4.1/FINAL_ASSESSMENT_20260719.md) - 轮次事实终稿:[`docs/v4.1/ROUND_SUMMARY_20260719.md`](./docs/v4.1/ROUND_SUMMARY_20260719.md) - 评分面冻结哈希:[`evaluator/reports/v4.1b_freeze_manifest.md`](./evaluator/reports/v4.1b_freeze_manifest.md) diff --git a/docs/v4.1/DEEPSEEK_V4_PRO_HARNESS_ANALYSIS_20260814.md b/docs/v4.1/DEEPSEEK_V4_PRO_HARNESS_ANALYSIS_20260814.md index 1a5d0b0..ecec297 100644 --- a/docs/v4.1/DEEPSEEK_V4_PRO_HARNESS_ANALYSIS_20260814.md +++ b/docs/v4.1/DEEPSEEK_V4_PRO_HARNESS_ANALYSIS_20260814.md @@ -251,6 +251,14 @@ standard/PTC 对照已关闭 OS、官方 harness 和推理档位三个主要混 更宽的 agent 接口下明显退化,说明它具备较高能力上限,同时存在强接口依赖和较弱的工具 策略泛化。** +## 2026-08-15 第三方独立复现附录 + +[@NineThoughts0521](https://github.com/NineThoughts0521) 在相同 frozen Project2 V4.1b task 上,使用 DSH `0.1.0-rc.6`、DeepSeek V4 Pro `max` 和同一 evaluator 完成了一次预注册机制消融:Anchored Standard `96`、Standard `89`、新增 Minimal-Full `85.5`。这三枪属于独立第三方证据,不追加到维护者原有 formal `n`、主榜排名、worst、均值或样本索引。 + +Minimal-Full 保留 Anchored 的 Minimal complete system condition、`complete: true`、`includeRuntimeContext: false` 和 Standard capability roster,但从 request 1 暴露完整 25 项工具;Anchored request 1 仅暴露 `pwsh/read`,首次 durable tool call 后恢复相同完整目录。静态 schema gate 通过,因此本批次观察到 Standard → Minimal-Full `-3.5`、Minimal-Full → Anchored `+10.5`,结果 **supports** 首请求工具目录具有额外贡献,并与维护者 Anchored 优于 Standard 的方向 **consistent with**。不过 Anchored 还包含目录 transition 及其时序,单次同题运行不能把它们拆成唯一变量,也不能证明跨任务普适因果。 + +同批次的 OpenCode `1.18.17` replacement 得到 `93`,但它是 post-preregistered exploratory harness comparison;原 partial 永久保留为不计分 infrastructure failure,二者都不进入 DSH mechanism ablation。完整结果、成本、轨迹聚合、工具目录快照和公开/私有证据边界见 [`DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md`](./DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md)。 + ## 证据索引 - 灰测:[20260718_212524 / 99](../../evaluator/reviews/v4.1b_DeepSeek-V4-Pro_opencode_20260718_212524.md)、 diff --git a/docs/v4.1/DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md b/docs/v4.1/DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md new file mode 100644 index 0000000..dd645bf --- /dev/null +++ b/docs/v4.1/DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md @@ -0,0 +1,101 @@ +# DeepSeek V4 Pro 首请求工具目录锚定:独立复现与 full-task 消融 + +**日期:** 2026-08-15 · **独立复现者:** [@NineThoughts0521](https://github.com/NineThoughts0521) · **上游目标:** `xiaobright/modeltest` + +**统计边界:** 本报告是第三方独立证据;下列 runs 不并入维护者原有 formal `n`、主榜排名、worst、均值或样本索引。 + +## 摘要 + +在固定 Project2 V4.1b、DeepSeek V4 Pro、reasoning `max` 和 DSH `0.1.0-rc.6` 下,本轮依次运行 Anchored Standard、Standard 与新增的 Minimal-Full。三枪 Ability 分别为 **96、89、85.5**。同一次静态 schema gate 证明 Minimal-Full 与 Anchored 的 Minimal complete system condition 和首请求非工具字段相同,Minimal-Full 从 request 1 起暴露完整 25 项 Standard 工具,而 Anchored 首请求仅暴露 `pwsh/read`,首次 durable tool call 后恢复相同的完整目录。 + +这组单次观测 **supports** 首请求工具目录与 Project2 表现之间存在独立关联,并与维护者原有 Anchored Standard 高于 Standard 的方向 **consistent with**。它没有证明普适因果:每个条件只有一枪,全部使用同一题面,且 Anchored treatment 同时包含窄首请求与随后目录 transition 的时序。外部 benchmark 未在本阶段执行,因此跨任务迁移仍未得到验证。 + +## 与维护者结果的边界 + +维护者原报告中的 DSH Anchored Standard `98/99`、Minimal `99/96`、Standard `91` 和其他 DeepSeek 样本保持原统计口径。本轮 Anchored `96` 是 [@NineThoughts0521](https://github.com/NineThoughts0521) 在独立环境中的单次复现,不追加到原 `n=2`,也不改变 scoreboard 的 worst、均值或排名。 + +独立 Anchored 分数低于维护者两枪,但相对同一独立批次 Standard 的差值为 `+7`,方向与维护者原观察一致。该关系比跨操作者直接比较绝对分数更有信息量,但仍只适用于当前 frozen task 和运行条件。 + +## 固定条件 + +| 项目 | 固定值 | +|---|---| +| modeltest base | `04255b55f16c4439e538239fb9783070c4165081` | +| benchmark / task | `project2-v4.1b` / `project2-v4-broken-seed` | +| candidate prompt SHA-256 | `576103f9a5a7a619c0669674cf384a975b89390bb034c368a53a148251f2df84` | +| model / provider | `deepseek-v4-pro` / `deepseek-official` | +| reasoning | `max` | +| DSH | `0.1.0-rc.6` / source commit `47f943859bef60e4160492346772ded9b24f765a` | +| endpoint | `https://api.deepseek.com` | +| evaluator | 原 V4.1b public/debug/hidden/ESP static/scorer;未运行 optional real ESP-IDF build | +| execution | 串行、每枪前 reset、结果无关重跑禁止 | + +正式顺序为 Anchored Standard、Standard、Minimal-Full。OpenCode comparison 只在三枪 DSH 完成后运行,并明确排除在 DSH mechanism ablation 之外。 + +## Project2 3+1 结果 + +| Harness / preset | Ability | Ship | Class | Hidden | ESP static | F9 | 账户成本 | model wall time | +|---|---:|---:|---|---|---|---|---:|---:| +| DSH Anchored Standard | **96** | 96 | A | 44/45 | 9/9 | 3/6 `skipped_env` | ¥2.58 | 35m41s | +| DSH Standard | **89** | 89 | B+ | 43/45 | 7/9 | 3/6 `skipped_env` | ¥1.64 | 27m33s | +| DSH Minimal-Full | **85.5** | 85.5 | B+ | 42/45 | 6/9 | 3/6 `skipped_env` | ¥1.13 | 11m45s | +| OpenCode 1.18.17 replacement | **93** | 93 | B+ | 43/45 | 8/9 | 3/6 `skipped_env` | ¥1.53 | 21m33s | + +四个有效 run 的账户成本为 ¥6.88;保留但不计分的 OpenCode partial 另消耗 ¥0.24,因此本阶段总账户成本为 **¥7.12**。按每枪运行时官方人民币单价复算的总 usage 成本为 **¥7.075304**。详细 token、balance window 和复算差异见实验目录的 machine-readable aggregate。 + +## Minimal-Full 消融 + +Minimal-Full 复制 Anchored Standard 的 Minimal complete system condition、`complete: true`、`includeRuntimeContext: false` 与 Standard capability roster,仅移除 `tool-bootstrap`。零费用 mock gate 得到以下断言: + +| 断言 | 结果 | +|---|---| +| Minimal-Full request 1 工具 schema = Standard request 1 | pass | +| Minimal-Full request 1 工具 schema = Anchored request 2 | pass | +| Minimal-Full system = Anchored system | pass | +| Minimal-Full 与 Anchored request 1 非工具字段相同 | pass | +| Anchored request 1 仅 `pwsh/read` | pass | +| Anchored request 2 恢复完整 25 项目录 | pass | + +Standard 到 Minimal-Full 的 Ability 变化为 `-3.5`,Minimal-Full 到 Anchored 为 `+10.5`,Standard 到 Anchored 为 `+7`。在这一次任务上,Minimal complete scaffold 本身没有解释 Anchored 的提升;观察结果 **supports** first-request tool-schema anchoring 具有独立贡献的解释。 + +该 A/B 的模型可见首请求差异经过 hash gate 收窄到工具目录,但 treatment 不只是“工具数量”这个静态变量,还包括 Anchored 在首次 durable tool call 后发生的目录 transition 及其时序。当前证据没有进一步拆分工具名称、描述、顺序或 transition 边界,也没有提供多 seed 方差估计。 + +## Trajectory fingerprints + +| Harness / preset | Ability | reasoning blocks | `we` | `let me` | `let's` | visible replies | tool calls | +|---|---:|---:|---:|---:|---:|---:|---:| +| DSH Anchored Standard | 96 | 191 | 324 | 31 | 167 | 1 | 244 | +| DSH Standard | 89 | 88 | 25 | 149 | 2 | 41 | 166 | +| DSH Minimal-Full | 85.5 | 47 | 67 | 14 | 16 | 8 | 83 | +| OpenCode replacement | 93 | 65 | 22 | 119 | 3 | 35 | 152 | + +这些统计只作为行为指纹。Anchored 的 `we`/`let's` 增多、可见阶段回复减少,与维护者报告的 minimal-like 方向 consistent with;但本批次 Minimal-Full 也有较少 `let me` 而得分最低,进一步说明措辞风格不是能力指标,也不是因果证据。 + +## OpenCode partial 与 replacement + +最初的 `P2-20260815-04-opencode` 在模型已响应且进程仍活动时受到外层 hosting tool session 中断。该 run 永久登记为 `infrastructure_failed_partial_after_model_response`,不计 benchmark score,不从中恢复继续执行,也不因结果重跑;¥0.24 计入总成本。private export 含 11 个 reasoning blocks 和 22 个 tool calls,event stream 记录 23 个 completed tool events 并留下 pending final tool,两种计数按来源分别保留。 + +经操作者明确批准后执行一次 `P2-20260815-04b-opencode-replacement`。replacement 固定 OpenCode `1.18.17` commit `02546dfc2e4515a4f90aaf9ceb3890df2ac2b479`、direct DeepSeek provider、`deepseek-v4-pro`、`--variant max`、官方 endpoint、同一 prompt/evaluator;只把进程改为 detached/background 生命周期。它以 exit `0` 完成并得到 `93/93/B+`,无论该结果高低都未再运行。该行只用于 exploratory harness comparison,不进入前三枪因果消融。 + +## 证据与隐私 + +公开目录包含 preregistration、run matrix、preset/config、schema gate、原 evaluator 派生结果、trajectory aggregate、token/time/cost aggregate、balance window hash、request tool-catalog snapshot、infrastructure event 和 SHA-256 manifest。完整 DSH session JSONL、OpenCode export/event stream、reasoning/CoT、credentials 与私人绝对路径保留在本地且由 Git ignore。 + +主要入口: + +- [实验 README](../../experiments/deepseek-v4-pro-anchoring/README.md) +- [完整结果表](../../experiments/deepseek-v4-pro-anchoring/RESULTS.md) +- [machine-readable comparison](../../experiments/deepseek-v4-pro-anchoring/artifacts/comparison.json) +- [public/private evidence SHA-256 manifest](../../experiments/deepseek-v4-pro-anchoring/artifacts/evidence-manifest.json) +- [Minimal-Full preset](../../tools/deepseek-harness-presets/minimal-full/agent.cordis.yml) + +## 限制 + +- 每个有效条件只有一枪,不能据此估计方差或统计显著性。 +- 全部能力结果来自同一个 Project2 task;DeepSWE 与 Terminal-Bench 未运行,跨任务问题仍开放。 +- 运行按预注册顺序串行执行,不能完全排除时间漂移、provider 负载或顺序效应。 +- Project2 未运行 optional real ESP-IDF build,所有行 F9 均为 `3/6 skipped_env`。 +- OpenCode 工具目录证据来自 pinned static agent resolution,不是 HTTP wire capture。 +- balance delta 与 usage 复算保留各自语义,未把差异静默分配给任一请求。 + +因此,本轮最克制的结论是:**独立 Project2 结果 supports first-request tool-schema anchoring 具有额外贡献,并与维护者原 Anchored 优于 Standard 的方向 consistent with;它尚未证明该效应能跨任务、跨版本或跨 provider 普适复现。** diff --git a/docs/v4.1/DEEPSEEK_V4_TRAJECTORY_ANALYSIS_20260814.md b/docs/v4.1/DEEPSEEK_V4_TRAJECTORY_ANALYSIS_20260814.md index eaf8621..61aa0e2 100644 --- a/docs/v4.1/DEEPSEEK_V4_TRAJECTORY_ANALYSIS_20260814.md +++ b/docs/v4.1/DEEPSEEK_V4_TRAJECTORY_ANALYSIS_20260814.md @@ -140,3 +140,16 @@ minimal 对齐的策略区域。 只丢一个 context reason 语义字符串;两轮 ambient 泄漏都被堵住。这已经足以否定第一轮只是 偶然抽到高分样本的简单解释。现有证据不值得再为同一题追加付费运行;下一次应换结构不同 的工程任务复验,检验两阶段锚定是否能跨题泛化。 + +## 2026-08-15 第三方独立复现指纹 + +[@NineThoughts0521](https://github.com/NineThoughts0521) 在同一 frozen Project2 task 上追加了预注册的 DSH 三枪和一枪 OpenCode exploratory replacement。公开聚合如下;`let me` 保留绝对数量,所有 wording 只作轨迹指纹,不作能力指标或因果证据。 + +| Harness / preset | Ability | reasoning blocks | `we` | `let me` | `let's` | visible replies | tool calls | +|---|---:|---:|---:|---:|---:|---:|---:| +| DSH Anchored Standard | 96 | 191 | 324 | 31 | 167 | 1 | 244 | +| DSH Standard | 89 | 88 | 25 | 149 | 2 | 41 | 166 | +| DSH Minimal-Full | 85.5 | 47 | 67 | 14 | 16 | 8 | 83 | +| OpenCode replacement | 93 | 65 | 22 | 119 | 3 | 35 | 152 | + +Anchored 的首请求仅含 `pwsh/read`,随后恢复完整目录;Minimal-Full 和 Standard 从首请求即暴露完整目录。该单批次的轨迹分离与分数方向 **consistent with** 首请求 schema anchoring 的解释,但不能把 `we`、`let me` 或可见回复数量解释为能力原因,也不能外推到其他任务或 provider。完整 machine-readable aggregate、成本和 evidence manifest 见 [`独立复现报告`](./DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md)。 diff --git a/docs/v4.1/DEEPSEEK_V4_TRIGGER_MECHANISM_EXPERIMENTS_20260814.md b/docs/v4.1/DEEPSEEK_V4_TRIGGER_MECHANISM_EXPERIMENTS_20260814.md index 432816a..d802b56 100644 --- a/docs/v4.1/DEEPSEEK_V4_TRIGGER_MECHANISM_EXPERIMENTS_20260814.md +++ b/docs/v4.1/DEEPSEEK_V4_TRIGGER_MECHANISM_EXPERIMENTS_20260814.md @@ -125,6 +125,12 @@ - 声称 `We need`、`Good` 或低阶段回复本身导致高分; - 声称 98/99 会在其他仓库、任务长度或 provider 上稳定复现。 +## 2026-08-15 第三方 full-task 消融更新 + +[@NineThoughts0521](https://github.com/NineThoughts0521) 对同一 frozen Project2 task 做了三枪 DSH 独立复现:Anchored Standard `96`、Standard `89`、Minimal-Full `85.5`。Minimal-Full 从首请求暴露完整 Standard 25 项工具,Anchored 仍先暴露 `pwsh/read`,首次 durable tool call 后恢复完整目录;其 system 和首请求非工具字段通过 hash gate 对齐。该结果 **supports** 首请求 schema anchoring 在本题上有额外贡献,并与原有 Anchored 优于 Standard 的方向 **consistent with**,但每条件只有一枪,且 transition 时序仍属于 treatment,不能据此声称唯一变量或跨任务普适效果。 + +这批运行的轨迹统计只用于行为指纹,OpenCode replacement 只作另行登记的 exploratory harness comparison。公开汇总、schema gate、成本和限制见 [`独立复现报告`](./DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md) 与 [`实验目录`](../../experiments/deepseek-v4-pro-anchoring/RESULTS.md);DeepSWE 和 Terminal-Bench 本阶段未运行。 + ## 发布与证据边界 原始 JSON/session 含完整 reasoning、system prompt、工具结果、绝对路径和可能的环境信息, diff --git a/docs/v4.1/README.md b/docs/v4.1/README.md index b298f0c..5868969 100644 --- a/docs/v4.1/README.md +++ b/docs/v4.1/README.md @@ -31,6 +31,7 @@ | [`DEEPSEEK_V4_PRO_HARNESS_ANALYSIS_20260814.md`](./DEEPSEEK_V4_PRO_HARNESS_ANALYSIS_20260814.md) | V4 Pro 灰测、正式版与 DSH 三 preset 对照 | | [`DEEPSEEK_V4_TRAJECTORY_ANALYSIS_20260814.md`](./DEEPSEEK_V4_TRAJECTORY_ANALYSIS_20260814.md) | 思维链风格、PTC 调用结构与统计方法 | | [`DEEPSEEK_V4_TRIGGER_MECHANISM_EXPERIMENTS_20260814.md`](./DEEPSEEK_V4_TRIGGER_MECHANISM_EXPERIMENTS_20260814.md) | Pro / Flash system 与工具目录触发消融、两阶段验证 | +| [`DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md`](./DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md) | [@NineThoughts0521](https://github.com/NineThoughts0521) 的独立 3+1 复现、Minimal-Full full-task 消融与证据边界;不并入维护者 formal `n` | ## 与 V4.0 / V5 边界 diff --git a/evaluator/reports/README.md b/evaluator/reports/README.md index ad4d2c9..f9e41dc 100644 --- a/evaluator/reports/README.md +++ b/evaluator/reports/README.md @@ -16,6 +16,7 @@ | [`../../docs/v4.1/ROUND_SUMMARY_20260719.md`](../../docs/v4.1/ROUND_SUMMARY_20260719.md) | 轮次事实终稿 | | [`../../docs/v4.1/DEEPSEEK_V4_PRO_HARNESS_ANALYSIS_20260814.md`](../../docs/v4.1/DEEPSEEK_V4_PRO_HARNESS_ANALYSIS_20260814.md) | V4 Pro 正式版 harness 与 preset 对照 | | [`../../docs/v4.1/DEEPSEEK_V4_TRAJECTORY_ANALYSIS_20260814.md`](../../docs/v4.1/DEEPSEEK_V4_TRAJECTORY_ANALYSIS_20260814.md) | 轨迹风格、PTC 与可复算聚合统计 | +| [`../../docs/v4.1/DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md`](../../docs/v4.1/DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md) | 第三方独立 3+1 复现与 Minimal-Full full-task 消融;明确排除在维护者 formal `n` 之外 | ## V4.1a(历史过渡锚点,不重算) diff --git a/evaluator/reports/v4.1b_scoreboard.md b/evaluator/reports/v4.1b_scoreboard.md index 318cef6..3c2b480 100644 --- a/evaluator/reports/v4.1b_scoreboard.md +++ b/evaluator/reports/v4.1b_scoreboard.md @@ -243,6 +243,19 @@ V4 Pro 的三条高能力路线在本题上进入 Fable 5、Opus 5、Sol 的同 | **DeepSeek-V4-Flash**(预览) | — | **81/60/D** | **$0.09**;明文+no-actor+ambient+PR缺项 | | Qwen-3.7 / M2.7 | 76 / 61 | **81** / **73/D** | | +## 第三方独立复现(不计入 formal n) + +[@NineThoughts0521](https://github.com/NineThoughts0521) 提交的 Project2 V4.1b 3+1 运行是独立第三方证据,不合并进维护者原有 formal `n`、主榜排名、worst、均值、多跑统计或本表样本索引。DSH 三枪属于预注册机制消融;OpenCode replacement 是另行登记的 exploratory harness comparison。 + +| Contributor | Harness / preset | n | Ability | Ship | Class | 统计口径 | +|---|---|---:|---:|---:|---|---| +| @NineThoughts0521 | DSH Anchored Standard | 1 | 96 | 96 | A | independent;excluded from formal n | +| @NineThoughts0521 | DSH Standard | 1 | 89 | 89 | B+ | independent;excluded from formal n | +| @NineThoughts0521 | DSH Minimal-Full | 1 | 85.5 | 85.5 | B+ | independent full-task ablation | +| @NineThoughts0521 | OpenCode replacement | 1 | 93 | 93 | B+ | exploratory only;excluded from DSH ablation | + +原 OpenCode partial 为 infrastructure failure,保留在独立实验 manifest 中但不计分;本批次账户成本、trajectory fingerprint、Minimal-Full schema gate 和限制见 [`独立复现报告`](../../docs/v4.1/DEEPSEEK_V4_PRO_INDEPENDENT_REPLICATION_20260815.md)。这些单次结果 **supports** 本题上的首请求 schema anchoring 解释,并与原有 Anchored 优于 Standard 的方向 **consistent with**,不构成跨任务或普适因果结论。 + ## 参考基线 | 说明 | Ability | Ship | Class | result_id | diff --git a/evaluator/trajectory_evidence/README.md b/evaluator/trajectory_evidence/README.md index 6f33afe..1794374 100644 --- a/evaluator/trajectory_evidence/README.md +++ b/evaluator/trajectory_evidence/README.md @@ -3,6 +3,8 @@ 本目录保存 2026-08-14 harness 对照分析的本地原始证据和可复算聚合统计,包括 Windows 上两阶段 `anchored-standard` 的 98/99 分双跑验证。 +本目录仍只负责维护者 2026-08-14 基线样本;[@NineThoughts0521](https://github.com/NineThoughts0521) 的 2026-08-15 独立复现聚合单独保存在 [`experiments/deepseek-v4-pro-anchoring/`](../../experiments/deepseek-v4-pro-anchoring/),不改变本目录的原始样本、formal `n` 或 scoreboard。该目录公开 `reasoning_blocks`、`we`、`let_me`、`lets`、visible replies、tool calls、token/time/cost 和工具目录 transition 的派生统计,不公开 reasoning/CoT 或 raw session。 + ## 目录 - `raw/`:DSH Session JSONL 与 OpenCode JSON 原始导出,仅本地保存,不公开。 @@ -10,6 +12,7 @@ Windows 上两阶段 `anchored-standard` 的 98/99 分双跑验证。 - `analyze_trajectory_exports.py`:统一解析 DSH/OpenCode 完成态消息的脚本。 - `derived/trajectory_stats.json`:完整聚合数据。 - `derived/trajectory_stats.csv`:适合表格分析的扁平数据。 +- 第三方 3+1 对比:[`experiments/.../RESULTS.md`](../../experiments/deepseek-v4-pro-anchoring/RESULTS.md) 与 [`artifacts/comparison.json`](../../experiments/deepseek-v4-pro-anchoring/artifacts/comparison.json)。 ## 复算 diff --git a/experiments/deepseek-v4-pro-anchoring/README.md b/experiments/deepseek-v4-pro-anchoring/README.md index 6acb44b..44b3709 100644 --- a/experiments/deepseek-v4-pro-anchoring/README.md +++ b/experiments/deepseek-v4-pro-anchoring/README.md @@ -1,7 +1,6 @@ # DeepSeek V4 Pro First-request Tool-schema Anchoring -**独立复现者:** [@NineThoughts0521](https://github.com/NineThoughts0521) -**证据角色:** 面向 `xiaobright/modeltest` 的第三方独立复现;本目录的 runs 不并入维护者原有 formal `n`、排名、worst、均值或样本索引。 +**独立复现者:** [@NineThoughts0521](https://github.com/NineThoughts0521) · **证据角色:** 面向 `xiaobright/modeltest` 的第三方独立复现;本目录的 runs 不并入维护者原有 formal `n`、排名、worst、均值或样本索引。 本目录保存 Project2 V4.1b 独立复现、Minimal-Full full-task 消融、OpenCode exploratory harness comparison 的预注册、运行器与可公开派生证据。结果见 [`RESULTS.md`](./RESULTS.md),机器可读汇总见 [`artifacts/comparison.json`](./artifacts/comparison.json)。 diff --git a/experiments/deepseek-v4-pro-anchoring/RESULTS.md b/experiments/deepseek-v4-pro-anchoring/RESULTS.md index 4abc8ec..0574265 100644 --- a/experiments/deepseek-v4-pro-anchoring/RESULTS.md +++ b/experiments/deepseek-v4-pro-anchoring/RESULTS.md @@ -1,7 +1,6 @@ # Project2 3+1 结果 -**独立复现者:** [@NineThoughts0521](https://github.com/NineThoughts0521) -**统计边界:** 以下 runs 是第三方独立证据,不并入 `xiaobright/modeltest` 维护者原有 formal `n`、排名、worst、均值或样本索引。 +**独立复现者:** [@NineThoughts0521](https://github.com/NineThoughts0521) · **统计边界:** 以下 runs 是第三方独立证据,不并入 `xiaobright/modeltest` 维护者原有 formal `n`、排名、worst、均值或样本索引。 本轮在同一 frozen Project2 V4.1b task `project2-v4-broken-seed`、同一 `CANDIDATE_PROMPT.md` 和同一 evaluator 下完成三枪 DSH 机制消融,并完成一枪经批准的 OpenCode exploratory replacement。模型为 DeepSeek V4 Pro,reasoning effort 为 `max`,价格按每枪运行时官方人民币单价记录,未使用峰谷价假设。 diff --git a/experiments/deepseek-v4-pro-anchoring/artifacts/evidence-manifest.json b/experiments/deepseek-v4-pro-anchoring/artifacts/evidence-manifest.json index 18a3cde..4bd95f0 100644 --- a/experiments/deepseek-v4-pro-anchoring/artifacts/evidence-manifest.json +++ b/experiments/deepseek-v4-pro-anchoring/artifacts/evidence-manifest.json @@ -6,13 +6,13 @@ "public_artifacts": [ { "path": "README.md", - "bytes": 1653, - "sha256": "7eeaa3afb8545b58db997f130625475fda4bb4e80da65f9c46233cdf44b66c90" + "bytes": 1654, + "sha256": "6642cb6135c9845b8422e364abfa7bda67068a7a1978c3a01bc06cf9c3dec27d" }, { "path": "RESULTS.md", - "bytes": 5883, - "sha256": "5b4bd969ffc0d3962199b42d1c552dbb583763d3d44e10c73a75556a022de96c" + "bytes": 5884, + "sha256": "35467caec8a10869d18e52058c085c53403837a833133c3b4d7a9df46164d6a1" }, { "path": "mock-prompt.txt",