Skip to content

(MOT-3873) feat: native code mode for the harness#423

Open
ytallo wants to merge 26 commits into
mainfrom
feat/code-mode
Open

(MOT-3873) feat: native code mode for the harness#423
ytallo wants to merge 26 commits into
mainfrom
feat/code-mode

Conversation

@ytallo

@ytallo ytallo commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Native code mode for the harness, consolidated from the stacked branch train (#409#422) into one reviewable PR.

Shell

  • str_replace op on coder::update-file (exact-literal, unique-match guard) and coder::context (workspace snapshot: roots, platform, git, instruction files)
  • coder::apply-patch (V4A patch format, all-or-nothing) + coder::worktree-add / coder::worktree-remove
  • post_write_checks: config-sourced report-only diagnostics appended to write results
  • Write journal (bounded before-images per workspace) + coder::undo (by steps or turn; undo-of-undo = redo) + coder::checkpoints

Harness

  • Mode::Code: coding system prompt, native exposure of the coder/shell surface, per-turn <environment> block
  • GPT-family code prompt selection; provider-less sends resolve the provider from the router catalog
  • harness::spawn gains isolation:"worktree" (child works on an isolated branch and commits; parent gets a disposition note)
  • harness::todo session checklist (full-replace semantics, live console card, TodoWrite-shaped payloads accepted)
  • Dispatch loop runs consecutive read-only calls concurrently (mutations and shell exec stay sequential; at-most-once checkpointing preserved); code prompts push batching and ride-along todo updates

Provider (openai-codex)

  • coder::apply-patch emitted as the freeform apply_patch custom tool — raw patch text on the wire, custom_tool_call SSE handling

Approval gate

  • Structured auto-scoped read-only coder allows in the default rules
  • Excerpts keep coder write payloads unclipped (secret redaction intact, 64 KiB budget) so approval cards render real diffs

Console

  • code mode picker; coder views for apply-patch diffs, check badges, context/worktree cards; live todo checklist card
  • Checkpoints dialog: write-journal history grouped by turn, one-click undo/redo
  • Default thinking level → medium

Fixes MOT-3873
Fixes MOT-3874
Fixes MOT-3875
Fixes MOT-3876
Fixes MOT-3877

ytallo added 21 commits July 5, 2026 15:20
- coder::update-file gains { op: "str_replace", old_str, new_str,
  replace_all? }: exact-literal replacement desugared onto the existing
  content-op machinery (escaped-literal regex + $$-escaped replacement).
  Without replace_all the op requires exactly one occurrence and fails
  C210 (nothing written) on 0 or N matches with prescriptive recovery;
  replace_all replaces every occurrence (>=1 required).
- new coder::context: one-call workspace snapshot (allowed roots,
  platform, bounded git branch/status/log, AGENTS.md / CLAUDE.md
  instruction files capped at 16 KiB) for seeding coding sessions;
  git state degrades to null on non-repo roots or missing git binary.
- wire-schema goldens regenerated; catalog grows to 10 functions.
…dentity

- new send/spawn mode "code": replaces the mesh identity prompt with a
  dedicated coding identity (prompts/code.txt) — read-before-edit,
  str_replace edit discipline, verify-via-tests, no-commit-unless-asked.
- code mode without an explicit functions policy defaults to the curated
  coding surface natively exposed (coder::*, shell::exec/exec_bg/kill/
  status); an explicit caller policy always wins, and spawned children
  still narrow through subset_policy.
- code-mode turns fetch coder::context once per turn (jail-scoped via the
  trusted fs_scope stamp) and append a rendered <environment> block (root,
  platform, git state, AGENTS.md/CLAUDE.md) to the system prompt; an
  unavailable coder surface logs and proceeds without the block.
- send/spawn wire schemas gain the "code" mode enum value (goldens).
Adds 'code' to the mode picker. A code-mode send omits the explicit
functions policy so the harness applies its curated native coding
toolset, and swaps in the coding identity prompt; the working-dir
fs_scope metadata is unchanged.
…rimary

The workspace snapshot ignored the harness-stamped filesystem scope and
always described the configured primary root, so a scoped session's git
state and instruction files were missing. The effective root is now the
canonicalized fs_scope root (session_root confinement) when present, and
instruction files resolve through resolve_opt against it.
Adds the apply_patch (V4A) format codex-family models are trained on:
*** Begin/End Patch with Add/Delete/Update File hunks, @@ context
markers, optional Move to, and End of File pinning. seek_sequence and
the replacement engine are ported from OpenAI's codex-rs apply-patch
crate (Apache-2.0) so context matching (exact -> rstrip -> trim ->
unicode-normalised) matches the reference; the parser is a compact
re-implementation pinned by the reference test cases.

All-or-nothing: every hunk resolves through the jail and computes
before anything is written (context mismatch fails C210 with re-read
guidance, filesystem untouched); writes land atomically per file and
modified files return a bounded echo of the first changed region.
Git worktree lifecycle for isolated sub-agent workspaces: worktree-add
creates .worktrees/<name> on branch wt/<name> from the effective root's
HEAD (fs_scope-aware, name-validated, requires a git work tree);
worktree-remove is clean-only — uncommitted changes keep the worktree
in place and are reported dirty, and the wt/<name> branch is deleted
only when fully merged so unlanded child work survives for the caller
to merge. Catalog grows to 13.
- spawn options gain isolation: "worktree" (dispatch path): the child
  gets its own git worktree via coder::worktree-add before seeding, its
  fs_scope root points at it, and the worktree rides the parent's call
  checkpoint. On child completion (including sweep-expired children,
  which route through the same deliver path) the harness removes the
  worktree clean-only and appends a disposition note to the parent's
  result: merge wt/<name>, inspect a dirty tree, or nothing to do.
- code mode selects a per-family identity: openai/openai-codex route to
  prompts/code-gpt.txt (apply_patch edit discipline); openai-codex now
  maps to the Gpt prompt family. Both code prompts gain a delegation
  section (spawn + worktree isolation + merge guidance).
- code_mode_policy adds harness::spawn to the native surface.
Shipped default rules gain structured, auto-scoped allows for the
read-only coder functions (context / info / read-file / search / tree /
list-folder), so an auto-mode coding session prompts on writes and exec
instead of every read. Manual mode is unaffected — mode-scoped rules do
not match there — and writes (update-file, apply-patch, create/delete/
move, worktree-*) plus shell::exec still gate in every mode.

default_rule_values() is now the single source of truth for both the
compiled default specs and the configuration schema default, and the
rules schema items widen to oneOf string|structured so the shipped
structured defaults pass configuration::register validation.
…writes

CoderConfig gains post_write_checks ({match_glob, command, timeout_ms},
default empty, hot-reloads like every coder knob): after a successful
coder::update-file / create-file / apply-patch write, each configured
check whose glob matches a written file's root-relative path runs ONCE
per call (deduplicated by command) via /bin/sh -c with the effective
root as cwd, and its bounded (4 KiB) output lands in the response's
top-level checks array. A failing, timing-out, or unrunnable check
never fails the edit — it is feedback for the caller to act on.
Config-sourced on purpose: workspace files must not choose commands.

Also promotes the fs_scope effective-root convention into
PathResolver::effective_root (context/worktree/checks now share it),
and both harness code prompts tell the model to read the checks array.
The code prompt's no-commit rule left isolated children's worktrees
dirty, so clean-only removal kept them and the parent had nothing on
wt/<name> to merge. A worktree spawn now appends an explicit commit
instruction to the child's task — the sanctioned exception that makes
the branch the handoff artifact.
…atch

- harness: a send (or parentless spawn) that omits provider resolves it
  from the router's model catalog before options freeze, so prompt-family
  selection and routing agree — headless codex sends now get the
  apply_patch code identity without passing provider explicitly. Router
  unreachable → warn and fall back to the previous default-family
  behavior.
- provider-openai-codex: coder::apply-patch travels as a freeform custom
  tool named exactly apply_patch — the trained name and shape. The wire
  emits { type: custom }, and the SSE state machine accumulates
  custom_tool_call raw input (delta + authoritative done) into
  { patch: <raw text> }, so patches cross the wire with no JSON escaping.
  Harness and shell still see an ordinary coder::apply-patch call.
Every mutating coder call (update-file, create-file, delete-file, move,
apply-patch) records bounded before-images into a per-root journal
(journal.dir / max_bytes 64MiB / max_records 500, hot-reloading,
oldest-first eviction, raw-byte blob sidecars — binary-safe). The
harness fs_scope stamp gains session_id/turn_id so records attribute to
the turn that wrote them.

coder::undo reverts the last N records or everything a turn changed:
restores run newest-first, every path re-validates through the LIVE
jail, files that did not pre-exist are removed, and the undo journals
its own pre-undo state first — undoing an undo redoes the change.
Unrecoverable gaps (oversized images, directory deletes/moves) are
reported in skipped, never silently ignored. coder::checkpoints lists
the journal (newest first) as the undo picker. Catalog grows to 15.
Full-list-replace todo semantics for multi-step coding runs: at most 50
items / 500 chars, exactly one in_progress, persisted to the
harness_todo state scope and mirrored into custom session entries
(type "todo", fresh entry id per update — session::append is a no-op
on repeated ids). In-turn calls get the CALLING session injected at the
invocation chokepoint, so a model can never write another session's
list. code_mode_policy gains harness::todo and both code prompts teach
the discipline; the console renders the latest todo entry as a live
checklist card (done-count header, status glyphs).
router::models::get requires a provider hint, so a provider-less send
resolved to None (default prompt family) — scan router::models::list
by model id instead.
Models trained on TodoWrite-shaped todo tools send { todos: [{ content,
status }] }; serde aliases (todos->items, content->text) accept both
spellings, extra fields like activeForm are ignored as before.
A live code-mode session spent ~30 of 66 minutes on standalone
harness::todo round trips (40 updates x ~45s of reasoning each) and
most call batches carried a single call. Function execution itself
measured milliseconds; the cost is round trips.

- Both code prompts now require todo updates to ride along with the
  next action calls (never a standalone message) and push the model to
  batch independent calls into one message.
- The dispatch loop runs in three phases: sequential policy/pre-hook/
  checkpoint pass, concurrent invoke for consecutive parallel-safe
  calls (read-only coder surface + bookkeeping via join_all), then
  in-order post-hook/append/done. Mutations and shell::exec stay
  strictly sequential. All Triggered checkpoints land in ONE durable
  write before any invoke, preserving at-most-once recovery.
The 256-char excerpt clip made held coder writes unreviewable — a
clipped patch can't render as a diff in the approval card. Excerpts are
now function-aware: the five coder write functions keep their full
arguments (secret-key redaction still applies) under a 64 KiB budget
that falls back to classic clipping; everything else clips as before.
…g default

Add a checkpoints dialog to the chat working-dir footer that lists the
shell's file-mutation journal newest-first, grouped per turn, and reverses
a group with one coder::undo call. Records authored by coder::undo are
labeled "revert" and offer "redo". The trigger is disabled until a working
directory is set. Also default the composer's thinking level to medium.
@vercel

vercel Bot commented Jul 6, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jul 6, 2026 1:01pm
workers-tech-spec Ready Ready Preview, Comment Jul 6, 2026 1:01pm

Request Review

@coderabbitai

coderabbitai Bot commented Jul 6, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@ytallo, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 42 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 92720ca6-c38d-4794-b1a4-1bd19e4cda88

📥 Commits

Reviewing files that changed from the base of the PR and between 14a4260 and fbb95e9.

⛔ Files ignored due to path filters (1)
  • harness/Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (98)
  • approval-gate/src/config.rs
  • approval-gate/src/functions/gate.rs
  • approval-gate/src/permissions/default_rules.rs
  • approval-gate/src/permissions/mod.rs
  • approval-gate/src/redact.rs
  • console/web/src/components/chat/ChatView.tsx
  • console/web/src/components/chat/CheckpointsDialog.test.tsx
  • console/web/src/components/chat/CheckpointsDialog.tsx
  • console/web/src/components/chat/Message.tsx
  • console/web/src/components/chat/coder/ApplyPatchView.tsx
  • console/web/src/components/chat/coder/ChecksView.tsx
  • console/web/src/components/chat/coder/CoderDiff.tsx
  • console/web/src/components/chat/coder/ContextView.tsx
  • console/web/src/components/chat/coder/CreateFileView.tsx
  • console/web/src/components/chat/coder/UpdateFileView.tsx
  • console/web/src/components/chat/coder/WorktreeView.tsx
  • console/web/src/components/chat/coder/__tests__/ApplyPatchView.test.ts
  • console/web/src/components/chat/coder/__tests__/checksContextWorktreeViews.test.ts
  • console/web/src/components/chat/coder/__tests__/parsers.test.ts
  • console/web/src/components/chat/coder/index.tsx
  • console/web/src/components/chat/coder/parsers.ts
  • console/web/src/lib/backend/coder-checkpoints.test.ts
  • console/web/src/lib/backend/coder-checkpoints.ts
  • console/web/src/lib/backend/harness-send.ts
  • console/web/src/lib/backend/real.ts
  • console/web/src/lib/sessions/entry-mapper.ts
  • console/web/src/stories/fixtures/coder-fixtures.ts
  • console/web/src/types/chat.ts
  • harness/Cargo.toml
  • harness/prompts/code-gpt.txt
  • harness/prompts/code.txt
  • harness/src/clients/router.rs
  • harness/src/deferred.rs
  • harness/src/filesystem_scope.rs
  • harness/src/functions/function_trigger.rs
  • harness/src/functions/mod.rs
  • harness/src/functions/send.rs
  • harness/src/functions/spawn.rs
  • harness/src/functions/subscribe.rs
  • harness/src/functions/todo.rs
  • harness/src/ids.rs
  • harness/src/policy.rs
  • harness/src/prompt/family.rs
  • harness/src/prompt/mod.rs
  • harness/src/prompt/mode.rs
  • harness/src/prompt/tests.rs
  • harness/src/prompt/variants.rs
  • harness/src/state.rs
  • harness/src/subagent.rs
  • harness/src/surface.rs
  • harness/src/trigger.rs
  • harness/src/turn_loop.rs
  • harness/src/types/turn.rs
  • harness/tests/golden/schemas/harness.send.json
  • harness/tests/golden/schemas/harness.spawn.json
  • harness/tests/golden/schemas/harness.todo.json
  • harness/tests/schemas.rs
  • provider-openai-codex/src/sse.rs
  • provider-openai-codex/src/wire/names.rs
  • provider-openai-codex/src/wire/tools.rs
  • shell/src/code/checks.rs
  • shell/src/code/config.rs
  • shell/src/code/functions/apply_patch.rs
  • shell/src/code/functions/checkpoints.rs
  • shell/src/code/functions/context.rs
  • shell/src/code/functions/create_file.rs
  • shell/src/code/functions/delete_file.rs
  • shell/src/code/functions/info.rs
  • shell/src/code/functions/mod.rs
  • shell/src/code/functions/move_file.rs
  • shell/src/code/functions/undo.rs
  • shell/src/code/functions/update_file.rs
  • shell/src/code/functions/worktree.rs
  • shell/src/code/journal.rs
  • shell/src/code/mod.rs
  • shell/src/code/patch.rs
  • shell/src/code/path.rs
  • shell/src/fs/host.rs
  • shell/src/fs/mod.rs
  • shell/tests/code_apply_patch.rs
  • shell/tests/code_context.rs
  • shell/tests/code_golden_errors.rs
  • shell/tests/code_golden_schemas.rs
  • shell/tests/code_lifecycle.rs
  • shell/tests/code_path_jail.rs
  • shell/tests/code_post_write_checks.rs
  • shell/tests/code_undo.rs
  • shell/tests/code_update_ops.rs
  • shell/tests/code_worktree.rs
  • shell/tests/common/world.rs
  • shell/tests/golden/schemas/coder.apply-patch.json
  • shell/tests/golden/schemas/coder.checkpoints.json
  • shell/tests/golden/schemas/coder.context.json
  • shell/tests/golden/schemas/coder.create-file.json
  • shell/tests/golden/schemas/coder.undo.json
  • shell/tests/golden/schemas/coder.update-file.json
  • shell/tests/golden/schemas/coder.worktree-add.json
  • shell/tests/golden/schemas/coder.worktree-remove.json
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/code-mode

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

# Conflicts:
#	harness/src/functions/spawn.rs
#	harness/src/subagent.rs
@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 33 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

…te-aware times

Dialog-initiated undos were journaled without turn attribution, so
their revert rows could only be redone while newest (every older redo
button rendered dead). The dialog now stamps each undo with a unique
console-undo turn id, making every revert row targetable. Timestamps
gain a date prefix once a record is from another day — time-only
rendering made yesterday's entries read as mis-sorted.
…o "now"

The silent model-key migration stamped updatedAt on every conversation
whose stored model was missing from the current catalog, so a catalog
change made the whole sidebar read "now" and scramble on each refresh.
Migration keeps the model rewrite but leaves updatedAt untouched —
ordering stays user-activity-driven.
# Conflicts:
#	console/web/src/hooks/use-conversations.ts
The write journal is per-workspace-root and shared by every session
using that directory, so the dialog listed other chats' (and CLI)
records under a description promising "this conversation". Records now
filter by the conversation's session id, dialog undos stamp session
attribution so their redo records stay in view, and undo always targets
a turn — a global steps-undo from a filtered list could have reverted
another session's newest change.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant