Pi Pocket
Pi Pocket is a web app for Pi agents. It uses Pi Durable to run the agent and adds multiple users, phone access, and remote access.
The server is one Node.js process with one SQLite file. The web app has no build step. Pi Durable commits each step before a tab shows it. Pi Pocket sends these commits to all tabs, so many people can watch and control one session from a phone or a computer.
Click a layer on the right to see its parts.
Dependencies
Feature map
The left column shows the Pi Durable primitives. The right column shows the Pi Pocket features that use them. Select an item in a column to see the related items.
Pi Durable primitive
Pi Pocket feature
Request path
Path of a message
A person sends a message from a phone. Pi answers, and all open tabs show the answer. The six steps below occur in this sequence.
app.ts · #committed
Commit listener
Pi Durable sends the changes of each commit in no fixed order. Pi Pocket uses one listener with two passes to process them. Thus, the order in which listeners register has no effect on spend.
All of these steps are synchronous. A commit listener must not call
the session APIs of the harness. Thus, a write that the listener
causes (the record of an author) runs later, in
setImmediate. With separate listeners, their
registration order would change who pays, and nobody could see this
rule.
attribution.ts · requests.ts
Attribution of work
The request id of a message to Pi can contain the ID of a person. Pi
works for the person of the newest message that has a person in its id.
Spend, approvals, schedules, and subagents all use
requesterOf to find this person. Click an action to see the
result.
u:<user>:<key> is a message that a person wrote.
p:<user>:<key> is a message sent for a person, for
example a scheduled message that Pi set up, or the task of a subagent. A
schedule that a person set up sends a u: message for that
person. If no person is known, Pi Pocket charges the person who started the
session. If that person is not known, it charges the owner.
Durability
Crash recovery rules
Pi Durable supplies tasks, checkpoints, and atomic commits. Pi Pocket uses them for the rules below. Thus, a restart loses no stored work. Pi does not get a message two times. A restart loses only the data in memory. The right column of the table below shows this data.
Show only stored data
Tabs show only committed state. Pi Pocket keeps its documents in the same storage as the transcript. Each change is an atomic commit. Changes that must agree are in one commit, for example a fork and its documents. Pi Pocket records the author of a message in a second commit. At startup, it repairs the authors that a crash did not record.
Send one time
Each message to Pi has a request id. If Pi Pocket sends a message
again with the same id, Pi Durable finds the stored message. Thus Pi
gets the message only one time. Scheduled messages and subagent
reports use the request id after a restart. A ! command
keeps its id until the server accepts it. Pi Pocket stores this id
in pocket.shell-requests. Thus a second try after a
lost reply runs the command only one time.
Ask one time
A hook keeps its result in the memo of its task. Examples are the answer to an approval and the result of a goal check. After a restart, the hook runs again and reads the memo. It does not ask again.
Run again only if safe
Artifact calls and schedule calls are replay-safe. Other calls are not replay-safe. Examples are bash, browser, and codemode calls. After a crash, the model gets a message that such a call stopped. Then the model examines the result.
Durable timers
A scheduled message is a task. Its send time is in the
pocket.schedules document. If a restart occurs before
that time, the task sleeps again. If a restart occurs after that
time, Pi Pocket sends the message immediately, one time.
Fork policy per document
Each document kind tells if a fork copies it or starts it empty. In the commit that makes the fork, Pi Pocket empties the copied chat. It also removes the copied pins after the fork point, and the copied authors of later messages.
Server restart
Amber items are in pocket.sqlite. Teal items are
only in the memory of the process.
Stored · read again after the restart
In memory · lost at restart
docs.ts
Data storage
Pi Pocket documents are in pocket.sqlite, together with the
transcript and the Pi Durable documents: the agent settings, the
provider ID, the inbox, the live run, and usage. Each document kind
tells what a fork gets. Click "Fork at reply 5" to see the result.
Outside the database, in ~/.pi-pocket/
Pi Pocket writes the config files atomically, with mode 0600. Do not rename a document kind. Pi Durable compares the scope, history, and fork settings of each kind with the stored data.
src/server/extensions/
Extensions
Pi Pocket adds most of its agent features as Pi Durable extensions. The server installs them in the sequence below. Each extension uses one or more of four parts: prompt sections, tools, hooks, and tasks.
Subagent structure
This structure comes from Pi Durable example 23. Each subagent is a separate conversation. A background anchor task owns it, so Esc in the parent does not stop it. A reporter task sends each answer back to the parent.
Anchors and reporters are durable tasks. After a restart, request ids prevent a reporter from sending a message or a report two times.
Self-editing
Live reload and restart
Pi often edits Pi Pocket while Pi Pocket runs. The result of an edit depends on the file that changed. Select a file to see the result.