Field manual

How cherry π works, section by section. The diagrams illustrate the ideas behind the screens.

Updated Extensions What if… Brand kit Download Markdown llms.txt llms-full.txt

Quick start

These four steps take you from a project folder to work you can check and undo.

  1. Open a workspace. Run cherrypi in a project folder. The launch screen shows where the data folder will be before anything is made (Open a workspace).
  2. Ask Picache. Picache is the agent you talk to in a Chat. Type in the prompt band above the status line and press Enter. Alt+Enter steers a turn that is already running, and Esc stops it (Send, steer or stop).
  3. Launch a vendor agent. Pick one in the rail, such as Claude Code or Codex. It starts in a terminal pane, inside a box that limits what it can reach (Launch a vendor agent).
  4. Check and undo. Every Picache turn takes a snapshot of the folder before it starts and after it ends, so you can see what it changed and put the folder back (Undo a turn's changes).
picache snapshots changes turn:<id>   # what a turn changed
picache layer restore turn:<id>:start  # the folder as that turn found it

When the status row shows N NEEDS YOU, something is waiting for your answer (When something needs you).

The workspace

A workspace is a folder that cherry π manages, with its work split into layers.

cd ~/src/api
cherrypi   # open the workspace here, or make one

The program has two halves. The harness, cherrypi.ape, is a Lua runtime built on redbean. It decides what is allowed and keeps every record. The desktop is a Go terminal program that draws panes.

The harness keeps running when you quit the desktop, and the next launch reconnects to it.

Each project layer is bound to a subfolder, with its own Picache conversation and its own panes. Layer Zero is a scratchpad. A layer can also be a git worktree, and removing it never deletes its branch.

Panes hold shells, vendor agents, files, editors, runs, models and more. You can split, stack, zoom or move them.

What runs where

Four kinds of process make up cherry π.

  • The desktop is the Go program you launch. It draws panes and makes no decisions.
  • There is one harness per workspace. It decides what is allowed, keeps the records and runs the work. Desktops attach to it on run/harness.sock.
  • Each terminal has a pump. The pump holds the terminal and its recent output, so a shell or an agent keeps running if the desktop or the harness goes away.
  • Sidecars run beside the harness. The signer holds the keys, the P2P sidecar talks to the network, and model servers run as jobs.
What runs where Desktops, this window, another window on the same workspace, or a paired machine's desktop over P2P, connect to one harness per workspace through run/harness.sock. The harness, cherrypi.ape, holds policy, grants and budgets, runs turns and Picache, keeps the stores, receipts and chains, runs jobs, Conduit and the P2P core, and is the one door for capsules. Each terminal is held by a pump of its own; a program in a pane reaches the harness through picache.sock with its pane's token. Sidecars: the signer holds the keys, the P2P sidecar runs on Bare in a box of its own, and llama-server runs as a job. Capsules, app handlers, scripts, clean rooms and Run one, start through the one door. A desktop quitting leaves the harness and pumps running; a new build replaces the harness and the pumps stay; a reboot ends everything. DESKTOPS this window the Go desktop another window the same workspace a paired machine in a pane here, over P2P THE HARNESS · cherrypi.ape one per workspace policy, grants, budgets turns, and Picache stores, receipts, chains jobs, Conduit, P2P core the one door for capsules ◂ run/harness.sock ◂ picache.sock PUMPS · ONE PER TERMINAL zsh run/tty/<id>, its ring claude, in its box picache, its own token SIDECARS signer · holds the keys P2P · Bare, boxed llama-server · a job CAPSULES · THROUGH THE ONE DOOR app handlers · scripts · clean rooms · Run one WHAT OUTLIVES WHAT a desktop quits: harness and pumps run on a new build: harness replaced, pumps stay a reboot: everything ends
What runs where, and what outlives what.

A program in a pane talks to the harness with the picache command (section 04), over picache.sock. Only you can read that socket. Every call carries its pane's token, which names who is calling (section 03).

Anything confined, such as an app's handler, a script or a clean room, starts as a capsule through one door in the harness (section 09).

One file

cherrypi.ape is one file that carries everything it needs for macOS on Apple silicon and Linux on x64 and arm64, including the Raspberry Pi. When you run it at a terminal, it starts this machine's desktop.

The desktop is inside the file too. It is built natively for macOS and statically linked for Linux, and it draws with the Ghostty terminal library.

On Linux, nothing the file carries needs a system loader, so it runs on NixOS. The file is in the Actually Portable Executable (APE) format, and on a Linux machine with no APE loader it writes its own. The same commit builds the same bytes from any checkout.

One file carries it cherrypi.ape is one file of about 150 MB: the Lua runtime on a pinned redbean base, Bare for each platform, the signer and P2P bundles, printimage and ptv, scope and pasta for Linux, the docs Picache reads, and the Go desktop for each platform on Ghostty's terminal library, with a manifest of every file that may come out. On first use, this machine's share comes out, checked by sha256, atomically and never through a link: macOS on Apple silicon, Linux on x64, or Linux on arm64 such as the Raspberry Pi. The same commit gives the same bytes from any checkout. CHERRYPI.APE · ONE FILE, ABOUT 150 MB the Lua runtime redbean base, pinned Bare, per platform signer + P2P bundles printimage, ptv scope, pasta (Linux) the docs Picache reads extraction manifest a desktop per platform Ghostty's vt library the same commit gives the same bytes FIRST USE: THIS MACHINE'S SHARE COMES OUT macOS · Apple silicon darwin-arm64 Linux · x64 linux-x64 Linux · arm64 linux-arm64 · the Raspberry Pi SHA256-CHECKED, NEVER THROUGH A LINK each build writes its buildinfo beside it: the commit, and whether the source was clean
The hermetic runtime: what it carries, and what each machine takes out of it.

Open a workspace

Run cherrypi in a folder. It looks upward for a .cherrypi/, the way git looks for .git, and stops below your home folder, so the home workspace, ~/.cherrypi, never swallows every project under it.

The launch screen offers the workspace in this folder or above it, a new workspace here, another folder, the home workspace and recent workspaces. Each choice shows where its data folder will be before anything is created. Then you pick the latest conversation, another one, or a new one.

Flags let you skip these questions:

cherrypi -state ~/src/api/.cherrypi         # this data folder
cherrypi -session <id>                      # this conversation
cherrypi -private                           # a harness of its own, ended with the desktop
cherrypi -remote studio:web                 # a paired machine's workspace
cherrypi -remote studio:web:L1              # ... one layer of it
cherrypi -remote studio:web:terminal:<id>   # ... one terminal
cherrypi -stop-harness                      # stop this workspace's harness

Inside cherry π, Open does what -remote does, and shows the result in a pane (section 08).

Each data folder carries a marker that says it belongs to cherry π. When it picks a data folder, cherry π only takes one that is empty, already marked, named .cherrypi or already holding its database. It refuses any other folder that holds files, and a data folder or marker that is a symbolic link.

  • In a git work tree, it offers to add the folder to .git/info/exclude, which is never committed.
  • Layer folders are stored relative to the workspace, so you can move the whole tree.
  • You're warned about synced folders such as iCloud Drive and Dropbox, because SQLite is fragile there.

Deleting a project folder deletes its history too, because the data folder is inside it. Settings warns you about this, and cherry π itself never deletes a data folder.

The data folder

A workspace keeps everything it knows in its data folder. What belongs to you or to the machine lives in the home data folder, ~/.cherrypi.

Your project's own files hold only what you put there, such as a saved layout or an agent you declared for a folder.

project/.cherrypi/
  .cherrypi-workspace-v1   the ownership marker
  cherrypi.db              the workspace: desktop, grants, settings,
                           tasks, offers, its evidence chain
  layers/<id>.db           one store per layer: its conversations,
                           runs, receipts and its own chain
  scratchpad/              Layer Zero's folder
  trash/                   what Files deleted
  run/harness.sock         where desktops attach
  run/tty/                 one socket per terminal pump
  picache.sock             where picache calls arrive
  harness.log              what the harness said
  apps/  exports/  logs/  backup-…/

~/.cherrypi/               the home data folder, and the home workspace
  identity.db              who this machine is; the keys stay in the signer
  p2p/p2p.db               pairs, contracts, the fleet
  p2p/rooms.db             rooms and their messages
  ai/models.json           models, referenced where they are
  preferences.json         theme, glyphs, keys: yours, in every workspace
  workspaces.json          recent workspaces, for the launch screen
  floats.json              where your floats were

Layers

Each layer has an id that survives renames and moves, and a SQLite store of its own. The store holds the layer's conversations, runs and receipts, and its own evidence chain, which is anchored in the workspace's chain (section 10).

Because a layer's history is in its own file, a layer can leave the workspace whole. It can be exported, shared, forked or kept in step with another machine.

Export and import a layer

picache layer export L1 --out api-layer.db   # history and folder
picache layer export L1 --history-only       # history alone
picache layer export L1 --git                # include .git
picache layer import api-layer.db            # as a working layer
picache layer import api-layer.db --as reference

An export is one SQLite file, signed by this machine. It holds the layer's history, its evidence chain and its folder: files of any size, empty folders, symbolic links that stay inside it, modes and modified times.

Files are split into chunks of up to 4 MiB and stored by SHA-256 hash, so a chunk that many files share is stored once. The signature covers the hashes, and every chunk is checked as it's read, so an import refuses a chunk that has been changed.

A layer leaves as one signed file A layer's folder and its store go into one SQLite file signed by this machine. Files are cut into chunks of at most 4 MiB, stored by SHA-256, so a chunk two files share is stored once, and the signature covers the hashes. The file can be imported as a working layer or as a read-only reference that you can attach as a brain, shared with a paired machine inline up to 10 MiB and by your Drive past it, or forked into a new layer beside the original. Measured on a Mac: 2 GiB plus 2,000 small files exported in 25 seconds, verified in 9 and imported in 23, with peak memory about 106 MB. A LAYER its folder files, folders, links, modes and times its store conversations, runs, receipts, its chain ONE SIGNED SQLITE FILE chunks of at most 4 MiB, stored by SHA-256 = a chunk two files share is stored once the signature covers the hashes import, working its files written back import, reference read-only, for a brain share inline to 10 MiB, then Drive fork a new layer beside it MEASURED: 2 GIB + 2,000 FILES · EXPORT 25 S · VERIFY 9 S · IMPORT 23 S peak memory about 106 MB, the same as for 64 MiB; a changed chunk fails verification, and the import refuses it
A layer's export: its history and its whole folder in one signed file.

On a Mac, exporting 2 GiB plus 2,000 small files took 25 seconds. Verifying the export took 9 seconds and importing it took 23, and memory use peaked at about 106 MB.

An export leaves out .git unless you pass --git. It also leaves out folders that can be rebuilt, such as node_modules, .venv and target. You can set your own list with layer.export_excludes. The archive lists everything it left out.

An import as a working layer writes the files to a new folder and opens the conversation it had in front. An import as a reference is read-only. Importing an archive signed by a machine you haven't paired asks you first.

Grants (section 03) never travel. They stay in the workspace that gave them.

Share or fork a layer

picache layer share L1 studio   # offer it to another workspace or machine
picache layer fork L1           # a copy beside it, under new ids

When you share with a paired machine, up to 10 MiB travels with the offer. Anything larger goes through your own Drive, which needs no cloud and no account.

Each chunk goes to the Drive once, and the receiver fetches only the chunks it doesn't already hold. The share command says how many chunks were new. If an accept stops part way, it picks up where it stopped.

A fork copies the folder, history and conversations. Nothing the fork does reaches the original, and both evidence chains record the fork.

You can also attach any layer or reference to the layer in front as a brain. Picache and agents can search a brain, and nothing writes to it.

Undo a turn's changes

Every Picache turn takes a snapshot of the layer's folder before it starts and after it ends.

picache snapshots                       # list them
picache snapshots changes turn:<id>     # what a turn added, removed, changed
picache layer restore turn:<id>:start   # the folder as that turn found it

Snapshots are stored as hashed chunks inside the layer's store, so a turn that touched one file costs the space of one file. A restore takes a snapshot first, so you can undo the restore. It also tells you whether the result is exact.

Snapshots never touch .git or rebuildable folders. Files over 32 MiB are listed by name, but their contents are not kept. When you export or fork the layer, its snapshots go too.

On cherry π's own source repository (1,296 files, 421 MB), the first snapshot took 2.65 seconds and the next one 0.02 seconds.

SettingWhat it does
layer.snapshotsTurns snapshots on or off
layer.snapshot_max_fileThe largest file kept
layer.snapshot_keepHow many to keep (1000 by default)

Share a layer live

A live layer stays the same on several machines. Each member's edits and conversations reach the others within a few seconds.

picache layer live L1 studio            # offer L1, live
picache layer live L1                   # list the members
picache layer live L1 --remove laptop   # owner only
picache layer live L1 --leave           # keep your copy, alone

Only the owner can offer a live layer. Whoever accepts it as a working layer becomes a member. The owner's machine adds you when your accept reaches it, and from then on you can write.

Every two seconds, each member runs a round:

  • It snapshots its folder and sends the files that changed.
  • It writes the other members' changes into its folder, even in the middle of a turn.
  • A file you changed since the snapshot is left alone and goes out in the next round.

Several members can edit one text file at once, and everyone keeps every change (Edit a file together).

Files travel as hashed chunks, and a chunk you already hold isn't fetched again. The rest come from the machine that wrote them, or from its Drive when that machine is offline. Copying to the Drive never holds up a round.

Sometimes a file can't be written, because its chunks can't be fetched or because it's a folder on one machine and a file on another. The file then waits, and you're told once. Every round tries it again.

When a member's turn ends, the next round sends it, and it shows up in your copy as a read-only conversation. Message bodies over 1 MiB are cut.

These never travel:

  • grants, and the layer's panes
  • .git and the folders in layer.export_excludes, such as node_modules
  • files over 32 MiB (layer.snapshot_max_file)

Live layers pick up where they left off when P2P starts. The owner can remove members but can't leave. A member who leaves keeps the folder as a layer of their own.

A live layer, one round Every two seconds, each member runs a round. It snapshots its folder, sends what changed since the last round to the layer's log, and writes what the other members sent, unless it changed that file too. The log runs in each machine's P2P sidecar. It holds text edits, whole files, deletes and finished turns. Every text edit is kept on every member; for other files, the later write wins everywhere. Chunks come from the member's own store, then the writer's machine, then the writer's Drive, each checked by its hash. Grants, desktops, .git, rebuildable folders and files over 32 MiB never travel. Only the owner offers the layer, and a member writes once the owner's machine has added it. YOU · A MEMBER your folder 1 snapshot it 2 send what changed text as edits 3 write what others sent unless you changed it too THE LAYER'S LOG in each machine's P2P sidecar edit notes.md: −cat +dog put logo.png (whole) turn: one that ended one order, on every member STUDIO · A MEMBER its folder the same round, every two seconds text: every edit kept others: later write wins CHUNKS: YOUR STORE → THE WRITER'S MACHINE → ITS DRIVE · EACH CHECKED BY HASH never travel: grants, desktops, .git, rebuildable folders, files over 32 MiB only the owner offers it; a member writes once the owner's machine adds it
A live layer: each round, text edits and whole files go out to one log and come back in, the same on every member.

Edit a file together

When members of a live layer edit one text file at once, every member ends up with every change. That holds for changes to different lines or different words, and even for one word changed two ways, where both new words stay, one after the other.

Two members edit one line at once You and studio both start from the line "the cat sat on the mat". You change cat to dog; studio changes mat to rug, before either sees the other's save. Each save goes to the layer's log as edits, not as the file: delete these characters, insert this text after that character. The edits name characters, not positions, so both fit. Every member ends with "the dog sat on the rug", whatever order the edits arrive in. When both change the same word, both new words stay, one after the other. Binary files, files over 1 MiB and paths in the layer's .lww file are replaced whole instead, and the later save wins. YOU notes.md before the cat sat on the mat you save the dog sat on the mat → 2 edits, not the file THE LAYER'S LOG you: − cat + dog studio: − mat + rug each edit names characters, not where they were STUDIO notes.md before the cat sat on the mat studio saves the cat sat on the rug → 2 edits, not the file EVERY MEMBER, WHATEVER ORDER THE EDITS ARRIVE IN ✓ the dog sat on the rug one word changed both ways: both words stay, one after the other binary, over 1 MiB, or in /.lww: replaced whole, the later save wins
Two saves at once: each goes out as edits that name characters, so both fit, on every member.

Each save goes out as a set of edits, not as a new copy of the file. The file is kept as a CRDT, a structure built so that every copy agrees whatever order the edits arrive in. Nothing is merged after the fact, and no change is lost.

If your editor saves a copy from before others' changes arrived, their changes are kept. If you delete exactly the text another member just added, it can come back once. If it does, delete it again.

A save from an old copy Your editor opens plan.txt with three lines. Studio changes the second line, and the change is written into your file, but your editor still holds the old copy. You change the third line and save. Cherry-pi compares the save with the file's recent versions. Against the newest, the save would also undo studio's line. Against the copy your editor opened, it is one change, yours, so only that goes out. Every member ends with both lines changed. If you delete exactly the text another member just added, it can come back once. 1 · YOU OPEN IT plan.txt one two three your editor holds this 2 · STUDIO EDITS plan.txt one two, by studio three written into your file 3 · YOU SAVE your editor's copy one two three, by you from the copy in step 1 4 · BOTH KEPT plan.txt one two, by studio three, by you ✓ on every member THE SAVE IS COMPARED WITH THE FILE'S RECENT VERSIONS ✕ against step 2: it would also undo studio's line ✓ against step 1: one change, yours, and only that goes out delete exactly what someone just added, and it can come back once
A save from an editor that held an old copy: only your change goes out, so studio's stays.

Edits from a save go out in the next round, every two seconds, not as you type. If two members create the same new text file at once, both texts are kept in it, and one of the two members is told.

Other files are replaced whole. Every member ends up with the version that comes last in the layer's shared log. This applies to:

  • files that aren't text: not UTF-8, containing a NUL byte, or over 1 MiB
  • paths that match the layer's /.lww file, which takes gitignore patterns. List logs and generated files there.

If your change to such a file is replaced, you're told, and the notice gives the command that shows your version, such as picache snapshots show 14 logo.png. A change made by someone who had already seen yours loses nothing, so it arrives without a notice.

For any file, you're also told when a delete removes your change or a change undoes your delete.

Arrange the desk

The layer in front is a tree of tiles. Any pane can also float over it, like a window.

picache split --right --label tests -- make test   # a new tile
picache float --cascade                            # pile up the floats
picache new-layer ~/src/site --label site          # a new layer

Actions → Float this pane lifts a pane out, and the tiles close up. Drag a float's title to move it and its edges to resize it. Programs such as lazygit, btop or htop open as floats, with a splash screen until they draw.

Floats stack. If you drop one on another's title, or use Actions → Cascade the floats, they pile up with every title showing.

A float buried under others still shows its agent's badge. A buried terminal's title ends with its last line of output, so you can read a cascade of monitors without raising any of them.

  • ◌ in a title makes a float see-through.
  • To dock a float, drag its title and hold Space. Drop it on a pane to join it as a tab, or on an edge to make a new tile.
  • After a restart, floats come back where you left them and run their programs again.
The desk: a rail, a tree of tiles, floats over it On the left, the rail is a tree of the layers and their panes, the conversations and the machines; F3 hides it. The layer in front is a tree of tiles: a terminal, an Actions pane acting on that terminal, and a chat where Picache asks before it changes the desk. Over the tiles sits a cascade of floats: two buried terminals show only their titles, each ending with the last line it printed, and the front float, lazygit, has the keyboard. RAIL · F3 LAYERS ▸ Layer Zero L1 · app L2 · docs PANELS TERMINAL ACTIONS CHAT THE LAYER IN FRONT: A TREE OF TILES TERMINAL $ make test ok internal/model 41s ACTIONS on TERMINAL ▌Float this pane Cascade the floats Save this layer as a layout CHAT · Picache split a pane for the tests? ? Allow this once · No TERMINAL · tests · 41 passed TERMINAL · tests · 42 passed TERMINAL · logs · GET /api 200 lazygit ◌ ⊡ ✕ the front float has the keys drag its title to move it FLOATS SIT OVER THE TILES · F10 HIDES THEM · A BURIED ONE SHOWS ITS LAST LINE
A layer in front, with a cascade of floats over its tiles.

Scripts can drive layers with picache new-layer, close-layer, focus-layer, label-layer and move-layer, and they receive layer events.

The Actions list can stay open as a pane (Open Actions as a pane). Each action then applies to the pane you were in before, and the header names that pane.

Save a layout

picache save-layout dev                     # or Actions → Save this layer as a layout
picache apply-layout dev --right            # beside the current pane
picache apply-layout dev --new-layer docs   # as a new layer

A layout goes into the layer's folder as .cherrypi/layouts/dev.json, and you can undo the save. It keeps every kind of pane, with each terminal's command and folder.

When you apply a layout, a terminal that's still running is reattached instead of started twice, unless layouts.live_panes is fresh. An agent's pane resumes its own conversation, using the conversation id the agent's hooks reported (Claude Code, Codex, Pi).

A layout, saved and applied A layer named dev, with a shell, an editor and an art pane, is saved with save-layout as .cherrypi/layouts/dev.json in its folder, with an undo. The file keeps every kind of pane and each terminal's command and folder. It is applied either beside the focused pane, splitting it and closing nothing, or as a new layer on a folder you choose. A terminal still running is reattached by default, or started fresh when the setting layouts.live_panes says so. LAYER dev shell editor art save-layout dev with an undo .cherrypi/layouts/dev.json every pane kind; each terminal's command and folder beside the focused pane split it; nothing closes as a new layer on a folder you choose running terminals: reattach (the default) or fresh: layouts.live_panes
A layout opens beside a pane or as a new layer.

Terminals keep running

Each terminal runs in its own small process, its pump. A shell or an agent keeps running when you quit the desktop, when a new build replaces the harness, or when you reload the desktop (Actions → Reload cherry-pi). It comes back in its pane with its recent output.

Closing a pane ends its program, and Actions → Stop all terminals ends them all. A reboot ends them too.

Use sudo

Your own shell can sudo, and sudo asks for your password in the pane. An agent's pane, and any pane an agent opens, can't use sudo.

SettingDefaultWhat it does
terminal.sudo.shellsonYour shells may sudo
terminal.sudo.agentsoffAgents' panes may sudo
terminal.sudo.forgetonsudo forgets your password before an agent or Picache types
terminal.sudo.unboxed_agentspasswordmacOS: an agent outside its box needs your password at sudo, or (refuse) is refused

A change to these settings applies to panes opened after it.

On Linux, cherry π can never gain privilege. redbean sets no-new-privileges on itself, and everything it starts inherits the flag. So a pane that may sudo is started by your systemd user session instead, on a terminal that cherry π opened for it. The pane's environment is passed in a file only you can read, never on a command line.

Even then, cherry π holds that pane, its record and its output. Without a systemd user session, no pane can sudo.

macOS has no such flag, so there an agent's box itself refuses sudo, su, login and the administrator prompt's tools.

Who starts a pane's program On Linux, cherry π's runtime has no-new-privileges, and it holds every pane: its terminal, its relay, its record and its token. For your own shell's pane it asks your systemd user session, which is not the runtime's child, to start the shell on that terminal; the shell has no flag, and sudo asks for your password there. While terminal.sudo.agents is off, the default, an agent's pane is started by the runtime itself and inherits the flag, so sudo is refused. On macOS there is no flag: an agent's box is a Seatbelt profile that refuses sudo, su and login, and for an agent outside a box the setting terminal.sudo.unboxed_agents decides. Before a send or send-key that is not yours, an agent's or Picache's, types into any pane, and after you approve it, sudo is told to forget your password. CHERRY π RUNTIME · NO-NEW-PRIVILEGES ON LINUX your shell's pane terminal · relay · record · token kept here, as every pane's an agent's pane started here: it inherits the flag ✕ sudo refused (terminal.sudo.agents off) asks YOUR SYSTEMD USER SESSION starts the shell on that terminal not the runtime's child: no flag ✓ sudo asks for your password MACOS · NO FLAG an agent's box: Seatbelt refuses sudo, su, login outside a box: your setting BEFORE A SEND OR SEND-KEY THAT IS NOT YOURS TYPES INTO ANY PANE agent or Picache asks ? you approve sudo -K password forgotten then it types THE RUNTIME HOLDS EVERY PANE · ONLY WHO STARTS THE PROGRAM CHANGES
Who starts a pane's program, and what stands between an agent and root.
Pane macOS Linux
Your own shell sudo, asking for your password sudo, through your user session
An agent in its box refused by Seatbelt never: bubblewrap sets no-new-privileges itself
An agent outside a box terminal.sudo.unboxed_agents: password (default) or refuse refused: the pane keeps no-new-privileges

On macOS, an agent's box is a Seatbelt profile. Only a Seatbelt profile of its own could refuse sudo to an agent outside a box, and Seatbelt boxes don't nest. An agent that brings its own sandbox, as Claude Code does, could then not start that sandbox. So the setting for an agent outside a box, terminal.sudo.unboxed_agents, is your choice.

sudo remembers your password for a few minutes. So before an agent or Picache sends text or keys to any pane, cherry π tells sudo to forget it, and sudo asks you again next time. If sudo can't be told, the typing is refused.

Typing from another of your machines needs the As me grant (section 08), so that it counts as yours.

None of this covers an agent CLI you start by hand in your own shell. It can do what that shell can, including use a password you typed a minute ago.

Open several desktops

If you open the same workspace from a second terminal window, both desktops attach to one harness. The status row shows CONNECTED · 2 desktops.

SharedEach desktop's own
Terminals: output shows on both, and either can typeThe layer in front
Each layer's layoutFocus
The Picache conversation, and any turn so farZoom

A question Picache asks appears on every desktop, and the first answer counts. The desktop you used last plays notifications. If two desktops change a layout at the same moment, the later save wins.

A paired machine's desktop can attach too, over P2P: to the whole workspace, to one layer or to one terminal. It acts as that machine, not as you, and holds only what you grant it (section 08).

Another machine's desktop opens here in a pane of its own, framed in the remote colour and marked ◎.

The desktop

The desktop draws your work as a tree. Each part of the tree is a single pane, a stack of panes shown as tabs, or a split.

Panes tile exactly, and a terminal resizes with its pane. A pane's kind decides how it draws and which keys it takes. Floats are panes that sit over the tree like windows (section 01).

Kinds of pane

Pane What it is for
Terminal A shell, a vendor agent or any other program, on libghostty and held by its pump (section 01)
Chat with Picache A conversation with Picache, with its input at the foot. Several can be open at once.
Editor, Viewer A file, read through the harness. Markdown is rendered, code is coloured, and a save is checked against the bytes it read.
Files A folder as a tree beside your work. You can open files beside it, copy, move, trash or archive them, and see each file's git change.
Runs What ran in this workspace, when it waited on you, and how it ended
Inbox Issues that you and the agents write in, and the offers, asks and tasks between layers, for you to accept or reject
Goals Goals given to vendor agents, with each one's steps, Picache's review and the whole diff, for you to accept or reject
Models What this machine can run, as measured, and each seat's model and whether it's loaded
Keys Your API keys, sealed on this machine and shown by their last four characters
Downloads, Tools, Media Durable jobs: downloads with exact bytes and checksums, the managed media tools, and imports and conversions
Art Clips, pictures, and video played by ptv
Machines, Capsules Your other machines, with pairing and shares, and every capsule, here or on another machine, each with Stop
Rooms, The Bakery People on paired machines, and public rooms you join by name
Extensions Installed .pi apps, what they ask for, and the panes and programs they add
Actions, Errors, Settings Actions lists every action and can stay open. Errors lists what failed or was refused. Settings holds every setting, on seven pages.

When a new pane asks what it should be, you can pick any of these kinds. You can also save it in a layout and find it by name with Go to (Find anything).

Mouse and keys

Everything you can do has something to click, and the keys are shortcuts.

A pane's header is a small toolbar with split, zoom, resize, move and close. When space runs short, the least important controls are removed from the header, so nothing you can't see is still clickable.

  • Drag a border to resize.
  • Drag a title onto another pane to swap or dock.
  • Drag across any text to copy it.
  • Click outside a dialog to close it.
  • Press F6 to hand the pointer back to your terminal, so it can make its own selection.
Key Does
F1, Ctrl+P Actions: every action, filterable
F3 Hide or show the left rail
F5 Move the keyboard between a terminal and the prompt
F8 Zoom the focused pane or float, and back
F10 Show, hide or focus the floats
Ctrl+Alt+P Go to: layers, panes, files, conversations, receipts, runs
Ctrl+Alt+A The next agent that needs you
Ctrl+Alt+T, Ctrl+Alt+E A Chat on this conversation, or a new conversation
Ctrl+Alt+R The Inbox of the layer in front
F4, Ctrl+Alt+F The layer's Files pane, or a new one beside
Ctrl+Alt+X Pick up the focused pane and move it with the arrows
Ctrl+Alt+S Send to: choose where the prompt goes
Alt+Enter, Esc Steer Picache's running turn, or stop it

The rail

The rail is the column on the left (F3 hides it). At its top is the layer's banner, then a tree of where you are, then AGENTS, the agents you can launch, and NEEDS YOU, everything that waits on you.

Each layer has its own banner. Under the picture are four buttons. The first shows how the picture is drawn (▣ pixels, ▀▄ half blocks, # ASCII, ⣿ braille) and steps to the next. ☰ chooses the banner: the theme's mark, one of cherry π's logos, or a picture of yours (png, jpg, gif or webp, up to 8 MiB). ‹ and › step through the logos. The choice is kept in the layer's own store, so it goes with an export or a fork. While Settings is open, the rail shows the banner of the layer you opened it from.

The tree has three sections:

  • Layers: each layer holds its panes as its tiles hold them (a stack's tabs, its floats, and a pane's own tabs), an Agents branch with the agents launched in it and the goals given in it, and the surfaces over it. Open Settings is a row of its own, ⚙ Settings, because it belongs to no layer.
  • Sessions: the conversation you are in and the recent ones, each saying where it is open.
  • Machines: this machine with its workspaces, each with its harness, then your paired machines and what each shares with you.

Click a row to go there. Tab from the prompt gives the tree the keys: the arrows move and open, Enter does what a click does, / filters it, and Esc gives the keys back. What you open and close is kept for the next start.

Each row says what is going on in it, in the shapes the agent badges use (Agent status):

Mark Means
! Waits for you. An agent's or a terminal's own row says on what, such as ! Bash: rm -rf build.
⋈ A machine asks you for a share, or asks to pair. A machine asking to pair is listed until you answer.
✗ The program reported a failure.
● It finished while you were looking elsewhere.
◔ Working, with the program's own progress (▰▰▱▱▱ 40%) when it reports one. A conversation works while one of its turns is under way.

A plain terminal shows these too when its program reports its state over OSC 7501. A closed row carries the most urgent mark of everything under it, with how many wait when there is more than one: !2, counted as NEEDS YOU counts them. So a closed layer, workspace or section never hides what needs you.

rail.motion (Settings, Desktop, The tree's activity) sets how the marks move. With full, the default, a working row's ◔ turns, and a row lights up for a moment when news arrives while you look elsewhere. calm holds the marks still, and off draws none. The rail ticks only while something on it moves.

Manage files

The Files pane shows a folder as a tree and opens files in a Viewer pane beside it. Its menu row and its File list hold the same actions. Actions that don't apply are dimmed and show the reason.

Every change (copy, move, rename, trash, archive, unpack, chmod) goes to the harness, which checks it, keeps it inside the folder and writes a receipt (section 03). The desktop can't write your files itself. Anything you trash can be restored.

  • A paste of many files is saved as a plan before the first one moves. If the paste is interrupted, Operations… shows it, and Finish completes the rest.
  • In a git checkout, the header names the branch, and each changed row shows its state. Show change opens the diff beside it.
  • If a file changed after you opened it, the save is refused and your edit is kept.

Find anything

Press Ctrl+Alt+P for Go to. It searches as you type, and every word you type must match.

It finds layers and panes by name, agent, kind, file or title. Through the harness it also finds file names and text in every layer's folder, saved conversations, messages, receipts and recorded runs.

Text indexing has published limits, and the workspace's own data is never indexed. The harness refuses to search the whole disk (/). You set search excludes and the cache budget on the Search page of Settings.

When something needs you

The status row shows N NEEDS YOU while the harness waits on you. It may be waiting for you to grant a capability that something asked for (Grants), or for you to deal with work a crash left behind (section 10).

Failures and refusals are counted in red on the status line until you see them. While cherry π runs, the Errors pane lists the last 500. The harness also keeps its own refusals as receipts, so Go to finds them.

A turn stops if Picache asks you something and nobody answers within 10 minutes, if no desktop is open, or if cherry π restarts. The question then waits in Recovery, marked asks you. When you Approve or Decline it there, the turn goes on from that call. The call is never made twice.

The setting picache.approval.unanswered chooses what happens to an unanswered question. It can wait (the default, stored as park), or refuse the call and go on without it.

For agents that need you, see section 05.

The prompt and the Chat

You write a governed turn (How a turn runs) in the prompt band above the status line. The band is always one row high. For a long prompt, a chip opens a bigger window (Ctrl+Alt+L).

The band names where the turn goes, and Send to changes that without moving you. Each layer has its own Picache conversation, so switching layers switches the Chat. A Chat pane follows the newest reply until you scroll up.

A reply can include a form (A2UI) that you fill in with the pointer or the keys. Its button sends your choices as your next message and does nothing else.

Pictures, video and icons

Terminals that draw pictures with the Kitty graphics protocol, such as Ghostty, kitty and WezTerm, show an image as a real picture, fitted to its pane. In other terminals, printimage draws it as text.

Video plays in an Art pane through ptv, as text with sound. Converting a video for ptv runs as a job. Both programs ship inside the APE, cherrypi.ape.

Icons need no patched font. Where the terminal supports the Glyph Protocol, cherry π registers its own shapes with it. Elsewhere it uses plain shapes.

There are seven themes, each a palette of named roles. Terminal Mono uses no colour at all, and nothing relies on colour alone.

What gets governed

The harness governs three things call by call: Picache's tools, file operations, and every picache call.

A vendor agent such as Claude Code is different. It runs in a box that limits what it can reach (section 09), but what it does inside the box is up to it. Only what it asks of the harness is governed.

How a turn runs

A governed turn Six steps. 1, record: what was asked becomes a durable task row before anything runs. 2, gate: what it means, one intent from a closed set, observe, mutate or converse. 3, commission: the allowed capabilities are frozen and checked again when used. 4, execute: only what was frozen runs, its effect saved before the call. 5, answer: the model, with tools, for up to 8 steps, each tool call a verb the policy decides, so you may be asked. 6, receipt: one entry in the hash chain binds every receipt of the turn, refusals included. A model may choose an intent, prose or a tool call; it never chooses a capability, a grant or a receipt. A steer and a stop land between steps, and a second prompt waits its turn. 1 · RECORD what was asked a task row, queued durable before anything 2 · GATE what it means observe · mutate · converse a closed set, nothing else 3 · COMMISSION what is allowed capabilities frozen checked again when used 4 · EXECUTE only what was frozen its effect saved first then the call 5 · ANSWER the model, with tools up to 8 steps each call: a verb, decided 6 · RECEIPT one entry in the chain binds every receipt refusals included A MODEL MAY CHOOSE ✓ an intent · prose · a tool call IT NEVER CHOOSES ✕ a capability · a grant · a receipt a steer (alt+enter) and a stop (Esc) land between steps; a second prompt waits its turn
A governed turn, in the order the harness runs it.

Each step is separate code, and the steps run in this order.

  1. The request becomes a durable task before anything runs.
  2. The gate maps the request to one intent from a closed set (observe, mutate or converse), using keywords or a model. It discards any answer outside the set, so a model can pick an intent but never its own capabilities.
  3. The commission freezes the capabilities that intent allows. Each one is checked again when it is used.
  4. Execution records the intended effect before each call, so a crash can be told apart from a finished call (section 10).
  5. Only then does the model write its answer. What the turn found is labelled as data, so the model does not take it as instructions.

Picache answers with tools, in up to eight steps. Each tool call is one picache verb, decided by the same policy as when you type it. You're asked whenever the policy says so.

Without a model configured, the answer is a fixed template. Any model failure falls back to it, so a broken model never fails a turn.

Send, steer or stop

One turn runs at a time in a conversation, whichever desktop sent it.

During a turnWhat happens
Send a messageIt waits as the next turn
Alt+EnterIt steers the running turn, read before its next step
EscThe turn stops at its next step

Waiting messages are kept in the workspace, so the queue survives a harness restart or a new build. A steer whose turn died runs first, and then the waiting messages run in order.

A call that changes something is written on the turn's task before it runs. That includes calls you approved and calls a script makes. If a crash cuts such a call off, it stays on record, marked as in doubt. Reads aren't written this way, because running a read again changes nothing.

The turn ends with one entry in the hash chain, and that entry binds every receipt the turn wrote (section 10).

Effect classes

Every picache verb declares what it does to the world, as one effect class. The policy decides by that class and ignores the verb's name.

Class Means For example
read Changes nothing ls, cat, grep, search, panes, layout, events, wait, history
record Writes cherry π's own records and settings offer, accept, ask, task, pair, link, box, app, loop
reversible Changes a file and keeps the way back write, patch, undo, save-layout
arrange Changes only your desk float, focus, move, resize, swap, zoom, label, and their layer forms
irreversible Cannot be taken back run, send, send-key, split, close, apply-layout, new-layer, close-layer

File operations

Reading, writing, renaming, moving, deleting, extracting and running all go through the harness. Each change passes three checks, in this order:

  1. A capability check.
  2. The path jail, which keeps the path inside its root.
  3. One receipt, saved in the same transaction as the change.
A change to a file The Files pane, picache write and patch, Picache's patch tool and an accepted offer all go the same way. 1, authority: a capability such as drive.write, refused with a capability code or asked of you. 2, the jail: the path must stay inside its root, walked by descriptor, with no symlinks and no parent steps, or it is refused as path_escape. 3, evidence: one receipt in the same transaction, committed, refused or failed. A refusal is a receipt too, and the order says whose fix it is: a missing grant is yours to give; a path that escapes is refused whoever asks. WHO ASKS the Files pane picache write · patch Picache's patch tool an accepted offer 1 · AUTHORITY a capability drive.write ✕ capability_* ? or you are asked 2 · THE JAIL inside its root walked by descriptor ✕ path_escape no symlinks, no .. 3 · EVIDENCE one receipt same transaction ✓ committed or refused, or failed A REFUSAL IS A RECEIPT TOO · THE ORDER SAYS WHOSE FIX IT IS a missing grant is yours to give; a path that escapes is refused whoever asks
Three checks, in this order, for every change to a file.

Because the checks run in this order, a refusal's code tells you which one failed. needs_confirmation or needs_operator means a grant is missing, and you can give it. path_escape means the path left its root, and no grant fixes that (When a call is refused).

Every operation opens its root, walks each part of the path without following links, and acts on the file where the walk ended, so nothing can be swapped in between the check and the act.

  • A copy keeps the source's mode and streams in 64 KiB blocks.
  • A save keeps the file's mode and is flushed to disk before and after its rename. It's refused as stale_write if the file changed since it was read.

Every change ends as committed, refused or failed. Refusals are recorded too, so you can find out whether anyone tried to delete a file.

Grants

When something lacks a capability, you're asked, and you can give one of three answers:

  • Just this once
  • Until cherry π closes
  • Always in this workspace

Grants are stored in the workspace's own database. If you refuse Picache, that kind of call stays refused for the rest of the turn.

Changing your files is a capability that belongs to you, the operator. Only you grant it, from the desktop, and no extension or app can hold it.

Grants never travel. An imported layer, or an app installed from elsewhere, has to be granted again here.

Vendor agents are observed

A vendor agent in a pane runs its own harness, with its own permissions and model. For each one, cherry π starts it in a box, watches its state, records its run and tells you when it waits on you. It doesn't approve what the agent does inside the box or write receipts for it.

So an agent's pane is marked observed, and a turn in the prompt band is marked governed.

What the agent asks of cherry π is governed call by call, under its pane's token (What a caller may do). This holds whether it types a picache verb or calls the verb over MCP (section 04). An agent can also join as a cohort, with its own write and shell tools turned off (section 05).

What a caller may do

Each pane gets a token that names who is calling and from which folder. The policy weighs the caller's ceiling (the most that caller may do), the workspace's grants and a budget per token.

You are asked in your client and never in the agent's own terminal, so an agent can't approve its own request by answering a prompt there.

Vendor agent's call What happens
Scoped read Allowed through the read interface.
Direct write or patch Refused, except on a goal: then only in the goal's copy of the layer (section 05).
split, send, send-key, close, apply-layout, new-layer, close-layer Asks you in your client, for every call.
Arrange panes: float, move, resize, zoom Allowed without asking on its own panes and the ones it opened. Any other pane asks, and so does moving your keyboard. Both are per-agent settings (picache box claude arrange).
run Refused by the cohort ceiling.
cohort, bringing in another vendor agent Refused by the cohort ceiling.

Picache can make reversible writes within its grant, and asks otherwise. Its irreversible calls ask every time, and so does any change it makes to your desk.

Approving a call covers that call only. It never becomes a standing grant.

Who is asked, for what Who is asked for what. Reads are free for everyone; a vendor agent asks to read a pane that is not its own. Writing a file: you and Picache write inside the workspace's grant and are asked outside it; an agent's writes are refused, except in the copy of its goal. Arranging panes: free for you, asked each time for Picache, free for an agent on its own panes. Moving your keyboard: asked for Picache and, by default, for an agent. Opening, typing into or closing panes and layouts: a destructive one is confirmed by you, and asked each time for Picache and an agent. Running a command: Picache asks each time and runs it in its box; an agent cannot. THE CALL YOU PICACHE A VENDOR AGENT read, list, wait ✓ ✓ ✓ another's pane asks write a file with an undo ✓ asks outside the grant ✓ in the grant asks outside it ✕ refused but on a goal, its copy arrange panes float, move, zoom ✓ ? each time ✓ its own panes others ask move your keyboard focus ✓ ? each time ? by default a setting open, type, close split, send, layouts ✓ a destructive one asks ? each time ? each time run a command ✓ a destructive one asks ? each time runs in its box ✕ refused beyond its ceiling ? ASKED IN YOUR CLIENT, ONE CALL AT A TIME · ✕ REFUSED · NOTHING IS ASKED IN THE AGENT'S OWN TERMINAL
Who is asked, for each caller and kind of call. These are the defaults. An agent's arranging and focus are settings.

picache and Picache

Picache is the agent you talk to in a Chat, and picache is the command it uses as its hands.

Picache is pronounced pee-KAH-cheh. The picache command is available in every pane cherry π opens, and it offers the harness's own tools as 95 verbs.

Every tool Picache calls is one of those verbs. When Picache calls one, it is run by the same handler, decided by the same policy and recorded in the same receipt as when you type it.

One registry, every way in Four callers reach the same verbs: you in a shell typing picache split, Picache making the split tool call in a Chat, a vendor agent typing picache split in its pane with its own token, and the same agent calling split as an MCP tool through picache mcp, with the same token. All go to one registry of 95 verbs, each with one handler, an effect class and a capability. The policy decides each call from the token's actor, the workspace's grants and a budget per token: it runs and the receipt names who called, or you are asked, at your terminal or in your client, or it is refused with the reason. For this split, yours runs, and the other three are asked. you, in a shell picache split -- make test Picache, in a Chat tool call: split {command} an agent in its pane picache split, its own token an agent over MCP picache mcp, the same token ONE REGISTRY 95 verbs one handler each an effect class a capability --json for programs THE POLICY, PER CALL who: the token's actor the workspace's grants a budget per token ✓ runs; the receipt names who ? asked: your terminal or client ✕ refused, and why THE SAME VERB, THE SAME CHECK, THE SAME RECEIPT · ONLY WHO IS CALLING DIFFERS
A verb takes the same path whoever calls it.

Who can call a verb

The same verb, from you, from Picache and from a vendor agent:

# you, in any pane
picache split --right --label tests -- make test

# Picache, in a Chat: the same verb, as a tool call
split {"side": "right", "label": "tests", "command": "make test"}
#   → you're asked in the client; on yes, the same handler runs,
#     and the receipt names picache as the caller

# a vendor agent, in its pane: the same verb, its own token
picache split --right -- make test
#   → you're asked in the client, naming the agent and its pane
Who calls How Who the harness sees Where it is asked
You, or a program in your shell picache <verb> … the operator At your terminal, for a destructive command or a write the workspace has not allowed. --yes answers in advance.
Picache A tool call: one verb, named parameters picache, with a token made for the turn and revoked when it ends, in the folder of the layer in front In your client: Allow this once, or No. A write the workspace has not allowed also offers Until cherry π closes and Always in this workspace.
A vendor agent in a pane picache <verb> … in its own pane agent:claude, from its pane's token In your client, naming the agent and its pane, and never in its own terminal, which the agent controls
A vendor agent over MCP An MCP tool, through picache mcp in its pane agent:claude, from its pane's token As for its pane: in your client, never in its terminal

picache itself is a few lines of shell. It passes its arguments to cherrypi.ape cli, which talks to the harness with the pane's token from PICACHE_TOKEN. Output is text for people, or --json for programs.

The policy decides each call from who made it, what the workspace has granted and the token's budget. The grid in section 03 shows the outcome for each caller. A refused call exits non-zero, says why, and leaves a receipt like any other call.

Find a verb

picache help          # every verb
picache help split    # one verb
picache help --json   # every verb, for a program
picache status        # who you are to the harness, and your folder
picache panes         # every layer's panes and their state

A few you'll use often:

picache wait claude --state blocked
picache send claude --wait "run the tests"
picache float --cascade
picache save-layout dev
picache apply-layout dev --new-layer docs
picache events --json terminal

Give an agent the verbs over MCP

picache mcp is an MCP server over standard input and output. Add it as an MCP server command in the agent, from the pane the agent runs in.

# for example, in Claude Code
claude mcp add cherrypi -- picache mcp

Its calls carry that pane's token, so the harness sees the agent as the same caller whether it types a picache command in the pane or calls a tool over MCP.

An agent's MCP server, in its pane In the agent's pane, the agent talks MCP to picache mcp on standard input and output. picache mcp carries each request to the harness over picache.sock with the pane's token. The harness lists only the tools this actor may ever be allowed: the reading tools, ask, the Inbox's tools, Cherrytop proposals and the desk, and the writes only while it works on a goal, but never run or cohort. Each call goes through the same policy, the same asks in your client and one receipt, and comes back with structured data, checked against the tool's output schema where it declares one, or as an error saying why it was refused. Spec 2026-07-28, stateless, with a handshake for 2025-11-25 and 2025-06-18. THE AGENT'S PANE the agent Claude Code, Codex, Pi… picache mcp MCP on stdin and stdout its pane's PICACHE_TOKEN THE HARNESS picache.sock as the pane, by its token lists only what this actor may ever be allowed ✓ reads, ask, inbox; the desk · writes: on a goal only ✕ run, cohort: not listed EACH CALL the same policy asked in your client one receipt ✓ structured data checked against the schema, where declared SPEC 2026-07-28, STATELESS · A HANDSHAKE FOR 2025-11-25 AND 2025-06-18 a refusal comes back as an error with the reason; a tool it may never call is not there to call
picache mcp: the verbs as MCP tools, under the pane's token.

It follows the MCP specification of 28 July 2026 and accepts clients of earlier versions. It offers tools only, and each caller sees only the tools it could ever be allowed to call.

An agent sees the reading tools, ask, the Inbox's tools, Cherrytop proposals and the desk. The desk is the set of tools that drive panes, layouts and layers (Picache's tools lists them). Arranging its own panes needs no approval, but you are asked before it opens a pane or types into one. It sees the writes only while it works on a goal (section 05). It never sees run (which needs a terminal an MCP client doesn't have), history (which reads Picache's conversation), search and jobs (which read past its folder) or cohort.

A result is checked against the tool's output schema where the tool declares one, and a mismatch is an error.

picache mcp is also the only MCP server a cohort gets. A cohort is a vendor agent you bring into a Chat (section 05).

Picache's tools

Picache has 46 of the verbs as tools. With them it can:

  • read the workspace and its panes, and its own conversation exactly (below);
  • change files with an undo;
  • run a command as a job in its box (section 09), or run a clean room or a script;
  • ask another layer or a vendor agent (section 05);
  • drive the desk: open, type into, arrange and close panes, save and apply layouts, and open and arrange layers;
  • propose opening, showing or arranging things on Cherrytop, for you to accept (When an agent proposes);
  • read and answer issues, see who an issue could go to, and write to agents (Agents write to each other);
  • read and stop the jobs it started.

A conversation starts with the core tools (status, layers, ls, cat, find, grep, write, patch, undo, run) and one more, tools, which lists the rest in groups.

GroupFor
panesReading panes, typing in terminals
arrangeWhere panes sit, and layouts
layersOpening, closing and moving layers
memoryHistory past the window, search, cherry π's own docs
workScripts, a clean room, asking a layer or an agent, its jobs
formsShowing you a form for a choice or an input
inboxIssues and replies, who an issue could go to, what an agent's hooks were seen doing, and agents' messages
webCherrytop: what is open, and proposals for you to accept
app.<id>One per installed app

A group loads when Picache asks for it or calls one of its tools, and stays for the conversation. Loading a group changes what the model is told and leaves what it may do as it was. With groups loaded this way, the start of each request is about 6,500 characters instead of about 20,200.

Picache's tools, and yours Left, the 49 verbs Picache calls as tools: reading (status, layers, ls, cat, find, grep, search, panes, brain, kb, history), changing files (write, patch, undo), running (run, cleanroom, script, jobs), issues and messages (issue, reply, assign, message, skill), asking another layer (ask) or a vendor agent (cohort), and the desk (send, split, send-key, wait, close, zoom, float, resize, swap, move, label, focus, layout, save-layout, apply-layout, new-layer, close-layer, focus-layer, label-layer, move-layer), proposals to Cherrytop (web), a local Rex session with the rex addon on (rex-layout, rex-read, rex-split), plus the tools of a granted .pi app. Right, the 46 verbs that are yours to type: the fleet and its ledger witnesses, identity, offers, links and what each layer accepts from them, agents' goals, boxes and evals, an agent's MCP server, apps, layer export, import, share, fork, restore and snapshots, events and reports, scripts and loops, and Bitcoin and cRED. Picache is told their names every turn, so it can say which to type. PICACHE'S TOOLS · 46 VERBS read status layers ls cat find grep search panes brain kb history change write patch undo run run cleanroom script jobs talk ask, a layer · cohort, an agent desk send split send-key wait close zoom float resize swap move label focus layout save-layout apply-layout new-layer close-layer focus-layer label-layer move-layer web issues issue reply assign message skill rex rex-layout rex-read rex-split + the tools of a .pi app you granted YOURS TO TYPE · 46 VERBS fleet pair machines fleet shares desktops serve reach you identity profile addons offers offer accept reject cancel inbox outbox task links link links unlink accepts agents agent goal box eval connect mcp apps app factory layers layer snapshots surfaces surface watch events capture runs models witnesses scripts context do help ask-operator loop chain bitcoin cred cred-miner Picache names these, and which to type
The 95 verbs, split by who calls them. picache help lists the full set.

An installed .pi app adds its own tools while you grant it. In a thread that only reads, such as another layer's question, Picache gets only the reading tools.

The other 46 verbs are yours to type. With them you:

  • pair machines and manage the fleet, your identity and profile, and the machines that witness your ledgers;
  • answer offers, link workspaces and set what each layer takes from them;
  • give agents goals (section 05), set agents' boxes, run evaluations and install apps;
  • export, import, share, fork and restore layers, and list their snapshots;
  • run a Bitcoin node, check cRED and mine it (section 12);
  • watch events, run your own scripts, and start and stop loops (below).

Two of them are not for typing. connect is what a confined agent's proxy asks about a connection, and mcp is the MCP server you give an agent.

Each turn, Picache is told the names of these 46 verbs. When you need one, it names the command for you to type instead of trying it.

How Picache learns cherry π

Picache learns from an atlas built from the same registry: every verb and tool, its effect, when it asks and how to use it. The atlas is rebuilt whenever the registry changes, so it can't describe a verb that isn't there.

Next to it are an operating shelf and these docs, packed into the runtime. Picache searches them with its knowledge tool before answering how to do something. You and your other agents can search them too.

picache kb search "share a layer"   # the docs and the atlas
picache kb index on                 # also search this layer's files
picache kb semantic on              # ... by meaning, not just words

Your other agents learn the command from picache skill setup.

  • It gives Claude Code, Codex, OpenCode and Pi a skill that explains cherry π.
  • It gives Claude Code, Codex, Cursor Agent, Devin and Kimi CLI hooks, so each one's pane shows its state. The hooks of Claude Code and Codex also tell a new session which pane it's in.

Each change is marked as cherry π's, and picache skill remove takes out only those.

What the model sees

A conversation is never compacted. Every message, receipt and checkpoint stays in the layer's store as written, and the model gets a window over that record.

A window over a record that is kept whole Left, the conversation's record in the layer's store, never compacted: every message, the receipt of every call, and a checkpoint for every turn. Middle, what the model is handed: one system message with Picache's rules, the latest checkpoint and the conversation's origin; the newest messages, by default 8 of up to 1600 bytes each and 8192 in all; and the request, whole. Right, Picache can search and read the record exactly with history_search and history_read, a chunk at a time with a digest, and each turn's receipt names the context version and its fingerprint. THE RECORD never compacted messages every one, whole receipts what each call did checkpoints one for every turn in the layer's own store WHAT THE MODEL IS HANDED ONE SYSTEM MESSAGE Picache's rules the latest checkpoint the origin: the first ask the newest messages 8, 1600 bytes each, 8192 in all the request, whole what is left out is counted LOOK IT UP history_search history_read exact, by chunk, with a digest ON THE RECEIPT its context version its fingerprint pinned by a contract CHECKPOINT_PLUS_RECENT · SETTINGS CONTEXT.*
The record stays whole. The model gets a bounded window and can look up the rest exactly.

Each turn leaves a checkpoint. It records how the turn came out (never rounded up to completed), which tools it had, any effect in doubt, the files, jobs and calls it touched, and the start of its reply. The next turn starts from it.

The model's one system message holds the latest checkpoint and the conversation's first request. Then come the newest messages that fit:

SettingDefaultWhat it limits
context.recent_limit8Newest messages in the window
context.message_chars1600Bytes of each message
context.window_budget8192Bytes in all

Your current request is always kept whole. The checkpoint counts what the window left out, and a checkpoint written when Picache had different tools is marked stale.

Beyond the window, Picache can look things up exactly. The harness fills in which conversation a lookup reads, so a call can't reach another conversation.

  • history_search searches its conversation's messages, receipts or checkpoints. It returns two previews with ids and a cursor.
  • history_read returns one record exactly, a chunk at a time, with a digest that shows if the record changed between chunks.

Everything the model is handed is versioned: the instructions, the tool declarations, the window's defaults and the strategy. Each turn records its version in a receipt, so you can tell which context a reply was made under.

picache context   # exactly what the model was given on its last call

Run a loop

A loop works toward one goal over several turns in the folder your pane opened in, checking each step with the layer's tests.

picache loop start --goal "make the API tests pass" --steps 10
picache loop   # list runs
picache loop status <run>
picache loop continue <run> --steps 4
picache loop stop <run>
A loop's steps picache loop start with a goal begins a run. Each step is a whole Picache turn, given the goal, a compact memory of the steps so far and the run's PROGRESS.md. Then the layer's test command runs as a job owned by the step. If it passes, the run is done. If it fails, its output is what the next step works on, until it passes or the steps run out, six by default, and the run stops as blocked for picache loop continue. One step runs per tick, and never while an effect in its conversation is in doubt. picache loop start --goal "…" STEP N a Picache turn the goal, the memory, PROGRESS.md VERIFY the test command a job owned by the step ✓ passes: done ✕ fails the next step's work its output goes to the next step until it passes, or the steps run out (six by default) and it waits for picache loop continue ONE STEP PER TICK · NEVER PAST AN EFFECT IN DOUBT
A loop: a whole turn per step, checked by the layer's tests.

A run works in that folder. Each step:

  1. is a whole Picache turn in the run's own conversation, listed as Loop: and the goal, and governed and receipted like any turn;
  2. gets the goal, a short memory of earlier steps and the run's PROGRESS.md, and is told not to redo finished work;
  3. ends by running the layer's test command as a job (loop.test_command.<layer id>, else loop.test_command).

If the tests pass, the run ends. If they fail, the next step works on their output.

When the steps run out (six by default), the run stops as blocked, and loop continue gives it more. With no test command, a run takes its steps and stops for you to review.

A run stops as blocked before its next step while anything in its conversation waits on recovery (section 10). After a restart, a running loop stays blocked until you continue it, unless recovery mode is automatic.

Anyone can list and read runs. Only you can start, stop or continue one, and a script cannot. An app you granted ai.redloop.manage (section 09) can start, stop or continue the runs it started, and you are asked each time it starts or continues one.

Agents

There are two kinds of agent in cherry π, and they differ in who governs them.

  • A folder's agent is Picache, or an agent you define for a folder. The harness governs it: it decides what the agent may do and records what it does. All of its tools are governed picache commands, so it can act only within the permissions the workspace has granted.
  • A vendor agent, such as Claude Code, Codex or Pi, runs under its own harness in a terminal pane, confined in a box (section 09). Its run is observed and recorded, but cherry π does not govern what the agent does.

Declare a folder's agent

picache agent new docs --role "Keeps the API docs in step with the code"
picache agent new docs --library   # keep it in the workspace, not the repo
picache agent list
picache ask L1/docs/api "which endpoints have no examples?"

Every folder is answered by an agent: its own, if one is defined for it, otherwise the nearest parent folder's, then the layer's, then the workspace's, and Picache when none of those exist.

picache agent new writes the agent's definition to .cherrypi/agent/agent.json inside the folder. It names a persona, a preferred model, and a folder of private Markdown notes, kb/, that only this agent reads. The definition lives in the folder, so it goes with the folder wherever the folder is copied or shared.

Use --library to store the definition in the workspace instead of in the folder. The workspace records which folder paths it belongs to, so the repository contains no agent files.

The persona applies only within cherry π's own rules and cannot override them. The agent's permissions always come from the workspace's grants, never from its definition file. Its actions are recorded in receipts under its own name.

When you send a question or a task to a layer, the layer's agent answers it. You can also address a question directly to a folder inside a layer, whether that folder is in this workspace, in a linked workspace, or on another machine.

Test and share an agent

picache eval               # run the cases
picache eval schedule on   # run them while the model is idle
picache agent pack --out docs.pi
picache agent install docs.pi

picache eval runs test cases against an agent: a standard set of core cases, plus any cases the agent defines itself. An evaluation cannot change anything: read-only operations run for real, and every other operation the agent attempts is recorded but not carried out. The core cases check that the agent:

  • reads a file before answering a question about it;
  • searches the folder to find a file before trying to read it;
  • looks at the pane when it is asked what a pane shows;
  • asks before running a command, instead of running it;
  • when a job belongs to the operator, names the command the operator should run (for example, picache pair) instead of attempting the job itself;
  • answers how-to questions about cherry π, such as how to unpair a machine or give a folder its own agent.

picache agent pack bundles an agent into a single signed .pi file, and picache agent install checks that signature wherever the file is installed. It refuses an agent that is unsigned or was changed after signing. An agent signed by someone you do not know needs --untrusted.

Launch a vendor agent

The rail lists the vendor agents cherry π can launch: Claude Code, Codex, Gemini CLI, Cursor Agent, Copilot, Pi, and others. Next to each one, it shows whether the agent is installed and whether an update is available.

When you launch an agent, cherry π starts it in a new terminal pane opened at the layer's folder. By default it runs confined in its box (section 09), and the run is recorded (section 10). The agent keeps its own conversation transcript, and cherry π does not take it over.

Ask a vendor agent in a Chat

If you start a Chat message with @claude, Claude Code answers that message instead of Picache. A vendor agent brought into a Chat this way is a cohort.

@claude what does the build script do?     # in a Chat
picache cohort                             # who can be asked, and who is running
picache cohort claude "what is in here?"   # ask once, from a shell
picache cohort stop all                    # stop a conversation's cohort

In a Chat, cherry π talks to the agent over ACP (Agent Client Protocol), and the agent works in the layer's folder. It is confined in the same box it would use in a terminal pane, with the same launch setting, network access, and login, but it has no terminal.

The agent's own tools for writing files, editing files, and running commands are turned off, which leaves it Read, Glob and Grep. Its only MCP server is cherry π's own (section 04), reached with a token made for it alone, so anything it tries beyond reading is checked against the same policy as any other agent's work.

If the agent asks for permission to do something, cherry π asks you, and your answer covers that one call only. If you do not answer, the call is refused.

A vendor agent as a cohort A message in a Chat that starts with @claude goes to Claude Code instead of Picache; the turn around it stays Picache's, with its task, receipts, ledger, snapshots and events. Claude Code's ACP program runs in its box, the one its terminal pane would have, without a terminal: the harness talks to it over a socketpair. Its own tools are only Read, Glob and Grep; writing, editing and the shell are turned off in its own settings. Its other tools are cherry π's, through picache mcp under a token of its own. What comes back is its words, marked external and unverified, with what it says it ran and what it asked; a permission it asks for is asked of you, per call. It stays for the conversation until it has been idle for 15 minutes, its folder changes or you stop it, and at most four stay at once. A CHAT you @claude what's in here? the turn stays Picache's task, receipts, ledger snapshots, events the next message: Picache, reading its answer ITS BOX, NO TERMINAL claude-agent-acp ACP on a socketpair its own tools ✓ Read Glob Grep ✕ write, edit, shell: off picache mcp a token of its own WHAT COMES BACK its words external, unverified what it says it ran what it asked ? asks you, per call no answer is a refusal STAYS FOR THE CONVERSATION · ENDS IDLE 15 MIN, ON A NEW FOLDER OR BY YOU · 4 AT MOST picache cohort claude <question> asks once, from a shell, and stops it
@claude in a Chat: a vendor agent over ACP, in its box, with cherry π's tools.

The reply holds the agent's answer, marked as external and unverified, with the tools it says it ran and any questions it asked. The turn still belongs to Picache and is recorded like any other.

Your next message goes to Picache unless it also starts with @claude. Picache can read Claude Code's answer as part of the conversation and knows that Claude Code wrote it.

Claude Code stays running for that conversation, keeping its context, until it has been idle for 15 minutes, the conversation moves to a different folder, or you stop it. At most four of these agents can stay running at one time. When a fifth is needed, the oldest one is stopped.

Bringing in a vendor agent cannot be undone: your question, and anything the agent reads, is sent from this machine to the agent's vendor. You can bring one in directly at any time. Picache can do so only with your approval for that call, and an agent brought in this way cannot bring in another one.

A vendor agent can be asked in a Chat only through the Agent Client Protocol (ACP). Two agents have it: Claude Code, through the @agentclientprotocol/claude-agent-acp adapter, and OpenCode, whose own opencode acp speaks it. Ask OpenCode with @opencode.

Give an agent a goal

A goal gives a vendor agent a piece of work to complete on its own. The agent works in a copy of your layer, and none of its changes are applied to the real layer until you accept them.

picache goal @claude "make the login tests pass" --steps 8
picache goal @claude,@opencode "make the login tests pass"   # one copy each
picache goal                     # the goals, newest first
picache goal status <run>
picache goal comment <run> src/login.ts:42 "check the token's expiry"
picache goal revise <run>        # send the open comments back
picache goal todo <run> "add a test for the expired case"
picache goal accept <run>        # merge its work into the layer
picache goal reject <run>        # leave it unmerged
picache goal push <run>          # its branch to a git remote, with your git
picache goal offer <run>         # its branch to a paired machine's layer
picache goal stop <run>
picache goal continue <run> --steps 4

Claude Code and OpenCode can take goals. Each is given the goal through ACP, with its own tools narrowed to reading, so every change it makes goes through cherry π. An agent starts in the agent box only once it has been measured there, and one that hasn't been is refused with confine_agent_unmeasured. OpenCode is measured under Seatbelt on macOS, not yet under bubblewrap on Linux.

A goal works like a loop (section 04), except that the vendor agent, not Picache, takes each step. A step is one turn for the agent, like a message you address to it with @claude in a Chat, and ends by running the layer's test command. A goal has six steps unless you set another number.

The agent works in the .worktrees/goal-<id> folder inside the layer, on a branch named cherrypi/goal-<id>. The copy includes the layer's dependencies and existing build output, and is copy-on-write where the file system supports it.

A goal, from copy to merge You give a vendor agent a goal with picache goal. It works in its own copy of the layer, .worktrees/goal-id, on a branch cherrypi/goal-id, and writes there and nowhere else. Each step is its turn, then the layer's tests; a test failure becomes the next step's work, and every step is committed to the branch. When the tests pass or the steps run out, Picache reviews the branch, reading only. Then you decide: accept merges it into the layer, reject keeps the branch for the record. The layer is untouched until you accept, and a conflict refuses the merge whole. Without git, the copy is the whole folder, snapshots stand in for commits, and the merge is three-way, with an undo. YOU picache goal @claude "…" only you give one ITS COPY OF THE LAYER .worktrees/goal-<id> branch cherrypi/goal-<id> a step: its turn then the layer's tests ✕ tests fail: next step every step: a commit it writes here, nowhere else PICACHE reviews it it only reads: what it did, what looks wrong, accept or not ✓ tests pass, or steps run out YOU DECIDE ✓ accept merged into the layer ✕ reject the branch kept for the record either: copy gone THE LAYER IS UNTOUCHED UNTIL YOU ACCEPT · A CONFLICT REFUSES THE MERGE WHOLE without git: a copy of the whole folder, a snapshot per step, and a three-way merge with an undo
A goal: worked in a copy, reviewed by Picache, decided by you.
  • The agent acts with a token created only for this goal. Using cherry π's write and patch commands, it may change files only inside the goal's working folder. Every change can be undone and is recorded with a receipt.
  • It cannot write to the copy's .git folder.
  • At the end of every step, the agent's changes are committed to its branch.

The run stops when the tests pass, when its steps run out or a time limit you gave it passes, or when a step's turn fails or needs your review. Failing tests do not stop it: their output is the next step's work. Picache then reviews the branch. The review is read-only: Picache compares the agent's work with the goal, points out anything that looks wrong, and recommends whether you should accept the work.

Accepting the work merges the branch into the layer. The merge is refused if either folder contains uncommitted work or if the merge would conflict, and a refused merge leaves both folders unchanged. Rejecting the work leaves the branch unmerged, so the agent's work stays on record. Either way, the working copy is removed and the branch is kept.

If a layer is not in git, the agent still works in a copy of the whole folder, but each step ends with a snapshot instead of a commit. Accepting the work performs a three-way merge against the snapshot taken at the start, and the merge is refused entirely if there is any conflict. The layer is snapshotted before the merge, so you can undo the merge afterwards.

If you set goal.workspace to worktree, or the layer's folder is below the top of its repository, the agent works in a git worktree of the layer instead of a copy.

The Goals pane lists every goal and shows its steps, Picache's review, and the complete set of changes.

Only you, the operator, can create, stop, continue, comment on, accept, reject, push or offer a goal. Agents and scripts cannot.

Several at once

New goal…, in the Goals pane's menu (n) and in Actions, asks for the goal, the agent and the number of steps. With Another after this on, the dialog opens again when you start one, so five goals are five presses of Enter.

Naming several agents, as in @claude,@opencode, gives the same goal to each, in a copy of its own. Every agent is checked before any starts. The list marks the goals of one group with ⇄, and each card names the others. When you accept one, cherry π asks whether to Reject them or Keep them; a rejection says which approach was accepted instead.

Commands, each asked of you

An agent on a goal can call run and script in its copy, and nowhere else. It never gets a shell of its own. You're asked about each command in the client: Allow this once, Allow it for this goal (that exact command line, kept on the goal and receipted), or No. The command runs as a job in the agent's box, writes only in the copy, never in its .git, and stops when the goal does. The card lists what you allowed and every command a step ran.

Setup, test and run

A layer can have three commands, each a setting with an optional layer-specific form, as the test command has (section 04): loop.setup_command, run once in a new copy before the first step; loop.test_command, run after each step; and loop.run_command, the app itself. Layer commands…, in the Goals pane's menu, sets all three for the layer in front.

Each goal gets a block of ten ports, starting at 42000, which it keeps until you decide it. Its first port reaches the setup, test and agent's commands as CHERRYPI_PORT, and the agent is told to listen there, so two goals' servers don't collide. A setup that fails stops the run, its output shown on the card, and Continue runs it again. The card's Run opens a terminal pane, run · goal <id>, in the copy, running the run command on that port.

Comment, and request changes

Click a line in the goal's diff to mark it, then Comment on <file>:<line>…. Comment… without a line comments on the change as a whole. A comment is drawn under its line and listed on the card, which keeps the line's text as it was. A comment is never deleted: Withdraw marks it withdrawn, and each one is receipted.

With comments open, the card's main button is Request changes (goal revise). The run goes on for two more steps unless you say otherwise, and the next step's prompt carries each comment with its file, line and text. A goal whose tests passed can be revised, as one that ran out of steps can, but a decided one cannot. Picache's earlier review moves to the goal's history, and a new one follows the revision, reading which comments were sent and what has changed since.

Todos, and Ready?

A goal keeps a list of todos. You add yours with Todo… or goal todo, and the agent can add its own and close them, but not yours. Each step's prompt lists the open ones. Accept is refused while any is open (goal_todos_open).

The card's ready? line sums up what Accept depends on: the test, Picache's review, open comments, todos, conflicts, and uncommitted work. A ✗ marks what Accept would refuse, or what you'd want to read first.

Push, and ask for a pull

Push… pushes a goal's branch to one of the layer's git remotes, under the same name. It works with any git host, or none, and cherry π holds no token for it: the push runs with your own git in a terminal pane, push · goal <id>, in the layer's folder, so your ssh agent, credential helper or password does the signing in. With several remotes, cherry π asks which. Once the branch is there, the pane prints the text of git request-pull, which asks for it to be pulled; send it to whoever merges, by any route.

The push names the commit cherry π read when you asked, so a branch that has moved since isn't pushed in its place, and it runs no git hooks. A goal outside git, or one still running, can't be pushed, and a layer with no remote is refused with goal_no_remote, naming git remote add. The card's pushed line names each remote that has the branch, as git last heard, and says when the branch has moved on since. Merging stays yours, here with Accept or where you pushed it.

Offer a branch to a paired machine

Offer… sends a goal's branch to a layer on a paired machine or of a linked workspace, peer to peer, with no git host between them. It lists each one, addressed to the layer of the goal's name there, and lets you change the address before it goes. The branch travels as a git bundle of the goal's commits, with its goal, agent, log and the files it changes, up to 10 MiB; past that, push it instead.

The offer waits in that layer's Inbox, and accepting it is that operator's. Accepting checks the bundle, then fetches the branch into that layer's repository under its own name, with nothing checked out and no hook run. It's refused if the repository doesn't hold the commit the goal started from (conduit_branch_unrelated), and it never moves a branch of that name that's at another commit (conduit_branch_exists). Merging it is theirs.

Agent status

Each agent shows both its state and how that state was determined, because a badge that said working while the agent waited for your answer would mislead you. The state comes from several signals, ordered from the most reliable to the least:

  1. The process itself: whether it has exited, whether it has started a child process, and whether it printed anything in the last moment. These checks know nothing about the particular agent, so they work for an agent cherry π has never seen.
  2. The agent's own reports: its terminal title, and reports sent through the Program Status Protocol (OSC 7501), where the agent states whether it is working, idle, finished, failed, or blocked waiting for a permission, an answer, or a sign-in.
  3. Hooks installed by picache skill setup, which make Claude Code, Codex, Cursor Agent, Devin and Kimi CLI report their state in the same way. With these hooks, every one of them but Cursor Agent reports a permission dialog as blocked, together with the tool and the command it concerns.
How cherry π knows what an agent is doing The ladder, certainty first: 1, the process exited, a fact; 2, it started a child process, a fact; 3, it printed in the last 1.5 seconds, a fact; 4, the terminal's title, reported; 5, its own report through OSC 7501 or its hooks, reported. The badge is one glyph naming your next act: working, wait; idle, type; unseen, read it; blocked, answer; error, inspect; exited, nothing; unknown, look. An inferred state is drawn dim, and the detail pane says why. An observed exit takes precedence over an agent's report, and a report takes precedence over inference; only a report can mark an agent blocked. THE LADDER · CERTAINTY FIRST 1 the process exited fact 2 it started a child process fact 3 it printed in the last 1.5 s fact 4 the terminal's title reported 5 its own report: OSC 7501, hooks reported THE BADGE · ONE GLYPH ◐ working wait ○ idle type ● unseen read it ! blocked answer ✗ error inspect × exited nothing · unknown look INFERRED · DRAWN DIM ◐ working, inferred the detail pane says why exit overrides report · report overrides inference · only a report says blocked
The status ladder and the badge. ● is a result you haven't seen.

The status badge is one symbol for what you should do next. An inferred state is drawn dim, and the agent's detail pane says how the state was decided. The more reliable signal wins: an observed exit overrides an agent's report, and an agent's report overrides an inference.

Only an agent's report can mark it blocked. The badge never guesses that state, because silence cannot distinguish a model that is still thinking from a question that is waiting for your answer. If two panes run the same agent and a report could belong to either pane, the report is withheld so that it never appears on the wrong pane.

The rail's tree shows the same marks on each pane's row, and a closed row carries the most urgent of what it holds (The rail).

A script or shell can wait until an agent needs you:

picache wait claude --state blocked   # until it needs you

Notifications

When an agent needs your attention or finishes its work while you are not looking at it, cherry π notifies you once and plays a sound. When several agents need you at the same time, one notification covers them all.

Press Ctrl+Alt+A to jump to the agent that has been waiting for you the longest. Questions are shown first, then failures, then results you have not yet seen. A result remains marked as unseen until you focus its pane.

Each agent can have an expression: a picture, a sound, and a line of text for each state. The picture appears next to the agent's name in the rail and on its profile card.

You choose how notifications are delivered: through the terminal cherry π runs in, through the operating system's notification system, or not at all. The setting applies in every workspace.

The software factory

The factory makes .pi apps (section 09). You describe what to build in a sentence or two, and a vendor agent builds it in a layer of its own. Checks that the agent never sees decide whether the app passes, and nothing is accepted until you accept it.

  • A schematic describes one kind of app: a working skeleton to start from, what every app of its kind must keep, the capabilities it may ask for, which agent builds it, and how much that agent may spend.
  • A product is one app a schematic made. It is a git repository and a layer of its own, beside the layer it was made from.
  • The builder is the vendor agent working on it, as a goal (section 05).
  • The hidden checks are the schematic's scenarios (Hidden checks). The harness runs them against the packed product, and the builder hears only what failed.
  • A Try runs a product before it is installed, on data of its own.

Make a product

Add a Product pane in any layer. In a layer that is not a product, the pane is where one is made:

  • Schematic. The layer's schematics, and an Add button for each starter cherry π ships: data-app (a collection in the app's own tables, with verbs, a pane and pages), pane-program (a program in a terminal pane, drawn from keys) and web-app (pages that are the product, with a Cherrytop widget). Adding one copies it into .cherrypi/schematics/, where you can edit it.
  • Builder. The vendor agents that can work over ACP, as a cohort does (section 05). The schematic's own is chosen unless you pick another. One that is not installed on this machine is named but not offered.
  • What it spends. Before anything starts, the pane says how many steps and minutes the builder may use. The steps are the builder's own turns, from its vendor, not Picache's.

Make a product… asks for the product's name (its layer and its app's id), then what it should be. Nothing starts until you have answered both, and then the pane shows the new product. From a shell:

picache factory                          # this layer's schematics, the starters, the products made
picache factory add data-app             # copy a starter into .cherrypi/schematics/
picache factory run data-app books "a reading list with ratings"
picache factory run data-app books "…" --agent @claude --steps 8

While it is built

The builder works as a goal does, in a copy of the product, one step at a time. Each step ends with the schematic's public test. After a step, the result of the last hidden check (in the schematic's words) is part of what the builder is told next.

The Product pane says where the build stands:

The pane saysWhat it means
building · step n of mThe builder is working. Stop ends its run, and you then accept or reject what it did.
Picache is reviewing itThe builder stopped. The hidden checks run on its copy, and Picache reads the result and the changes.
waiting on youPicache's review is shown. Accept merges the build into the product. Reject… leaves it out, with your reason if you give one. Open its chat shows its steps and the review.
its build was accepted / rejectedDecided. A rejected build's branch is kept for the record.

A build is accepted only on a current pass of the hidden checks, on exactly what is in its copy now. A pass on an earlier version, or under scenarios that have changed since, does not count.

A run stops for review when its steps or its minutes run out. picache goal continue <run> --steps n --minutes n gives it more.

Each step's commit says what made it, as git trailers: Cherry-pi-Goal, Cherry-pi-Agent (with the name and version the agent gave), Cherry-pi-Launch (confined or standard) and Cherry-pi-Factory-Run. The merge that accepts it says why: the goal, Picache's review, how many hidden checks passed on which candidate, and who accepted it.

Hidden checks

A scenario is a small Lua data file in the schematic's scenarios/ folder. The harness packs the product once and runs each scenario on its own copy, in a capsule. When the product asks for its own tables, the request is carried out for real. Any other request is answered from the scenario's answers (or the schematic's finish.answers) or refused.

KindChecks
callA verb or tool answers with this text, pattern or data
asksWhat it asked the host for: these capabilities, never those
rowsAfter these calls, its own tables hold these rows
pageA page answers with this status, type and content
programThese keys give a screen that contains this, and fits the pane
staticIts manifest is valid, it asks only for what the schematic allows, it packs and its signature verifies, and it is small enough

The builder never sees a scenario. With feedback = "summary" (the default) it hears which scenarios failed, what was expected and what was observed. With "verdicts" it hears only which failed. A builder launched confined cannot read them. One launched standard could, and every verdict records which it was.

To run the hidden checks now, use Run hidden checks in the pane, or picache factory finish <product>.

Try a product

Try it runs what is in the product now, before it is installed, on data of its own and only with what the schematic allows. The pane then offers what the product has:

  • each of its panes, and each program in a terminal pane;
  • its pages: Open in Cherrytop, or https://try-<id>.pi;
  • Open its console: type a verb and its arguments, tool <name> <json>, tables or rows <table>, and see the answer, what it asked for, and what changed in its tables. keep <name> saves the last call as a new hidden check that expects what you just saw.

Close Try ends it, and its data goes with it. A Try never installs anything, and <id>.pi stays the installed app's.

picache factory try books
picache factory call books add "Dune"
picache factory close books

Pack for review

Pack for review, or picache factory pack <product>, runs the hidden checks. If they pass, and only then, it packs the product as a signed .pi onto the review shelf, <state>/factory/shelf/. The product's evidence is inside the signed file: the order, the schematic, every check and scenario result, and whether the builder was confined.

picache factory says how each schematic is doing: its runs, how many were packed, accepted and rejected, and how often its hidden checks passed.

Turn it off

The factory is a first-party addon, on by default. picache addons disable factory turns it off, and picache addons enable factory turns it back on.

While it is off, there is no picache factory and no Try, and the Product pane says the addon is off and how to turn it back on. A build the factory started cannot be accepted while it is off, because its hidden checks are not there to pass it.

Models and scripts

Picache runs on a model you choose, and you can script cherry π in Lua.

Choose a model

Open Actions → Open Models. Its two tabs are This machine and Catalog.

RowShows
MachineThe chip, its memory and cores
GPUHow much of its memory is in use
Runtimecherry π's own llama.cpp (b11819), with Install when it's missing
SeatsPicache, Small, Search, and Cloud in hybrid mode: each one's model, with Load and Empty
ThinkingWhether a reasoning model thinks first (off by default)

Each model gets a verdict: runs, tight, too large or won't load. llama.cpp measures it from the model's header, so a catalog model is measured before you download it. Try loading confirms it with one real load.

A model loads only into free GPU memory, with 1 GB to spare. If there isn't room, cherry π stops models it started to make room: those in no seat first, then the oldest, and never the one serving Search. If even that would not make room, the load is refused before anything stops, and the reason names what holds the memory. Model servers cherry π didn't start are listed, with Stop.

Seats' models stay loaded side by side while they fit. Empty clears a seat, and unloads its model if no other seat uses it. Search takes only an embedding model, and no other seat takes one.

The pane also finds or imports models you already have, and uses them where they are. Downloads are checksummed, resumable jobs, and llama-server runs as a job too.

Picache can also use any OpenAI-compatible service. Without a model, it answers with a fixed template (section 03).

Vendor agents (section 05) keep their own models, subscriptions and settings.

Your API keys

Open Actions → Open Keys. The signer seals your API keys on this machine (Where your secrets go), and the pane shows each one only by its last four.

TabHolds
ProvidersHugging Face, OpenRouter, Qwen Cloud, DigitalOcean inference
AgentsEach vendor agent's own keys, such as a login token you handed in

A row reads one of these:

  • stored (••••abcd);
  • an environment variable ($HF_TOKEN ••••9876), which is read but never copied in;
  • not set;
  • locked, when the sealing key has changed.

A stored key wins over the variable. Listing your keys opens none of them anywhere else, because the pane gets only the last four from the signer (Where your secrets go).

Press Enter to set a key, masked as you type, and x to clear one, which asks first. Only this machine's own desktops can see or change keys, and no verb reaches them. Each change is a receipt naming the key, never its value.

An agent's keys go into its environment when it starts, or stay with the signer when its Key is set to inject (Keep the key out of the box). Provider keys belong to the Cloud seat (Which model answers).

Which model answers

Without a router, every step goes to the Picache seat with the workspace's thinking setting. A retry or a continued turn stays on the same model, so the prompt cache holds (route.sticky).

A router is an installed app's route hook. It proposes a seat, thinking and limits, or handing the turn to a vendor agent. Each proposal is checked before it is used:

  • the app is granted that seat (route.seat.small, …);
  • the seat's model is loaded;
  • the model has qualified for the job;
  • anything leaving the machine is allowed.

A proposal that fails a check is overruled, and the record says why. A router never answers a permission prompt or changes a tool call.

picache eval qualify small   # a seat's model must pass before a router uses it
picache layer egress ask     # may this layer's requests leave the machine?

Leaving the machine means the Cloud seat, in hybrid mode with a key from Keys, or a handoff to a vendor agent. It's off by default (route.egress). The Chat says when a step was sent to a cloud model or handed to an agent.

Run a script

A Lua script runs in one of two places, depending on who wrote it.

picache do layout.lua name=dev               # yours: runs as you
picache script check.lua --budget calls=20   # anyone's: runs in a capsule
Compared picache do picache script (code mode)
Written by You A model, or anyone you don’t vouch for
Runs In your terminal, as the picache command you started In a capsule: an empty folder of its own, confined by Seatbelt on macOS
Can use Lua’s whole library, including io.popen and os.execute Computation, JSON and the verbs it was given. No files, no programs, no network.
Governed Each picache.* call, as the pane Each verb call, as its caller, within a fixed list and budget

picache do takes its scripting shape from Rex, the Superlogical terminal. Both run a local Lua script that can arrange terminals, wait for events and run commands. Rex removes io.popen and os.execute; cherry π leaves them working. Cherry Pi also applies its own pane authority and receipts to calls made through picache.*. With the rex addon on (picache addons enable rex) and Rex running on the same Mac, the same script has a rex table that talks to that server: rex.call, rex.session, rex.layout and rex.block.call, as in Rex's own Lua. picache.* still talks only to cherry π.

Blocking them would not protect anything, because the script is your program running in your terminal, and redbean's unix library does the same with fork and execve. A script reaches what you can reach at the prompt, and no more.

When an agent runs picache do in its pane, the script runs in that agent's box.

A script you didn't write belongs in picache script, where the whole environment is the limit. Picache's script tool is that same capsule, limited to the tools its turn has, so a script there can do no more than Picache could do one call at a time.

Each picache.* call carries the pane's token and leaves a receipt. A command started with io.popen never reaches the harness, so, like a command typed in the pane, it leaves no receipt.

Still, prefer picache.exec over io.popen:

  • It takes a list of arguments instead of a shell line, so nothing needs quoting.
  • It has a timeout.
  • A command it starts stops when the part of the script that started it ends.
  • The script keeps reading events while it waits. io.popen blocks the whole script until its command ends.

Machines, offers and rooms

Conduit is how cherry π talks to itself and to others: layer to layer, workspace to workspace and machine to machine.

The three differ only in how the bytes travel. The records, an offer's lifecycle, the receipts and the authorization are the same in all three.

One stack, three carriers Conduit is a stack, layer on layer: features (asks, offers, tasks, rooms, shares) on Conduit (typed offers, outbox, inbox, receipts) on a typed session (signed envelopes, stored before any effect) on a carrier, which moves bytes and says what it is. Three carriers: local, to another layer such as L1, inside one harness; direct, to another workspace on this machine such as web:L1, over a Unix socket once the two are linked; fleet, over Holepunch, to another machine such as studio:web:L1, over Noise, forward-secret, between paired machines. The records, lifecycle, receipts and authorization are the same; only the carrier differs. FEATURES asks · offers · tasks · rooms · shares CONDUIT typed offers · outbox · inbox · receipts TYPED SESSION signed envelopes, stored before effect CARRIER moves bytes, and says what it is local another layer: L1 inside one harness direct another workspace: web:L1 a Unix socket, linked once fleet · Holepunch another machine: studio:web:L1 Noise, forward-secret, paired THE SAME RECORDS, LIFECYCLE, RECEIPTS AND AUTHORIZATION ONLY THE CARRIER DIFFERS
Layers, workspaces and machines differ only in how the bytes travel.
AddressMeans
L1A layer in this workspace
L1/docs/apiA folder inside it
web:L1A layer in a linked workspace on this machine
studio:web:L1A layer on a paired machine

Every machine keeps a durable queue, so all of this works without a server.

  • An offer is written to the outbox before the network is touched.
  • It counts as delivered only once the receiver has saved it.
  • A repeated message gets its stored answer instead of running twice.
  • An offer that doesn't arrive stays queued and is sent again. A refusal is final.
  • Both ends keep the record.

Offers, asks and tasks

From a shell, the three look like this:

picache offer L2 message "the schema moved to v3"
picache offer L2 patch src/api.ts oldName newName
picache ask L2 "which tests cover login?"
picache task L2 "update the client for the v3 schema"
An offer, from outbox to applied Layer L1 sends: an offer, say a patch to layer runtime, made by you, Picache or an agent, is written to the outbox, queued and then sent, before the network is touched. Any carrier takes it; delivered means the receiver committed it. Layer runtime receives it in its inbox, with a count on its rail, and you answer there. Accept applies it through runtime's own patch verb, its jail and its undo; Reject changes nothing. How it ended goes back to the sender. Nothing changes in a layer until it is accepted there, and every step is a receipt. L1 · SENDS offer runtime patch … you, Picache or an agent outbox: queued, sent written before the network any carrier; delivered means committed there RUNTIME · RECEIVES inbox: offered ✉1 on its rail; you answer ✓ Accept · apply here its own patch, jail, undo ✕ Reject nothing changes how it ended goes back NOTHING CHANGES IN A LAYER UNTIL IT IS ACCEPTED THERE · EVERY STEP IS A RECEIPT
An offer between two layers.

An offer is a message, a patch or a whole file for another layer. Nothing changes there until you accept it in that layer's Inbox (Ctrl+Alt+R).

A patch is applied through that layer's own patch verb, jail and undo, and it applies in full or not at all. Anyone may offer, agents and Picache included, but only you answer.

An ask puts a question to the other layer's agent. The question runs in a thread of its own, which can only read and can't see either layer's chat.

The answer comes back labelled as coming from that layer, and it is treated as information, never as instructions. The thread opens as a tab on your Chat.

A task is work. Once accepted there, it runs as a thread with the usual tools. Its report comes back marked external_unverified until you promote it.

Writes never cross layers directly. Reads can, limited to the other layer's folder:

picache cat --layer runtime README.md
picache link ~/src/web --accept message,patch,ask

The command links this workspace to another one open on this machine and names the kinds of offer it may send. The other workspace is asked to link back. Once linked, its layers are addressed as <workspace>:<layer>.

An offer to a workspace that isn't open waits in the outbox until it opens.

A layer can decide for itself what it takes from one linked workspace or paired machine, or from all.

picache accepts L1                   # show the lists
picache accepts L1 web message ask   # only these kinds from web
picache accepts L1 web none          # nothing from web
picache accepts L1 all inherit       # hand the choice back

A list can allow fewer kinds than the link allows, or more. A list for one peer takes precedence over the list for all.

A layer with no list takes what the link allows, and any kind from a paired machine. Only you set these lists.

Identity and the signer

An installation has one identity. It is made of the machine's keys (Ed25519, Curve25519 and secp256k1) and a key for each agent, Picache first, ready to be named on Nostr. Workspaces and layers have no keys, because they're addresses under the machine.

The private keys never reach the Lua runtime. They are held by a separate signer process on Bare, which receives what cherry π wants signed and sends back a signature.

picache identity   # the node id and each agent's public key

Pair your machines

P2P is an addon, off until you turn it on. It runs the Bare and Holepunch stack in a sidecar, boxed apart from the signer (section 09).

picache addons enable p2p_holepunch
picache fleet new             # on the first machine: make a secret
picache fleet join <secret>   # on each other machine
picache pair studio           # ask; it shows a code
picache pair approve <code>   # on studio: the code, typed

Asking with picache pair studio shows a code on your machine. The code is typed on the machine you're joining: its operator reads it off the asking machine and approves with it. Studio holds the request for ten minutes.

Studio's list of requests never shows the code, so a stranger who asks under your machine's name can't be approved by someone copying a code from it.

A code typed before its request has arrived is kept for ten minutes. The asking machine asks again while it waits, and it is approved when the request comes in.

P2P belongs to the machine, so a pairing made in one workspace holds in all of them.

A fleet is the set of machines that can find this one. Fleet machines announce themselves on rednet, the public peer-to-peer network, under a topic hashed from the fleet's secret. Only machines with the secret can work out the topic.

  • Without a fleet, and with the open directory off, a machine is listed nowhere.
  • The open directory stays off until you turn it on (picache fleet open on).
  • On one local network, fleet machines also find each other over mDNS, and they keep working with no internet. The mDNS beacon shows the machine's id and key to everyone on that network. Only its fleet is hashed. picache fleet lan off stops the beacon.
  • A beacon is only a hint. Seeing one never makes a machine trusted.

Pairing pins each side's key and sets up a contract in each direction. Each contract has exact scopes and can be revoked. It lasts 30 days and is renewed for as long as you stay paired. Unpairing revokes what this machine issued.

Finding a machine, then pairing with it Finding: picache fleet new makes a secret, and fleet join adds each other machine. Fleet machines announce on a rednet topic hashed from the secret, and on one local network also send an mDNS beacon that hashes only the fleet tag. The open directory is off unless you turn it on. Pairing: the laptop runs picache pair studio and sends a signed invite that spends a one-time nonce. Studio holds it as a request for ten minutes, and its operator approves it with pair approve. Studio answers with its acceptance and a contract offer, and the laptop sends its own contract back. Each side pins the other's key, and each direction has a contract of six exact scopes, for 30 days, renewed while the machines stay paired. A beacon is a hint, never trust: nothing admits an unpaired machine. FINDING picache fleet new then fleet join <secret> on each rednet: a topic hashed from the secret LAN: an mDNS beacon only its fleet tag hashed open directory: off laptop asks studio approves a signed invite, one-time nonce ? pair approve within 10 minutes accepted, with its contract its contract back KEYS PINNED · A CONTRACT EACH WAY · 6 EXACT SCOPES · 30 DAYS, RENEWED a beacon is a hint, never trust: nothing admits an unpaired machine unpairing revokes what this machine issued
Finding a machine takes a shared secret, and pairing with it takes the other operator's yes.

A paired machine may ask this one to run a capsule (section 09). The request is refused unless you allow it for that machine (picache machines host studio on).

Open another machine's desktop

picache machines open studio                     # its workspace
picache machines open studio:web:L1              # one layer
picache machines open studio:web:terminal:<id>   # one terminal

You can also use ◎ Open on that machine's card in the Machines pane. Either way, the workspace opens in a pane here.

The pane has the remote colour (theme roles remote and remote-focus), and its title starts with ◎, so you won't mistake it for a local pane. Close the pane to leave.

Only you can open one. An agent can't, and neither can a desktop that is itself remote. While desktops from other machines are attached here, the status line says so, as in 1 from studio (view).

The other machine lets the desktop in when its workspace accepts remote desktops and its grant to you allows it. Its operator sets this:

picache desktops edit   # on the other machine: off, view, edit or full
picache desktops off

A desktop given the whole workspace reaches the files, git and trash of its layers' folders, by root, never by path.

Folders outside the layers stay out of reach, such as folders you searched or pinned, or a job's scratch folder. A grant therefore never grows with what you do after giving it.

This machine's own data folders (.cherrypi and ~/.cherrypi) are never reachable, even with the As me grant (below).

Share one layer or one terminal

picache machines desktop studio edit --layer L1
picache machines desktop studio view --terminal <id>

You can let a machine's desktop into one layer and nothing else, with the first command above or with Desktop per layer… on the machine's card.

A layer's grant works even when the workspace takes no desktops. When several grants cover what was asked, the narrowest one decides.

That machine can also ask for a layer as a share, which you approve (Actions → Share this layer with paired machines). It then opens the layer with ◎ Open or picache machines open.

The other machine sees that layer's view, conversations, terminals, files, evidence, offers, goals and git, and nothing else. Its terminals open in the layer's folder, and it can't add, close or move layers.

Changing a grant closes at once any desktop it no longer covers. Presence names the layer, as in 1 from studio (full, L1 alone).

Narrower still, you can share one open terminal: use Actions → Share this terminal with paired machines and approve it, or grant it with --terminal as in the second command above.

A terminal without a grant of its own uses its layer's grant. The other machine sees only that terminal, in one pane. The grant and the desktop end when the terminal closes.

What a remote desktop may do

A desktop from another machine acts as that machine, not as you, and it's recorded as that machine (node:<id>, desktop on studio).

Its grant is a preset over seven capabilities: read, files.write, chat, layout, git.write, terminal.type and goals.decide.

Grant Holds
Watch Reading
Edit files (where a new grant starts) Reading, changing files, talking to Picache, saving layouts
Full Everything except typing in terminals
As me (your own machines only) Everything, acting as you. The record still names the machine
picache machines desktop studio view      # a preset
picache machines desktop studio read,git.write # a custom grant
picache machines desktop studio inherit   # back to the workspace's

You can also use Desktop… on the machine's card. Custom grant… takes a list such as read,git.write.

The workspace's own setting caps the grant for the whole workspace: No desktops, Let them watch, Let them edit files or Let them act.

A desktop closes at once if its grant loses a capability it came in with, or moves to or from As me.

Only As me can type in a terminal, because a shell runs as you, with all of your home, and can commit to git. Other desktops, custom ones included, can watch terminals but can't open any. A watched terminal's header says watching.

Messages to Picache from a remote desktop get the same limits. A turn it starts or steers can read, and it writes files only if the grant allows. It can't run commands, type, arrange the desk, or start an agent or a goal.

When another machine's words reach a chat

Once another machine's words reach a conversation, its later turns stay held until you acknowledge them. A held turn reads only what that machine could read. Anything it would change is asked of you as usual.

If two machines' words reach one turn, it gets only what both grants allow.

The chat says whose words they were, with an Acknowledge button. Click it once you've read what those turns did. It works only on this machine's own desktop, and not while a turn is running.

picache history acknowledge <conversation>   # the same, from a shell

The command works only in a shell you opened by hand on this machine, and the desktop asks you to confirm it. Panes an agent asked for, As me panes and other machines' panes can't use it. An agent that you start yourself, in a shell you opened by hand, can acknowledge.

What stays with this machine

Every call names the capability it needs. A call from another machine is answered only when its grant holds it, and anything undecided is refused.

Some things stay with this machine's own desktops, even under As me:

  • settings, pairing and grants;
  • approvals and recovery;
  • installing onto this machine;
  • running jobs, capsules and app panes;
  • speaking as this machine in rooms and the lobby;
  • stopping the harness.

On a trusted network, a machine with no grant may watch but can never change anything. An explicit off still holds there. You mark a network as trusted like this:

picache desktops trust 192.168.1.0/24

On the remote desktop, what its grant leaves out stays visible but dimmed, with the reason underneath, and can't be clicked.

A remote desktop acts as its machine Studio's desktop is open on alpha's workspace, with an Edit files grant. It edits README.md: the call needs files.write, which the grant holds, so alpha answers it and records it as studio's node, from its desktop, not as alpha's operator. The grant leaves out git.write, terminal.type and goals.decide, so on studio's desktop Stage and Commit are drawn dimmed, with why under them, and never sent. As me, for the operator's own machines, acts as the operator, and the record still names the machine. STUDIO · ITS DESKTOP edit README.md needs files.write Stage Commit… ✕ never sent: staging and committing needs a grant from alpha ALPHA · ITS GRANT TO STUDIO Edit files ✓ read · files.write ✓ chat · layout ✕ git.write ✕ terminal.type ✕ goals.decide each call names its need ALPHA'S RECORD edited README.md actor node:7f3a… client desktop on studio As me: the operator, still naming studio A DESKTOP ACTS AS ITS MACHINE · ITS GRANT IS A PRESET OVER CAPABILITIES a call its grant does not hold is refused; settings, pairing, grants and approvals stay this machine's
Who a remote desktop acts as.
One layer, one grant A paired machine, studio, opens alpha:work:L1 in a pane and asks for one layer. On alpha the grants are read narrowest first: L1's own grant lets studio act there, L2's own lets it watch, and the machine's grant for the whole workspace is off. The narrowest grant that covers what was asked decides, so studio's desktop is admitted to L1 alone. It sees L1's view, conversations, terminals, files, evidence, offers, goals and git; Layer Zero and L2 are not there for it. Changing a grant closes a desktop it no longer covers. STUDIO · A PAIRED MACHINE its desktop asks for one layer machines open alpha:work:L1 sees L1 by alpha's name, in L1's folder there ALPHA · ITS GRANTS TO STUDIO ✓ L1's own: act · L2's own: watch ✕ the machine: off the narrowest grant that covers what is asked decides set in Machines → Desktop per layer… ALPHA'S WORKSPACE Layer Zero · not there L1 · alone view, chats, files, git, terminals, evidence, goals L2 · not there A LAYER'S OWN GRANT IS ITS OWN SWITCH · THE NARROWEST DECIDES change a grant and a desktop it no longer covers is closed at once
A desktop given one layer.

Profiles and shares

A machine shows its paired machines a profile: a name, a handle, a status, a bio, a time zone, an avatar and a banner. Its avatar and banner are kept in the machine's Drive and checked by hash.

picache profile set name "Studio"
picache profile avatar ~/me.png
picache shares add layer L1 request   # paired machines may ask for L1

A machine also shows whatever you share: a workspace's desktop, a layer, a terminal, a room, an agent or an app. A share is asked for, approved and revoked like a pairing.

A shared workspace, layer or terminal then has ◎ Open on its card. The Machines pane does all of this too, and runs the same commands underneath.

Issues

An issue is a thread in the Inbox that you and your agents write in, like mail.

ListHolds
Waiting on youEvery decision waiting on you, as the rail's NEEDS YOU lists them
IssuesOpen issues
ConversationsWhat agents wrote to each other, and to you or Picache (below)
OffersOffers between layers
DoneIssues done or declined

Start an issue with New issue in Actions (F1) or the Inbox menu, or press n in the Inbox. Then type in the deck at the bottom of the screen. The first line is the title, and the rest is the body, in Markdown.

Each issue is numbered (#12) and tagged with your current layer. While an issue is on screen, what you type in the deck is a reply to it.

Done, Decline and Reopen move it between lists, and the thread notes each move. ● marks an issue an agent answered since you last read it.

Agents write from a shell:

picache issue   # the open issues
echo "Login fails on Safari." | picache issue new "Safari login"
picache reply 12 "Fixed in the session cookie."

An agent sees only the issues tagged with its own layer and the ones assigned to it. What it writes shows its name and the end of its pane's run id (@claude·3f2a), and is marked unverified. Only you can move or retag an issue.

An issue sent to another machine On the machine laptop you send issue 12 to the machine studio. It goes as an issue offer, written to the outbox first and retried while studio is away, and arrives in studio's Inbox as mail does: open and unread, numbered 4 there, from laptop, with no accept step. Replies on either side reach the other as issue events, an agent's marked unverified. Only studio moves it: Done or Decline there comes back, and laptop's copy follows and says they decide its state. Each side keeps its own thread. LAPTOP · SENDS #12 Login page times out you: Send to… studio you and your agents reply an agent's: unverified ↗ #12 follows they decide its state issue · outbox first retried while away issue_event replies, both ways its state comes back Done, Decline, Reopen STUDIO · RECEIVES ● #4 · from laptop open and unread, as mail they and their agents reply same thread, its own number ✓ Done ✕ Decline only studio moves it EACH SIDE KEEPS ITS OWN THREAD · THE SIDE THAT RECEIVED IT DECIDES ITS STATE
An issue sent to a paired machine.

Send an issue to another machine

To send an issue to a linked workspace or paired machine, use Send to… on an open issue, or:

picache issue send 12 studio

The issue arrives in the other Inbox like mail, open and unread, and marked with where it came from (from laptop).

The receiving side has no step to take it in, because pairing or linking already gave consent. A layer's own list (picache accepts) can still refuse issues, and a linked workspace takes none unless its link allows issue.

Each side keeps its own thread with its own number, and every reply reaches the other side. An agent's reply is still marked unverified and names its machine (@codex·91c0 (studio)).

The receiving side moves the issue, and the sender's copy follows. If a machine can't be reached, the issue stays queued and is retried, and the issue reader shows that it is waiting.

Only this machine's own desktops and As me desktops write in a shared issue. Another machine's desktop attached here with any other grant can only read it.

Agents write to each other

An agent in one pane can write to an agent in another, the way Claude Code's SendMessage writes to another session. Writing takes one command, and the message reaches that one pane, not every agent of that kind:

picache message who                          # the agent panes you can write to
picache message codex·91be "The API is ready on main."
picache message read                         # what waits for you, now

An agent is named by its vendor and the end of its pane's run id (codex·91be), or by the vendor alone (codex) when only one is open. You can write the same way from your own shell, to any agent.

How a message reaches an agent The agent claude·3f2a runs picache message codex·91be. cherry π keeps the message in a conversation in the Inbox, between the two of them, and queues it for that one pane run; codex·91be's header shows an envelope and a count. The agent's own hooks hand it over: when its next turn starts, when its turn ends (the Stop is held with the message), or when it runs picache message read. An agent idle at its prompt fires no hook, so cherry π types one line into it once you pause there. A message is information, not an order, delivered once, to one exact pane. CLAUDE·3F2A · WRITES picache message codex·91be "The API is ready." CHERRY π a conversation in the Inbox, kept whole claude·3f2a ↔ codex·91be waiting for 91be that one run, no other its header: ✉ 1 idle? wake it CODEX·91BE · GETS IT its next turn starts its prompt hook its turn ends the Stop is held with it it reads picache message read INFORMATION, NOT AN ORDER · ONCE · TO ONE EXACT PANE ✓ through the agent's own hooks ✕ never typed into a pane that waits on you
One message, one pane, handed over by that agent's own hooks.

Each message carries a type. An answer names the question it answers, and a handoff names what to take up.

TypeFor
messageAnything (the default)
questionSomething the other agent should answer
answerAn answer, with --re <id> naming the question
handoffA result for the other to take up, with --ref a branch, commit, path or goal
blockedThe sender can't go on without something
doneThe sender finished what it was asked
picache message codex "Is the build green?" --type question
picache message claude "Green on main." --type answer --re ev_81c2
picache message codex - --type handoff --ref fix/parser < notes.md

picache message on its own lists your conversations, and picache message show codex shows one.

An agent that opened another agent's pane also hears from cherry π about that pane, the way herdr tells a parent. It hears done when the other agent finishes a turn (with what it last said), needs you when it waits on your permission, and exited when it exits.

Each message spends the sender's budget for offers, so two agents can't keep writing to each other forever. Reading spends nothing.

Waking an idle agent

The agent's own hooks hand a message over: when its next turn starts, when its turn ends, or when it runs picache message read. An agent sitting at its prompt fires no hook, so on its own it would get a message only when you next type to it. To avoid that wait, cherry π types one short line into its pane, which starts its turn:

[cherry-pi] message from @claude·3f2a (in your context)

Like herdr's hc send, the wake waits for you. If you're typing in that pane, it waits until you stop.

Waking an idle agent A message arrives for codex, which is idle at its prompt. You are typing in its pane, so its header says the envelope waits after pause. Once three seconds pass without a key there, cherry π types one line and Enter. Its turn starts, and its hook hands the message over. It never types into an agent that waits on you or a pane that has exited; after two minutes without a pause it gives up, and the message waits for the next prompt. A second message while one wake waits types nothing more. 1 a message comes codex is idle at its prompt 2 you're typing there ✉ after pause in its header 3 3 s, no key one line is typed, then Enter 4 its turn starts its hook hands the message over THE KEYBOARD IS YOURS ✕ never into an agent that waits on you ✕ never into a pane that has exited after 2 min without a pause: it gives up, and the message waits for the next prompt ✓ a second message types nothing more ✓ off in Settings: no line, ever
A wake waits for you to pause, and never types into a pane that waits on you.

If that agent's hooks have never been seen handing a message over, the line asks it to run picache message read instead.

Tell Picache, and hear back

You can also leave the command to Picache. Tell it in plain words, such as ask codex to check the build, and it writes the message itself, from your chat. The agent writes back with picache message picache …, and Picache's next turn in that chat has the answer.

Picache asks an agent, and hears back In a chat you ask Picache to have codex check the build. Picache writes to codex·91be from that chat, signed picache·a1b2. Codex's next turn starts with it. Codex answers with picache message picache, which reaches only a chat that wrote to it. Picache's next turn in that chat has the answer, and the chat shows it to you dimmed: from codex·91be, an answer, build is green. YOUR CHAT WITH PICACHE you: ask codex to check the build Picache: message → codex·91be signed picache·a1b2, this chat ↩ @codex·91be · answer build is green on main dimmed: theirs, not yours from your chat only to a chat that wrote to it CODEX·91BE from @picache·a1b2 at its next turn picache message picache "build is green on main" --type answer
Picache writes from your chat. The answer comes back to that chat, and you see it too.

An agent can write to a chat only after Picache has written to it from that chat, so no agent can start talking into your conversations. As an address, picache means the newest such chat, and picache·a1b2 names a particular one.

What you see

WhereWhat
An agent pane's header✉ 2 while two messages wait for its next turn, and ✉ after pause while a wake waits for you to stop typing there. It clears when the agent takes them.
Inbox → ConversationsEvery conversation between agents, and with you or Picache, each message with its type (question, answer to ev_81c2, handoff · fix/parser)
The deck, with a conversation selectedIt says MESSAGE, and what you send goes to every agent in it whose pane is open
A Picache chatWhat an agent wrote back, dimmed, at the turn that took it. It stays in the chat's history and isn't fed into later turns
The agent's own paneWhatever that vendor's app draws for context its hooks add, and the wake line

Who may write to whom

By default an agent writes only to agents in its own layer. Each layer can let other layers in, or keep them out, for itself:

picache layer messages from web   # this layer takes web's agents' messages too
picache layer messages none       # its own agents only
picache layer messages any        # any layer's
picache layer messages default    # as the workspace says
picache layer messages            # what it says now
Who may write to whom Three layers: web with claude, api with codex, docs with gemini. Within a layer agents write to each other. The api layer ran picache layer messages from web, so claude in web writes to codex in api, but gemini in docs is refused. The workspace's inbox.messages is layer by default, or workspace, or off; a layer's own word narrows or widens it, off stays off, and you write to any agent. LAYER WEB claude·3f2a LAYER API codex·91be messages from web its own word LAYER DOCS gemini·0c4d ✓ ✕ THE WORKSPACE · INBOX.MESSAGES layer (default) · workspace · off a layer's own word narrows or widens it ✓ you write to any agent ✕ off stays off, whatever a layer says only you set either; an agent that tries is refused
The receiving layer decides who writes into it.
SettingDefaultWhat it does
inbox.messageslayerWhom an agent may write to: its own layer, any agent pane here (workspace), or no one (off)
inbox.message_notesonAn agent hears when an agent pane it opened finishes a turn, waits on you, or exits
inbox.message_wakeidleAn idle agent is woken with one line once you pause in its pane. off leaves the message for its next prompt

Picache has the same commands as tools (message and messages, in the inbox group), and so does an agent given the verbs over MCP.

The web desktop doesn't wake an idle agent, so a message to one there waits for its next prompt. How a vendor's app draws what its hooks add is up to that vendor.

Messages stay on one machine. To reach another machine, send an issue (above).

Rooms and the Bakery

Rooms are for people on paired machines, across the internet.

  • Each message is signed by the machine that wrote it.
  • It's accepted only from a member.
  • It waits in an outbox while a machine is away.

Beside each room is a side room with the same people, where agents may be asked in. An agent named there with @ answers once. It reads the room's last few messages and its own folder, and it changes nothing.

The Bakery is public: a set of rooms on rednet, joined by name like IRC, and off until you join. Each person goes by a name generated from their key. Nothing is kept unless you choose, and agents are never allowed.

Rooms carry messages between people. A single layer can be kept in step with another machine (section 01), but a whole workspace can't.

Confinement and .pi apps

A capsule is a confined process that can talk only to the harness.

Every capsule starts through a single entry point, which this manual calls the door. The door picks the strongest confinement the machine has. A machine that can't confine runs no capsules, and says why.

PlatformConfinement
macOSSeatbelt
Linuxbubblewrap where it starts, otherwise Landlock

Capability checks govern what a capsule asks of the harness. The confinement limits what it can do on its own.

The signer and the P2P sidecar

Two processes start outside the door: the signer, which holds the keys, and the P2P sidecar, which faces the network.

The sidecar has a box of its own, apart from the signer's keys. It can write only in its own folder. The only local sockets it can reach are those in its own socket folder (its own, each workspace's remote-desktop socket and the ones desktops dial from) and the system's name lookup.

  • On macOS, the box is Seatbelt.
  • On Linux, it's bubblewrap. On kernel 6.12 and later, Landlock stops the sidecar from connecting to the host's abstract Unix sockets, though it can still bind a name there. Otherwise it gets a network of its own behind pasta, which keeps it from them entirely. With neither, it's boxed but can reach those sockets, and its log says so.
  • Where only Landlock is available, the sidecar runs unboxed and logs that.
One door, or nothing Agents, scripts, app handlers, clean rooms and capsules from paired machines all pass through one door. On a machine that can confine they run confined, by Seatbelt on macOS or bubblewrap or Landlock on Linux. On a machine that cannot, they are refused with the reason. A capsule has no unconfined path. The signer and the P2P sidecar are not capsules and start outside the door; the sidecar has a box of its own. an agent a script an app's handler a clean room a paired machine's capsule ONE DOOR A MACHINE THAT CAN CONFINE Seatbelt on macOS bubblewrap or Landlock on Linux ✓ runs confined; its record says how A MACHINE THAT CANNOT ✕ refused, with the reason a capsule has no unconfined path
Everything that runs confined starts through one door.
The P2P sidecar's own box The signer, which holds the keys, starts the P2P sidecar through cherrypi.ape p2p-box, apart from itself. On macOS the box is Seatbelt. On Linux, bubblewrap binds the sidecar's own socket folder whole, never the folder that holds the signer's socket. The sidecar can't connect to the host's abstract Unix sockets: through scope, keeping the host's network, where the kernel has Landlock's scope (6.12 and later), otherwise by a network of its own behind pasta. With neither, it is boxed but can reach them, and its log says so. Under Landlock alone the sidecar runs unboxed, and its log says so. the signer cherrypi.ape p2p-box the signer holds the keys and starts the sidecar, which faces the internet, in a box apart it writes only its own folder and reaches only the sockets in its own socket folder and the system's name lookup MACOS ✓ Seatbelt: its folder, its sockets LINUX · BUBBLEWRAP binds its socket folder whole /tmp/cherrypi-<uid>/<tag>.p2p/ abstract sockets kept out by: ✓ 6.12+: scope, host's network ✓ older: pasta, its own network ! neither: reachable, logged LINUX · LANDLOCK ALONE ! unboxed, and its log says so
The P2P sidecar is boxed apart from the signer's keys.

Hosting capsules for other machines is opt-in, and a host that can't confine refuses them.

Capsule profiles

A capsule is defined by a named profile with five parts. Both the operating system's boundary and the harness's seal (the verbs the capsule may ask the harness for, fixed when it starts) are built from that one profile, so the two can't disagree.

Part Decides For example
image What runs inside cherry π's own APE running Lua, an agent's CLI, or a command
ground The files it sees Sealed (copies only), one folder, or the pane's folder and named read-only roots
network Where it can connect Nowhere, a proxy with a list, or open (labelled as such)
broker What it may ask the harness for Verbs sealed at start: a script's are its caller's tools, a clean room's none
limits How much Seconds, CPU time and file size. A script's budget also caps its calls, writes and runs.

The seal can only narrow what a capsule may do. A request outside it is refused, and a request inside it still goes through the policy for its verb.

A capsule can never do more than whoever started it could do directly, and it ends when whoever started it ends.

If the harness itself dies first, a clean room (below) on macOS, or under Landlock on Linux, runs on, still confined, until cherry π next opens. Then it is stopped. Its out/ folder is kept and its copies of the inputs are removed.

Each capsule's record names the confinement that held it, which is not always the one that was asked for.

Run a clean room

A clean room runs a command, or a Lua script with --lua, on copies of files. Use it to open an archive you were sent, run a repository's tests offline, or convert a document.

picache cleanroom invoice.zip -- unzip invoice.zip -d out/
picache cleanroom notes.docx --for 15m --tools -- pandoc notes.docx -o out/notes.md
picache cleanroom data.csv --lua summarize.lua
  • It has no network, and no view of your folder, your home or cherry π's state.
  • What it writes to out/ is kept in cherry π's state, and the reply lists it with its path. Everything else is thrown away, and the originals are never touched.
  • It runs for five minutes by default. You can set another time with --for, and anything longer than an hour is cut to an hour.
  • It can use the system's own tools. With --tools it can also use what Homebrew installed, read-only.
A clean room, from copy to out/ picache cleanroom copies its inputs, which must be inside your folder, into a capsule's ground: a folder of its own. The command runs there with no network and nothing to ask the harness, under Seatbelt on macOS, or bubblewrap or Landlock on Linux. It lives five minutes by default and an hour at most. When it ends, out/ is kept in cherry π's state and listed in the reply, with the end of its log and its exit; the capsules chain records its start and end; everything else is deleted. Your originals are never touched. With no way to confine here, the run is refused, never run unconfined. YOUR FOLDER invoice.zip inputs from inside your folder only ✓ never touched THE CAPSULE · ITS GROUND a copy of invoice.zip unzip invoice.zip -d out/ ✕ no network, nothing to ask Seatbelt, bubblewrap or Landlock 5 min by default, 1 h at most WHAT COMES BACK out/ kept in cherry π's state its log's end, its exit a start and an end on the capsules chain ✕ the rest is deleted NO WAY TO CONFINE HERE: THE RUN IS REFUSED, NEVER UNCONFINED a script runs the same way: picache cleanroom data.csv --lua summarize.lua
A clean room takes copies in and gives out/ back. Nothing else passes in either direction.

Picache also offers the clean room as a tool.

In the Capsules pane, Run one starts a capsule for a purpose: one prompt and its answer, a room for inspecting files, or a fifteen-minute agent-code session. The capsule runs on this machine, or on a paired machine when this one can't confine. An interactive capsule runs only on this machine.

Agent boxes

A vendor agent such as Claude Code, Codex or Pi starts confined in a box by default. The whole CLI runs inside it, including its hooks, its file tools and the MCP servers it starts.

picache box claude                   # its box settings
picache box claude launch standard   # or confined, the default
The agent canThe agent can't
Write its pane's folder and its own configurationRead the rest of your home
Read its own installRead cherry π's state
Use its terminal and picacheBecome root (Use sudo)

Launch is a per-agent setting with two values, confined and standard, and only you can change it. A standard launch starts the agent without the box, and its record says so.

When an agent starts, cherry π looks through its folder for cherry π state and repositories. It goes at most 8 folders deep, through at most 100,000 entries, for at most five seconds. In a node_modules folder it checks each package but does not walk every file inside it, so a .git or cherry π state deeper inside a package isn't found.

If cherry π can't look through the whole folder, or can't open a folder inside it, the agent won't start in a box there. Start it in a smaller folder, or launch it standard.

On Linux, the box hides the cherry π state it knows of, and any it finds in the agent's folder at the start. State made there later isn't hidden.

On macOS, an agent can commit, merge and stash in the repositories its folder holds at the start. It can't change their config or hooks, or make a .git anywhere in its folder. So git init, cloning into its folder and updating submodules fail there. Do those yourself, outside the box.

It also can't rebase. Both kinds of rebase (including --apply), git am and a cherry-pick of several commits fail in the box. Each would leave state behind that your next git rebase --continue or git am --continue would pick up and carry on with as you. Rebase outside the box.

On Linux, those repositories are read-only, so the agent can't commit. It can make a new repository in its folder, though. When you run git in that repository, git runs its hooks and settings, such as core.fsmonitor. Check a repository an agent made before you run git in it.

On either system, an agent can leave a bare repository in its folder. Git accepts one in any folder, with no .git in its path, and the box doesn't catch it. Run git config --global safe.bareRepository explicit so your git won't use one unless you name it.

On macOS, when a boxed agent ends, cherry π waits up to three seconds for what it started to finish, and stops the rest. Then it looks again through the places in the agent's repositories where git writes, for a link or a file with a second name (a hard link). Your git would write through such a file as you. If it finds any, the agent's terminal shows each path:

cherry-pi: the agent's box left in its repository what your git would write through, as you, and git does not make. Look at each and remove it before you run git there:
  /path/to/project/.git/logs/refs/heads/x/main

The same text goes into git-sweep.txt in the agent's run folder, under confine/agents/ in cherry π's state. Remove each path before you run git there. The agent's next box in that folder won't start until you do.

Before it makes its own git writes in a repository that a box guarded, cherry π looks there too. Its own writes are staging or committing in the Changes pane, and accepting a goal. If it finds anything, the write is refused with confine_git_sweep, naming each path.

Git you run yourself isn't checked, so don't run git in an agent's repositories while the agent runs.

When the agent ends, cherry π doesn't wait for a process the agent started in a process group of its own.

The first look at a repository goes through at most 200,000 entries, for at most five seconds. After a look that finds nothing, cherry π looks again only in folders that have changed since. A look that is cut short counts as finding something.

When an agent won't start in its box, it says why. These codes concern its repositories:

  • confine_agent_gitdir: a .git file or link, or a repository's commondir, leads to a place in its folder the agent could make or swap. Such a place is a folder that doesn't exist yet, a file, a path through a link, or the agent's folder itself. Your git would use whatever the agent put there. Point the .git at the repository itself, or remove it.
  • confine_agent_git_link: on Linux, a .git in its folder is a link. The box keeps what it leads to read-only, but not the link, which the agent could swap. Replace it with a .git file that says gitdir: <path>.
  • confine_agent_filesystem: on macOS, its folder isn't on APFS, or its file system can't be identified. The box matches names the way APFS finds them. On HFS+, for example, git finds .git under names the box doesn't match. Use a folder on APFS.
  • confine_agent_git_sweep: a repository in its folder still holds what an earlier box left, as described above. The message names each path. Remove them.

For any of these codes, you can also launch the agent standard.

Box the agent, not its commands Left: an agent's own sandbox boxes only its shell commands; the agent, its hooks, MCP servers and login stay outside, so a hook can read ~/.ssh. Right: cherry π boxes the whole agent with its hooks and MCP servers; only its folder, its config and its own temp folder are writable, and the same hook stops at the box wall. AN AGENT'S OWN SANDBOX the agent its hooks its MCP servers its login shell commands the only thing boxed ~/.ssh a hook reads ~/.ssh CHERRY Π THE BOX the agent its hooks · MCP servers its login token its folder · its config ~/.ssh your home cherry π state ✕ the same hook stops at the wall their sandbox holds the commands cherry π holds the agent
The agent's own sandbox is drawn dashed, and cherry π's box solid.

Network access

When an agent's network setting is ask or allowlist, the only thing it can reach from the box is a proxy that cherry π runs outside it. The proxy carries connections to what the agent's list names.

picache box claude allow registry.npmjs.org   # add a rule
picache box claude allow '*.githubusercontent.com:443'
picache box claude unallow registry.npmjs.org
picache box claude network ask                # for anything else
networkFor a host not on the list
askYou're asked (the default)
allowlistRefused
openAllowed, with no list (below)

Each list starts with the vendor's own endpoint, measured under the box. Names that resolve to this machine or a private network are refused unless a rule names that address.

The proxy decides by host name. It carries HTTPS connections, which it never reads inside, and plain HTTP requests. When you're asked, your answers are kept as receipts, and the proxy's log keeps the rest.

With open there is no list. A proxy runs only when the agent's key is injected, to carry its vendor's hosts to the injector. The agent can reach this machine's loopback and your local network, and it can listen on TCP ports.

It can also reach other agents' proxies on 127.0.0.1, which don't check who is calling. If another agent has its key injected (Keep the key out of the box), an open agent can send requests through that agent's proxy, and they go out with the other agent's key.

On Linux, open needs kernel 6.12 or later, where Landlock stops the agent from connecting to this machine's abstract Unix sockets. An X server's socket, for example, takes keystrokes. On an older kernel, an open box is refused.

The network is a list In ask mode, the agent's only way out of the box is a proxy outside it. A listed host goes through. An unlisted one is put to the operator, who answers always, and it joins the list. A name that resolves to a private address is refused. A connection around the proxy has no route. Your answer is a receipt; the proxy logs the rest; a connection around it is recorded nowhere, because it never leaves the box. THE BOX the agent ONE WAY OUT PROXY · OUTSIDE ITS LIST api.anthropic.com example.com api.anthropic.com example.com a name → 192.168.1.20 ✓ ✓ ✕ example.com is not on the list once · this run · always · no ✕ no route around the proxy WHERE EACH IS RECORDED ✓ listed: proxy log ? always: a receipt ✕ private: proxy log ✕ around: not recorded
An agent's network in ask mode, the default.

Sign-in in the box

Claude Code keeps its macOS login in the Keychain, which the box can't reach, so in the box it keeps its login in a file in its own config. Sign in… on the agent's Profile menu runs its own sign-in command in its box, with the network open for that command alone. Token… keeps a token from claude setup-token sealed instead.

On Linux the box is bubblewrap. With ask or allowlist, the proxy is bridged into the agent's own network namespace. An agent that hasn't been measured under bubblewrap won't start in a box there. The pane says why, and you can launch it standard.

The box has two limits:

  • A confined agent can read and write a config file that a dotfile manager links from elsewhere.
  • The box shares the kernel with the rest of the machine. It isn't a virtual machine.

Keep the key out of the box

A key handed into the box sits in the box's environment. Anything the agent runs can read it, such as a test runner, a package's install script or an MCP server.

picache box claude credentials inject    # a stand-in goes in the box
picache box claude credentials hand-in   # the real key goes in the box

When an agent's Key setting is inject, it gets a stand-in key of the same shape (sk-ant-oat01-cherrypi-…) and starts as usual.

  1. When it calls its vendor's API, the proxy hands that connection to cherry π's signer, outside the box.
  2. The signer, which already holds the key sealed, opens it and swaps it in for the stand-in.
  3. It sends the request on to the vendor over verified TLS. The answer streams back unchanged.

The key is never in the box, and outside it only the signer opens it.

The key stays out of the box Inside the box the agent holds a stand-in key and trusts the run's certificate authority. Its request to api.anthropic.com leaves the box carrying the stand-in, goes through the proxy to the signer's injector outside the box, which opens the real key, swaps it in, and sends the request to Anthropic. A script in the box that reads the stand-in gets nothing it can use, and a request carrying any other key is refused. THE BOX the agent its key: a stand-in sk-ant-oat01-cherrypi-… trusts this run's CA, for its vendor's hosts PROXY · OUTSIDE api.anthropic.com → the signer anything else → its list SIGNER · OUTSIDE the injector your key, sealed opened only here api.anthropic.com ✓ LEAVES THE BOX authorization: Bearer the stand-in REACHES ANTHROPIC authorization: Bearer your key ✕ a script in the box reads the stand-in: useless to anyone ✕ a request with any other key: refused, 403 the key never enters the box
Claude Code's request to its API, with credentials inject set. Until an agent has run injected on this machine, the default is hand-in (below).

The agent trusts a certificate made for this run alone, which can vouch only for its vendor's hosts. That certificate's private key is deleted as soon as the run's certificates exist. The signer's injector ends when the agent does.

Which sign-ins can be injected

Claude Code can be injected when its token or API key is kept in cherry π. With inject set, the login file in its config is hidden from the box, so the real key isn't beside the stand-in.

Codex can be injected when it uses an API key from Keys. Its ChatGPT sign-in can't be, because Codex renews that sign-in with a token in the request body and the injector changes only headers. With inject set, that sign-in's file is hidden from the box and Codex uses the API key. To use the ChatGPT sign-in, choose hand-in.

You give the agent its key the same way for both, with Token… on its Profile menu or a key in Keys. Choose between inject and hand-in with the Key row in the agent's Profile, or with the commands above.

An agent defaults to hand-in until it has run injected on this machine, and to inject after that. If the setting is inject and a launch can't inject, the launch is refused with the reason. It does not fall back to handing the key in without saying so.

Where your secrets go

A key, a token or a password is opened only where it's used. Everything else gets its name, its last four characters, or nothing.

Where a key goes A key comes in three ways: typed masked into the Keys pane, piped to picache box for an agent's token, or piped to picache app set for an app's secret. The signer seals it into the credentials store, 0600, under its own root key. It's opened only into one of four places: the harness's request to a provider, an agent's box when its key is handed in, the signer's injector when it's injected (never the box), and the call that hands an app its own secret. Receipts, hooks, cherrypi.db, command lines, the P2P sidecar, the program that opens a browser and the key log never hold it. The Keys pane shows the last four, which the signer gives it. IN the Keys pane typed, masked … | picache box an agent's token, piped … | picache app set an app's secret, piped THE SIGNER seals it credentials.db sealed, 0600 its root key alone OPENED ONLY INTO a provider request the harness, that call an agent's box hand-in: its environment the signer's injector inject: never the box an app's own capsule its secret, that call NEVER HOLDS IT ✕ a receipt · an app hook · cherrypi.db ✕ a command line anyone can read ✕ the P2P sidecar · the program that opens a browser ✕ the key log ✓ the Keys pane: ••••abcd the last four, from the signer; the key stays there
A key is kept in one store and opened by the signer alone, into one of four places where it is used.

A token or an app's secret typed on the command line is refused, because it's then in your shell's history and in the list of running processes. Pipe it in instead:

pbpaste | picache box claude token
pbpaste | picache app set my-app api_key
picache app set my-app api_key --clear   # forget it

Every picache call is recorded as a receipt, so what a receipt keeps passes through two filters. First, a verb that knows an argument is secret hides it. Then anything shaped like a key, wherever it appears, is cut down until only what kind of key it was remains.

What a receipt keeps A call's arguments pass two filters before they're receipted or handed to an app's hooks. First the verb's own: picache box claude token with a value becomes claude token and an ellipsis, and the call is refused. Then a shape filter over every argument and every detail: a known key prefix such as sk-ant- keeps the prefix and loses the rest, a bearer token loses its value, a variable named as a key keeps its name, and a password in a URL is cut. What's left goes to the receipt and the hooks. A verb whose whole answer is a secret, such as fleet secret, gives hooks no text at all. THE CALL picache box claude token sk-ant-oat01-Zz9… 1 · THE VERB'S OWN claude token … ✕ refused: pipe it in 2 · BY ITS SHAPE, ANYWHERE sk-ant-oat01-Zz9… → sk-ant-… Bearer eyJhb… → Bearer … HF_TOKEN=hf_x9… → HF_TOKEN=… --token=t0k3n → --token=… https://me:pw@host → https://me:…@host what it was stays; what it is goes the receipt cherrypi.db, 0600 an app's hooks or no text at all a whole answer that is a secret (fleet new, fleet secret) reaches no hook
A call passes two filters before it is recorded.

Each pane's PICACHE_TOKEN is that pane's authority. The pane's record keeps only a digest of the token, so a copy of cherrypi.db gives nobody a pane's rights.

A pane's token, across a restart The harness mints a token for a pane and puts it in the pane's environment; the program there calls picache with it. The pane's record in cherrypi.db keeps only the token's SHA-256 digest. When the harness restarts and adopts the running pane, it restores the digest; the first call whose token hashes to it gets the pane's identity back. The digest itself is not a token, and is refused. A PANE OPENS the harness mints pct_ and 48 hex the pane's environment PICACHE_TOKEN its program calls picache with it the pane's record, cherrypi.db picache.token = sha256:64 hex, never the token THE HARNESS RESTARTS · THE PANE RUNS ON adopts the pane restores the digest the program's next call sha256(its token) = the digest ✓ its identity back ✕ the digest presented as a token: refused
After a restart, the record lets a pane get its identity back, but the digest in it can't be used as the pane's token.

Files that hold a secret, or might hold one, are open to your user alone:

FileModeHolds
cherrypi.db, its -wal and -shm, and its backups0600Receipts, pane records
secrets/credentials.db0600, folder 0700Your keys, sealed
identity/0700The signer's root key and identity keys
A job's log0600What a job printed
The key log (CHEWY_KEYLOG)0600Keys pressed, with typed text kept only as <text>

Other processes get no more than they need. A capsule inherits no environment. The P2P sidecar gets none of the signer's variables and no variable named as a key, and neither does the program that opens a browser.

On Linux, a pane that may use sudo starts through your systemd user session. The session reads the pane's variables from a file only your user can open, which is removed once it has been read, so no value appears on a command line.

An agent's key is handed into its box by default until that agent has run injected on this machine (Keep the key out of the box). Text an agent types into a pane with picache send is filtered only by its shape, and a password has no shape the filter can recognise.

How it compares

Agents that ship a sandbox put the commands they run inside it and leave the agent itself outside. The box in cherry π holds the whole agent, for every vendor alike.

Each row was checked against that tool's documentation on 18 October 2026:

Tool What is boxed On by default Network
cherry π The whole agent, any vendor, and every capsule Yes Per agent: ask, list only or open
Claude Code Its shell commands No A proxy whose domain list starts empty
Codex CLI The commands it spawns Yes On or off
Gemini CLI Tool calls, or the whole CLI in a container No Open, or a proxy you supply
Cursor Terminal commands Not stated A domain list, plus its defaults, or all
Pi Nothing in core. A community extension boxes its tools. No Rules, plus a reviewer per connection
Docker Sandboxes The whole agent, in a microVM When used A host proxy with policy modes

Compared with these tools, cherry π has no virtual-machine tier for hostile binaries. Like Docker Sandboxes, it can keep the key out of the agent's reach (above) when the agent's Key setting is inject.

Sources: Claude Code, Codex, Gemini CLI, Cursor, pi-sandbox, Docker Sandboxes.

Addons and .pi apps

There are two tiers of extension: addons and .pi apps. The Extensions page covers both in full, along with Cellar Door's worlds.

ComparedAddons.pi apps
Made bycherry π (first-party)Anyone
ShipInside cherrypi.ape, with no folder to drop one intoAs one .pi file
Plug intoRegistries the core owns: P2P backends, peer discovery, signers, model backends, knowledge, worlds, terminal carriers, verbs, goal gates, app providersVerbs, tools, panes, terminal programs, hooks
RunIn the harness, and may run a sidecarIn a capsule, holding only what they were granted
picache addons   # list them
picache addons enable p2p_holepunch

The core itself is never a plugin. It is made up of authority and policy, receipts and the ledger, confinement, the addon loader and the verb registry.

A .pi app A .pi app is one SQLite file. Signed: its manifest, which holds the signature, its handlers, its assets and any other table it ships. Unsigned and written in place: the tables it declares for its own data, and a journal of every write, so its data never breaks its signature. Install: signed here or by a paired machine installs; anything else asks first; a file changed since it was signed is refused. Run: handlers run only in the app's capsule, with each capability granted by you, among them app.terminal, app.hook and appdata:own. Hooks may refuse, warn, note or suggest, and never approve, change or widen. notes.pi · ONE SQLITE FILE SIGNED _manifest _assets handlers other shipped tables the signature is a row in _manifest UNSIGNED · WRITTEN IN PLACE its declared tables · _app_outbox INSTALL ✓ signed here or by a paired machine ? anything else asks first ✕ changed since signed: refused RUN handlers only in its capsule each capability granted by you app.terminal · app.hook · appdata:own HOOKS may refuse, warn, note or suggest ✕ never approve, change or widen
A .pi app is one file. Its signature covers what was shipped and never the app's own data.

A .pi file is one SQLite file that holds the whole app: its manifest (which holds its signature), its handlers and its assets.

Outside the signature, it holds tables the app declares for its own data. Those tables are written through the harness's gate (section 10), one statement at a time, and journaled. When a new version is installed over the old, they are carried over and the old file is kept as a backup. A version that changes a table's shape without a migration is refused before anything is written.

An app signed by this machine or a paired one installs at once. Anything else, including an unsigned development folder, asks first and is marked as such wherever it shows. Each capability it asks for is granted separately.

What an app can add

An app can add:

  • picache <app> <verb> commands
  • tools for Picache
  • panes drawn from A2UI
  • its own program in a terminal pane, which needs a grant of its own, app.terminal, because a program there hears every key
  • hooks, which need app.hook

A hook hears each call before it runs and its result afterwards. It may refuse a call, warn, add a note or make a suggestion, and whatever it does is labelled as the app's.

A hook can never approve a call, change a call or its result, or answer a question cherry π asks you. A hook that is slow or fails counts as silent.

Meta, the first app, inspects and packages .pi files, including itself, and asks you each time it signs.

After a crash, an app's work is recovered the same way as a tool's. Work that takes longer than one call runs as a loop the harness steps, and never as app code left running (section 10).

An app's own data, on every machine

An app with appdata:own keeps rows in the tables it declares. Its pages in Cherrytop, the browser desktop, read and write those rows by revision.

A write names the revision it read. If another window wrote first, the write is refused and the reply gives the revision that is there now, so nothing is overwritten unseen. Every other page of the app is told to read again.

An app's rows on two machines On this machine a page of notes.pi writes a row by its revision. The change is kept in the app's outbox in the same write. Once you've shared the app with a paired machine and approved its request, the two send each other changes every two seconds, and at once after a page writes, as rows, never as SQL. The paired machine applies the row and its pages read again. When both machines wrote the same row, every machine keeps the same one, the later write; the other is kept as a conflict and you're told in the Inbox. THIS MACHINE · NOTES.PI a page writes a row naming the revision it read the app's outbox the change, in the same write BETWEEN THEM the share you approved this app, these machines every 2 seconds at once after a write A PAIRED MACHINE · NOTES.PI the row, applied its pages read again both wrote it? the later write, everywhere ✓ the other write is kept, and you're told in the Inbox ✕ rows only, never SQL
Both machines keep the same row, and the losing write is kept as a conflict.

To keep an app's rows the same on a paired machine that has the same app installed:

  1. Share it here: picache shares add app <id>.
  2. Ask for it there: picache shares ask <machine> <share>.
  3. Approve the request here: picache shares approve <request>.

From then on each side sends the other its changes. To see what syncs, run picache app sync. To see the writes that lost a conflict, run picache app conflicts <id>. To stop syncing, run picache shares revoke <request>.

Only the two apps linked by the share, signed by the same publisher, take each other's rows.

Recovery and evidence

After a crash, cherry π can say what happened and what didn't, because it keeps two records apart: how far a task got, and whether its effect changed anything.

RecordValuesSays
A task's statusqueued, running, blocked, completed, failed, cancelledHow far the work got
An effect's outcomeapplied, not_applied, uncertainWhether the world changed

A step that ran hasn't necessarily had its effect.

Settle an effect in doubt

An effect in doubt Before a tool runs, its intent is saved; then the call runs. Its effect ends applied, not applied, or uncertain, which is what a crash in the middle of the call leaves. An uncertain effect blocks a retry until someone settles it: you decide in Actions, Review recovery, with Mark Applied, Mark Not Applied, Retry Safely (offered only when repeating is harmless) or Stop. After a crash, running tasks go back to queued, a resume is a new turn, and work in doubt never reruns. A decision is checked against a fingerprint of what you were shown and committed to the chain. intent saved before the call the call the tool runs a crash here leaves the effect in doubt ✓ applied ✕ not applied ? uncertain blocks a retry until someone settles it YOU DECIDE · ACTIONS → REVIEW RECOVERY Mark Applied Mark Not Applied Retry Safely Stop AFTER A CRASH running → queued a resume is a new turn in doubt never reruns a decision is checked against a fingerprint of what you were shown, and committed to the chain
Three-valued effects and the retry gate.

The harness writes down the intended effect before the call runs. If the call is cut off midway, its effect is left uncertain, along with any output it had saved. An uncertain effect blocks a retry until someone settles it.

Actions → Review recovery lists the waiting work of every conversation, including the ones you don't have open. It shows what's in doubt and the output saved before the stop, then offers the harness's choices: Mark Applied, Mark Not Applied, Retry Safely (offered only when repeating is harmless) or Stop.

Your choice is checked against a fingerprint of what you were shown. If the view changed under you, nothing is done and you're told so.

Resume interrupted work

When a harness opens, any work it finds still marked as running goes back in the queue, whatever the recovery mode (below). This restores the record and reruns nothing.

Running the work again is another matter, because its effect may already have happened. Who resumes it is a setting, Actions → Recovery mode, and the recovery list's title shows the current mode.

Mode Resuming
Manual A person, task by task
Supervised A person: the default
Automatic Unattended, within a budget, gated

A resume is a new turn. The old ids and receipts stay as the record of the interruption. A task holding an effect in doubt is refused. It is neither resumed nor skipped.

  • The new turn picks up where the old one stopped, in the same folder and under the same limits, and never beside a running turn.
  • It's told what the interrupted turn already changed and how that came out, so it doesn't repeat it.
  • The old task is marked as resumed by the new turn, so it isn't offered again.

Actions → Resume all ready runs the ready queue up to a budget and reports each outcome, as in 2 resumed · 1 deferred · 1 in doubt.

A command that Picache runs is a job (see Jobs below). The job is tied to the call that started it before that call begins waiting on it. If the harness dies during the wait, the job keeps running and writes its own exit status.

When the harness opens again, an effect whose job has exited is settled from that record and the job's output, the same way you would settle it. If the job is still running, the effect stays in doubt until the job ends. If it was cancelled or is gone, the effect is left for you to settle.

Apps after a crash

An app's work after a crash An app's handler runs in its capsule and reaches nothing, so each thing it changes it asks the harness for. Each ask passes one gate: the capability granted to the app, your answer when it must ask you, and, inside a turn, the write saved on the turn's task before it runs. The write ends applied, not applied, or uncertain, which a crash in the middle leaves; an uncertain one names the app in Recovery, and the app is not upgraded or removed until you settle it. Work longer than one call is a loop run the app starts under ai.redloop.manage: its own definitions, a layer named by id, asked of you, stepped and recovered by the harness. the app's handler in its capsule reaches nothing: it asks THE GATE · EVERY ASK ✓ granted to the app ? asked of you, when it must in a turn: the write saved on the turn's task first the answer goes back to the app ✓ applied ✕ not applied ? uncertain names the app in Recovery; no upgrade or removal until you settle it LONGER THAN ONE CALL · AI.REDLOOP.MANAGE a loop run: its own definitions, a layer by id, asked of you stepped, saved and recovered by the harness; never app code left running
An app's asks and the retry gate.

An app's handler runs in its capsule (section 09) and can reach nothing from there. It asks the harness for every change, one at a time, even changes to its own tables.

Inside a turn, each ask is saved on the turn's task before it runs, like Picache's own calls (section 03). That holds whether the ask comes from a tool, a command or a hook. A crash midway leaves the ask uncertain, so it is never done twice without anyone noticing.

Review recovery names the app and what it asked. The resumed turn is told whose write it was and goes on as a new turn. The app's call is not revived.

Until you settle it, the app can't be upgraded or removed, so the version you are deciding about is always still there.

Work that takes longer than one call never runs inside the app. The app starts a loop run instead, and the harness steps it, saves between steps and recovers it like any other run (section 04). Two grants control this.

GrantLets an app
ai.redloop.manageWrite and delete its own loop definitions, and start, continue or stop its runs
ai.redloop.readRead loop definitions and its own runs

The app names its definitions under its own id and names a layer by id, never by path. The answers it gets contain no folders or file paths.

You're asked each time the app writes or deletes a definition, or starts or continues a run, and it sees only its own runs. The approvals inside a run stay yours, and so does the scheduler.

Jobs

Long work is a job: downloads, tool installs, conversions, captures, indexing a layer's files, and the commands Picache runs.

A job is a durable record of its kind, state, phase, exact bytes, rate and log. The record is updated as the job changes, and each change is announced as an event.

  • Many run at once.
  • Each can be cancelled, and a download can also be paused and resumed.
  • Each survives a restart.
  • A finished download is checked against its pinned SHA-256 before it is moved into place.

Panes only show jobs. That is why the Downloads and Tools panes never disagree about how far a job has got.

Runs

Every terminal is a run, and every run has a record. The Runs pane keeps these records across restarts. Each one says:

  • which agent or program, in which layer and folder;
  • when it started;
  • each time it waited on you, and why;
  • how it ended: closed by you, exited with its code, or ended when cherry π closed.

The evidence chain

Receipts are stored as rows. The ledger is separate, and it holds commitments.

Each governed turn commits one ledger entry, turn_receipt, in the same transaction as its last receipt. The entry's digest binds every receipt the turn wrote. A file operation commits an entry of its own, and so does settling an effect in doubt.

The entries form a hash chain. Each entry's id is a BLAKE2b-256 hash of its body, and each body names the entry before it. The newest entry therefore commits everything before it, and changing a covered receipt breaks verification.

Evidence that travels with its layer The workspace's chain, in cherrypi.db, runs genesis, a turn, an anchor for layer L1, a file operation, an anchor for layer L2, and its tip. Layer L1's own chain, in its own store, runs genesis, a turn, a turn and an export. L1's genesis names the workspace's tip when it began, and the workspace's anchor names L1's tip. Verify evidence chain checks every chain entry by entry and each layer against its anchor, with BLAKE2b-256 per entry. Each entry hashes the one before it, and a layer carries its own chain when it leaves. THE WORKSPACE'S CHAIN · cherrypi.db genesis turn ⚓ L1 file op ⚓ L2 tip L1'S CHAIN layers/<id>.db genesis turn turn export its genesis names the workspace's tip the anchor names L1's tip VERIFY EVIDENCE CHAIN ✓ every chain, entry by entry · ✓ each layer against its anchor EACH ENTRY HASHES THE ONE BEFORE IT (BLAKE2B-256) · A LAYER CARRIES ITS OWN CHAIN
The workspace chain and a layer's chain, anchored both ways.

Each layer keeps its own chain in its own store, and the workspace's chain anchors it. The layer's first entry names the workspace's newest entry, and the workspace records an anchor that names the layer's newest entry.

A layer that leaves the workspace takes evidence with it that verifies on its own. Actions → Verify evidence chain checks every chain and anchor, and says where a chain broke.

Actions → Seal this session records a receipt in the chain that is bound to a conversation's messages and receipts. Seals are unsigned.

A seal proves only that the covered rows haven't changed since it was made. It isn't third-party attestation, and it proves nothing against an owner who rewrites the whole store.

In a browser: the web desktop and .pi names

cherrypi -browser opens Cherrytop, this machine's web desktop, in your browser, signed in, starting its web server first if needed. cherrypi.ape --browser opens the lander instead.

cherrypi -browser            # Cherrytop; -web and -gui do the same
cherrypi.ape --browser       # the lander
cherrypi.ape names install   # serve it at https://cherry.pi
cherrypi.ape web status

The lander is the cherry mark on black. Click it and you hear a short boot sound, then Cherrytop opens.

  • /site/ is this website, which the APE serves itself with no network involved.
  • /guide/ is the build's own docs.
  • /machine lists the workspaces and the apps served here.

There's one web server per machine, and it belongs to no workspace. It listens only on this machine's loopback and answers only the names it serves. A request for any other name is refused, so a page elsewhere can't point its own name at the server and read what it serves.

Its pages are open to read. The web desktop and its APIs need a browser that you have signed in (see Sign a browser in).

Before you install the names, the lander is at http://127.0.0.1:3141.

One request to https://my-game.pi Four steps, all on this machine. One: your browser asks where my-game.pi is. Two: the system sends every question under .pi (on macOS /etc/resolver/pi, on Linux a drop-in for systemd-resolved or NetworkManager's dnsmasq) to cherry π's DNS on 127.0.0.2 port 5314, which answers A 127.0.0.2 for any .pi name and nothing else, forwarding nothing. Three: 127.0.0.2 port 443, and port 80, go to the web server's port 3141: on macOS by two pf rules in the anchor com.apple/250.cherrypi, on Linux by a systemd socket. Four: the web server, loopback only and one per machine, completes TLS with my-game.pi's own certificate, chosen by SNI, and the Host picks what answers: cherry.pi gets the lander, Cherrytop, the website and the guide; my-game.pi gets its own files, read-only, at its own origin; any other name is refused with 421. 1 · YOUR BROWSER https://my-game.pi asks where it is no port in the URL 2 · DNS · 127.0.0.2:5314 A 127.0.0.2 for every .pi name nothing else; no forwarding 3 · PORTS 443 AND 80 127.0.0.2:443 → :3141 and :80 → :3141 pf, or a systemd socket 4 · THE WEB SERVER · LOOPBACK ONLY · ONE PER MACHINE TLS by SNI: my-game.pi's own certificate · then the Host picks what answers cherry.pi lander, website, guide /machine: what is here my-game.pi its own files, read-only at its own origin ✕ any other name 421, refused no page rebinds onto it ONLY .PI QUESTIONS COME TO CHERRY π'S DNS · EVERY OTHER NAME RESOLVES AS BEFORE
How a .pi name reaches this machine's web server. Every step stays on this machine.

Install the names

cherrypi.ape names script    # read what will run as root
cherrypi.ape names install   # asks for your password (twice on macOS)
cherrypi.ape names status    # check it as a browser would

names install puts the lander at https://cherry.pi, with no port in the address. On Linux it asks for your password once. On macOS it asks twice: once for administrator rights, and once more to trust the certificate authority in your login keychain.

On macOS it asks in a dialog, then puts four things in place:

  1. The address 127.0.0.2 on the loopback, so cherry π's names leave 127.0.0.1:443 to whatever else on this machine already uses it.
  2. Two rules for pf, the macOS packet filter, that send that address's ports 443 and 80 to the web server. They load under an anchor macOS already provides, so /etc/pf.conf is never edited.
  3. /etc/resolver/pi, which sends lookups for .pi names, and only those, to cherry π's DNS.
  4. A LaunchDaemon, pi.cherry.names, that restores the address and the rules at boot.

Then it asks to trust this machine's certificate authority in your login keychain.

Install the names on Linux

On Linux, sudo asks for your password once, in the same terminal. Lookups for .pi names, and nothing else, go to cherry π's DNS through the system's own resolver. The files that do this belong to cherry π, and which ones it adds depends on the system:

  • Where systemd-resolved runs (Ubuntu, Fedora), a drop-in for it.
  • Where NetworkManager writes /etc/resolv.conf instead (Debian's desktop), drop-ins that turn on its dnsmasq and forward .pi names.

A systemd socket passes ports 443 and 80 on 127.0.0.2 to the web server, and it does so at boot too. This machine's certificate authority (see A certificate authority that can only sign .pi) goes into the system's certificate store (update-ca-certificates or update-ca-trust).

Your own NetworkManager settings are never edited. If you set dns=none or rc-manager=unmanaged yourself, install leaves it alone and tells you.

On macOS and Linux alike, browsers with their own certificate store (Firefox everywhere, Chrome and Chromium on Linux) get the authority too, through NSS's certutil. If certutil is missing, install names the package that has it.

names status explains any failure. What was installed is recorded in ~/.cherrypi/web/names.json.

A certificate authority that can only sign .pi

This machine's CA, and what it may sign Each machine makes its own certificate authority with the system's openssl. It may sign names under .pi only, and no authority below it; its key is kept with owner-only permissions in ~/.cherrypi/web/tls, and you trust it once: in your login keychain on macOS, in the system's store on Linux. It issues one certificate per name: cherry.pi, and one for each served app such as my-game.pi, each for 825 days. A certificate for any name outside .pi, such as bank.com, falls outside its constraint and is refused. No private key ships in the APE, and no *.pi wildcard is used, since browsers refuse a wildcard directly under a top-level name. THIS MACHINE'S CA cherry pi local names may sign names under .pi only and no authority below it made here, by the system's openssl key 0600 in ~/.cherrypi/web/tls trusted once: keychain or system store ✓ cherry.pi the lander's own certificate ✓ my-game.pi one certificate per served app ✕ bank.com outside .pi: refused EACH CERTIFICATE 825 DAYS · NO PRIVATE KEY SHIPS IN THE APE · NO *.PI WILDCARD
A certificate authority per machine, bounded by a name constraint, so trusting it trusts nothing outside .pi.

Each machine makes its own certificate authority the first time a name needs a certificate, and that authority is constrained to .pi.

It can sign cherry.pi and my-game.pi, and browsers refuse any certificate it signs for another name. Its key, which only you can read, therefore can't vouch for a site outside .pi, such as your bank's.

names install prints its SHA-256, so you can check it before you trust it.

The web server picks each name's certificate as the connection opens, and sends a plain http:// request on to https://. Each certificate lasts 825 days, and the server issues a new one 30 days before it ends, so you never need to renew one yourself.

Serve an app's pages

Serving an app at its own name An app's manifest names the folder of its assets that is its site, web = { root = "site" }, and asks for the capability domain.serve. You, and only you, type picache app serve my-game. That grants domain.serve for good, records the app in ~/.cherrypi/web/served.json for the web server to read, issues my-game.pi's certificate from this machine's authority, and restarts the web server if it is running. Unserving it, denying it domain.serve or removing the app takes it out again. MANIFEST.LUA web = { root = "site" } capabilities = { "domain.serve" } its pages: assets/site/ picache app serve my-game · you only ✓ grants domain.serve for good, in its workspace records it for the server ~/.cherrypi/web/served.json issues its certificate my-game.pi, from this machine's CA restarts the web server if it is running UNSERVE, A DENIED DOMAIN.SERVE OR REMOVING THE APP TAKES IT OUT
Serving is off by default. You turn it on yourself, one app at a time.

A .pi app can have pages. In its manifest, web = { root = "site" } names the folder of assets that is its site. It may name an index other than index.html.

picache app serve my-game   # https://my-game.pi
picache app unserve my-game

The app must ask for the capability domain.serve, and nothing is served until you run app serve. An agent can't run it for you.

Its files are then served read-only at https://<id>.pi, straight from its .pi, or from its assets/ folder while you are developing it.

Each app has its own origin, so no app's page can read another's or the lander's. Its pages may run their own scripts, but they can't load anything from elsewhere.

An app without pages gets a card at that address instead, showing its name, description, version and signer.

Names belong to the machine, and these rules apply:

  • cherry.pi is reserved, and so is picache, the id of Picache's built-in chat.
  • An app's id must be a single DNS label to get a name.
  • An id served from one workspace can't be served from another until it's unserved there.

app unserve stops serving an app. Denying it domain.serve or removing the app also stops it.

The web desktop

Click the cherry on the lander and you're in Cherrytop, this machine's desktop in a browser tab. cherrypi --gui opens it directly. Cherrytop is a separate desktop from the TUI. What you do in one doesn't change the other, except where both read the same harness.

Cherrytop: windows over a canvas Left: one browser tab holds two layers. Windows, tiled or floating, hold a shell, the TUI, a served app or Picache's chat. Under them is the canvas, with widgets, icons, frames and spaces; a machine can have up to 24 canvases, each with its own windows. Pinning a window puts the same frame on the canvas, still running, and nothing reloads. Right: zoomed out, each canvas item is drawn by the width it has on screen: 240 pixels or more, live; from 96, a card with its title, a state and its last lines; under 96, an icon. A shell stops drawing while it is a card. ONE TAB · TWO LAYERS windows, tiled or floating a shell, the TUI, an app, Picache's chat the canvas, under them widgets, icons, frames, spaces · up to 24 ⇣ pin: the same frame, still running ZOOMED OUT · BY WIDTH ON SCREEN live 240 px + as it is card 96 px + title a state last lines icon under 96 glyph, title ✓ moving, pinning or zooming reloads nothing ✕ a card's shell doesn't draw
Windows on top, the canvas under them, and how much of each canvas item is drawn as you zoom out.
  • Windows hold a shell (/terminal), the desktop TUI itself (/tui), a running terminal attached from a workspace, this website, a served app, or Picache's chat (Picache in a window).
  • The canvas under the windows is an infinite surface that you pan and zoom, with icons, widgets, frames and named spaces. Ctrl+Alt+D shows it. Press m to change its mode (ember, glass, graph, mono, silk), or g followed by a number to fly to a space.

The widgets read every workspace this machine has opened. One whose harness isn't running is read from its store, with nothing live. They show:

WidgetShows, and what you can do from it
waiting for youEach question a turn stopped to ask (When something needs you): the call, why, and whether it's destructive. Approve once or decline answers exactly that question and resumes the turn, as Recovery does.
goalsEach goal's progress. You can stop a running one, accept or reject a finished one's work, or continue one that stopped short (Give an agent a goal).
runningEach open terminal run. View puts it on the canvas, live, and window opens it in a window. Both attach to the run itself (section 13).
agents, inbox, workspacesAgents' states, active Issues, and which harnesses are running.
appsServed apps. An app can offer widgets of its own: pages of its site, in the same sandbox as its windows. A widget can show live data by running one verb the app names for it, but only to read: it can't make the app change anything. Meta's Installed apps is one (right-click the canvas, Add app widget).
clock, machine, notesThe time, a count of what's here, and notes kept on this machine.

An answer, a goal verb or an attachment from the canvas goes through the same harness calls the desktop uses, so each is receipted the same way. A question is asked in the page, never in the browser's own pop-up.

The windows and canvases are kept in ~/.cherrypi/web/web.db. Every change is recorded as an event, so another open tab shows it at once.

Choosing Open in browser on a terminal pane in the desktop shows that same running terminal in a browser tab, already signed in for you. Typing in either shows in both, and closing the tab leaves the program running (From a browser).

Canvases, pinning and zoom

  • You can have several canvases. Make, rename and remove them from the canvas button in the bar or from the palette (Ctrl+K, then canvas). Each canvas has its own windows. Ctrl+Alt+] and [ go to the next canvas and the one before. Canvas Zero is always there, and you can have up to 24 in all.
  • You set placement from the canvas's right-click menu: free, snap to grid, or locked. You can also turn on push others aside when dragging. A single item can be locked on its own with Lock in place.
  • Pinning a window (with ⇣ in its header, or Pin focused window to the canvas in the palette) turns it into a canvas item that keeps running. Popping it out again turns it back into a window. The frame is never reloaded, so a shell keeps its session and an app keeps what it holds.
  • When you zoom out, items turn into cards and then icons, depending on the width each has on screen. A run's card shows the agent's own report, or that the run ended. Otherwise it says no report and does not guess. A shell's card shows its last lines, and the shell stops drawing until it's live again.

Picache in a window

Picache is the one app built into Cherrytop. Open it from the apps and it talks to the harness of a running workspace. You pick the workspace and the conversation, and the conversations are the same ones the TUI lists. A new conversation you start here shows up in the TUI too.

How Picache's chat reaches a harness The chat runs in a sandboxed frame that can run scripts and nothing else, and that holds no sign-in. It asks Cherrytop, which is signed in, and Cherrytop carries each request to the harness of a workspace this machine has opened. There, your words start a turn in the same conversation the TUI lists, and Picache's tools work in the workspace's folder. The turn's events ring the workspace's bell and stream back to the chat as they happen; the model's words are shown as text, never as markup. When the turn asks a question, declining from the chat is sent as asked; approving opens Cherrytop's own dialog, showing the call as the harness states it. THE CHAT · SANDBOXED your words no sign-in, scripts only the turn, as it runs shown as text, never markup picache asks to run… decline · approve once… CHERRYTOP · SIGNED IN carries it to an opened workspace streams it back woken by the bell its own dialog the harness's words THE HARNESS · YOUR WORKSPACE the conversation the one the TUI lists the turn and its events tools in the workspace folder ✓ declining is sent as asked ✕ the chat page can't approve: only Cherrytop's dialog can
The chat holds nothing of yours. Cherrytop carries what it says, and approving always happens in Cherrytop's own dialog.

The chat is sandboxed because it shows a model's words, and a model's words are untrusted text. Even if something in them got past the page, it would reach nothing, because the frame has no sign-in and can only ask Cherrytop.

  • A turn streams into the chat as it runs: your words, each tool call, a question, and the reply. stop ends the turn at its next step. Something you send while it runs is queued for when it ends.
  • A question the turn asks shows in the chat. Pressing decline answers it at once. Pressing approve once… opens Cherrytop's dialog, which shows the call as the harness states it, not as the chat page shows it. Only that dialog can approve.
  • The chat's card on a zoomed-out canvas shows the last exchange.

The chat's tools work in the workspace's own folder. Every model call, the generic openai_compatible backend's included, runs in the inference worker, so the harness keeps answering while a model thinks.

When an agent proposes

Cherrytop belongs to you. An agent can see what's open on it, but it can't open, focus or move anything there. Instead it makes a proposal, with a note, and you answer it in the browser.

An agent proposes; you answer An agent runs picache web open with a surface and a note, or picache web arrange with a plan of where items go. Cherrytop records the proposal in its own store, web.db: three waiting per agent, twelve in all, each for ten minutes. The browser shows it at once with the note, as text. An arrangement can be previewed as ghosts where each item would go; a locked item stays. Accepting does it as your own click would, so it's recorded as yours; declining does nothing. The agent reads your answer. AN AGENT · IN ITS PANE picache web open terminal --note the build failed picache web arrange - where each item goes on stdin, at most 40 CHERRYTOP'S STORE a proposal, waiting 3 per agent · 12 in all 10 minutes, then expired YOUR BROWSER shown, with its note as text preview as ghosts a locked item stays accept · decline ✓ accepted, it's done as your click ✕ an agent never opens, focuses or moves anything itself
Nothing changes on Cherrytop until you accept.
CommandDoes
picache web statusWhether this machine's web server is serving, how many canvases, and how many proposals wait (yours among them)
picache web canvasesThe canvases, by id and name
picache web instances [--canvas <id>]What's open on one canvas: its windows, and each item by the id focus takes
picache web open <surface> [--note …]Proposes opening terminal, tui, site, activity, app:<id> or an attached run
picache web focus <item> [--note …]Proposes showing one item on the canvas
picache web arrange <plan | -> [--note …]Proposes where items go: [{ item, x, y, w?, h? }], as the argument or on stdin
picache web proposals [--all]An agent's own proposals still waiting. --all adds the answered ones, with your answer. You see everyone's.
picache web withdraw <id>Takes back one of your own proposals that is still waiting

An arrangement moves canvas items and leaves windows where they are. A paired machine's turn can't propose.

Picache has the same commands as tools, in the web group (Picache's tools).

Sign a browser in

The web server listens only on loopback, but every program on this machine can reach loopback, including an agent. So the desktop's pages and their APIs need a browser that you have signed in.

cherrypi --browser               # opens Cherrytop, signed in
cherrypi.ape web login           # sign a browser in to the desktop
cherrypi.ape web login --print   # print the link instead of opening it
cherrypi.ape web sessions        # the browsers signed in, each with an id
cherrypi.ape web logout <id>     # sign that one browser out
cherrypi.ape web logout          # sign every browser out
Signing a browser in Three steps. One: a launcher on this machine, cherrypi --browser or web login, mints a one-time code and writes only its digest into ~/.cherrypi/web/login, good for two minutes. Two: the browser opens /login with the code in the fragment, which no server log sees; the page removes it from the address bar and posts it to /api/login. Three: the server consumes the code, so it works once, and opens a session for this browser: a random cookie, HttpOnly and SameSite=Strict, recorded on this machine by a MAC of it under the secret in ~/.cherrypi/web/access.key, so no cookie is kept on disk. Below, what is refused: a request with no cookie gets 401 login_required; a request that changes something without an Origin header gets 401; another site's page gets 403. A confined agent's box doesn't see ~/.cherrypi, so it can't sign itself in. 1 · A LAUNCHER HERE mints a one-time code its digest in ~/.cherrypi/web good for two minutes 2 · THE BROWSER /login#code=… code off the address bar posted to /api/login 3 · THE WEB SERVER code consumed, once cookie: HttpOnly, Strict a session of its own REFUSED ✕ no cookie: 401 login_required ✕ a change with no Origin: 401 ✕ another site's page: 403 an agent's box doesn't see ~/.cherrypi, so it can't mint a code or read the secret THE LANDER, THE WEBSITE, THE GUIDE AND SERVED APPS STAY OPEN WITHOUT SIGNING IN
Only someone at this machine can sign a browser in. Each browser gets its own session, and web logout ends them all.
  • Opening /cherrytop, /terminal, /tui or /machine without signing in takes you to /login, which explains how to sign in and then brings you back.
  • http://127.0.0.1:3141 and https://cherry.pi are different sites to a browser, so each is signed in on its own. web login --loopback signs in the first of the two.
  • A browser's session ends after 30 days without use, or after 400 days in all. Sign this browser out in Cherrytop's palette ends only that one.
  • Apps shown in Cherrytop run sandboxed in a separate origin: an app's script can't call the desktop's APIs or read its page.

Commands

Command What it does
cherrypi -browser, -web The same as cherrypi --gui: starts the web server if needed and opens Cherrytop, signed in
cherrypi --gui Opens Cherrytop, the web desktop, signed in
cherrypi.ape web login Signs a browser in with a one-time link; --print prints it instead of opening it
cherrypi.ape web sessions Lists the browsers signed in: an id, which browser, where, and when each was last used
cherrypi.ape web logout <id> Signs that one browser out
cherrypi.ape web logout Signs every browser out
cherrypi.ape names install Sets up the address, the ports, the resolver and the boot job, and trust for the certificate authority in the system and in browsers' own stores (one password on Linux, two on macOS)
cherrypi.ape names script Prints what the install runs as root
cherrypi.ape names status Tries https://cherry.pi as a browser would, ticks off each piece and store, and lists when each certificate ends
cherrypi.ape names remove Takes every piece away and stops trusting the authority
cherrypi.ape web start, stop, restart, status The web server's lifecycle. stop waits up to five seconds before it forces the server to stop
picache app serve <id> Serves an app's pages at https://<id>.pi
picache app unserve <id> Stops serving them

Serving pages this way opens no port to the network.

Bitcoin and cRED

cRED is a credit mined against Bitcoin. A claim is an ordinary Bitcoin transaction that pays up to three addresses beginning 1red, ground for the block before it. Anyone checks a claim from public Bitcoin data and the published rules alone: there is no server to ask and no witness to trust.

Four first-party addons carry it, each off until you turn it on:

AddonWhat it doesNeeds
bitcoinRuns Bitcoin Core on this machine, and gives the others the chain
cred_coreReads the chain for claims and counts them by the rulesbitcoin
cred_minerMines here: a wallet of coins, the grinder, claimscred_core, bitcoin
cred_frontendsThe hand loop, a full-screen program for a terminal pane, and the miner's page in Cherrytopcred_core, and cred_miner to play
picache addons enable bitcoin
picache addons enable cred_core
picache addons enable cred_miner
picache addons enable cred_frontends

Each network counts claims from a pinned block. Regtest, a private chain this machine mines by itself, counts from block 0. Signet, Bitcoin's public test network, counts from block 329,000, and every machine that follows it counts the same markers the same way. Mainnet is not open yet: its block is not pinned, so a mainnet node counts nothing.

Run a Bitcoin node

picache bitcoin install            # download and verify the pinned Bitcoin Core, as a job
picache bitcoin start regtest      # starts detached: it runs until you stop it
picache bitcoin                    # the install, and each network's node
picache bitcoin mine 101           # regtest only: mine blocks to this node's own wallet
picache bitcoin readers regtest    # who follows the chain, and how far each has read
picache bitcoin log regtest 50
picache bitcoin stop regtest

A node outlives the harness and the desktop; only picache bitcoin stop ends it. Each network keeps its own data folder, its own ports and its own RPC credentials. Only you install, start, stop or mine; anyone may read the status. The build pins Bitcoin Core 30.2 for macOS (ARM, x64) and Linux (ARM, x64). On Linux it is Bitcoin Core's own release, which needs glibc's loader at its usual path; NixOS has none, and there the install is refused with bitcoin_no_glibc.

Every command in this section takes signet, or main, where it says regtest; only mining blocks is regtest's alone. A signet or mainnet node is pruned: it keeps only the blocks its readers have still to read. A keeper job, shown in the Jobs pane, asks Bitcoin Core to drop the rest every minute, from the first sync on. A synced signet node takes about 4.5 GB, nearly all of it the chain state; its block files stay under 150 MB. Leave about 6 GB free for the first sync: Bitcoin Core stops, saying the disk space is too low, when it can't write its cache.

How a claim counts

A claim spends up to three of your coins, each P2TR (key path) or P2WPKH. Input i carries a nonce in its sequence number, and the transaction's lock time is the block it was ground against, the anchor L. Output i pays the marker that input derives: a P2PKH address made from the coin's key, the coin's own outpoint, the anchor block's hash and height, and the nonce. Nothing else can produce it, so a marker can't be ground ahead of time or claimed with someone else's coin.

A marker counts when:

  • the claim is mined in block L + 1, the very next one;
  • its address begins 1red, in any case;
  • its address meets the difficulty: a hash of the address, the policy and the anchor falls under a fixed target (difficulty 12 on every network);
  • it is one of the first three outputs; any after are ignored;
  • its epoch still has room under the schedule.

The address is the amount. Its digit groups are added up into naks (10,000 naks make one CRED), and its tier multiplies them. In 1ReDfLGrbgawLxKaA71zYBWXwC76629Aa the groups 1, 71 and 76629 make 76,701; the 66 makes it a Pair, ×1.5, so it pays 115,051 naks: 11.5051 CRED.

TierAfter the brand, the address hasPays
Mythica run of five of one character, or 1red again×150
Jackpot777, or a run of four×30
Double Redred again, in any case×12
Straightthree digits in a row, up or down×8
Tripsa run of three×4
Two Pairruns of two of two different characters×2
Pairone run of two×1.5
Plainnone of these×1

No marker pays more than 2,097,151 naks. Moving a marker's sats later does not move its cRED, which stays with the address.

Check cRED yourself

picache cred scan regtest          # read the chain to its tip for claims
picache cred                       # the policy, how far the scan has read, markers by status
picache cred markers regtest 20    # the latest markers, and what each came to
picache cred balances regtest      # naks per counted marker
picache cred miners regtest        # naks per claimant key
picache cred card 1ReDfLGrbgawLxKaA71zYBWXwC76629Aa   # what an address alone says
picache cred policy regtest        # the rules and their hash

The scan reads each block with its inputs' previous outputs, so every claim is checked from the chain alone. A reorganisation takes back what it removes. A block whose previous outputs this node no longer has stops the scan instead of being guessed at. All of it is public data, so anyone may run these.

Mine

The miner keeps a wallet of its own: dedicated coins, one key each, every key in the signer. It never shares a key with another wallet, because a marker's key is the coin's key plus public numbers. The grinder needs no key at all.

picache cred-miner address regtest          # a new key to fund (p2tr, or add p2wpkh)
picache cred-miner settle regtest           # read the chain: funding arrives as ready coins
picache cred-miner split regtest 3 2000000  # three coins of 0.02 BTC: a hand needs three
picache cred-miner grind regtest            # grind every ready coin against the tip
picache cred-miner hand regtest             # the best hit per coin, and the hand they make
picache cred-miner preview regtest          # what shipping now would send, and its postage
picache cred-miner ship regtest             # sign and send the claim: it must make the next block
picache cred-miner settle regtest           # after the next block: what counted goes in the ledger
picache cred-miner sweep regtest 1          # spend markers back into a coin when it pays

A hand can be one, two or three coins. A claim pays each marker 546 sats and its fee, and must leave at least 546 sats of change, so the smallest a signet faucet gives, about 1,400 sats a coin, makes a two-coin hand at 2 sat/vB.

The grinder is a pinned sidecar, one job per coin, shown in the Jobs pane with its hits, rate and tries, such as 2 hits · 412,113/s · 9,301,544 tried. Its log is a stream of JSON lines, the same events Rednode's miner wrote: progress about every fifty thousand tries (attempts, rate, matches) and each match the moment it is found, so a frontend can follow a grind live. Every hit it reports is derived again before it is used, so a faulty grinder can waste time but never send a bad claim. A hand whose block has been followed by another is refused: it would be late. Settling records each counted marker in the ledger once, as cred.claim.miner.settled.

status, coins, hand and preview are anyone's to read. The rest make keys, spend your coins, use this machine's processors or record in the ledger, so they are yours alone. An agent can say which to type; it can't type them.

Play a hand

Run the hand loop in any terminal pane:

cherrypi.ape cred-play regtest

Every block deals a hand. The loop grinds your ready coins against the tip, and the reel shows each qualifying address as it is found. Each is real, and each would count if shipped in time. Your hand is the best hit on each coin, up to three, with what it pays. Under it are the odds that the next minute improves it. They come from this machine's rate and the hits this session has seen, and say so. When the next block comes, the loop settles what you shipped and deals again.

KeyWhat it does
sShows the claim first: inputs, markers, change, size and postage. y sends it; any other key sends nothing
fFolds: stops grinding this block. The next block deals a new hand
gGrinds the current block again
wSweeps markers worth spending back into a coin
mRegtest only: mines a block
qQuits, and stops grinding

Postage is the ante. A hand worth less than its fee is yours to fold. A block can come at any second, and the time already waited doesn't make it any sooner, so shipping is always a call you make. A claim that misses block L + 1 spends its postage and counts nothing. Status is always written as a word, never shown only as a colour, and NO_COLOR turns colour off.

Watch the miner in Cherrytop

With cred_frontends on, Cherrytop serves the miner's page as an app, cRED miner (/app/cred/). Open it in a window like any served app (section 11). Once a second it shows the grind as it happens: the tip, and whether the node is still syncing; the rules; the rate on a dial; hits found and tries made; each coin's best hit and the hand they make; your claims and how each was judged; and the latest markers in the ledger. A new hit is celebrated.

The page only reads. Grinding, shipping and sweeping stay the verbs above, and yours. With no node running, or the miner off, it says so instead.

The Sakura protocol

Sakura is how a client reaches something that's already running in a workspace without starting a copy of it. That something is a terminal: a shell, a vendor agent, the desktop TUI in a pane. A client attaches to that run, gets its current screen, follows its output, types into it and leaves, and the program never notices.

The browser speaks it, and so does the Go client's library. The desktop's own panes don't use it: they reach their terminals directly. A browser tab showing a terminal, Cherrytop's terminal widgets and the desktop's Open in browser are Sakura attachments to the same run your pane shows (section 11).

The rest of this section is the wire reference: the frame, the handshake, the terminal stream, services, recovery, the browser carrier, and limits and errors.

Three planes over one run

A terminal has exactly one owner, its pump. The pump is the small process that holds the terminal's PTY and a ring of its recent output (section 01). Sakura adds no second owner. Over each run it carries three planes:

  • The control plane decides who may attach to which run, and for how long. It covers tickets, the handshake, detach, resume and revoke.
  • The stream plane carries the screen: a snapshot first, then live output in order, sent within the credit the client grants.
  • The service plane carries typed calls with receipts. Typing, resizing and reading go here, so the client doesn't have to guess an input's outcome.
Three planes over one run Left, clients: the Go client and a browser tab. Middle, one Sakura connection context per client, with three planes on it. Control, on channel zero: HELLO, CHALLENGE, AUTH, ATTACH, DETACH, RESUME. Service, also on channel zero: SERVICE_CALL and SERVICE_RESULT for the terminal's read, input, resize and resume. Stream, on channel one: snapshot, READY, DATA, EVENT, with ACK and WINDOW back. Right, the host side: the harness admits each connection and the run's pump owns the PTY, its output ring and a headless terminal engine that makes snapshots. Several clients attach to the same run; the program runs on when they leave. CLIENT Go client local-control/1 CLIENT browser tab browser-http/1 A CONNECTION CONTEXT · ONE PER CLIENT control · channel 0 HELLO CHALLENGE AUTH ATTACH · DETACH RESUME service · channel 0 SERVICE_CALL → SERVICE_RESULT, with receipts stream · channel 1 snapshot · READY · DATA · EVENT ⇄ ACK WINDOW HOST harness admits, scopes, leases the run's pump PTY · output ring input queue · receipts terminal engine the only owner of the program and its screen CLIENTS COME AND GO · THE RUN, ITS OUTPUT AND ITS INPUT ORDER STAY WITH THE PUMP
Each client gets its own connection context over the same run. Control and services share channel 0, and the terminal stream is channel 1.

Carriers

Sakura frames are bytes. A carrier moves them and says who the caller is, but adds no authority of its own: the frames still authenticate, and the host still checks the run.

CarrierWho uses itHow a frame travels
local-control/1 The Go client on this machine The harness's private control socket, which only a holder of its 0600 client key can use. sakura_open scopes a ticket to a run, sakura_exchange carries one frame each way as base64, and sakura_next takes the stream's next frame.
browser-http/1 A browser on this machine POST /api/sakura on the machine's web server, which forwards only exchange, next and revoke into the harness's sakura_browser entry. Every request carries the attachment's bearer (below).

Both carriers work only on this machine. A desktop on a paired machine can't call any Sakura entry point. It attaches over the remote-desktop path instead (section 08).

The frame

Every frame is a 40-byte header and a payload of at most 64 KiB. Integers are big-endian. The Go codec (internal/sakura/frame.go), the Lua host (runtime/addons/sakura/frame.lua) and the browser (sakura-client.js) check the same golden vectors.

The SKRA v1 frame header A 40-byte header, drawn to scale in two rows of 20 bytes. Row one: bytes 0 to 3, the magic SKRA; byte 4, version 1; byte 5, the frame type, 1 to 31; bytes 6 and 7, flags, bit 0 response and bit 1 error; bytes 8 to 11, the channel, 0 for control and services, 1 for the terminal stream; bytes 12 to 19, seq. Row two: bytes 20 to 27, ack; bytes 28 to 31, the payload's length, at most 65536; bytes 32 to 39, the epoch. The payload follows at byte 40: JSON for control and service frames, raw bytes for DATA and snapshot chunks. BYTES 0–19 "SKRA" 0–3 magic 1 ver type 5 flags 6–7 channel 8–11 · 0 or 1 seq 12–19 · the sender's, per channel BYTES 20–39 ack 20–27 · highest peer seq taken length 28–31 · ≤ 65536 epoch 32–39 · the host's generation PAYLOAD FROM BYTE 40 · JSON FOR CONTROL AND SERVICES · RAW BYTES FOR DATA AND SNAPSHOTS
The header, to scale: each box's width is its size in bytes.
FieldMeaning
magic, versionSKRA, then 1. Anything else is refused.
typeOne of the 31 types below. An unknown type is refused.
flagsBit 0: a response (a service result). Bit 1: an error, after which the context is gone unless the error says it is recoverable. Other bits are refused.
channel0 for control and services, 1 for the terminal stream.
seq, ackPer channel and per direction. Control frames carry 0 and 0. Service calls count from 1. Stream frames count from 1 after the stream's generation begins.
lengthThe payload's size. Trailing bytes after it are refused.
epochThe host's generation. A client's first HELLO carries 0. Every later control and service frame carries the host's. A host restart changes it, and an old attachment is then refused. Stream frames carry the stream's generation instead, which a fresh snapshot changes.

Frame types

Thirty-one types are defined. The terminal attachment uses the ones marked used. The host refuses the ones marked refused.

CodeTypeDirectionHandling
1HELLOclient → hostused: version, nonce, features
2CHALLENGEhost → clientused: host nonce, principal, carrier, limits
3AUTHclient → hostused: ticket and proof
4ATTACHclient → hostused: the run, channels, window
5ATTACHEDhost → clientused: channels, input acknowledgement, resume token
6 to 8SNAPSHOT_BEGIN, _DATA, _ENDhost → clientused: the current screen, in chunks
9READYhost → clientused: the stream is live
10DATAhost → clientused: output bytes
11 and 12INPUT, RESIZEclient → hosttaken on channel 1 after READY, refused on channel 0. The Go client and the browser type and resize through services instead
13EVENThost → clientused: a resize of the run
14WINDOWclient → hostused: more credit
15ACKbothused: a frame taken, or credit granted
16 and 17PING, PONGclient → host, host → clientused: liveness, and renews the lease
18CLOSEhost → clientused: the program ended
19ERRORhost → clientused: a refusal, fatal or recoverable
20 and 21DETACH, RESUMEclient → hostused: leave without closing, or come back
22 to 29OBJECT_BEGIN … OBJECT_ABORTbothrefused: reserved for object transfer
30SERVICE_CALLclient → hostused: a typed call
31SERVICE_RESULThost → clientused: its result or refusal

The handshake

An attachment begins with a ticket: 32 random bytes written as 64 hex digits, scoped to one connection context (each client gets its own) and one existing run. A ticket is good for 60 seconds and one authentication attempt. Four exchanges follow, all on channel 0 with seq and ack set to 0:

The handshake, and the ticket spent at AUTH A sequence between a client on the left and the host on the right. One: the client sends HELLO with epoch 0, version 1, a 64-hex-digit client nonce and the features it speaks, service.json/1 and terminal.ghostty-snapshot/1. Two: the host answers CHALLENGE with its own nonce, the real principal, local-uid followed by the user id, the carrier and its limits. Three: the client sends AUTH with the ticket and a proof, an HMAC-SHA256 under the ticket of the protocol name, the epoch, the connection, principal, carrier, run and both nonces, each length-prefixed. The host compares in constant time and the ticket is spent: one attempt only. A browser is answered with a fresh bearer here, and from then on the pasted ticket authorizes nothing. Four: the client sends ATTACH naming the same run, channel 1 and an initial window; the host answers ATTACHED with the stream's generation, the input acknowledgement and a resume token. The snapshot follows on channel 1. CLIENT HOST 1 · HELLO epoch 0 · version 1 · nonce · features 2 · CHALLENGE host nonce · principal local-uid:… · carrier · limits 3 · AUTH ticket · proof = HMAC(ticket, transcript) the ticket is spent: one attempt, compared in constant time a browser gets its own bearer; the pasted copy is useless 4 · ATTACH the same run · channels [1] · window 16 KiB–1 MiB ATTACHED stream generation · input_ack · resume token ▶ ◀ ▶ ▶ ◀ ✓ snapshot follows on channel 1
HELLO, CHALLENGE, AUTH, ATTACH. The proof binds the ticket to this connection, this run, this principal and both nonces.

The proof is HMAC-SHA256, keyed with the ticket's 32 bytes, over:

"sakura.local/1\0" ‖ epoch (u64)
  ‖ len‖connection ‖ len‖principal ‖ len‖carrier
  ‖ len‖run ‖ len‖client nonce ‖ len‖host nonce      # each len a u32
  • The principal is the host's real local-uid:<uid>, never a field the client sends.
  • A lost answer is safe to retry: the identical frame gets the identical answer, and nothing runs twice. A retry with different bytes is a new request and is judged as one.
  • ATTACH must name the run the ticket was scoped to. Asking for another is refused with sakura_session_scope and ends the context.

The terminal stream

Channel 1 carries the run's screen, using the codec terminal.ghostty-snapshot/1. Nothing is live until the client holds a whole, checked snapshot.

  1. SNAPSHOT_BEGIN gives the codec, the exact native ABI, the size (at most 16 MiB), its SHA-256, the grid, and the output cursor and resize revision the snapshot was taken at.
  2. SNAPSHOT_DATA chunks of at most 16 KiB follow, in order.
  3. SNAPSHOT_END repeats the SHA-256 and gives the scrollback marker. The client checks size and digest, and decodes it fully, before it shows anything.
  4. READY commits it. From then on, DATA carries output bytes (at most 16 KiB each) and EVENT carries the run's resizes, in the order they happened after the snapshot.

A snapshot holds both screens, the saved primary screen, scrollback, cursor, title, working directory and any half-parsed escape sequence, so the first live byte continues exactly where the snapshot ends. The pump's own terminal engine makes it, and answers the program's screen queries itself, so a browser never sends a reply a native client also sent.

ACK and WINDOW on the stream The host's stream on channel 1 sends frames in sequence: SNAPSHOT_BEGIN as seq 1, SNAPSHOT_DATA as 2 and 3, SNAPSHOT_END as 4, READY as 5, then DATA as 6. The host keeps one unacknowledged frame and sends it again if asked again, so a lost frame is never skipped. The client answers each frame with ACK, which retires it, and separately with WINDOW, which grants byte credit for what it has drawn; the host answers a WINDOW with an ACK of its own. A duplicate WINDOW grants nothing twice. The host sends DATA only within its credit; when the credit is spent it waits, and the program keeps running, its output kept in the pump's ring. HOST → CLIENT · CHANNEL 1 · THE SNAPSHOT, THEN LIVE BEGIN seq 1 chunks seq 2–3 END seq 4 · sha256 READY seq 5 DATA · EVENT seq 6… live CLIENT → HOST ACK ack=n this frame is taken: the host retires it until then, asking again returns the same frame WINDOW {bytes} credit for what was drawn; answered by an ACK a duplicate grants nothing twice OUT OF CREDIT, THE HOST WAITS · THE PROGRAM DOESN'T · ITS OUTPUT WAITS IN THE PUMP'S RING
ACK and WINDOW are separate: one retires a frame, the other grants credit. A slow tab holds back its own stream, never the program.
  • The initial window, the credit the client grants in ATTACH, is between 16 KiB and 1 MiB. Credit never exceeds 1 MiB.
  • If the client falls further behind than the pump's ring holds, the host says so with a recoverable ERROR, sakura_snapshot_required. The client asks for a fresh snapshot (see below). The screen it already shows stays until the new one is ready.
  • A run that has drawn Kitty graphics can't be snapshotted: the host refuses with snapshot_graphics_unsupported, so it never sends a screen with its pictures missing.

Services

The Go client and the browser send typing and resizing as service calls on channel 0, so each one gets an answer. The host also takes INPUT and RESIZE frames on the stream after READY, and refuses them on channel 0. A call and its result look like this:

SERVICE_CALL    seq=n  ack=last result
  {"service": "terminal", "op": "input", "request_id": "browser-7",
   "args": {"data": "<base64>", "seq": 12}}

SERVICE_RESULT  flags=response  seq=m  ack=n
  {"request_id": "browser-7", "result": {"input_ack": 12}}
ServiceOperationsNotes
terminal input, resize, read, resume Input is at most 16 KiB per call. Resize asks for 1 to 500 columns and 1 to 300 rows. Input and resize share one writer sequence, seq in args, counted from the attachment's input_ack.
sakura.service describe Lists the services and their limits.
  • Each context has one call outstanding at a time. Calls count from 1. Results have their own sequence and acknowledge the call they answer. A gap, a future ack or a changed duplicate ends the context.
  • input_ack means the pump admitted the bytes to the program's input queue, in order. It doesn't mean the program ran them.
  • The host supplies the run and the attachment. Fields in args can't name another run or another lease.
  • A refusal in a result (because the queue was full, for example) is still an answer. The sequences advance and the attachment stays. Only an ERROR frame that isn't marked recoverable ends it.

Lost answers, detach and resume

Sakura's rule is that an operation is never repeated by accident, and never silently dropped.

  • If an answer is lost, the client sends the exact same bytes again. The host recognises them and returns its stored answer, so nothing runs twice. Until that answer arrives, nothing later may overtake it: the browser pauses input and offers Retry.
  • If the pump can't say whether an input was admitted (command_outcome_unknown, command_backpressure), the client resolves that same input, with the same writer sequence, in a new service call. The pump acknowledges a duplicate it already has without typing it again.
  • DETACH leaves the attachment, its counters and the run as they are. RESUME names the same run and the resume token, with the last frame the client took, and the host answers ATTACHED with its counters. With snapshot: true, or when the client's history is gone, the stream starts a new generation with a fresh snapshot. Frames from the old generation can't touch the new one.
  • A harness restart makes a new host and changes the epoch. Old attachments are refused, and a client never resubmits uncertain input under a new attachment. Instead it gets a new credential and starts from a snapshot.
  • An attached context holds a lease that lasts five minutes without a call, and any call, poll or ping renews it. A host keeps at most 64 contexts, counting those still waiting to authenticate. Leaving, expiry and revocation end the attachment, but the program keeps running.

From a browser

A browser attaches with a credential the harness issues for one run. You can get one in two ways without ever seeing it:

  • Open in browser, from a terminal pane's menu or Actions, opens a one-time sign-in link to /terminal/attach?workspace=<id>&run=<record>. That page asks the web server for its own credential, so the credential never passes through the clipboard, the terminal or a recording of it.
  • Cherrytop's running widget opens the same page as a widget or a window.

A script can also ask for one:

cherrypi.ape desktop -state <workspace>/.cherrypi -browser-attach <record>

That prints the credential, which is a secret, for you to paste into /terminal/attach within a minute. It looks like this:

{"protocol": "cherrypi.browser-attachment.v1", "carrier": "browser-http/1",
 "connection": "<32 hex>", "ticket": "<64 hex>", "epoch": 71,
 "session": "<the run>", "principal": "local-uid:501",
 "workspace": "<sha256 of the data folder>", "expires": …, "max_payload": 65536}
Open in browser, end to end Four steps. One: in the desktop, Open in browser asks the harness for a link, sakura_browser_link, which returns a one-time sign-in link and no credential. Two: the browser opens /login with the code in the fragment; the page drops it from the address bar and redeems it once for the sign-in cookie, then goes to /terminal/attach for that workspace and run. Three: the signed-in page posts to /api/desktop/act, kind attach; the web server checks the workspace is catalogued and asks the harness, over a one-call connection, for a credential scoped to that one run; the answer goes straight into the page's session. Four: the page speaks Sakura over POST /api/sakura with the bearer: HELLO, CHALLENGE, AUTH, which rotates the bearer, ATTACH, then next and exchange polls. The web server forwards only exchange, next and revoke to the harness's sakura_browser entry, which checks the bearer on every request. 1 · THE DESKTOP Open in browser sakura_browser_link ✓ a link, no secret 2 · /login one-time code → HttpOnly cookie code gone from the bar 3 · /terminal/attach /api/desktop/act kind attach · one run credential into memory 4 · SAKURA POST /api/sakura Bearer · every call AUTH rotates it THIS MACHINE · NOTHING FACES THE NETWORK web server · /api/sakura exchange · next · revoke, nothing else workspace by id, from the catalogue ✕ a path, a method, an actor harness · sakura_browser bearer digest checked every request one context, one run, one lease revoke ends it; the run goes on NO CREDENTIAL IN A URL, STORAGE, THE CLIPBOARD, THE CONSOLE OR A LOG
From the pane menu to a live terminal in a tab. The only thing that travels by URL is a code that works once, for two minutes.

Each request on the browser carrier looks like this:

POST /api/sakura
Authorization: Bearer <ticket until AUTH, then the bearer AUTH returned>
{"workspace": "<id>", "connection": "<32 hex>",
 "operation": "exchange" | "next" | "revoke", "frame": "<base64, exchange only>"}

200  {"frame": "<base64>" | ""}            # AUTH's answer also has "bearer"
400  a request the host will refuse again   401  no bearer
403  bearer refused, or the context expired  503  the harness can't be reached: retry
  • The ticket is spent at AUTH. The answer to AUTH carries a new bearer that only the page holds. After that, the pasted credential can do one thing: repeat that exact AUTH if its answer was lost, until the attachment moves on. A copy left in a clipboard history can't read, type or revoke anything.
  • The workspace is named by its id, a SHA-256 of its data folder, and must be in this machine's catalogue. A filesystem path, an actor or an RPC method in the body is never forwarded.
  • /api/sakura doesn't need the desktop's sign-in cookie. The bearer is its own authority, and the page sends no cookies with it. Getting a credential from /api/desktop/act does need the cookie (section 11).
  • The page polls next when it has nothing to do, backing off from 25 ms to 400 ms, and wakes at once when you type. Leaving the page revokes the attachment, with a request that outlives the tab.
  • Fit to window resizes the shared run for every viewer, so the page never resizes on its own. Joining keeps the grid the run already has.

Limits and errors

LimitValue
Frame payload64 KiB
DATA, snapshot chunk, one input16 KiB
Snapshot16 MiB
Stream window16 KiB to 1 MiB
Browser input queue64 KiB. A larger paste is refused whole.
Grid a browser may ask for500 columns × 300 rows (a run may report up to 500 rows)
Ticket60 seconds, one attempt
Idle attachment5 minutes, renewed by any call
Connection contexts per host64

An ERROR frame's payload is {"code": …, "recoverable": …}, and recoverable appears only when it is true. Without it, the context is gone, but the run is not.

CodeMeans
sakura_auth_refusedThe ticket or the proof was wrong. The ticket is spent either way.
sakura_session_scopeATTACH or RESUME named another run or token.
sakura_epoch_mismatchThe host restarted. Get a new credential.
sakura_sequence_gapA service call skipped or acknowledged the future.
sakura_snapshot_requiredRecoverable: the client has fallen behind the ring. Resume with a snapshot.
command_outcome_unknownRecoverable: resolve the same input again before anything else.
snapshot_graphics_unsupportedThe run has drawn Kitty graphics, which snapshots can't carry.
sakura_connection_limitThe host already holds 64 connection contexts.
terminal_not_persistentThe run has no pump to attach to (when opening a credential).
sakura_invalid_stateA frame the attachment's phase doesn't allow.

Reference

This section lists every picache verb and every setting in the Settings pane.

Read from cherry π's source at e67c71a8: 95 verbs, 49 of them Picache's tools, and 25 settings. picache help lists the verbs as they are now.

Every verb

Run picache help <verb> for a verb's usage and what it needs. A verb's effect class says what it does to the world, and it decides when you're asked (section 03).

VerbCalled byEffectWhat it does
acceptyourecordAccept an offer to your layer (a patch is applied here, undoable)
acceptsyourecordWhat a layer takes from a linked workspace or a paired machine, narrowed per layer
addonsyourecordThe first-party addons in this build; addons enable <id> grants what one needs and starts it
agentyourecordThe agent for a folder: which answers here, declare one, attach or detach one
appyourecord.pi apps: what is installed, install and remove, pack a dev folder, grant what one asks for
apply-layoutPicache, youirreversibleOpen a layout's panes beside a pane (yours unless --at), or as a new layer; it closes nothing
askPicache, yourecordAsk another layer a question; it is answered there, reading its files, in a thread
ask-operatoryoureadPut a form in front of the operator (A2UI JSON) and print their answer
assignPicache, yourecordAssign an issue to the agent in a pane run, and see or decide its delivery (the operator's)
bitcoinyourecordThis machine's Bitcoin nodes (regtest, signet, main): install Bitcoin Core, start, stop, status, readers
boxyourecordHow each agent is started (confined or standard, its network, its list, its login) and how far it reaches on the desk
brainPicache, youreadBrains attached to your layer: other layers or references, read-only
cancelyourecordTake back an offer from your layer that nobody has answered
captureyoureadWhat a pane shows: panes read, by Rex's name
catPicache, youreadPrint a file (bounded at 256 KiB)
cleanroomPicache, youreadRun a command or Lua script on copies of files, with no network and nothing else to reach; out/ is kept
closePicache, youirreversibleClose a pane you opened (the operator may close any)
close-layerPicache, youirreversibleClose a layer: its panes' programs end (Layer Zero stays)
cohortPicache, youirreversibleAsk a vendor agent one question over ACP, in its box, with cherry-pi's tools; the answer is marked external
connectyoureadA confined agent's proxy asks whether a connection may go out
contextyoureadExactly what Picache's model was given on its last call
credyourecordCRED as this machine reads it from the chain: the scan, markers, balances, and checking an address or a claim input
cred-mineryourecordMine cRED on this machine: dedicated coins, the grinder against the tip, claims, settling, sweeping
desktopsyourecordDesktops on paired machines: whether this workspace takes them, from which trusted networks, and who is attached
doyoureadRun a Lua script whose calls go through your token: picache.call, .split, .wait, .on …
evalyourecordMeasure an agent: its core and own cases, non-executing, with a report kept
eventsyoureadThe harness's events as they happen, one per line; --json for programs
factoryyourecordMake .pi apps from a schematic: a product layer from its skeleton, a builder's goal, and checks that pack it for review
findPicache, youreadFiles whose path contains <text> (under your folder, or a subfolder)
fleetyourecordThe fleet this machine is in: who can find it on the network
floatPicache, youarrangeFloat a pane over the desk, out of the tree, raise or lower a float, hide and show the floats, or cascade them
focusPicache, youarrangeGive a pane the keyboard: by name, or left, right, up, down, next, prev
focus-layerPicache, youarrangeBring a layer to the front: by name or place, or next, prev
goalyourecordGive a vendor agent a goal to work on in its own copy of this layer; Picache reviews it, you comment and send it back, and you accept or reject its branch
grepPicache, youreadLines containing <text> in files under your folder
helpyoureadThe verbs, what each may do, and how to call them; help <verb> describes one
historyPicache, youreadThis conversation's own messages, receipts and checkpoints, searched or read exactly, past what the context holds
identityyourecordThis node's identity and its agents' keys (identity init makes them)
inboxyoureadOffers to your layer that wait on an answer (--all for every one)
issuePicache, yourecordThe Inbox's issues: list, open, show, reply; the operator moves and tags them
jobsPicache, yourecordLong work the runtime is doing: downloads, conversions, model servers, measurements
kbPicache, youreadAn agent's knowledge: search it (the atlas of every verb and tool, the operating shelf, the docs), list its sources, or refresh it
labelPicache, youarrangeGive a pane a title
label-layerPicache, youarrangeGive a layer a name
layeryourecordExport a layer whole (history and files, signed), import one, fork one, share one live, list references
layersPicache, youreadThe workspace's layers, their folders, and the offers waiting in each
layoutPicache, youreadThe layer in front as a tree: how it splits, each split's share, each pane; --json as a layout, --saved the saved ones
linkyourecordLink another workspace (its folder): it may offer this one what --accept names
linksyoureadThe workspaces linked to this one, and what each may offer it
loopyourecordA goal pursued over several turns in this folder, each checked by the layer's test command, until it passes or the steps run out
lsPicache, youreadList a folder (default: your pane's folder; --layer <name> for another layer's)
machinesyourecordThis machine's fleet: paired machines, requests to pair, the directory; machines name names one
mcpyoureadThis pane's verbs as an MCP server on stdio, for the agent the pane runs
messagePicache, yourecordWrite to another agent's pane, as SendMessage: it gets it at its next turn or as its turn ends; read yours, see whom you can write to
modelsyoureadThe models this machine has, and which one Picache uses
movePicache, youarrangeDock a pane beside another: its old place closes up
move-layerPicache, youarrangePut a layer at a place in the rail, from 2 (Layer Zero stays first)
new-layerPicache, youirreversibleOpen a new layer on a folder (the caller's unless named), in front unless --focus=false
offeryourecordOffer another layer a message or a change; it is applied only if accepted there
outboxyoureadOffers from your layer that nobody has answered yet (--all for every one)
pairyourecordPair with a machine, or answer one that asks (pair approve [<request>] <code>, pair decline <request>, pair decline-all <node>)
panesPicache, youreadThe panes on the desk, or panes read <pane> for what a terminal shows
patchPicache, youreversibleReplace text in a file (once, or exact with --all); whole lines match despite spacing or quotes; the old bytes are kept for undo
profileyourecordThis machine's profile -- name, handle, bio, status, timezone, avatar, banner -- which paired machines see
reachyourecordReach an address another machine serves: a loopback port here that connects to it
rejectyourecordReject an offer to your layer
replyPicache, yourecordReply in an issue's thread (markdown; the text, or stdin)
resizePicache, youarrangeMake a pane wider, narrower, taller or shorter by cells, or --to a size: it moves the divider beside it
rex-layoutPicache, youreadInspect the configured local Rex session's window and pane layout
rex-readPicache, youreadRead recent terminal text from a pane in the configured Rex session
rex-splitPicache, youirreversibleOpen a Rex pane and run an approved local shell command
runPicache, youirreversibleRun a shell command, after the shell policy has classified it
runsyoureadWhat ran in this workspace, newest first (an agent, or a folder's own: what ran in its folder)
save-layoutPicache, youreversibleSave the layer in front as a layout in the folder (.cherrypi/layouts/<name>.json)
scriptPicache, youreadCode mode: a Lua script in a capsule whose only way out is the verbs, each one decided and receipted
searchPicache, youreadThe workspace's search: conversations, runs, receipts and files
sendPicache, youirreversiblePaste text into a terminal pane and press Enter (a shell runs it); --wait answers when the turn it starts has settled
send-keyPicache, youirreversiblePress keys in a terminal pane: enter, tab, esc, up, ctrl+c …
serveyourecordServe a port, or an installed app from its own capsule, at an address that is a key: to paired machines, approved ones, or anyone holding it
sharesyourecordWhat this machine shares with its paired machines, and what they share with it
skillPicache, youreadTeach Claude Code, Codex, OpenCode and Pi cherry-pi: show the guide, install the skill and hook
snapshotsyoureadYour layer's folder as each turn found and left it: list, what changed, a file as it was
splitPicache, youirreversibleOpen a terminal pane beside yours, or as a float over the desk, running a command or a shell
statusPicache, youreadWho you are to the harness, your folder, and Picache's model
surfaceyouarrangeArrange a temporary team surface; dismissing never stops its agents
surfacesyoureadList lightweight pane arrangements in the caller's project
swapPicache, youarrangeTwo panes trade places; the tree keeps its shape
taskyourecordGive another layer a task; it runs there once accepted, and its report comes back to you
undoPicache, youreversiblePut back what a write or patch replaced, if the file is still as it left it
unlinkyourecordWithdraw this workspace's link to another: it may no longer offer this one anything
waitPicache, youreadWait for a pane's program to end (its exit status), for text to appear, or for its state
webPicache, yourecordCherrytop, the browser desktop: what is open where; propose opening or showing something
witnessesyourecordPaired machines that witness this machine's ledgers, one log at a time, and the logs this one witnesses
writePicache, youreversibleWrite a file from stdin or a text argument; the old bytes are kept for undo
zoomPicache, youarrangeFill the window with a pane, or restore it: --on, --off, or toggle

The Settings pane

Ctrl+Alt+Q opens Settings, a surface of its own, where you left it. Its Settings pane has seven pages, drawn as cards, and each card says whether it's for this workspace, this machine or you. A choice saves at once. A choice for this workspace is also recorded as a receipt.

PageHolds
WebWhether https://cherry.pi works, setting it up, and the certificate authority
Terminalsterminal.sudo.*
AgentsRecovery, goals, Picache and routing, the Inbox, and new agents
Sharingclients.remote, and Machines
DesktopYours alone: theme, images, notifications, sounds, hints, layouts
SearchThe search cache, its size, and what search leaves out
WorkspaceThe data folder, layer snapshots, and the rest

Each setting in the next table has a fixed set of choices. Others are described where they apply, such as context.* and loop.test_command.

SettingChoices (default in bold)What it sets
terminal.sudo.shellson, offYour shells can sudo
terminal.sudo.agentsoff, onAgents' panes can sudo
terminal.sudo.forgeton, offForget your sudo password before an agent types
terminal.sudo.unboxed_agentspassword, refuseAn agent outside its box, at sudo (macOS only)
clients.remoteoff, view, edit, fullWhat a paired machine's desktop may do here
recovery.modesupervised, manual, automaticWork interrupted by a crash
goal.workspacecopy, worktreeWhere an agent works on a goal
goal.commandson, offA goal's agent may ask to run commands
picache.patch.matchforgiving, exactHow Picache's edits find their text
picache.approval.unansweredpark, refuseWhen nobody answers Picache's question
picache.model.retryonce, offWhen Picache's model fails to answer
route.stickyon, offKeep a turn on one model
route.egressoff, ask, onA router may send a request off this machine
route.qualifyrequired, warnA router's model must have qualified
picache.run.time10m, 1h, 4h, offHow long Picache's command may run
picache.run.memory4g, 16g, offHow much memory Picache's command may use (Linux)
picache.budget.turnoff, 50k, 200k, 1mTokens one turn may use
picache.budget.cloudoff, 100k, 1m, 10mTokens the cloud seat may use in a day
evidence.on_failureclosed, openWhen a turn's evidence can't be recorded
apps.dev_changeshold, followWhen a dev folder app's files change
inbox.deliverynext_turn, when_idle, checkWhen an agent gets an issue assigned to it
inbox.messageslayer, workspace, offWhom an agent may write to
inbox.message_noteson, offTell an agent about the panes it opened
inbox.message_wakeidle, offWake an idle agent for a message
layer.snapshotson, offSnapshot a layer at each turn

When a call is refused

A refused picache call prints a code on standard error, usually followed by a sentence, and the refusal is recorded as a receipt.

picache <verb>: <code>: <what happened>      # exit code 1
picache: cannot reach the harness at <socket>   # exit code 2

With --json, the refusal goes to standard output as {"ok":false,"error":…}. In a Chat, Picache sees refused: <code>. Failures of the desktop's own calls go to the Errors pane.

CodeMeansWhat to do
needs_confirmationYour own call needs a yes: a write without the drive.write grant, or a command the shell policy calls destructive.Answer Go ahead? in the terminal, or pass --yes.
needs_operatorPicache or an agent needs your yes for this one call.Answer in the client. A yes covers that call only.
declined, deniedYou said no.Ask again if you meant yes.
… nobody could be askedNo desktop on this machine could ask you, so the call failed closed.Keep a desktop attached on this machine.
waiting_approvalNobody has answered yet, and the question is kept.Approve or Decline it in Recovery (When something needs you).
path_escapeThe path, or where a link in it points, is outside the folder. Files refuses any symbolic link in the workspace.Use a path inside the folder. No grant fixes it.
bad_pathThe path has a .. in it.Name the file without ...
unknown_verbThere's no such verb.picache help lists them (Every verb).
cohort_proposesAn agent tried to write without a goal.Give it a goal, and its work comes back for review (Give an agent a goal).
goal_todos_openA goal still has todos open, so it can't be accepted yet.Finish them, or mark yours done (Give an agent a goal).
goal_no_remoteThe layer's repository has no remote to push the goal's branch to.Add one with git remote add, or offer the branch to a paired machine.
confine_agent_unmeasuredThis agent hasn't been measured under this machine's agent box, so it isn't started in one.Use a measured agent, or start it unboxed, as you: picache box <agent> launch standard.
conduit_branch_unrelated, conduit_branch_existsAn offered branch doesn't start from a commit this layer holds, or a branch of its name is here at another commit.Fetch the base first, or rename the branch that's here.
cohort_ceilingAn agent tried something irreversible. The most an agent may do is read and propose.Do it yourself. No grant changes it.
operator_decidesOnly you can do this. The caller may propose it.Do it yourself.
budget_spentA pane's caller used its share (200 writes, 50 runs, 100 offers, 500 arranges), or a script its budget.If it is a script of yours, give it a larger --budget.
focus_neverThe agent's box says it never moves your keyboard.Run picache box <agent> focus ask.
hook_blockedAn installed app's hook stopped the call.The message names the app and its reason.
host_cannot_confine, confine_unavailableThis machine has no Seatbelt, bubblewrap or Landlock to confine a capsule.For Run one, pair a machine that hosts capsules and send it there (section 09).
… is not on this agent's listA boxed agent tried to reach a host its list doesn't name.Allow it when asked, or picache box <agent> allow <host> (Network access).
sudo fails in an agent's paneAgents' panes can't sudo by default.Change the terminal.sudo.* settings (Use sudo).
effect_uncertainA crash left a change in doubt, and cherry π won't repeat it blind.Settle it in Recovery: Mark Applied, Mark Not Applied, Retry Safely or Stop (Settle an effect in doubt).
remote_offThis workspace takes no desktops from other machines.Change the clients.remote setting.
remote_not_grantedThat machine has no grant here.On this machine, run picache machines desktop <machine> edit.
remote_not_allowedThe machine's grant doesn't give what the call needs.Give it a wider grant (What a remote desktop may do).
layer_live_read_onlyThat conversation is another member's, in a live layer.Continue it on their machine.
agent_bundle_unsigned, agent_bundle_untrustedAn agent without a signature, or signed by a key this machine doesn't trust.An unsigned agent never installs. With --untrusted, an agent from an unknown signer installs.
refusing nonempty unowned state root, state root must not be a symbolic linkThe data folder has other files in it, or is a symbolic link.Pick an empty folder, or one cherry π made (The data folder).
no harness to talk topicache ran outside a cherry π pane.Run it in a pane.

Words used here

WordMeans
BakeryThe public rooms on rednet, joined by name, like IRC.
boxWhere a vendor agent runs by default. It sets what the agent may read, write and reach.
capsuleAnything confined, such as an app's handler, a script or a clean room. Each starts from a named profile, through one door in the harness.
clean roomA command or Lua script run on copies of files, with no network.
cohortA vendor agent asked from a Chat, as with @claude. Its own write and run tools are off.
ConduitHow cherry π talks layer to layer, workspace to workspace and machine to machine.
DriveA machine's own peer-to-peer file store, with no cloud and no account. Large shares and its profile travel from it as hashed chunks.
effect classWhat a verb does to the world: read, record, reversible, arrange or irreversible. It decides when you're asked.
evidence chainA hash chain of entries. Each turn adds one that binds every receipt it wrote.
fleetThe machines that can find this one. Only machines with its secret can.
goalWork a vendor agent does on its own, in a copy of your layer. Nothing lands until you accept it.
grantA capability you give. It stays in the workspace that gave it.
harnessThe process, one per workspace, that decides what's allowed and keeps every record: cherrypi.ape.
in doubtA change written down before it ran, whose outcome a crash hid.
layerA project folder with its own Picache conversation, panes and store. Layer Zero is the scratchpad.
offerA message, patch or file for another layer. Nothing changes there until you accept it.
Picache, picachePicache is the agent you talk to in a Chat. picache is the command it, you and agents call the harness with.
pumpThe small process that holds one terminal, so it outlives the desktop and the harness.
receiptThe record of one governed call, refusals included. A change and its receipt are saved together.
rednetThe public peer-to-peer network. Machines running cherry π find each other on a subnet of it.
seatA place in Models for one model: Picache's, Small, Search, or Cloud.
signerThe sidecar that holds this machine's and its agents' keys.