Skip to content

feat: Cude Claw, agent modes, checkpoints and MCP - #20

Open
Emrevrg wants to merge 7 commits into
fix/audit-critical-agent-and-safetyfrom
feat/cude-claw-agent-platform
Open

feat: Cude Claw, agent modes, checkpoints and MCP#20
Emrevrg wants to merge 7 commits into
fix/audit-critical-agent-and-safetyfrom
feat/cude-claw-agent-platform

Conversation

@Emrevrg

@Emrevrg Emrevrg commented Aug 12, 2026

Copy link
Copy Markdown
Owner

Builds Cude into an agent platform rather than a one-shot task runner: an interactive session, modes with real permission boundaries, undo, and MCP.

Stacked on #19. This branch is based on fix/audit-critical-agent-and-safety, and several features here depend on that PR — Claw relies on the F2 tool-call protocol, and checkpoints complement the F5 workspace boundary. Merge #19 first; GitHub will retarget this to main automatically.

No new runtime dependencies. MCP is implemented against the protocol directly; the REPL is readline and chalk.

Feature Why it exists Tests
Cude Claw Context across turns, every edit approved claw.test.mjs — 15
Modes A permission boundary, not a prompt modes.test.mjs — 10
Project rules Standing instructions live in the repo modes.test.mjs
Checkpoints A wrong edit inside the boundary is still wrong checkpoints.test.mjs — 8
MCP Tools beyond the built-in 22 mcp.test.mjs — 17

Cude Claw — cude claw

cude run starts from nothing every time. Claw keeps the conversation, and — the part that actually matters — the user is present, so every edit can be shown before it happens.

  write_file src/api/client.ts
  edit  +12 -3
   export async function fetchUser(id: string) {
  -  return fetch(`/users/${id}`);
  +  const response = await fetch(`/users/${id}`);
  +  if (!response.ok) throw new ApiError(response.status);
  +  return response.json();
   }

  ? Apply this? [y]es / [n]o / [a]lways / [s]top:
  • A declined edit tells the model not to retry it and to ask what to do differently — otherwise it loops on the same rejected change.
  • Stopping mid-turn still answers every tool call the model made. Leaving one unanswered produces exactly the malformed conversation fix: ten defects from the end-to-end audit (F1-F10) #19's validateTurnSequence rejects; there is a test asserting the invariant holds after an abort.
  • Read-only tools are not put through the prompt — approving read_file is noise.
  • @path/to/file attaches a file's contents to your message, so naming a file doesn't cost a tool round-trip.
  • Slash commands: /mode /model /tools /mcp /rules /cost /undo /checkpoints /auto /clear /exit. Mode and model changes apply mid-conversation.

One bug worth calling out, because it only appeared when driving the real CLI end-to-end and no unit test would have found it: input originally used a short-lived readline interface per question, the way run.ts does. That drops whatever the terminal had already buffered, so the nested approval prompt waited forever for a line that had already been read. It is now one interface for the session with a queue in front of it, which behaves identically whether input is typed or piped.

Agent modes — --mode, cude modes

A mode is a system prompt plus a tool budget. The budget is the point: "answer questions about this codebase" should not be able to write to it, and a restriction that exists only in the system prompt is not a restriction.

Mode Tools
code all 22 (default)
architect 14 — reads anything, writes only Markdown
ask 12 — read-only
debug all 22
orchestrator all 22

Enforced twice: when the tool list is built for the model, and again in the agent loop before each call — so a model asking for a tool it was never offered is refused rather than obeyed. There's a regression test for exactly that, driving a stub that requests write_file in ask mode and asserting the file does not appear.

Architect's "writes only Markdown" is a path pattern checked at call time, not a claim in its description. Every other route to a source file (replace_in_file, apply_patch, move_file, delete_file, run_command) is closed off by the tool budget, and that's asserted too.

Project rules — cude rules

AGENTS.md, CUDE.md, .cuderules, .cude/rules/*.md. Discovered from the filesystem root down to the workspace root, so a monorepo-wide rule applies to a package inside it and the nearest file lands last in the prompt. AGENTS.md is deliberately first in the list — it's the convention other agent tools already read, so a repo that has one gets the behaviour for free. Capped at 32K chars so a rule file cannot crowd out the actual task.

Checkpoints — cude checkpoint

#19's workspace boundary stops the agent writing where it shouldn't. It does nothing about a wrong edit inside the boundary.

Before every mutating tool call, the prior state of the target is recorded.

cude checkpoint list                 # grouped by run
cude checkpoint restore-run <id>     # undo a whole run
cude checkpoint restore <id>         # undo one tool call
  • Whole prior file contents — wasteful for large files and completely reliable, which is the right trade for an undo path. Files over 5MB are recorded as uncaptured rather than silently appearing restorable.
  • A file the agent created is deleted on restore, not left behind.
  • move_file captures both ends.
  • Works without git, and never touches git if present — an agent run is not a commit, and hijacking someone's index or stash to implement undo would be worse than not having it.
  • cude run prints the undo command when the run changed anything. Pruned to the last 20 runs.

There's a test asserting every tool that writes files is in the checkpoint map — a mutating tool missing from it produces an unreversible edit, silently.

MCP — cude mcp

Model Context Protocol servers, over stdio and HTTP.

cude mcp add files --command npx -- -y @modelcontextprotocol/server-filesystem .
cude mcp add docs --url https://example.com/mcp
cude mcp test
  • Written against the protocol, not the SDK. The client half of MCP is four JSON-RPC calls, and the constraint here was no new runtime dependencies.
  • Tools are namespaced mcp__<server>__<tool> so a server cannot shadow a built-in, and dispatch through executeTool — so mode budgets, checkpoints and the agent loop treat them like any other tool, for free.
  • Servers are independent: one that fails to start is reported and skipped rather than taking the run down. Requests time out instead of hanging. Every server is stopped when the run ends — a stdio child would otherwise hold the CLI open, and there's a test for that.
  • mcp add connects and lists the server's tools before saving, so a typo is caught at configuration time rather than mid-run.
  • Modes that promise read-only don't receive arbitrary external tools (allowMcp is false for ask and architect) — a third-party server's tools are unknown, so handing them to a read-only mode would make its promise false.
  • ~/.cude/mcp.json uses the same mcpServers shape as other MCP clients, so an existing configuration copies across unchanged.

Tests drive a real stdio MCP server (test/helpers/mcp-stub-server.mjs) rather than a mock. That caught three bugs that a mock would have sailed past:

  1. shell: true on Windows with an unquoted command path — C:\Program Files\nodejs\node.exe split at the space.
  2. Passing an args array alongside shell: true (Node's DEP0190) — nothing escapes it.
  3. The namespace separator was mcp__ rather than __, producing mcp__probemcp__echo.

Verification

npm run build       clean
npm run type-check  clean
npm run lint        0 errors, 16 warnings — identical to main
npm run test:only   130 passing (80 after #19, 24 before), 0 failing

Claw was also driven end-to-end through the real CLI against a scripted server: the edit is previewed, approved, applied, the session exits 0, and stderr stays clean.

Not included

Deliberately left out rather than half-built — each needs its own design pass:

  • Sub-agents / task delegation. orchestrator mode currently works through sub-tasks in one context. Spawning isolated child agents is a different thing and needs a context-passing story first.
  • Streaming token output in Claw. The tool-calling path is non-streaming today; making it stream means reworking chatWithTools across 13 providers.
  • Custom user-defined modes from .cude/modes/*.md. The built-in five cover the common shapes; user modes need a permission-declaration format that can't be used to hand ask mode write access by accident.

🤖 Generated with Claude Code

Emrevrg and others added 7 commits August 12, 2026 09:30
… files

A mode is a system prompt plus a tool budget. The budget is the part that
matters: "answer questions about this codebase" should not be able to write to
it, and a restriction that lives only in the system prompt is not a
restriction.

Modes: code (everything), architect (reads anything, writes only Markdown), ask
(read-only), debug, orchestrator. The allow-list is applied twice — once when
building the tool list handed to the model, and again in the agent loop before
each call, so a model asking for a tool it was never offered is refused rather
than obeyed. Architect's "writes only Markdown" claim is enforced by a path
pattern, not just asserted in its description.

Project rules: AGENTS.md, CUDE.md, .cuderules and .cude/rules/*.md are
discovered from the filesystem root down to the workspace root — so a monorepo
rule applies to packages inside it and the closest file wins — and appended to
every agent system prompt, capped so a rule file cannot crowd out the task.

  cude run "task" --mode architect
  cude modes list | cude modes show ask
  cude rules

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The workspace boundary stops the agent writing where it shouldn't; it does
nothing about a wrong edit inside the boundary. Before every mutating tool call
the prior state of the target is now recorded, so a bad run can be put back.

- Whole prior file contents are snapshotted — wasteful for large files and
  completely reliable, which is the right trade for an undo path. Files over
  5MB are recorded as uncaptured rather than silently appearing restorable.
- A file the agent created is deleted on restore, not left behind; move_file
  captures both ends.
- Works without git, and never touches git if present: an agent run is not a
  commit.
- AgentResult exposes runId; `cude run` prints the undo command when the run
  changed anything.

  cude checkpoint list | show <id> | restore <id> | restore-run <id> | clear

Old checkpoints are pruned to the last 20 runs at the start of each run.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Written against the protocol rather than the official SDK, because the client
half of MCP is four JSON-RPC calls and Cude ships no runtime dependency it does
not need.

- Two transports: stdio (a local server run as a child process, newline-framed
  JSON-RPC) and HTTP (one POST per request, accepting a JSON body or an SSE
  stream).
- Tools are namespaced mcp__<server>__<tool>, so a server cannot shadow a
  built-in tool and the model can see where a capability came from. They
  dispatch through executeTool, so mode budgets, checkpoints and the agent loop
  treat them like any other tool.
- Servers are independent: one that fails to start is reported and skipped
  rather than taking the run down. Requests time out instead of hanging, and
  every server is stopped when the run ends — a stdio child would otherwise
  hold the CLI open.
- Modes that promise read-only do not receive arbitrary external tools
  (allowMcp is false for ask and architect).
- ~/.cude/mcp.json uses the same "mcpServers" shape as other MCP clients, so an
  existing configuration copies across unchanged.

  cude mcp list | test | add <name> --command … | --url … | remove | disable

`mcp add` connects and lists the server's tools before saving, so a typo is
caught at configuration time rather than mid-run.

Tests drive a real stdio MCP server (test/helpers/mcp-stub-server.mjs) rather
than a mock, which is how two Windows spawn bugs and a namespacing bug in this
commit were caught.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Cude Claw keeps context between turns, where `cude run` starts from nothing
each time. The difference that matters is not the prompt — it is that the user
is present, so every edit can be shown and approved before it happens, the mode
and model can change mid-conversation, and cost is visible as it accumulates.

- Edits are previewed as a diff and approved individually: yes / no / always /
  stop. A declined edit tells the model not to retry it, and stopping mid-turn
  still answers every tool call the model made — leaving one unanswered would
  make the next request malformed, which the F2 invariant rejects.
- Read-only tools are not put through the approval prompt.
- @path in a message attaches that file's contents, so naming a file does not
  cost a tool round-trip.
- Slash commands: /mode /model /tools /mcp /rules /cost /undo /checkpoints
  /auto /clear /exit. Mode changes take effect immediately, including taking
  the write tools away when switching to ask.
- Every applied edit is checkpointed under the session id, so /undo reverts the
  whole session.

Input goes through one readline interface with a queue in front of it. A
short-lived interface per question drops whatever the terminal had already
buffered, which left the nested approval prompt waiting for a line that had
already been read — caught by driving the real CLI end-to-end, not by the unit
tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The bottom notch was drawn with the group's round line cap, so each leg bulged
half a stroke-width (21 units) past its end point. That nearly doubled their
visible length — 11% of the mark's height against the artwork's 5.8% — and made
them read as two feet hanging off the mark rather than a split through its
bottom vertex. The legs now use butt caps.

The CLI block art and the README screenshot had both drifted from the artwork:

- tools/generate-logo.mjs rendered the SVG in Chromium, which CI deliberately
  never downloads and which cannot be installed here at all, so the art could
  not be regenerated. The mark is three stroked polylines with round joins, so
  its inked region is a closed form — tools/mark-raster.mjs samples it directly,
  no browser needed.
- --write had never worked on Windows: it anchored its regex on "\n" and a
  Windows checkout has CRLF, so it always reported that it could not find
  LOGO_ART. That is why the art could go stale unnoticed.
- The bounding box was hand-measured in a comment. It is now computed, because
  changing a cap changes the box and getting it wrong shifts the whole mark.
- assets/cude-cli.png was a hand-captured screenshot showing the old mark, feet
  and all. It is now assets/cude-cli.svg, generated from the same LOGO_ART the
  CLI prints, with the art drawn as rectangles so a viewer whose monospace font
  lacks quadrant blocks does not see tofu.

Publishes the new banner (assets/cude-banner.png) and keeps the reference
artwork (assets/cude-mark.png) so the SVG can be checked against it.

test/brand.test.mjs asserts the CLI art equals what the SVG reduces to, that
the legs use butt caps and keep the artwork's proportions, that the mark stays
open on its right side, and that every image the README points at exists.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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