🇮🇹 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

fig. 1: the pack, from the outside
  • 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, memory and todo for 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 @path or 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 modelthe tool runsthe answersession-startpre-toolpost-toolturn-end

THE YELLOW STOPS ARE YOUR SCRIPTS

~/.config/zaino/config.json

{
  "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.

zaino · ~/work/app

› m 
INSERT claude-opus-5 · $0.14

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

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 / 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
⏎ send⌥⏎ newline⌃j⌃k walk the transcript↑↓ earlier prompts⇧⇥ permission modeesc stop the turn⌃c stop, then quitPgUpPgDn scroll

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