jrswab/axe: single-purpose LLM agents defined in TOML and run from the shell
A lightweight cli for running single-purpose AI agents. Define focused agents in TOML, trigger them from anywhere; pipes, git hooks, cron, or the terminal.
At a glance
- What is it?
- Axe is a Go CLI that runs one focused LLM agent per TOML file, reads stdin, writes stdout, and leaves scheduling to cron, git hooks or pipes. This review covers the config layout, the Docker hardening, the memory model and where the design gets in the way.
- Who is it for?
- Adopt axe if you want one binary, TOML files under version control, and agents that behave like Unix filters rather than chat sessions. Skip it if your work needs a scheduler, a GUI, or a long interactive context, because the README states axe is the executor and not the scheduler.
- Can I use it commercially?
- Yes. Apache-2.0 is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 15 days ago.
- What is it written in?
- Mainly Go, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on September 30, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The problem axe targets: agents as Unix filters, not chat sessions
The README opens with a direct complaint about the prevailing shape of AI tooling: most of it assumes you want a chatbot, a long-running session with a massive context window doing everything at once. Axe's answer is to treat an agent the way Unix treats a program. One agent, one job, defined in a TOML file, invoked from the command line.
That framing decides who the tool is for. If you already reach for cron, git hooks, pipes and file watchers, axe slots in as the thing those systems call. The README is explicit that axe is the executor, not the scheduler, and that it is meant to be composed with standard Unix tools rather than reinventing workflow orchestration. If you were hoping for a daemon that watches a queue and retries failed jobs across restarts, this is not that project, and the README does not pretend otherwise.
The practical consequence is that an agent becomes a file you can review in a pull request. Its system prompt, model choice, skill references and context files all live in one declarative document. That is a different bet from prompt libraries embedded in application code, and it is the reason the TOML format matters more here than the LLM call itself.
How an axe agent is assembled at run time
Axe reads an agent definition from TOML and resolves it into a request. According to the README, each agent carries its own system prompt, model selection, skill files, context files, working directory, persistent memory, and the ability to delegate to sub-agents. The executor walks that list, loads what it needs, and issues the call through the configured provider.
Provider support is deliberately broad: Anthropic, OpenAI, Ollama for local models, OpenCode, and AWS Bedrock. The go.mod file confirms the dependency story the README advertises. Direct requirements are BurntSushi/toml, the modelcontextprotocol/go-sdk, spf13/cobra and golang.org/x/net. Everything else is indirect. LLM calls are made with the standard library rather than a vendor SDK per provider, which is why the dependency list stays at four.
Two mechanisms are worth separating. First, agent discovery: axe auto-discovers agents from a local directory at axe/agents/ under the current working directory before falling back to the global config, and the --agents-dir flag points somewhere else entirely. That ordering means a repository can carry its own agents and override your personal ones. Second, delegation: agents can call other agents through LLM tool use, with depth limiting and parallel execution. Depth limiting is the guardrail that stops a delegation graph from recursing without bound, and the README does not specify the default depth, so that is a value to check in your own config rather than assume.
Tools are sandboxed. File operations for reading, writing, editing and listing are confined to the working directory. Shell execution, URL fetching and web search are available as built-in tools. For url_fetch and web_search there is an output allowlist that restricts calls to specific hostnames, and the README states that private and reserved IPs are always blocked as SSRF protection. That last point is a real design decision, not a checkbox: it means an agent cannot be pointed at an internal service by a prompt that talks it into it.
Installing axe and running a first agent against a git diff
Axe requires Go 1.25 or newer if you build or install from source. The README also points to pre-built binaries for Linux, macOS and Windows on the GitHub Releases page, which is the path to take if you would rather not manage a Go toolchain.
Install with the Go toolchain and confirm the binary is on your path:
# Requires Go 1.25 or newer
go install github.com/jrswab/axe@latest
# If this fails with "invalid go version", your toolchain is older than 1.25
axe --helpThe README warns about one specific failure here. If the install fails with invalid go version, your Go toolchain is older than 1.25, and the fix is to upgrade from go.dev/dl or download a pre-built binary instead.
Next, create the configuration directory. The README says this writes the structure under $XDG_CONFIG_HOME/axe/ with a sample skill and a default config.toml for provider credentials:
axe config init
axe agents init my-agent
axe agents edit my-agentThen run the agent. The README's own example pipes a staged diff into a reviewer agent, which is the shape most people will copy first:
git diff --cached | axe run pr-reviewer
cat error.log | axe run log-analyzerWhat you should see is the agent's output on stdout, which means it composes with anything that reads stdout. Before spending tokens, use dry-run mode, which the README describes as inspecting resolved context without calling the LLM. That is the fastest way to find out whether your skill file path actually resolved or silently loaded nothing. For scripting, JSON output is available with metadata, and token usage can be capped per run either through a [budget] block in the agent config or the --max-tokens flag.
Running axe in Docker, and why the container is locked down
The repository ships a Dockerfile and a docker-compose.yml, so container use is a first-class path rather than an afterthought. The image is built from golang:1.25-alpine and runs on alpine:3.21 with ca-certificates added. The binary is compiled with CGO_ENABLED=0 and trimmed with -trimpath and -ldflags="-s -w".
The runtime container is where the intent shows. It creates a non-root user with UID 10001, sets HOME and the XDG variables, and declares USER axe before the entrypoint. The compose file goes further: read_only: true on the filesystem, a tmpfs mounted at /tmp/axe with a 10m size limit, cap_drop: ALL, and no-new-privileges:true.
docker build -t axe .
docker run --rm -i \
-v ./my-config:/home/axe/.config/axe \
-e ANTHROPIC_API_KEY \
axe run pr-reviewerOne behaviour is easy to trip over. The README states that without a config volume mounted, axe exits with code 2, a config error, because no agent TOML files exist. That is a sane failure, but it means a container that appears to start and immediately stop is not necessarily broken. It found no agents.
Memory needs its own volume. Agent memory persists across runs only when you mount a data volume at /home/axe/.local/share/axe, which the README shows as a named volume in the persistent data example. Skip it and every run starts cold. The compose file defines two profiles, cli and ollama, so the Ollama sidecar is opt-in; the axe service points at it through AXE_OLLAMA_BASE_URL=http://ollama:11434 and depends on the ollama service being started. Running docker compose run --rm axe run my-agent gives you the cloud-provider path without the sidecar.
Persistent memory and its garbage collector
Memory is timestamped markdown logs that carry context across runs. The format choice is deliberate: you can read and diff the memory of an agent with the same tools you use on any other text file, and it lives under the XDG data directory rather than in a database.
Axe also ships memory garbage collection, described as LLM-assisted pattern analysis and trimming. That is an unusual feature and it deserves scrutiny. Trimming memory with an LLM means the thing deciding what to forget is itself a model, and the README does not document what guarantees exist about what gets removed. If an agent's memory contains something you cannot afford to lose, this is a component to test on a copy before pointing it at a live memory directory.
The interaction with Docker is the practical constraint. Memory only survives a container run if the data volume is mounted, as noted above. In a hardened, read-only container without that volume, each invocation is stateless, which is fine for a diff reviewer and wrong for anything that is supposed to accumulate judgement over time.
Where axe is the wrong tool
The README's own framing is the clearest limitation. Axe is the executor, not the scheduler. There is no queue, no retry-across-restart story, and no workflow engine. Configurable retry exists, with exponential, linear or fixed backoff for transient provider errors such as 429, 5xx and timeouts, but that retry lives inside a single run. If the process dies, the run dies with it. Anything that needs durable job state has to get it from the system calling axe.
Interactive work is the second mismatch. The design assumes you pipe data in and get results out. There is no chat loop described in the README, and persistent memory is the mechanism for continuity between runs, not a conversation. If your task is exploratory, where you want to steer the model turn by turn, axe adds a config file between you and the model for no benefit.
Token budgets are a guard, not a cost control. A [budget] block or --max-tokens caps cumulative token usage per agent run, which stops a runaway delegation loop from spending without limit, but it does not tell you what a run costs in currency, and the README does not describe any accounting output beyond the JSON metadata.
Finally, memory garbage collection and sub-agent delegation are the two features most likely to surprise you in production. Both involve a model making decisions about scope, and neither has documented rollback in the README.
How axe differs from agent frameworks and from calling a provider directly
The obvious alternative is a general agent framework, the kind where you write Python or TypeScript and register tools in code. The difference is where the agent definition lives. In a framework, the agent is a program with dependencies, a runtime and a deployment. In axe, the agent is a TOML file plus optional skill markdown, and the runtime is a single static Go binary. That makes the definition reviewable in a pull request and portable across machines, at the cost of expressiveness. You get the configuration surface the project chose to expose, and nothing else. There is no plugin API described in the README for adding new tool types.
The other alternative is calling the provider's HTTP API from a shell script or a small program. Axe is a superset of that: it handles provider differences, retry with backoff, the tool sandbox, the output allowlist, MCP tool connections over SSE or streamable-HTTP transport, and context assembly. If your need is a single prompt with no tools and no context files, a curl call is smaller and has no upgrade path to maintain. Axe earns its place once you have several agents, shared skills, and a memory directory you want to keep.
MCP support is worth noting because it changes the calculus. Through the modelcontextprotocol/go-sdk dependency, axe can connect to external MCP servers for additional tools, which means the tool surface is not frozen at the four built-ins. That is the escape hatch for teams that need a capability the built-ins do not cover.
Maintenance, upgrades and the Apache-2.0 licence
The last push to the repository was on 2026-07-03, and the most recent release listed is v1.10.0 on 2026-05-05, following v1.9.0 on 2026-04-25 and v1.8.0 on 2026-04-21. The repository is not archived. The release cadence visible in those three versions suggests active change, and anyone pinning axe in CI should expect to read the CHANGELOG between versions rather than assume config stability.
Upgrade cost is dominated by the Go toolchain floor. The module declares go 1.25.0, and the README repeats the requirement. That is a recent floor, so environments pinned to older Go releases cannot build axe at all. Pre-built binaries sidestep this, which is the reason the README offers them first.
Licensing is Apache-2.0, declared in the repository and repeated in the Docker image label org.opencontainers.image.licenses. Apache-2.0 is a permissive licence with an explicit patent grant and a notice requirement, which matters if you redistribute the binary inside a product. That is a description of the licence, not legal advice; the terms you are bound by are in the LICENSE file, and your own counsel is the right place to resolve questions about notice files and derivative works.
The dependency surface is small enough to audit. Four direct dependencies, all LLM calls through the standard library, and the Makefile exposes exactly three targets: test, lint and check, where check runs lint then test. If you build from source, go test ./... and golangci-lint run are the two commands that tell you whether your checkout is healthy.
Editorial conclusion
Adopt axe if you want one binary, TOML files under version control, and agents that behave like Unix filters rather than chat sessions. Skip it if your work needs a scheduler, a GUI, or a long interactive context, because the README states axe is the executor and not the scheduler. Before relying on it, run axe run --dry-run on your own agent to confirm which context and skill files actually resolve, and check that your Go toolchain is 1.25 or newer.
Frequently asked questions
How do I install jrswab/axe?
The README gives two routes: pre-built binaries for Linux, macOS and Windows on the GitHub Releases page, or go install github.com/jrswab/axe@latest, which requires Go 1.25 or newer. You can also clone the repository and run go build . in the project root.
Can jrswab/axe schedule agents on its own?
No. The README states that axe is the executor and not the scheduler, and that it is designed to be composed with cron, git hooks, pipes and file watchers rather than reinventing scheduling. Retry with backoff exists, but only for transient provider errors within a single run.
Which LLM providers does jrswab/axe support?
The README lists Anthropic, OpenAI, Ollama for local models, OpenCode, and AWS Bedrock. Provider credentials go in the config.toml created by axe config init, and the Docker examples pass keys such as ANTHROPIC_API_KEY as environment variables.
Does jrswab/axe keep memory between runs?
Yes, through timestamped markdown logs under the XDG data directory, and the README notes that memory garbage collection uses LLM-assisted pattern analysis and trimming. In Docker, memory only persists if you mount a volume at /home/axe/.local/share/axe.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/jrswab-axe)