DEV Community

小蝦 (craypi)
小蝦 (craypi)

Posted on

Your agent reads whole files because that is all anyone gave it

Every coding agent I know pays the same tax: to change one function it reads the whole file. On a large
repository that tax is the budget — context spent on code the agent will not touch, and a diff it cannot
review because it never saw the file's shape.

I built the structural half of the answer and made it boringly safe: opencrayast
— an MCP server (plus a CLI on the same core) that lets an agent work on source code as a syntax tree:
outline a file, fetch exactly one function, search by syntactic shape, and edit only through a plan a human
can read first.

MIT, Rust, prebuilt binaries for Linux, macOS and Windows. Six tier-1 languages: Rust, TypeScript, TSX,
JavaScript, Python, Go.

What it actually did, on a file I know

All numbers below are from opencrayast 0.20261017.1 (the published release), MCP over stdio, against a real
Rust file of mine — cdp.rs, 3,786 lines / 155,016 characters.

Outline instead of the file.

ast_outline crates/cray-tools/src/cdp.rs -> 17537 chars, 169 lines
crates/cray-tools/src/cdp.rs  rust  3786 lines
  struct CdpBrowser L50-53  pub struct CdpBrowser
  impl std for CdpBrowser L55-59  impl std::fmt::Debug for CdpBrowser
    method fmt L56-58  fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
  fn refuse_ssrf_navigate L117-148  fn refuse_ssrf_navigate(url: &str) -> Result<(), BrowserError>
  ...
Enter fullscreen mode Exit fullscreen mode

17.5 KB of skeleton for a 155 KB file — about 11%, with line ranges, signatures and nesting. The agent
can decide what to open instead of opening everything.

One function, not the file. ast_get with symbol: "act_js" returned 4,549 characters: the doc comment,
the signature and the body, with its exact line range. That is the unit of work for most edits.

Search by shape, not by text. Patterns capture and name the parts:

ast_search {"pattern": "json!($A)", "paths": ["crates/cray-tools/src/cdp.rs"], "limit": 5}
Found 5 matches in 1 files for: json!($A)
crates/cray-tools/src/cdp.rs:591:17-591:67  json!({ "expression": js, "returnByValue": true })  $A = { "expression": js, "returnByValue": true }
Enter fullscreen mode Exit fullscreen mode

That is "every call of this form, structurally", which a regex cannot do and a language server will not do.

It says what it is allowed to do. ast_info is the agent's first call:

opencrayast 0.20261017.1 (mode: read-only)
limits: file 4 MiB · output 64 KiB · results 200 · plan 50 files / 500 edits
write: disabled (needs BOTH --allow-write AND policy.allow_write = true …)
Enter fullscreen mode Exit fullscreen mode

And in that mode the write tools are not in the list at all — tools/list returns eight read tools, not
eleven. Calling ast_edit_apply anyway is an unknown-tool error that explains the two gates, rather than a
permission prompt you get tired of clicking.

The part that matters more than the parsing

Most AST servers are good at searching. The hard part is letting an agent change code without trusting
the agent
:

  • Preview, then apply. ast_edit_preview stores a plan: the exact edits, a diff and a content-addressed plan id. edit apply writes byte-for-byte what was previewed — it does not re-run the search.
  • Two gates for writing. The operator must start the server with write mode and the operator configuration must allow it. Neither alone is enough.
  • Journaled and undoable. Every apply is journaled; edit undo puts the file back; a crash mid-write is recoverable. Journals hold the only copy of your original files, so they live in the user state directory — never inside your repository — and nothing deletes undo history as a side effect of someone else's apply.
  • One boundary. Every path goes through one small, heavily tested component. Source files are treated as hostile input: size, wall-clock, depth and node-count budgets.
  • No daemon, no shell, no network. There is no socket to authenticate and nothing is ever executed.

opencrayast doctor is honest about the machine it is on:

ok     workspace writable yes
ok     languages    6 available: rust (tier 1), typescript (tier 1), tsx (tier 1), javascript (tier 1), python (tier 1), go (tier 1)
warn   write mode   disabled: only reading commands are allowed (pass --write and set policy.allow_write = true)
Enter fullscreen mode Exit fullscreen mode

What it is not, yet

The README lists the gaps instead of hiding them, and I would rather repeat them here:

  • Pre-1.0. No stable tool-catalogue freeze; the version scheme is 0.YYYYMMDD.N.
  • The isolated parse worker is not implemented. Parsing is designed to run with no filesystem or network access; today the size/time/depth/node budgets are the only bound on a hostile file.
  • It is structural, not semantic. It knows the shape of the tree, not types or cross-file references. The intended workflow pairs it with a language server (one finds candidates by shape, the other confirms meaning, then diagnostics check the result).
  • Writing on Windows uses temp-file plus rename: atomic, but no directory fsync.

Try it

curl -fsSL https://raw.githubusercontent.com/amgio38/opencrayast/main/install.sh | sh
claude mcp add opencrayast -- opencrayast-mcp --workspace .
Enter fullscreen mode Exit fullscreen mode

Repo, docs, security model, test strategy and release notes: https://github.com/amgio38/opencrayast —
MIT licensed, pre-1.0, issues and dissent welcome.

Top comments (0)