Burin compass: choose safer edit tools
Harn ships a set of AST-precise edit primitives — see the structured refactorings cookbook. They pay off when structural addressing, parse validation, or semantic-neighbor updates prevent a real failure mode. For an exact localized change, a hash-guarded text patch can be simpler and equally safe.
The burin compass helps specialized coding agents make that choice. It is
a built-in
system-reminder provider, compass_ast_edits,
that injects a standing reminder at session start (and on resume):
Choose the simplest safe edit mechanism for each change. Use the AST-precise tools when structural addressing or semantic reach materially reduces risk; use
edit_safe_text_patchfor exact localized changes even when grammar support is available. Preview risky or multi-operation plans withedit_dry_run.
Both the reminder and routing layer ship opt-in. Unlike the other canonical providers —
which are conditional and stay silent until they have something to say
(project facts, a workspace anchor, token pressure) — a steer toward code
edits is only wanted in code-editing sessions, not in every sub-agent or
one-shot loop. So coding-agent personas and configs turn it on
explicitly; once enabled it reaches every agent surface that runs the
Harn agent loop — TUI, IDE, cloud-supervised. The reminder is marked
preserve_on_compact, so the guidance survives a context compaction and
keeps steering through a long session.
The active routing layer
The reminder is the steer; the tool-rewrite router is the active half of the compass (#2612). It is a per-tool-call hook in the agent loop that observes a freeform edit before it runs and acts on it. It sits at the single chokepoint every agent surface funnels through — after permission and pre-tool hooks, before the tool is dispatched — so it reaches the TUI, the IDE, and cloud-supervised loops with no per-surface wiring.
It recognises a freeform / whole-file edit on a parseable source file:
- a
str_replace-shape call ({path, old_text, new_text}or ahunksarray), - a whole-file
write_file/create_file, - a single-hunk edit on a rename-capable language that reads like a symbol rename.
For anything else — a structural call, a file with no tree-sitter grammar, an arg shape it doesn't understand — the router is inert and the call dispatches unchanged. It is conservative by construction: it never touches a call it cannot reason about.
It runs in one of two modes:
suggest(the default after enabling Compass) — advisory. The router injects a one-turn system reminder naming the structural primitive the edit maps to (edit_apply_nodefor a node,edit_rename_symbolfor a rename, or the hash-guardededit_safe_text_patch), then dispatches the original call unchanged. The model stays in control; nothing is rewritten.rewrite— silent substitution, but only when the structural form is provably equivalent to the freeform call. The one substitution the router can prove without reading the file is a raw text replace →edit_safe_text_patch: identicalold_text → new_textmatcher, but with a stale-base hash guard and staged-fs atomicity. A rename or a whole-file write is not byte-equivalent (a project-wide rename touches other files; a whole-file write rewrites untouched bytes), so the router falls back to a suggestion rather than guess.
Observability
Every decision increments a harn.compass.* counter via the standard
counter(...) instrument, tagged with harn.compass.persona,
harn.compass.tool (the freeform tool), and harn.compass.target (the
structural tool):
harn.compass.suggested— an advisory routing decision fired.harn.compass.rewritten— a call was silently substituted.harn.compass.fell_back—rewritemode considered a substitution but could not prove equivalence, so the original freeform call ran.
The router also emits a live compass_routing_decision agent event before
dispatch. ACP surfaces it on _harn/agentEvent with toolCallId, mode,
action (suggested, rewritten, or fell_back), persona,
originalTool, routedTool, targetTool, and path when the edit call
named a file. The event carries routing metadata only; it does not include
old or new file contents.
These surface in eval dashboards as the agent-loop edit-reliability signal.
Why a reminder, not a hard rewrite
In suggest mode the compass steers rather than rewrites. A reminder
keeps the model in control: it can choose an exact text patch when that is
the simplest safe fit, and the router never silently changes the bytes a tool
call would produce. That makes the behaviour predictable and auditable — the
reminder is visible in the transcript like any other system reminder.
rewrite mode is opt-in for exactly this reason: it only ever substitutes a
provably-equivalent call.
Turning it on
The compass is a normal reminder provider, so the standard controls apply:
- Enable it for a session or persona via the reminder config:
reminders.providers.compass_ast_edits = true. It is registered as a canonical provider but shipsdefault_enabled: false, so this opt-in is what activates the steer. - Inspect it alongside the other canonical providers —
compass_ast_editsappears in the provider metadata listing with the purpose of helping coding agents choose the safest edit primitive for each change.
Configuring the router (escape hatches)
The router is off by default. Enable and configure it with the compass
option passed alongside the agent-loop tools:
- omitted,
compass: false, orcompass: null— off. Edit calls dispatch with no observation, reminder, or counter. compass: true— enable advisorysuggestmode.compass: {enabled: false}orcompass: {mode: "off"}— equivalent off switch in dict form.compass: {mode: "suggest"}— advisory reminders only.compass: {mode: "rewrite"}— silent substitution of provably-equivalent calls, with a fall-back-to-suggest safety net.compass: {prefer: ["edit_apply_node", ...]}— a persona's ordering hint. The router consumes theedit_strategy.preferlist a persona already declares (e.g.personas/fixer/manifest.harn), so a persona that prefers node-level editing nudges a plain hunk edit towardedit_apply_nodein its suggestion. When nocompass.preferoverride is set the router readsedit_strategy.preferdirectly.
The model can always ignore an advisory suggestion. A
suggest reminder never blocks or alters the call, and even a rewrite
only ever swaps in a behaviourally identical patch — so a deliberate
localized text edit, unsupported file, or one-off raw write is always
available.
Composes with
- Structured refactorings cookbook — the tools the compass points at.
- Rename a symbol cookbook — the cross-file rename the compass calls out by name.
- System reminders — the delivery mechanism.