๐Ÿ‡ฎ๐Ÿ‡น zaino /หˆdzaiฬฏ.no/ ยท noun. backpack

An agent harness
you can carry.

Five providers, eight tools, subagents and MCP in one Go binary, and not an SDK in it. It asks before it writes or runs anything, and keeps every road a conversation ever took.

$ curl -fsSL https://raw.githubusercontent.com/zenodea/Zaino/main/install.sh | sh

Go 1.26.5+ ยท lands in ~/.local/bin ยท --uninstall takes it back out

A backpack, with each part labelled as a part of zaino: the lid is five hand-rolled provider clients, the main compartment is the turn loop, the front pocket is eight tools, the buckle is permission, the side pocket is MCP servers, and the reinforced base is the test suite.ZTHE LIDfive hand-rolled clientsMAIN COMPARTMENTthe turn loopFRONT POCKETeight toolsHAUL LOOPone binary, no runtimeTHE BUCKLEasks before it actsSIDE POCKETMCP, over stdioREINFORCED BASEtested throughout
fig. 1: the pack, from the outside
  • The lid five hand-rolled clients
  • Main compartment the turn loop
  • Front pocket eight tools
  • Haul loop one binary, no runtime
  • The buckle asks before it acts
  • Side pocket MCP, over stdio
  • Reinforced base tested throughout

What's in the pack

The clients, the loop, the composer and the transcript are written in this repo, not wrapped from an SDK.

Packing list

  • Streaming, and the turn loop that drives it
  • Eight tools: read write edit bash grep find ls fetch
  • task, which hands work to a second agent
  • MCP servers, spoken over stdio
  • Compaction when the context window fills
  • Anthropic, Gemini, OpenAI, Grok & OpenRouter, hand-rolled
  • Two frontends: full-screen TUI and a plain REPL
  • A vim-style composer, and a walkable transcript
  • Permissions, sessions, prompt recall
  • /rewind and /journey: the session as a tree, every branch kept
  • Images, attached with @path or read from disk
  • Prompt caching on Anthropic, marked by hand
  • Config files under ~/.config/zaino, with profiles
  • Markdown rendering, wire logging

The plain REPL

A pipe is
a terminal too.

When stdin isn't a TTY, zaino skips the interface and reads lines: each one a prompt, slash commands included. Only the answer lands on stdout; the tool chatter, thinking and permission questions all go to stderr, so whatever you redirect stays clean.

# answers on stdout, everything else on stderr
$ echo "say hi" | zaino
$ echo "sum up this repo" | zaino > NOTES.md
$ zaino -v < prompts.txt   # one turn per line
$ 

Tools

Eight tools, and one
that hands the work away.

Withhold one with -exclude-tools bash, hand out a subset with -tools read,grep, or turn the lot off with -no-tools. /tools lists what the model has.

  • read
  • write
  • edit
  • bash
  • grep
  • find
  • ls
  • fetch
  • task

task: a second agent

It runs with its own conversation and hands back only what it concluded. A search that reads twenty files costs this conversation one paragraph instead of twenty file contents.

The child inherits the parent's gate, so it is not a way around a refusal: same policy, same approver. It cannot ask you anything itself, so the prompt has to carry everything it needs. Nesting stops two deep.

MCP servers plug in over stdio, declared in mcp.json. Their tools arrive named server__tool and ask for approval like anything else that leaves the process.

fetch returns a page with the markup stripped. It leaves the process too, so manual and accept-edits both stop to ask first.

Four pockets

Permission is a mode.

โ‡งโ‡ฅ cycles it mid-conversation; -permission sets it at the door.

manual

Ask before writing, running or fetching.

default

accept-edits

Edits go through. Still asks before running or fetching.

plan

Read only. Nothing written or run, but pages can still be read.

bypass

Everything goes through unasked.

on you

One rule the modes can't lift. Paths outside the working directory are refused, not asked about. Symlinks are resolved first, so a link pointing out of the tree is still out of the tree. -allow-outside is the only thing that moves it.

The journey

Nothing you tried
is lost.

/rewind takes the conversation up again from an earlier turn: your prompt comes back to the composer to be changed and asked again, and everything after it leaves the context. Nothing is deleted: the turns you walk away from stay in the file, on a branch of their own. The file has always been a tree.

/journey draws that file whole: every turn on every road, the abandoned ones dimmed. Pick any stop, lit or not, and the context is rebuilt as it stood there. A rewind walks back along the road you are on; a journey can cross to one you left.

TRAIL MAP

scaffold the pageadd a navstyle the herotry floatsclear floatsuse flexboxadd the footercenter with marginsuse grid

THE OLDEST ATTEMPT KEEPS ITS LANE

Commands

Driving it

A line starting with / acts on the session instead of going to the model. A prompt that merely starts with a slash (/etc/hosts is wrong) is still a prompt, and the panel knows it.

A command that takes a value and is given none asks instead of explaining: it opens a list with what is currently in effect marked. Passing the value outright (/effort low) skips it.

/clear
forget the conversation
/model
show or change the model
/provider
switch provider
/effort
show or set output effort
/thinking
show or hide the model's reasoning
/system
show, set, or drop the system prompt
/profile
switch to a named bundle of settings
/config
what the config files came to
/compact
fold the conversation into a summary
/limit
stop when the context passes a ceiling
/rewind
take it up again from an earlier turn
/journey
the tree of turns, every branch included
/permission
when tools stop to ask
/tools
list the tools the model has
/usage
token usage for this session
/sessions
pick up an earlier conversation
/vim
modal editing in the composer
/bro
say the last answer again, simply
/help
list the commands
/quit
leave zaino
โŽ sendโŒฅโŽ newlineโŒƒjโŒƒk walk the chatโ†‘โ†“ earlier promptsโ‡งโ‡ฅ permission modeesc stop the turnโŒƒc stop, then quitPgUpPgDn scroll

Modal editing, on by default

The composer starts in insert, so nothing is different until you press esc. Then motions, operators with counts, and a visual mode the composer draws itself, since the text box underneath can't show a selected range. -vim=false turns it off.

Walk the transcript

โŒƒj and โŒƒk move a bar through the transcript one entry at a time. With a tool call under the bar, โŽ opens the arguments it was called with and everything it returned. Typing hands the keyboard back.

The mouse is left to the terminal

Select and copy the way you do anywhere else; scrolling is on the keyboard instead. -mouse gives the wheel to zaino, at the cost of needing โ‡ง-drag to select.

/clear deletes nothing

A session is one append-only file of things that happened. What gets sent is worked out from that record, so clearing just marks where the context starts; the transcript before it stays readable.

Come back to it

-continue is the newest run from this directory; -resume takes any prefix of an id. The model, prompt, effort and thinking come back as you left them.

Stopping, and leaving

esc stops a running turn, but only once vim has nothing else for it to do. โŒƒc stops it too; with nothing running it arms the quit and says so in the footer, and any other key stands it down.

๐ŸŽ’

Pick it up.

$ curl -fsSL https://raw.githubusercontent.com/zenodea/Zaino/main/install.sh | sh