autocode — one file, one tool, and permission to rewrite itself Empero-org released autocode, a coding agent contained in a single Python file of about 640 lines that ships with exactly one tool, bash, and is permitted to rewrite its own source code while working. The runner copies runner.py into each project, checks between steps whether the file changed, keeps the previous version in .autocode/runner.prev.py, and re-execs itself if the new version compiles, while any file in .autocode/tools/ defining a SCHEMA and run() function is picked up on the next step. The agent compacts context when it passes 80% of the window, and the release notes a pre-release bug in which some OpenAI-compatible servers ignored tool_choice: "none" and let a one-line preamble become the entire new context. autocode — one file, one tool, and permission to rewrite itself autocode is out — a coding agent that fits in a single Python file, ships with exactly one tool, and is allowed to edit its own source while it works. No dependencies, no framework: the least an agent needs, if it gets to build the rest. Agent frameworks have a habit of growing. Every release adds a tool, every tool adds a schema, every schema adds a paragraph to the system prompt, and before long the model spends its first few thousand tokens reading instructions about instructions. Abacus sits deliberately at the full-featured end of that: plans, approvals, subagents. autocode is the experiment at the other end — what is the least an agent needs, if it is allowed to build the rest itself? The answer we shipped: one Python file, one tool, the standard library, and permission to rewrite itself. autocode on GitHub: empero-org/autocode https://github.com/empero-org/autocode One file, and it's yours Install once, then run autocode in a project. The first run copies runner.py into that directory and starts it. That file is the whole agent — the loop, the streaming client, sessions, compaction, the REPL — in about 640 lines, with no imports outside the standard library. It is copied, not imported, on purpose. The agent is told where its own source lives and that it may improve it. Between steps the runner checks whether runner.py changed; if the new version compiles, it saves the session, keeps the previous version in .autocode/runner.prev.py , re-execs itself and carries on with the same task. If it doesn't compile, the model is told about the syntax error and the working version keeps running. So every project ends up with its own agent, shaped by the work done there. autocode --diff shows how far it has drifted from the one we shipped. autocode --reset puts it back. One tool The model gets bash . Nothing else. Commands run exactly as written — no wrappers, no rewriting, no parser second-guessing the model. The runner only does what a shell can't do for itself: a timeout that kills the whole process group, a closed stdin so nothing waits for a human, a working directory that persists between calls, and long output cut to its head and tail with the full text saved to a file the agent can page through. Search, edit, test, git — everything a model reaches for is already a command away. Tools it writes itself When a shell isn't enough, the agent writes a tool. Any file in .autocode/tools/ that defines a SCHEMA and a run function is picked up on the next step. A tool that fails to import doesn't disappear: it shows up to the model marked BROKEN , with the error attached, so the agent can fix what it wrote. The tools are plain Python in your repository — worth committing, if they turn out to be good. A system prompt you can read in ten seconds Three lines. Who it is and where; work until the task is done and verify as you go; your own source is editable; here is how to add a tool. Plus the project's AGENTS.md , if there is one. That's the whole prompt. Everything else the model knows about your project, it found out with the shell. Long sessions, honestly A small agent still runs long sessions, so compaction had to be right. When the context passes 80% of the window, the model summarizes the conversation and the summary replaces it — except your newest message and the latest step, the last model message with its tool results, which stay word for word. The summary request reuses the exact prefix of a normal request, so providers with prompt caching reuse the cache. We found a bug in it before release, and it's worth telling. The first version asked for the summary with tool choice: "none" . Some OpenAI-compatible servers ignore that field, so a model can answer "summarize this" with a tool call and a one-line preamble — and that preamble became the entire new context. Against a scripted fake server, a session's request and its failing test output were replaced by "Let me look at the test file first." Now a reply that isn't a summary is rejected and retried as a plain transcript with no tools to call, a failed compaction never blocks the session, and the full transcript is archived before anything is replaced. There's a test for exactly that case. Not a feature. A fix we'd rather tell you about than have you find. Any endpoint The first run opens a short setup. It marks the backends it can already use — a key in your environment, a local server that's running — lists the server's models, runs a one-line test call and saves the config. OpenAI, OpenRouter, DeepSeek, Groq, a Qwythos on Ollama or llama.cpp: if it speaks /chat/completions , autocode runs on it. Thinking is read from whichever field your server uses - reasoning content , reasoning , OpenRouter's reasoning details , or