One binary, the whole protocol
ago runs a per-workspace daemon over a typechecked snapshot. Queries answer in milliseconds. Mutations validate against the in-memory type graph before anything touches disk.
$ ago --help
semantic edit protocol for Go: query the typechecked workspace, submit compiler-checked mutations
Usage:
ago [command]
reads and setup:
help the versioned op catalog: args, examples, ceilings
init scaffold an agent-first module: MCP wiring, AGENTS.md
inspect kind, signature, position, doc for one symbol
query semantic questions by --kind (callers, implementations, ...)
refs every reference, tests included
search name fragment to exact symbol addresses
status load or refresh the snapshot: packages, files, errors
view declaration as annotated text with node handles
mutations (validated before anything touches disk):
add-param add a parameter, call sites updated with --default
patch ordered multi-op edit, atomic and generation-checked
rename rename a symbol, every reference proven to resolve
set-body replace a function body, typechecked first
test go test, scoped, structured pass/fail
upsert add or replace a whole declaration
lifecycle:
daemon run the workspace daemon in the foreground
mcp serve the MCP tools over stdio
skill install or print the embedded agent skill
Additional Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
Flags:
-C, --dir string workspace directory (default ".")
-h, --help help for ago
Use "ago [command] --help" for more information about a command.
Rejections are data, not errors
A bad edit changes nothing on disk and comes back as the compiler's own diagnostics, ready for the next attempt. The agent loop is query → mutate → (rejection → adjust → retry).
{
"status": "rejected",
"reason": "edit does not typecheck",
"diagnostics": [
{"pos": "config.go:114:29", "msg": "undefined: slices"},
{"pos": "config.go:9:2", "msg": "\"reflect\" imported and not used"}
]
}
One handle per statement
Edits address the compiler's own syntax tree, not lines of text. The tree below is parsed with go/parser from the op catalog's set_body example on every build. Every node is real.
func Double(v int) int { return v + v }
Claims
An accepted edit introduces no new compiler diagnostic. Problems the code already had never block it; they are measured up front and reported separately as
pre_existing.A rejected patch changes nothing: disk, snapshot, or generation.
Rename and move prove every rewritten reference still points at the intended object. A reference captured by shadowing is rejected even when the compiler would accept it.
Patches apply whole or not at all; a patch built on a stale generation is rejected.
Queries see accepted mutations immediately. A patch that reshapes one declaration gets that declaration's fresh view back in the response, so the next edit needs no extra call; multi-declaration patches say why the view was omitted (
views_omitted).Edits made outside the protocol are detected on the next request and trigger a full reload.