Expand description
§2 module 29 formatters (COMPOSABLE-HARNESS-DESIGN.md line 479):
“D10/oc§10 format-on-write” — reuses the EXACT crate::tools::WriteObserver
seam P5-9 built for checkpoint (D-5: “write-path interception seam
shared with checkpoint”), rather than a second interception point.
§C10 (design line 534) — the critical correctness rule
“Post-write formatting invalidates the model’s file memory; formatter
must diff-back into the result (oc§10).” Concretely: once a formatter
rewrites a file the model just wrote/edited, the model’s IN-CONTEXT
belief about that file’s bytes is stale. FormatObserver::after_write
(when [capabilities.formatters] diff_back = true, the default) returns
a unified diff of exactly what the formatter changed, appended to the
calling tool’s result — so the model’s next action is informed by the
ACTUAL on-disk bytes, not its own pre-format draft. diff_back = false
still runs the formatter (the file changes) but withholds the
annotation — the C10-UNSAFE mode, legal but never the default.
§Wire model — stdin -> stdout filter
A configured formatter is invoked as command args... with the
JUST-WRITTEN file’s bytes piped to its stdin; its stdout (bounded,
MAX_FORMATTER_OUTPUT_BYTES) becomes the new file content IF the
process exits 0 and produces non-empty output that differs from the
input. This is the standard “formatter as filter” contract real tools
already support in this mode (gofmt reads stdin/writes stdout by
default; rustfmt --emit stdout; prettier --stdin-filepath <name>;
black -), and it needs no %f-style path-templating in the config
schema — the weakest form that still composes with arbitrary real
formatters. A non-zero exit, a timeout, or empty output is treated as
“formatter had nothing useful to say” and never corrupts the file: the
on-disk content from the write/edit tool is left exactly as that tool
produced it.
§Ordering (composes with checkpoint + lsp on the shared seam)
crate::agent::build_tool_context installs observers in the order
checkpoint -> formatters -> lsp via
crate::tools::WriteObserverChain: checkpoint’s before_write
captures the pre-image before ANY mutation; this module’s after_write
reformats the just-written file; lsp’s after_write (running AFTER
this one in the same chain) then reads the FINAL, formatted file for
diagnostics — never the model’s pre-format draft.
Structs§
- Format
Observer - The
crate::tools::WriteObserver[capabilities.formatters]installs.before_writeis a true no-op (format-on-write only ever acts AFTER a mutation).after_writeruns the configured formatter (if any matchespath’s extension), rewrites the file when the formatter’s output differs from what was just written, and — whenSelf::diff_backistrue— returns a unified diff annotation so the calling tool’s result stays truthful about the file’s final bytes (C10). - Formatter
Spec - One
[capabilities.formatters.<name>]entry — a user-configured formatter command, invoked as a stdin->stdout filter (see module doc comment).command/argsare config-borne code execution (D-10) — stripped from an untrusted project layer exactly like[capabilities.lsp.servers.*]/hooks/mcp.servers.
Constants§
- DEFAULT_
FORMATTER_ TIMEOUT_ SECS - Default per-invocation timeout — a hanging formatter can’t hang the write-path loop (build brief: “timeout + kill like hooks”).
- MAX_
DIFF_ CHARS - Bound on the unified diff text appended to a tool result — a formatter
that rewrites a huge file can’t blow the model’s context with a huge
diff either (same bounded-annotation posture as
crate::lsp’s diagnostics cap). - MAX_
FORMATTER_ OUTPUT_ BYTES - Bound on a single formatter invocation’s stdout — a hostile or broken
configured formatter can’t force unbounded memory growth (same
rationale as
crate::mcp::MCP_MAX_RESPONSE_BYTES).
Functions§
- observer_
for_ config - Build the
FormatObservera freshcrate::Agentshould install, given a resolvedcrate::Config— called once, fromcrate::agent::build_tool_context.Config::formatters_enabledis the ONE gate:false(the default) returnsNone— no formatter ever runs, byte-identical to before this module existed.