How mimon is built.

One product, three layers, zero cloud. This page is the honest map of how the pieces fit — the same diagram the code follows. If you want the feel before the anatomy,replay the live demo.

The system, layer by layer

your terminal — the TUI

src/tui

keystrokes in · ANSI frames out · repaints on resize

pixel-font boot screeniterm + kitty image protocolshand-measured glyphs

mimon engine — local, no cloud

src/engine

loopback-only server · the CLI refuses non-loopback engines

agent looppermission gateappend-only auditcontext compactortier routerSQLite sessions

tools: fs · git · shell · search · computer

model providers

reached with your key, from your machine

anthropicopenaideepseekglmgeminiany OpenAI-compatible URL

Nothing in this picture is a service. The engine is a process on your machine, the session store is a SQLite file, and the only traffic that leaves is model traffic — dialled with your key, to the provider you picked.

One turn, traced

A scripted sample turn, annotated with what the engine is doing at each step. The step labels on the left are the actual phases of the loop.

one turn — annotated
in~ ❯ fix the failing test in src/tui
loadsession restored · 14 messages → rebuilt + compacted to 6,812 tok
routetier fast · glm-4.6 · stream on
toolread src/tui/chat.ts · read-only → runs free
tooledit src/tui/chat.ts · write → gate
gate⚠ diff on screen · waits for your key
→ a · patch applied · 1 hunk
audit#1042 · edit allowed · appended — the log never rewinds
toolexec bun test src/tui · exec → gate
done✓ 42 pass · 0 fail · turn closed in 6 of 24 steps

Reads never interrupt you — that is where a turn gets its speed. Writes and execs each stop at the gate, and both the call and your decision land in the audit log before the loop moves on.

The decisions, and the why

Keys in the Keychain

Provider keys go to the macOS Keychain (a 0600 file elsewhere). They are never logged, never written to the audit log, and never returned by any endpoint — the engine holds them in memory only.

Loopback only

The server binds 127.0.0.1:7800 and refuses to serve anything else, and the CLI refuses an engine that is not on loopback. A local agent should not be a network service.

Ask-first tools

Read-only tools run free — that is where the speed lives. Anything that lands on disk or spawns a process shows a visible diff and waits. The gate is a hard step in the loop, not a setting.

Append-only audit

Every tool call and every permission decision lands in an append-only log. It is the receipts of the session: the app only ever appends to it, never edits or deletes a row.

A PNG decoder from scratch

src/tui/png.ts parses chunks, inflates, and unfilters Paeth in plain TypeScript — no native image dependency, validated by magic bytes end to end. ~600 lines instead of a linked library.

Hard step cap

Every turn is bounded by a step cap. A confused run stops instead of spiraling — the loop cannot spend your tokens forever.

One sandbox

File tools touch only AGENT_WORKSPACE_ROOT, and custom provider endpoints are validated before the engine dials: public https only, private ranges and embedded credentials rejected.

How the code is laid out

Two deployables and a shared core: the TUI you see, the engine that thinks, and the types both agree on. Two runtime dependencies total — hono for the API, zod for the boundaries.

src/
├── tui/terminal client — ANSI frames · pixel font · image protocols · png.ts
├── engine/
│ ├── core/agent loop · permissions · audit · context · verifier
│ ├── tools/fs · git · shell · search · computer
│ ├── models/providers · tier router · keystore (Keychain)
│ ├── api/HTTP routes + WebSocket for the TUI
│ ├── db/SQLite session store + migrations
│ └── app/bootstrap · config · server (binds 127.0.0.1)
└── shared/brand · glyphs · images · types

See it run.

The demo replays a full session — the same loop you just read, at typing speed. Or put the real thing in your terminal; setup takes one command and a key.

replay the demo$npm i -g mimon-cli