PI DURABLE · PART 2

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.

TypeScript, no buildPreact + htmSQLite through Pi DurableNode.js 22.19+v0.11.0

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.

ONE COMMIT doc: pi.live doc: pi.usage submission u:ana:7 changes come in any order PASS 1 · USAGE FIRST spend.usageChanged charge the person Pi worked for before this commit PASS 2 · EACH CHANGE #documentCommitted busy runs, agents, session list, room docs attribution.submissionCommitted record the author (in setImmediate) THEN · ROOMS, TILES, LIST batched and sent to tabs (right)

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.

90 msa room's changes
400 msthe session list, when it changes
1 speek tiles
commitupdate sent to tabs (the number shows the commits in the update)tall: the commit changes the session list
A streamed answer makes a commit approximately every 100 ms. Tabs get fewer updates than there are commits.

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.

Pi works for

    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.

    commit, then show

    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.

    request ids

    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.

    memos

    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.

    replay: "safe"

    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 tasks

    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 policies

    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

        $ pi-pocket running · pid 48211

        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.

        Parent session · 8 replies
        copied contentnew content in the forkempty or removed

        Outside the database, in ~/.pi-pocket/

        config.jsonpeople, roles, hashed tokens, settings
        push.jsonVAPID keys, push subscriptions
        uploads/files sent to Pi
        worktrees/sessions' git worktrees
        extensions/extensions that the owner adds
        browser/the profile of the built-in browser

        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.

        PARENT CONVERSATION pi.user "divide the work" tool: subagent spawn not replay-safe · returns immediately pi.assistant … idle follow-up input the subagent answer, one time ANCHOR TASK pocket.subagent-anchor background · ends at once REPORTER TASK pocket.subagent-reporter deliver → report CHILD CONVERSATION a person can open it and send messages pi.user (task) pi.generation · tools… pi.assistant (answer) creates owns delivers waits reports

        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.