diff --git a/ROADMAP.md b/ROADMAP.md index 56ecd867..93db094b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -6571,3 +6571,5 @@ Original filing (2026-04-18): the session emitted `SessionStart hook (completed) 507. **Anthropic requests can serialize/render the full message body three times before the real `/v1/messages` call: local preflight JSON byte estimate, remote `count_tokens` request body, then final message request body** — dogfooded 2026-05-20 from the `#clawcode-building-in-public` 18:30 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with branch/origin `docs/roadmap-workdir-provenance@7bc373f`, after Jobdori filed the sibling OpenAI-compatible double-build issue (#508). Code inspection: `AnthropicProvider::message` and `stream_message` call `self.preflight_message_request(...)`; that first invokes `super::preflight_message_request(request)`, whose `estimate_serialized_tokens` serializes `messages`, `system`, `tools`, and `tool_choice` via `serde_json::to_vec`. If a model limit is known, `preflight_message_request` then calls `count_tokens`, which does `self.request_profile.render_json_body(request)?`, strips fields, and posts the full `/v1/messages/count_tokens` body. After preflight succeeds, `send_raw_request` renders the same full body again with `self.request_profile.render_json_body(request)?` and sends `/v1/messages`. So a large session pays at least one local serialization plus two full Anthropic-body renders; if `count_tokens` fails, the fallback still paid for rendering that body before the final render. **Required fix shape:** (a) memoize/render the Anthropic request body once per call and reuse it for count_tokens and final send where schemas are identical or share a base projection; (b) use a streaming/estimated byte counter for the local guard instead of serializing large subtrees into throwaway `Vec`s; (c) skip remote `count_tokens` when the local estimate is far below known limits unless strict mode requires it; (d) add an instrumentation test with a large message vector proving one-shot and streaming calls do not render/serialize the full request more than once per network target. **Why this matters:** long-context sessions are already context-window and latency sensitive. Doing multiple complete JSON render/serialization passes before every Anthropic call wastes CPU/memory and makes prompt delivery look slower or stalled under large histories, especially when paired with retries and stream startup. Source: gaebal-gajae dogfood response to Clawhip message `1506725730392870952` on 2026-05-20. 508. **Config/plan-mode writes use direct `std::fs::write` with no atomic rename or advisory lock, so crashes and concurrent tool calls can corrupt `settings.json`, `settings.local.json`, or plan-mode state** — dogfooded 2026-05-20 from the `#clawcode-building-in-public` 19:00 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with branch/origin `docs/roadmap-workdir-provenance@e2b96ea`. Code inspection: `execute_config` reads a settings JSON object, mutates it, and persists through `write_json_object`; `EnterPlanMode`/`ExitPlanMode` also call `write_json_object` for `.claw/settings.local.json` and `write_plan_mode_state` for `.claw/tool-state/plan-mode.json`. Both `write_json_object` and `write_plan_mode_state` create parent directories and then call `std::fs::write(path, serde_json::to_string_pretty(...))` directly on the destination. There is no temp file + fsync + rename, and no lock around the read-modify-write window. By contrast, the OAuth credentials writer already uses a safer `.tmp` then rename pattern. Two concurrent `Config`/plan-mode calls can both read the same old document and last-writer-wins one update; a crash/interruption mid-write can leave a truncated JSON settings file that later startup/doctor must diagnose. **Required fix shape:** (a) introduce a shared `atomic_write_json(path, value)` helper using same-directory temp file, flush/fsync, and rename; (b) wrap read-modify-write config mutations in an advisory lock (`settings.json.lock` / `settings.local.json.lock`) or a compare-and-swap retry loop; (c) use the same helper for `write_plan_mode_state`, `write_agent_manifest` where appropriate, and any future JSON state files; (d) add stress/regression tests with two concurrent config writes and a simulated partial-write failure proving no malformed JSON and no lost sibling setting. **Why this matters:** config and plan-mode are control-plane state. A supposedly safe tool call should not be able to silently lose another setting or leave the workspace unable to load settings after an interrupted write; that turns a small config action into startup friction and stale permission-mode confusion. Source: gaebal-gajae dogfood response to Clawhip message `1506733276037910700` on 2026-05-20. + +509. **`write_file`/`edit_file` tool results echo full file contents (`content`, `original_file`) even though they also compute structured patches, so large file edits can double-return megabytes of text into the model context** — dogfooded 2026-05-20 from the `#clawcode-building-in-public` 19:30 UTC nudge on `/home/bellman/Workspace/claw-code-pr2967` with branch/origin `docs/roadmap-workdir-provenance@8c62fff`. Code inspection: `runtime/src/file_ops.rs::write_file` reads the prior file (if any), writes the new content, then returns `WriteFileOutput { content: content.to_owned(), original_file, structured_patch: make_patch(...) }`. `edit_file` similarly reads the full original file, computes `updated`, writes it, and returns `EditFileOutput { old_string, new_string, original_file: original_file.clone(), structured_patch: make_patch(&original_file, &updated), ... }`. The tool already has a structured patch/diff, but the serialized result still includes full pre/post content fields. Updating a 1MB file can return roughly 1MB `content` plus 1MB `original_file` plus patch metadata; a tiny `edit_file` change on a large file returns the entire original file even when a short diff would suffice. This is the file-edit sibling of the NotebookEdit/TodoWrite/REPL output-amplification cluster. **Required fix shape:** (a) make write/edit results patch-first and omit full `content`/`original_file` by default; (b) include bounded previews plus `original_bytes`, `new_bytes`, `content_truncated`, and `original_truncated` metadata when useful; (c) expose an explicit debug/full-output flag only for small files or trusted callers; (d) add regressions for editing/writing a large file proving serialized tool output remains bounded while the structured patch still identifies the change. **Why this matters:** file editing is the core coding surface. Returning full file bodies after every update wastes context, raises costs, and can force compaction precisely during code-review/debug loops where the model only needs a concise diff and path/byte metadata. Source: gaebal-gajae dogfood response to Clawhip message `1506740829912567824` on 2026-05-20.