🇮🇹 zaino /ˈdzai̯.no/ · noun. backpack
An agent harness
you can carry.
A coding agent in one Go binary. It talks to Anthropic, Gemini, OpenAI, Grok and OpenRouter over plain HTTP with no SDK in between, asks before it writes or runs anything, keeps every branch of a conversation, and can put your files back when you rewind.
$ 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
- The bedroll five hand-rolled clients
- Main compartment the turn loop
- Front pocket twelve tools
- The lid one binary, no runtime
- The buckle asks before it acts
- Side pocket MCP, over stdio
- Reinforced base tested throughout
What's in the pack
All of it is written in the repo. The three dependencies are for drawing the UI, and nothing else is wrapped from an SDK.
Packing list
- The turn loop: stream, run the tools, feed the results back, repeat
- Eight tools for the code:
read write edit bash grep find ls fetch task,job,memoryandtodofor the work around it- MCP servers, spoken over stdio
- Anthropic, Gemini, OpenAI, Grok & OpenRouter, each a few hundred lines of HTTP
- Compaction when the context fills, sized to the model you are on
- A full-screen TUI, and a plain REPL for pipes and scripts
- Vim editing in the composer, a transcript you can walk
- Permission modes, allow and deny rules, hooks you write yourself
- Sessions as a tree:
/rewind,/journey, and the files put back to match - Images, attached with
@pathor read from disk - Prompt caching on Anthropic, a spend cap, per-model context windows
- Config files under
~/.config/zaino, with profiles - Markdown rendering, and a wire log of every request
The plain REPL
Pipes and scripts
get a plainer zaino.
When stdin is not a terminal, zaino skips the interface and reads lines:
each one a prompt, slash commands included. Answers go to stdout and
everything else, tool calls, thinking, permission questions, goes to
stderr, so whatever you redirect stays clean. -p asks one
thing and exits, and -json wraps the answer with the usage
and the cost for whatever is reading it.
# answers on stdout, everything else on stderr $ git diff | zaino -p "review this" -permission plan $ zaino -p "what does install.sh do" -json | jq -r .answer $ echo "sum up this repo" | zaino > NOTES.md $ zaino -v -max-spend 2 < prompts.txt # one turn per line, two dollars at most $
Hooks
Your scripts,
inside the loop.
Four places to run something of your own: when a session starts, before
and after every tool call, and when a turn ends. A pre-tool script that exits 2 blocks the call, and its stderr is what the model
reads instead of a result. Whatever a post-tool script prints
goes on the end of the result, so a formatter or a linter gets a say after
every edit, and the model fixes what it sees.
THE TURN LOOP
THE YELLOW STOPS ARE YOUR SCRIPTS
{
"hooks": {
"session-start": [{"run": "git fetch -q"}],
"pre-tool": [{"tool": "bash", "run": "sh .zaino/guard.sh"}],
"post-tool": [{"tool": "edit,write", "run": "gofmt -l . 2>&1"}],
"turn-end": [{"run": "notify-send zaino done"}]
}
}Declared per event, in either config file, with tool to
narrow one to some tools. Each runs with sh -c in the
project directory. The tool, its input and, afterwards, its result
arrive as JSON on stdin; ZAINO_EVENT and ZAINO_TOOL are in the environment for one-liners. Exit 0
lets it through, exit 2 says no, anything else is reported to you and
ignored. Subagents run the same hooks. /hooks lists what is
set up.
The composer
It speaks vim.
Not a plugin and not a keymap on top of a text box: the composer has a
normal mode, a visual mode and an undo stack of its own. It starts in
insert, so nothing is different until you press esc. Then h j k l w b e 0 ^ $, counts, d and c with a motion, x, D, C, p, u, and v or V to select. The footer shows the mode.
The transcript keeps the same habits: ⌃j and ⌃k walk it, and every picker takes j, k, g and G. -vim=false gives you a plain
text box instead.
The journey
Every branch kept,
files too.
/rewind takes the conversation back to an earlier prompt.
The prompt returns to the composer, everything after it leaves the
context, and nothing is deleted: the turns you leave stay in the session
file on a branch of their own. /journey draws the whole
file, every turn on every branch, and you can jump to any point on it,
including ones you left behind.
Files go with you. write and edit keep what
they changed, so moving to another point on the tree offers to put the
files back the way they were there, however many jumps and branches
away. Anything changed outside zaino in the meantime is left alone
unless you say otherwise, bash writes are not tracked, and
nothing under .git is ever touched. /files lists what the current branch changed.
TRAIL MAP
THE OLDEST ATTEMPT KEEPS ITS LANE
Commands
Driving it
A line starting with / is for zaino, not the model. The slash
opens a fuzzy menu; a command given no value opens a picker. Your own go
in commands/<name>.md.
- /model
- show or change the model
- /provider
- switch provider
- /effort
- how hard it thinks before answering
- /profile
- a named bundle of settings
- /permission
- when tools stop to ask
- /limit
- a ceiling on the context
- /spend
- a ceiling on the cost
- /usage
- tokens and dollars so far
- /rewind
- back to an earlier prompt
- /journey
- the whole tree, every branch
- /sessions
- pick up an earlier conversation
- /files
- what write and edit changed here
- /todo
- the plan, and how far it is
- /memory
- what it has noted about this project
- /jobs
- what is running in the background
- /hooks
- the scripts your config runs
The mouse stays the terminal's
Select and copy the way you do anywhere else. Scrolling is on the
keyboard; -mouse gives the wheel to zaino at the cost of ⇧-drag to select.
Walk the transcript
⌃j and ⌃k move a bar through the transcript. ⏎ on a tool call opens what it was asked and what it returned. A running turn does not lock the composer: type, and it goes in with the next tool results.
Come back to it
-continue picks up the newest session here, -resume any prefix of an id, settings and all. A fresh
session starts on whatever provider, model and effort you last picked
in this project.
Pick it up.
$ curl -fsSL https://raw.githubusercontent.com/zenodea/Zaino/main/install.sh | sh