How mugen works end to end
mugen is an AI agent in one chat that never ends. It is UniiChat written in Bend 2. It runs in your terminal, full screen, and remembers everything. By default it runs on your Claude plan (Pro or Max) through the official claude command.
Starting mugen
mugen comes as a Nix flake: nix run github:hhefesto/mugen runs it once, and adding the flake's mugen package to your system or home packages puts mugen in every shell. It needs Claude Code's claude command. Sign Claude Code in once with mugen login; mugen checks the sign-in every time it starts. On NixOS, a shell that was open before the switch that installed mugen (tmux keeps such shells) has the old environment: run . /etc/set-environment there first.
# once: signs Claude Code in (claude auth login)
mugen login
mugen
export ANTHROPIC_API_KEY=... mugen --api
mugen takes the whole terminal, as an editor would, and gives it back as it was when you leave: Esc Esc, C-d, C-c on an empty input, or /quit. Anything still running when you quit carries on at the next start. It opens with a status and the last messages of the chat, so after a restart you see where you were.
無限 mugen one chat that never ends account (your Claude email) · Max plan, on Claude Code limits 5-hour 0% (resets 16:20) · weekly 11% (resets 2026-10-27) · as of 12:25 ❯ fix the typo in notes.txt ✻ The user wants the typo fixed. I will read the file, then edit the one line. ▸ read ~/notes.txt [lines 1-4 of 4] │ one │ tow │ … +2 lines (click to open) ▸ edit ~/notes.txt +1 −1 │ @@ -1,4 +1,4 @@ │ 1 one │ 2 - tow │ 2 + two │ 3 three ● Fixed: tow is now two. · cache: 3.9k read, 595 written, 2 new in, 30 out ────────────────────────────────────────────────────────────── ⠼ mugen works · 12s · ▸ shell 9s nix build .#mugen ⧗ 1 waiting: «and run the tests after» · C-s sends now opus-5.5 · medium · 87% left · auto λ █
What you see
The chat fills the top. Each entry is a block: your message after ❯, mugen's reply after ● (with its code blocks, headings, bullets and code set apart), its summarized thinking after ✻, notes after ※, an agent's report after ◆.
- A tool call is one block. Its header says the tool, what it was asked (a shell's command, a file's path) and how it went on the right:
✓ 0.4 s,✗ exit 1 · 0.2 s,✂ cut,■ stopped, orrunning…. Under it, the first lines of its output. - An edit or a write shows its diff, with the line numbers of the file, each taken-out line on a red background and each added one on a green one (so too the lines of a
```diffblock in a reply), long lines wrapped under their own text. - Blocks come open, whole. Click a block to fold it, click again to open it; its header stays where it is. M-o (or C-o, where tmux does not take it as its prefix) folds or opens all of them.
- Scroll with PgUp, PgDn and the mouse wheel. While you are scrolled up, new entries don't move what you read; the rule says how far below the end is, and M-> (or End on an empty input) follows it again.
Under the chat, a rule, then what runs: a spinner, what mugen does and for how long, the tool it is running and for how long (a stuck command shows here at once), and the agents at work. Then the messages waiting for mugen, or handed to its running call, then the status line: where mugen works, the model, its effort, the context left before mugen folds the oldest lines of the view, and the mode. Last, the input.
xclip, if it is there) and to tmux's buffer (which tmux hands the terminal too). A click that doesn't move still opens or folds a block. To select with the terminal instead, hold Shift while you drag (most terminals), or type /mouse to hand the mouse to the terminal, and /mouse again to take it back.In tmux
mugen works in tmux as it does outside it, with nothing to set up: the mouse and PgUp/PgDn reach it whether tmux's mouse option is on or off. As it starts in tmux, mugen turns tmux's extended-keys on (unless it is set), so that Shift-Enter can reach it as a key of its own; that is a server option and lasts while the tmux server does. It names its window mugen, shows mugen … while mugen works, and rings the window's bell when a reply comes while you look at another window.
Whether Shift-Enter comes through also depends on the terminal under tmux: kitty, foot, WezTerm, Ghostty and iTerm2 send it; GNOME Terminal and other VTE terminals don't. Alt-Enter is the new line that works everywhere; C-j too reaches mugen only where the terminal sends it apart from Enter.
Talking to it
| Keys | What they do |
|---|---|
| Enter | Sends the message. While mugen works it waits, and the line under the chat says so; it shows in the chat once mugen reads it. |
| Alt-Enter | A new line in the message (Shift-Enter and C-j too, where the terminal sends them apart from Enter). A paste comes whole, its new lines kept, as one message. |
| C-s | Sends the waiting messages now (/now). If mugen is running a shell command, the command is cut: mugen gets its output so far, then your messages. Otherwise its turn stops, and a new one starts with them. |
| C-g | Stops mugen's turn (/stop). |
| Esc Esc | Leaves mugen. So do C-d and C-c on an empty input; C-c on text clears it (C-y brings it back). |
| PgUp PgDn, the wheel | Scroll the chat. M-< goes to its start, M-> back to its end. |
| a click, M-o (C-o) | Opens or folds a block; all of them. |
| ↑ on an empty input | While a message waits for mugen (until its next tool call or its turn's end), takes it back to edit: only the version sent at last is logged and shown. |
| a click on a tab, M-← M-→ | While subagents work, tabs follow the status line: all, mugen, each agent. The chat shows only that one's blocks. |
| C-a C-e C-b C-f M-b M-f | Start and end of the line; back and forward a character, a word. |
| C-k C-u C-w M-d C-y | Cut to the line's end, to its start, the word before, the word after; paste what was cut. |
| ↑ ↓ C-p C-n | Move between the lines of a message; past its first or last line, the history (kept in ~/.mugen/history). |
| C-r | Searches the history as you type; C-r again finds an older match, C-g goes back. |
| Tab | Completes a command, a model, an effort. |
| C-l | Draws the screen anew. |
| Type | What it does |
|---|---|
/stop [name] | Stops mugen's turn and the agents, or the one agent named. |
/now | As C-s: the waiting messages, sent at once. |
/goal TEXT | A goal mugen keeps working toward. After each turn a check asks whether it is met; until it is, mugen is told to keep working. /halt drops it. |
/model LABEL | opus-5.5 (default), sonnet-5.5, haiku-5.5, or a model id. |
/effort LEVEL | low, medium (default), high, xhigh, max. |
/plan, /auto | Plan mode: mugen reads and proposes a plan, and runs no shell, writes and edits nothing, starts no agents. Auto mode (the default) lets it act. The mode shows on the status line. |
/status | The opening status again: account, limits, chat and view, models, mode, work dir, goal, agents. |
/image PATH | The image at PATH (png, jpeg, gif or webp; /image alone: the clipboard's) goes with the message being written: a line [image docs/images/NAME] in it names it. The model sees it with that message; zoom(id, 1) shows it again. |
/mouse | The mouse to the terminal (its own selection), or back to mugen. |
/tree, /export | The whole memory tree as a page, ~/.mugen/tree.html; the memory as ./mugen-memory.json. |
/help, /quit | The commands and keys; leave, killing every process the tools started. |
Subagents
mugen can spawn subagents, one per task, which run in parallel. Each one is its own claude -p call. It sees the view as it was at the spawn, plus its own log, and works in ~/.mugen/work/<Name>. Its tool calls show in the chat, marked [Name]. When it finishes, its report arrives as a ◆ block, and that starts a turn. mugen can tell an agent more, and zoom("Name") opens an agent's whole chat.
What happens when you send a message
One computation owns the chat: the hub. Another owns the terminal: the screen. Everything else (the call to the model, the summaries, each subagent) is a worker that reports back to the hub. Here is one turn, in order.
Your message is logged
It goes into
~/.mugen/main/2026-10-21.jsonlword for word, with its number and date. Nothing in the log is ever changed.mugen waits for the summaries to catch up
Every message before yours needs its one-line summary first. This is usually instant; the line under the chat says when it isn't.
It builds the request
mugen's prompt, plus your
rules.mdif you have one. Then the view of the chat: the whole conversation as one-line summaries. Then which devices are open (each computer where mugen runs, by host name) and which agents are at work. Last, your message:Turn: the user's new message:and your text.It starts
claude -pin the backgroundOne process per turn, in its own process group, signed in with your plan's login. The request goes in on its stdin and its events come out as JSON, one per line, thoughts summarized. mugen reads them as they arrive.
Claude Code runs the model and calls mugen's tools
The tools
zoom,date,spawn,tell,shell,read,writeandeditcome from mugen over MCP. When the model calls one, the screen shows it running; mugen runs it on the device the call names, in its work dir (~/.mugen/work/mainfor the chat), logs the call and its result, and hands the result back. Web search is Claude Code's own tool.Messages you type meanwhile wait their turn
The line under the chat shows them. mugen hands them to the running call, which reads them with the next tool result; if no tool call comes, they start the next turn in the same process, so the history is still cached. C-s doesn't wait: it cuts a running shell command so its result comes at once, or stops the turn and starts one with your messages.
The answer ends the turn
mugen's reply is logged and shown with the cache line, and
claudeexits. The reply is now part of the chat, and the summaries pick it up.
/stop) kills the turn's whole process group, including a shell job it started, and the chat logs "Stopped by the user". Leaving kills every group the tools started. If mugen itself dies any other way (killed, crashed, its terminal closed), a watchdog kills those groups within a second, a watchdog in each call ends claude, and the next start kills whatever an earlier one still left: no command outlives mugen.Log, tree and view
The chat never ends, so it can't all fit into one request. mugen keeps three things:
- The log. Every message as it was, forever. The kinds are
user,unii(mugen's replies),toolandecho(a tool call and its result),work(a subagent's report) andnote. - The tree. A binary tree of lines of at most 512 bytes. Each message has a leaf: a short message is its own line, and a longer one gets a summary from Haiku. Above that, every pair of neighbors is merged into one line, and so on up.
- The view. What each turn sees: 64 to 128 KB of lines from the tree that cover the whole chat. Recent messages appear one line each. Older stretches appear as one line for many messages.
Eight messages and their tree. id+n means the n messages starting at message id. The highlighted lines form a view: one line for the oldest four messages, one for the next two, and the last two on their own. Together they cover every message once.
When mugen needs detail, it calls zoom on a line. zoom({"id": 0, "n": 4}) opens 0+4 into its two halves, and n = 1 gives one message whole. That's how an old conversation comes back without sitting in every request.
The view grows only at its end. Once it passes 128 KB, a batch merges its oldest lines until it is back near 64 KB. Between batches, everything already in the view stays exactly the same, and that is what makes caching work.
Claude Code or the API
The memory, the tools and the request layout are the same on both. They differ in who runs the tool loop and whose account pays.
Claude Code
- Runs the official
claude -p, signed in with your Claude plan. Anthropic allows plan logins only in its own apps, which is why mugen callsclaudeinstead of using the login itself. - Claude Code runs each turn's tool loop and calls mugen's tools over MCP.
- Each summary is one Haiku call through
claude, at most 8 at a time. - Usage counts against your plan's limits, summaries included.
- Cache marks last an hour.
- Turns, agents and goal checks are saved as Claude Code transcripts.
Messages API
- Calls the API with curl.
ANTHROPIC_API_KEYis read from the environment and never appears in a command line. - mugen runs the tool loop itself, exactly as UniiChat does.
- Up to 48 summaries at a time.
- Billed per token to your API account.
- Cache marks last five minutes, the API's default.
- Not yet tried against the live API.
MUGEN_WIRE=api does the same as --api.
Why most of each request is cached
Anthropic caches the start of a request. If the next request starts with exactly the same text, that part is read back from the cache instead of being processed again. On the API a cache read costs about a tenth of fresh input. Each request mugen sends looks like this, top to bottom:
The API allows four cache marks per request. Claude Code places three. mugen places one: on the view's last full block of four lines. On the next turn, everything up to that block is still the same, because the view only grows at its end. That whole stretch is read from the cache.
- Read from the cache
- 3,911 tokens
- Written to the cache
- 595 tokens
- Uncached input
- 2 tokens
That chat was tiny. As the view fills toward 64 to 128 KB, the cached part grows with it, while the new part stays about one message long. Within a turn, each step of the tool loop also reads everything before it from the cache.
What starts the cache over
- More than an hour without a turn. On your plan, the marks last an hour, and every read renews them.
- A batch. When the view passes 128 KB and is merged back down, the next turn writes the new view once.
- A different model.
/modelstarts a new cache, since each model has its own.
mugen also keeps Claude Code's own context out of the prompt: your CLAUDE.md files, its memory, git status, attachments and tool search. These would change from call to call and break the cached prefix.
Where the record goes
mugen's own log is the complete record. On the Claude Code backend there can be two more, kept the way Claude Code keeps them:
- Claude Code sessions. Each turn, agent call and goal check is saved as a Claude Code session in
~/.claude/projects/-home-you--mugen-work-main/(Claude Code names the folder after the work dir), or a folder per agent. Your user settings are loaded for these calls, so your hooks (a transcript renderer, say) run on them like on any other session. - Debug logs. If
MUGEN_DEBUG_DIRnames a directory, each of those calls writes Claude Code's debug log there, named like2026-10-21_1122-mugen-main-0261e97f.debug.log. Unset (the default), there are none.
Summaries leave no transcript, since there can be hundreds a day. MUGEN_TRANSCRIPTS=all keeps them too, and none turns transcripts off. If your claude is a wrapper that writes logs of its own, point MUGEN_CLAUDE at the real binary, so summaries don't each leave one.
What lives in ~/.mugen
~/.mugen/ main/2026-10-21.jsonl the log: one message per line, by day tree/2026-10-21.jsonl the summaries main.idx tree.idx indexes into both view.json the view, the model, the goal, what is running docs/agent/<Name>.jsonl each subagent's log work/main work/<Name> where the tools run rules.md your standing instructions (optional) history what you sent, for ↑, C-p and C-r run/ the running calls' files limits.json the plan's limits, as the last call reported them main.cc.out the current turn's events from claude main.cc.mcp.in / .out tool requests and answers main.cc.in claude's stdin (a pipe) main.pid main.cc.pid the running shell command's group, claude's groups every group the calls started: pid and start time lock one mugen per data dir: its pid and tmux pane
The log and tree use UniiChat's own format, so UniiChat can open mugen's chat and the other way around.
Environment variables
| Variable | Default | What it sets |
|---|---|---|
MUGEN_CLAUDE | claude | The Claude Code CLI to run: a name looked up on PATH, or a path. |
MUGEN_DEBUG_DIR | Where chat calls write Claude Code's debug logs; unset, they write none. | |
MUGEN_TRANSCRIPTS | chat | Which calls are saved as sessions: chat, all, none. |
MUGEN_WIRE | api does the same as --api. | |
MUGEN_PLAIN | 1: no full screen; lines as they come and a λ prompt (also when TERM=dumb, as in Emacs' shell). | |
MUGEN_DIR | ~/.mugen | The chat's data dir. Another dir is another chat, which another mugen can hold at the same time. |
MUGEN_WORK | ~/.mugen/work | Where the tools run, a dir per agent. |
MUGEN_PICK | opus-5.5:medium:slow | The chat's model, effort and speed, until /model or /effort. |
MUGEN_COMPACTOR | haiku-5.5:high:slow | The summaries' model and effort. |
MUGEN_JOBS | 8 / 48 | Summaries at once: 8 on Claude Code, 48 with --api. |
MUGEN_RULES | ~/.mugen/rules.md | Your instructions, added to mugen's prompt. |
MUGEN_SYNC | 1 | Flush each log write to disk. |
Using mugen from your phone
mugen needs no web page. The phone connects to the same terminal you use at your computer, over a private Tailscale network, so nothing opens to the LAN or the internet:
mosh keeps the session alive through sleep and network changes, and tmux keeps mugen running between visits, so you pick up exactly where you left off; a reply that came meanwhile has rung tmux's bell. On the phone, Alt-Enter (or a \ before Enter) makes a new line.
Run Tailscale, ssh and mosh on the computer
On NixOS:
services.tailscale.enable(withextraSetFlags = [ "--operator=you" ]to runtailscalewithout sudo),services.openssh.enableandprograms.mosh.enable, with mosh's UDP ports 60000–61000 open ontailscale0only. The README has the whole block.Sign the computer and the phone in to the same tailnet
Run
tailscale upon the computer and sign in with the account the phone will use; sign the Tailscale app in with the same one. In the admin console, approve the machine if it asks and disable its key expiry. Its MagicDNS name is its hostname.Give the computer the phone's key
Create a key in the ssh app (Blink on iOS, Termux on Android) and add its public key to your
authorizedKeys.Connect
mosh you@HOSTNAME,tmux new -A -s main,mugen. To land in tmux on every login, add to your shell's init:[ -z "$TMUX" ] && [ -n "$SSH_TTY" ] && exec tmux new -A -s main.
Common problems
- "mugen: ~/.mugen is open already, by mugen 1234 in tmux pane %3"
- A data dir is one chat, and one mugen holds it. Go to that one (
tmux switch-client -t %3, as the message says), quit it, or run a second, separate chat beside it withMUGEN_DIR=~/.mugen-2 mugen. - mugen seems stuck
- The line under the chat says what runs and for how long: a shell command that never ends shows its time growing. C-s cuts it and sends what you typed; C-g stops the turn.
- Shift-Enter sends instead of making a new line
- The terminal sends it as a plain Enter (GNOME Terminal and other VTE terminals do). Use Alt-Enter.
- "mugen: cannot run claude (MUGEN_CLAUDE)"
- mugen can't find Claude Code. Install it and check that
claude --versionworks, or setMUGEN_CLAUDEto its path. On NixOS, a shell from before the switch may lack the newPATH: run. /etc/set-environment. - "Claude Code is not signed in"
- Run
mugen login. At startup, mugen stops and tells you to. During a chat, run it in another window; the chat goes on with your next message. - "The AI service is busy: asking again in N s."
- The API is overloaded or your plan has hit a limit. A turn is retried a few times and then reports that the service is busy. Summaries keep retrying every minute, so they only get delayed.
- "No reply: stopped: refusal. Send a message to try again."
- The model declined that turn. Messages you sent during it wait for the next turn.
- The terminal looks wrong after mugen was killed
- mugen gives the terminal back on every way out but
kill -9. After one, typeresetand Enter.