fast, durable terminal agents that SWIM between clients π built in Zig
current release: v0.2.2
curl -fsSL https://marlin.wtf/install.sh | sh
prefer Homebrew? brew install jespern/tap/marlin
marlin is a daemon-native terminal AI agent harness and session multiplexer. Its own agent runs native models; installed Claude Code and Codex agents can run beside it as guests using their existing vendor logins. The daemon owns the structured sessions while the TUI, web UI, and headless CLI remain thin clients.
Close a terminal and the turn keeps running. Reattach later, reach it through SSH, mosh, or your tailnet, or compose it with ordinary shell commands. The transcript, plans, status, approvals, diagnostics, and full tool results are still thereβand inspectable without opening SQLite.
GARAGE PICK If Marlin were a car, it would be a first-generation Honda NSX: supercar fast, Honda reliable, usable every day, and direct enough that the driver always knows what the machine is doing. No touchscreen maze, no ornamental controls. Just a small, deliberate machine that starts every time and wants to be driven hard.
!! turns a session into a login shell that the daemon keeps alive, and its output joins the transcript when you hand the session back/web pair, or p on the Web tab); unpairing cuts its live streams at once. [web] listen, hosts, and proxy replace tailscale = true, which is now ignored: see docs/MOBILE.md/voice's local engine, into the composer for review/mcp login, and MCP servers that plugins bringmuse/default)~/.config/marlin/profile.md reaches every agent, native or guest, fresh each turn/shutdown says what it was running/compact for Claude Code and Codex, and live output from their subagents and background commands!c copies the last reply's prose (!co the tool output), marlin watch streams a session read-only, asked questions stay in the transcript with their answer, and private GitHub plugin marketplaces clone through gh/config pacing on); replies still render line by line/compact no longer hangs, recaps and the context gauge survive tab switches, reattach, and restarts, and CLI output redirected to a file keeps every line!ss takes one, and JetBrains Mono is fetched on first use instead of being built inβ― or $ before each tab's number tells agent sessions from shells!! turns any session into a login shell on the daemon host, emulated by libghostty-vt. Shells survive detaching, quitting, reconnects, and restarts; scrollback, Vim normal mode, / search, the mouse, full-screen programs, and Kitty images all work. Ctrl+B reaches Marlin's keys, and Ctrl+D hands the session back with the shell's output in its transcriptAlt+S or /screenshot [slim] copies a PNG of exactly what Marlin rendered/pause [all] [--force] suspends a session and its children once their workers stop, the pause survives reboots, and /resume picks the work back up/cwd completes paths on that hostCtrl+N opens in the current tab's directory, session permissions and input history persist, /rename titles stick, and /exit plus /shutdown are newmarlin discover finds running Marlin daemons nearby/cwd completion, better word navigation and SSH prompts, and a moon rocket screensaverOlder releases are on GitHub.
Alt+S copies a screenshot.!! turns a session into a login shell that lives in the daemon, emulated by libghostty-vt. It keeps running through detaches and restarts, with scrollback, Vim normal mode, search, full-screen programs, and Kitty images. Ctrl+D hands the session back, the shell's output kept in its transcript./pause suspends a session tree once its workers stop, or every session with all. Pauses are durable, so they survive a daemon reboot; /resume continues the work./sessions, per-session view state, and rolled-up child activity.!c copies the last reply; !co the last complete tool result.marlin --remote host carries the structured protocol over SSH, the status line names the daemon's host, and /cwd completes paths over there. marlin web is a daemon-managed loopback UI, on by default (/web disable turns it off), that a phone reaches through your own HTTPS proxy after pairing once with a code from /web./reboot --build while developing Marlin inside Marlin.Ctrl+S or marlin top shows every session with its live work underneath: children, guest subagents, running commands, and background jobs. Enter opens a job's live output; x kills, a archives down a list.claude binary or stable codex app-server protocol as first-class multiplexed sessions using their existing logins. Guest permission prompts and questions share marlin's approval bar, background jobs and subagents show up in the work tree, and switching a session between native, Claude Code, and Codex models carries a validated handover briefing..marlin/skills and .agents/skills plus your user skill directories, and invoked as slash commands. marlin plugin marketplace add and install import Claude Code plugin marketplaces, their skills and their MCP servers.marlin run works headlessly; ls, search, and inspect --json expose daemon-owned state for shell scripts without screen scraping or SQLite queries.task and task_batch create up to eight durable, attachable, read-only children. Named councils fan questions across models for parent synthesis./attach uploads signed image data through the client boundary, stores it by content hash, and maps it to native provider vision blocks. Images can steer a running turn, too.parallel_safe really run concurrently, with at most eight workers. Durable results stay in provider-call order./voice setup adds optional push-to-talk or toggle dictation with whisper.cpp or parakeet-mlx. Audio stays at the client edge, and transcripts land in the composer for review before submission.$ curl -fsSL https://marlin.wtf/install.sh | sh $ export OPENROUTER_API_KEY=... $ marlin run "why is my build broken" # ...the daemon keeps the structured session... $ marlin # attach the TUI $ marlin --remote devbox # same TUI, carried over SSH $ marlin ls # short stable handles, including children $ marlin inspect 63df --json # metadata, state, plan, blocks, diagnostics $ marlin search "permission denied" # search durable transcripts $ marlin attach 63df # any unique prefix (minimum four) # installed vendor agents can join the same multiplexer: $ marlin run --model codex/default "review this patch" # inside the TUI: /council new core # pick models, then choose Done /review core audit this design before I ship it
The installer downloads the matching tagged release for macOS or Linux
on aarch64 or x86_64, verifies its SHA-256 checksum, and installs it in
~/.local/bin without sudo. Homebrew also works:
brew install jespern/tap/marlin.
Tracking main instead? Clone the
repository and run
zig build with Zig 0.17.
| track | where it is | status |
|---|---|---|
| M0βM3 | agent loop, daemon, TUI, context engine, self-hosting reboot | β BUILT |
| M3.5 | permissions + secret boundary (reads refused, secrets redacted, macOS+Linux sandbox) | β ENFORCED |
| M4 | structured session multiplexer | β VERIFIED |
| M5 | managed MCP, executable tools, hooks, skills | β PRODUCTIZED |
| M6a | durable child sessions + bounded task fan-out | β VERIFIED |
| M6b | named multi-model councils + durable reviewer sessions | β SHIPPED |
| M6c | SSH-carried remote clients + tailnet web UI | β SHIPPED |
| now | cancellation, reconnect, extension diagnostics, dogfood, and performance | π¨ HARDENING |
| later | workspace snapshots, isolation, leases, land/discard | π EVIDENCE FIRST |
The source-of-truth details live in the milestones and the active M6 execution plan.
marlin web is on by default but loopback-only and tokenless there, so it can drive anything the daemon can; phones reach it through your own HTTPS proxy and must pair, turn it off on multi-user machines, and never treat it as a public deploy surface/land, and /discard remain one deliberately later trackSOURCE on GH made w/ ZIG RELEASES ARCHITECTURE
one handcrafted HTML file. the fish rejected React.