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.

Start it

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.

On your Claude plan (the default)
# once: signs Claude Code in (claude auth login)
mugen login
mugen
On the Messages API, with a key
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.

your input, after the lambda what runs and what waits a call that went well, lines added a call that failed, lines taken out
The screen

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, or running…. 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 ```diff block 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.

Selecting text. The mouse goes to mugen, for clicks, the wheel and selecting: drag over the chat and the text shows reversed; let go and it is copied, to X's clipboard (by 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.

Keys and commands

Talking to it

KeysWhat they do
EnterSends 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-EnterA 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-sSends 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-gStops mugen's turn (/stop).
Esc EscLeaves 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 wheelScroll 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 inputWhile 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-fStart and end of the line; back and forward a character, a word.
C-k C-u C-w M-d C-yCut to the line's end, to its start, the word before, the word after; paste what was cut.
↑ ↓ C-p C-nMove between the lines of a message; past its first or last line, the history (kept in ~/.mugen/history).
C-rSearches the history as you type; C-r again finds an older match, C-g goes back.
TabCompletes a command, a model, an effort.
C-lDraws the screen anew.
TypeWhat it does
/stop [name]Stops mugen's turn and the agents, or the one agent named.
/nowAs C-s: the waiting messages, sent at once.
/goal TEXTA 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 LABELopus-5.5 (default), sonnet-5.5, haiku-5.5, or a model id.
/effort LEVELlow, medium (default), high, xhigh, max.
/plan, /autoPlan 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.
/statusThe opening status again: account, limits, chat and view, models, mode, work dir, goal, agents.
/image PATHThe 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.
/mouseThe mouse to the terminal (its own selection), or back to mugen.
/tree, /exportThe whole memory tree as a page, ~/.mugen/tree.html; the memory as ./mugen-memory.json.
/help, /quitThe 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.

One message

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.

  1. Your message is logged

    It goes into ~/.mugen/main/2026-10-21.jsonl word for word, with its number and date. Nothing in the log is ever changed.

  2. 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.

  3. It builds the request

    mugen's prompt, plus your rules.md if 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.

  4. It starts claude -p in the background

    One 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.

  5. Claude Code runs the model and calls mugen's tools

    The tools zoom, date, spawn, tell, shell, read, write and edit come 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/main for the chat), logs the call and its result, and hands the result back. Web search is Claude Code's own tool.

  6. 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.

  7. The answer ends the turn

    mugen's reply is logged and shown with the cache line, and claude exits. The reply is now part of the chat, and the summaries pick it up.

Stopping and leaving. C-g (or /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.
The memory

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), tool and echo (a tool call and its result), work (a subagent's report) and note.
  • 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.
0+8
0+4
4+4
0+2
2+2
4+2
6+2
0+1
1+1
2+1
3+1
4+1
5+1
6+1
7+1

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.

0+4|user asked how mugen caches; unii explained the view and the marks … 4+2|user ran echo mugen-live; unii reported the output … 6+1|user: what did I ask you to run, and what did it print? 7+1|unii: You asked me to run `echo mugen-live`, and it printed `mugen-live`.

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.

Two backends

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.

Default

Claude Code

mugen
  • Runs the official claude -p, signed in with your Claude plan. Anthropic allows plan logins only in its own apps, which is why mugen calls claude instead 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.
Flag

Messages API

mugen --api
  • Calls the API with curl. ANTHROPIC_API_KEY is 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.

Caching

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:

Tool definitions (mugen's tools, web search)same every turn
Claude Code's identity lineClaude Code
mark · Claude Code
mugen's prompt and your rulessame every turn
mark · Claude Code
The view, up to its last full block of 4 linesgrows only at its end
mark · mugen
The view's last lines, devices, agents at worknew this turn
Your message, then Claude Code's environment notenew this turn
mark · Claude Code
read from the cache new, and written to the cache for next time

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.

Measured: turn 2 of a live test on a Max plan87%
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. /model starts 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.

Transcripts

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_DIR names a directory, each of those calls writes Claude Code's debug log there, named like 2026-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.

Files on disk

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.

Settings

Environment variables

VariableDefaultWhat it sets
MUGEN_CLAUDEclaudeThe Claude Code CLI to run: a name looked up on PATH, or a path.
MUGEN_DEBUG_DIRWhere chat calls write Claude Code's debug logs; unset, they write none.
MUGEN_TRANSCRIPTSchatWhich calls are saved as sessions: chat, all, none.
MUGEN_WIREapi does the same as --api.
MUGEN_PLAIN1: no full screen; lines as they come and a λ prompt (also when TERM=dumb, as in Emacs' shell).
MUGEN_DIR~/.mugenThe chat's data dir. Another dir is another chat, which another mugen can hold at the same time.
MUGEN_WORK~/.mugen/workWhere the tools run, a dir per agent.
MUGEN_PICKopus-5.5:medium:slowThe chat's model, effort and speed, until /model or /effort.
MUGEN_COMPACTORhaiku-5.5:high:slowThe summaries' model and effort.
MUGEN_JOBS8 / 48Summaries at once: 8 on Claude Code, 48 with --api.
MUGEN_RULES~/.mugen/rules.mdYour instructions, added to mugen's prompt.
MUGEN_SYNC1Flush each log write to disk.
From your phone

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:

Blink or Termux on the phone→Tailscale→mosh you@HOSTNAME→tmux "main"→mugen

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.

  1. Run Tailscale, ssh and mosh on the computer

    On NixOS: services.tailscale.enable (with extraSetFlags = [ "--operator=you" ] to run tailscale without sudo), services.openssh.enable and programs.mosh.enable, with mosh's UDP ports 60000–61000 open on tailscale0 only. The README has the whole block.

  2. Sign the computer and the phone in to the same tailnet

    Run tailscale up on 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.

  3. 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.

  4. 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.

When it goes wrong

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 with MUGEN_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 --version works, or set MUGEN_CLAUDE to its path. On NixOS, a shell from before the switch may lack the new PATH: 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, type reset and Enter.