diff --git a/packages/chord/docs/next/p3-features/book.md b/packages/chord/docs/next/p3-features/book.md new file mode 100644 index 000000000..4d45c3c26 --- /dev/null +++ b/packages/chord/docs/next/p3-features/book.md @@ -0,0 +1,329 @@ +--- +title: p3: Feature Set and Work Packages (prep for the design meeting) +cover: p3 / Features +kicker: Meeting prep · p3 on Chord v2 · 2026-11-03 +subtitle: What p2 does today, what p3 should ship first, how hands, sync and the plugin API fit, and a first cut into work packages +stats: + - 28 | feature areas in p2 today + - 21 | model-facing tools in p2 + - 78 | coding-agent extension examples checked against the p3 plugin API + - 12 | proposed work packages +note: | + Written by the redesign session for the meeting on 2026-11-03, from three research passes over p2 + (`apps/p2` at `b7af55ace`), the coding agent's extension examples and p2's environments and Radius code + (`/tmp/p3-prep/features.md`, `extensions.md`, `envs.md`), the owner's notes and Armin's summary of the 2026-11-02 + meeting. Chord v2's slices 1-13 are built (the app kit included); slice 11 (web and TUI) is building. Everything + marked "proposed" is for the meeting to decide. +colophon: p3 Feature Set and Work Packages · meeting prep · 2026-11-03 +--- + +# Before we begin {.preface} + +p2 grew feature by feature on the phone: 33 plugins, about 65,000 lines, a web shell and a TUI. p3 is its rewrite on +Chord v2, with an app kit that already gives hosts, workers, HTTP with sessions and pairing, plugins with versioned +APIs, files and export. The meeting's job is to decide what p3 ships first, what waits, and how the work splits. + +Some of it was settled yesterday (Armin's summary): sessions sync to R2 through Radius by commit log, the session index +is a replicated Chord table, every host kind loads plugins through registries, hands connect proactively. The owner +added: voice waits; environments turn around into hands; continuing a local session on Radius is core; subagents and +handoff are in, artifacts are questionable; the plugin API stays small enough to evolve. + +## Ten things to know + +:::findings +1. **Proposed base:** sessions (with delete), the agent and conversations, subagents, history and handoff, code mode, + shell commands, models and login, prompt resources, workspace (read and review), file index, status and notices, + search through the index, the web shell and the TUI, hands, session sync and continue-on-Radius. +2. **Proposed later:** voice, terminals (need channels and hands), artifacts, port forwards, chat mode and guest + sharing, MCP and `tool_search`. +3. **Hands replace SSH-dialled daemons:** a long-lived daemon dials the server (or Radius), is enrolled once by a + pairing code, and carries p2's existing daemon protocol over one multiplexed WebSocket. +4. **The sync handshake needs a driver identity.** Two machines writing one session both push "from Radius's last + seq" and silently diverge; an epoch or lease per session (Chord's rule S7) closes it. +5. **The index has two writers** once a session continues on Radius; three options, one recommended (derive rows from + session stores). +6. **Tools in base:** `read`, `write`, `edit`, `bash`, `codemode`, `subagent`, `team`, `agent`, `conversations`, + `history_*`, location tools, `rename_session`, `send_file`; artifacts, ports and `reload_plugins` wait or change. +7. **One CLI, no environment variables:** `p3 serve`, `p3 tui`, `p3 hand`, `p3 owner-link`, `p3 install`; settings as + arguments and files, per the app kit. +8. **The plugin API v1 is seven clusters,** almost all already existing: Durable's extension, a `Conversation` + contract, input transforms, environments and models, questions, UI registries, objects for state. +9. **Litmus test:** all 78 coding-agent extension examples are expressible; Doom needs two small fixes (frame splitting + in the TUI bridge, a path for large plugin assets); streaming frames from a worker and raw terminal ownership do + not fit, by design. +10. **Twelve work packages,** with p3's skeleton on the app kit and the session/index/sync core first. +::: + +## Words used throughout + +A **hand** is a daemon on a machine that connects out to a p3 server or to Radius and offers execution (commands, +files, watching, terminals). A **driver** is the single host allowed to write a session's store at a time; a +**replica** follows its commits. An **epoch** numbers driver changes. The **index** is the table of session metadata +(title, status, idle state) the session list reads. The **app kit** is Chord's `startApp`/`startWorker` layer. + +# p2 Today, and the Proposed Cut {#features lede="Every feature area, what it is, and where it goes in p3."} + +Sizes are non-test lines in p2. "Base" means p3's first release; "later" means designed for, built after; "drop" means +not carried over. + +| Area | What it does | p2 size | Proposed | +| --- | --- | ---: | --- | +| Sessions | list by machine and directory, create, rename, pin, archive, relocate, clone, fork, unread, automatic titles | 2,186 | **base**, plus delete; clone and fork in the web too | +| Conversations and agent | transcript, send, steer, follow-up, outbox, stop, model and thinking, cost, compaction | 3,682 + web 2,700 | **base** | +| Subagents, one-shot agents, cross-session messages | named background agents, `team`, `agent` from scripts, `conversations` | 2,182 | **base** (owner: "awesome") | +| History and handoff | re-read earlier entries after compaction, hand off with a continuation | 533 | **base** | +| Code mode | scripts calling tools as nested durable calls | 155 + Durable | **base** (small, Durable owns it) | +| Shell commands | `!cmd` and `!!cmd` in the conversation's environment | in chat | **base**, web too (TUI only today) | +| Chat mode | people talk without a turn, `@pi` mentions | 876 | later | +| Sharing and guests | share links, guest names, revocation | 1,047 | later (the app kit's users and invites cover the start) | +| Devices and owner login | owner links, device cookies | 684 | **base**, replaced by the app kit's sessions and pairing | +| Models, credentials, provider login | picker, scope, API keys, OAuth flows | 3,828 | **base** | +| Settings | `settings.json`, hot reload | 87 | **base** | +| Prompt resources | AGENTS.md, skills, templates, read in the session's environment | 958 | **base** | +| Workspace | files, git status, diffs, search, review comments | 2,655 + web 1,341 | **base** (read and review; uploads later) | +| File index and `@` | ranked file completion | 242 | **base** | +| Terminals | detached PTYs, ghostty-vt in the web | 3,518 | later (channels, hands) | +| Environments and locations | SSH environments, directory picker, agents moving themselves | 2,546 | **base, redesigned as hands** | +| Artifacts and files | attachments, `send_file`, versioned artifacts with previews | 1,713 | attachments and `send_file` base (app kit `Files`); artifacts later | +| Search | sidebar search over titles and messages (FTS5) | 775 | **base** over the index; message text search through the same sync mechanism later | +| Status and presence | busy, tasks, usage, viewers, worker failures | 566 | **base** (feeds the index) | +| Logs and notices | owner log viewer, notices | 487 | **base** | +| Voice | rooms, barge-in, speech service | 3,770 + Python | later (owner) | +| Radius relay and port forwards | brain WebSocket, up to 50 forwards | 2,000 | relay **base** (as the hands/sync link), forwards later | +| Web shell | PWA, sheets, navigation, reconnect | 9,252 | **base**, rebuilt on slice 11 ("look and feel great") | +| TUI | launcher, sandbox, 40 commands, themes, keybindings | 6,720 + launcher | **base** on slice 11 | +| Dev and reload | supervisor, dirty marks, reload tool | 468 + scripts | **base** as the app kit's "apply changes"; the supervisor goes | +| Fake provider, `PI_MOBILE_*` aliases, `P2_DEV_RESUME` | test and legacy leftovers | 218 | drop | +| `tool_search`, MCP | not installed in p2 | - | later | + +Rough edges p3 should not inherit: features only in the TUI (shell commands, clone, fork, compact, new context), no +session delete, terminals and voice without a TUI side, docs that drifted from code (voice routes, TUI notes), the +plugin loader's module leak on reload (solved by Chord's plugin component). + +# Hands {#hands lede="Environments turned around: the machine connects to the server, not the other way."} + +## What p2 does + +p2's server and workers start a daemon (`pi-env`) over SSH on demand, one connection per session and per use (four +connection pools: each worker, the directory picker, the connection test, the terminal host). The daemon is +stateless and kills its commands 30 seconds after its connection goes quiet. Its frame protocol (exec, files, watch, +file listing, PTYs) does not depend on SSH. + +## What a hand is (proposed) + +| | p2's daemon | hand | +| --- | --- | --- | +| who dials | the server, over SSH | the hand, over WSS, to the server or to Radius | +| needs | sshd, a key, a network path to the machine | outbound HTTPS only | +| lifetime | per connection | a service (systemd, launchd, Termux:Boot), reconnecting | +| install | deployed and verified at each start | installed once, signed updates | +| machine identity | SSH host key | a hand key pair, enrolled once | +| connections | one per session and use | one per hand and server, multiplexed | + +- **Enrollment:** `p3 hand enroll ` shows a code; the owner approves it in the UI (the app kit's device + pairing); the server pins the hand's public key; fingerprints shown on both sides. Revocation in a Hands panel. +- **Reachability:** a hand reaches the owner's own server directly (LAN, own tunnel) or through Radius, which relays + hands to whichever host drives a session (the upsell: no tunnel to set up; Radius can also offer sandboxed machines + and hands of its own). +- **Protocol:** p2's daemon protocol over the WebSocket, unchanged in shape; two changes: several sessions share one + connection (cancellation, handles and backpressure per session), and a disconnect starts a grace period instead of + killing everything (detached commands and PTYs reattach on reconnect; this moves the terminal host's job into the + hand). +- **Binding:** a session's location becomes `{ hand, cwd }`; one server object holds the hand connections and the + `HarnessEnvironments` entry resolves a hand id to an execution environment; workers reach hands through the server's + links. An SSH machine stays possible as a hand kind the server dials. +- **Offline:** an operation needing an offline hand fails with a clear error the model sees; the hand's status shows + in the session list. + +# Session Sync and the Index {#sync lede="What was decided yesterday, and the three gaps it leaves."} + +## Decided + +The worker updates the session index from events and sends it to the server; sessions sync to R2 through Radius: +Radius reports per session its last sequence number, pi uploads the commit log from there; R2 holds the SQLite file +and the commit log, a SQLite Durable Object applies them; sessions have an owner id; the index is a Chord table +replicated like a session, its row updated only on metadata, session idle and subagent idle changes. + +Chord v2 has the pieces: a versioned outbox written in each commit's transaction, export archives, batch apply that +refuses gaps and divergence, export and import per address (app kit). Missing (the sync layer): upload and trim, +epochs, pull-back after a power loss, the split, starting from an archive. + +## Gap 1: who is the driver + +Two machines both write session X (the laptop resumed it while Radius continued it). Both ask Radius for its last +seq, both upload from there; Radius applies whichever arrives first and refuses the other as "not following". The +copies have now diverged and nobody knows. + +**Proposed:** a driver epoch per session, handed out by Radius. An upload names its epoch; an old epoch is refused; +the refused copy becomes a new session (Chord's rule S7: a split, never a rollback, because tools already ran). +"Continue on Radius" is an epoch change: the local driver waits for idle, uploads the tail and the session's files, +Radius bumps the epoch and starts the agent from its store, the local side becomes a replica or closes. Back is the +reverse. + +## Gap 2: two writers of the index + +When a session continues on Radius, Radius writes its index row, and so does the local server. + +| Option | How | Cost | +| --- | --- | --- | +| a. one index per writer | each host keeps its own index, Radius merges per owner, newest epoch wins per row | a second merge rule | +| b. rows derived from session stores | every host computes a session's row from the session's own commits | no index sync at all; each host needs the session's commits | +| c. rows tagged `{ epoch, seq }` | Radius keeps the highest per row | rows must carry their source position | + +**Recommended:** (b) where the session's commits are present (Radius has them all), with (c)'s tag on the rows a local +server writes for sessions it drives, so a list without the session store still sorts correctly. + +## Gap 3: what else moves + +Files outside the store (uploads, sent files, the app kit's `Files`) must sync as blobs before the batches naming +them, R2 as their home. Model access and secrets for a session continued on Radius: Radius's own models, or the +owner's provider keys stored on Radius (decide). Local prompt resources (the device's `AGENTS.md`, skills) must be read +through a hand or synced. Closed sessions have no worker: the server reads a session's outbox without starting it, or +starts it to sync. + +# Tools Built In {#tools lede="p2's 21 model-facing tools, and which ship with p3."} + +| Tool | p2 plugin | Proposed | +| --- | --- | --- | +| `read`, `write`, `edit`, `bash` | coding-tools (Durable) | base | +| `powershell` | powershell | base (deferred tool, Windows hands) | +| `codemode` | codemode | base | +| `agent` (from scripts) | agent | base | +| `subagent`, `team` | subagents | base | +| `conversations` | conversations | base | +| `history_search`, `history_read`, `history_handoff` | history | base | +| `location_envs`, `location_dirs`, `location_move` | locations | base, renamed around hands (`hands`, `hand_dirs`, `move`) | +| `rename_session` | sessions | base | +| `send_file` | send-file | base (on the app kit's `Files`) | +| `artifact` | artifacts | later (owner: maybe not as useful) | +| `ports` | ports | later (with forwards) | +| `reload_plugins` | dev-tools | base as an "apply changes" tool for the owner, not tied to a checkout | +| `tool_search` | Durable | later (with MCP) | + +Every tool is a plugin in the app's own root, so any of them can be removed, replaced or added without touching base +code (the app kit's rule: base code is the API, bootstrap and a start-data facet). + +# Running p3 {#running lede="Starting the server, the CLI, themes and keybindings."} + +## The CLI (proposed) + +One binary, `p3`, and no environment variables (app kit decision); settings are arguments or files in the data +directory. + +| Command | Does | +| --- | --- | +| `p3 serve [--data-dir ] [--host ] [--port ]` | starts the server (`startApp`); prints the one-time owner link | +| `p3 owner-link` | prints a new owner link for a running server (through the owner token file) | +| `p3 tui [url] [--pair]` | the sandboxed TUI; on the same machine it uses the owner token file, elsewhere device pairing | +| `p3 hand enroll `, `p3 hand run` | enrolls this machine as a hand, runs it (or installs a service) | +| `p3 radius login` | signs the server into Radius (device code) | +| `p3 install ` | installs a plugin into the user's root | +| `p3 export `, `p3 import ` | the app kit's export per address | + +The data directory defaults to `~/.p3`. Its files: `settings.json`, `models.json`, `auth.json` (locked), +`AGENTS.md`, `skills/`, `prompts/`, `plugins/` (the user's root), the server store, a directory per session worker, +`owner-token`, `logs/`. Plugin-specific configuration moves into plugin objects (documents) instead of more files. + +## Themes and keybindings + +Themes follow the looks-and-themes proposal (decided 2026-11-02): design tokens as CSS variables on the web, roles in +the TUI, one `Themes` registry plugins contribute to, one dark and one light choice per user. Keybindings: commands are +registry entries with default keys (p2's `TuiCommands` with `when` conditions, 78 binding ids); the user's overrides +live in a preferences document (per user, synced like any data), not in files on each client; the web gets the same +command registry with its own key handling. + +# The Plugin API {#api lede="The smallest surface that still expresses everything the coding agent's extensions do."} + +## The rule + +Offer few things, each stable: what an API offers, it must keep (versioned in the import path, `@p3/app/v1`). Every +cluster below is a registry or a contract that already exists in Chord, Durable or p2; p3 v1 publishes those and +nothing else. Legacy concepts of the old extension API are not carried: CLI flags, an event bus, RPC-mode UI, `hasUI` +checks, "destructive switch" gates, `pi.exec` running locally regardless of the environment. + +## Seven clusters + +| Cluster | Host | Mechanism | Covers | +| --- | --- | --- | --- | +| A. The agent's turn | worker | Durable's `Extension` in `HarnessExtensions`: tools, `beforeTool`/`afterTool`, `beforeRequest`, sections, wraps, `onYield`, `afterRun`, `beforeCompact` | 20 examples: hello, permission-gate, protected-paths, tool-override, plan-mode, todo, custom-compaction, ... | +| B. Conversation control | worker object | a `Conversation` contract: `submit`, `configure`, `append` (typed entries), `compact`, `fork`, `rename`; state: busy, agent, usage | send-user-message, file-trigger, session-name, preset, handoff, qna, ... | +| C. Input | worker | `InputTransforms` registry (rewrite or answer input before the model) | input-transform, inline-bash | +| D. Environments and models | worker, server | `HarnessEnvironments` (ssh, gondolin, hands as kinds), `HarnessModels` (providers, virtual models), `LoginProviders` | ssh, gondolin, custom providers, jev-router | +| E. Asking the user | server object, UI renderers | `Questions.ask` from worker code; `QuestionKinds` per UI kind | permission-gate, question, questionnaire, timed-confirm, project-trust | +| F. UI | tui, web | registries: `Commands` (slash, palette, keys), `ToolRenderers`, `EntryRenderers`, `Slots` (header, footer, status, widgets), overlays through the UI context, editor extension points | every UI-only example, the games | +| G. State and standing behaviour | any | `local` objects owning natives, documents for state | file-trigger's watcher, gondolin's VM, game saves | + +```ts +// a plugin with a tool, a gate and a renderer: plugins/todo/ +// worker.ts +self.contribute(HarnessExtensions, { name: "todo", tools: [todoTool], hooks: [hook(ToolTask, { beforeTool: guard })] }); +// tui.ts +self.contribute(TuiToolRenderers, { todo: { call: (args) => todoCall(args), result: (r) => todoList(r.details) } }); +self.contribute(TuiCommands, { "todo.show": { slash: { name: "todos" }, run: (ui) => ui.overlay("todos", new TodoView(ui)) } }); +// web.tsx: the same registries with React components +``` + +## The litmus test + +All 78 examples map onto the seven clusters (table in `/tmp/p3-prep/extensions.md`). The examples that mix agent and +UI split into a worker facet and a tui/web facet sharing a contract file: a tool that wants a dialog asks the +`Questions` object, which works from any client, several clients or none. + +- **Snake, Space Invaders, tic-tac-toe:** a command plus an overlay in `tui.ts` and a canvas in `web.tsx`, engine in a + shared file, saves in a user-keyed document. Fit today. +- **Doom:** fits with two fixes in the TUI: the sandbox bridge caps a frame at 64 KiB and a Doom frame is about 160 KB + (split writes at escape boundaries), and the sandbox has no files or network, so large plugin assets (a 4 MB WAD) + need a documented "asset of my plugin" path (chunked reads through the plugin's server facet). +- **Does not fit, by design:** streaming 35 FPS frames from a worker to clients (objects carry state; needs channels), + engine state surviving an in-place reload (needs an explicit save), raw terminal ownership (`interactive-shell` + becomes a terminal view; terminal notifications need a launcher action). + +## What v1 leaves out + +Editor replacement (extension points only), raw provider stream events and response headers, a fork hook, a +`turn_start` hook, an event bus. Each has one or two examples; add when a real plugin needs it. + +# Work Packages {#packages lede="A first cut, in dependency order, for the meeting to rearrange."} + +| # | Package | Contains | Depends on | +| --- | --- | --- | --- | +| 1 | p3 skeleton | `startApp` with p3's API v1 modules (shared, web, tui, worker), core plugins in the app's root, `p3` CLI | Chord slices 11-13 | +| 2 | Sessions and conversations | Durable on the Chord store, session lifecycle with delete, the conversation contract, subagents, history and handoff, code mode, shell commands | 1 | +| 3 | Session index | the index table, worker-to-server updates on metadata and idle changes, the session list, search over it | 2 | +| 4 | Session sync to Radius | outbox upload and trim, epochs, pull-back, split, files as blobs, continue on Radius and back | 2, 3, Radius side | +| 5 | Radius side | per-session SQLite DO, commit log and SQLite on R2, epochs, the sync handshake, hand relay | with 4 | +| 6 | Hands | daemon as an outbound service, enrollment by pairing, multiplexed protocol, reattach after disconnects, server hand pool, `HarnessEnvironments` kind, SSH as a hand kind | 1 | +| 7 | Web shell | p2's layout and flows rebuilt on slice 11 ("look and feel great"), themes | 1, slice 11 | +| 8 | TUI | p2's TUI on slice 11's sandbox, frame splitting, plugin assets path, keybindings and themes | 1, slice 11 | +| 9 | Models and settings | catalog, credentials, provider login flows, settings, prompt resources | 1 | +| 10 | Workspace | files, git, diffs, search, review comments, file index and `@`, through hands | 6 | +| 11 | Plugin API v1 and docs | the seven clusters published in `@p3/app/v1`, authoring guide, ported examples as the litmus (hello, permission-gate, todo, plan-mode, snake, doom) | 1, 2, 7, 8 | +| 12 | Access and status | owner, devices and pairing (app kit), notices, logs, status and presence | 1 | + +Later: voice, terminals (channels and hands), artifacts, port forwards, chat mode and guest sharing, MCP and +`tool_search`. + +# Open Questions {.epilogue lede="To decide in the meeting."} + +1. The proposed cut: base versus later per area (first table). +2. Driver identity: epochs per session, handed out by Radius, with splits on conflict? +3. The index with two writers: derived rows plus `{ epoch, seq }` tags, or one of the other options? +4. Who syncs a closed session (the server reading its outbox, or starting its worker)? +5. Files outside the store as blobs on R2, uploaded before their batch? +6. Models and secrets for sessions continued on Radius: Radius's own models, or the owner's keys stored there? +7. Hands: through Radius as a relay to the driver, and may one hand serve the local server and Radius at once? +8. Do hands keep commands and terminals alive across disconnects (absorbing p2's terminal host)? +9. Hand install and updates: signed binary, service install, version negotiation. +10. Is SSH kept as a hand kind for machines without a service? +11. Plugin API v1: the seven clusters as listed, and what is explicitly left out? +12. Work package owners and order. + +# Sources {.appendix} + +| Source | What | +| --- | --- | +| `/tmp/p3-prep/features.md` | p2's feature areas with files, sizes, tools, rough edges; the CLI, environment variables and data files | +| `/tmp/p3-prep/extensions.md` | all 78 extension examples, the capability matrix, the clusters with sketches, the hard cases | +| `/tmp/p3-prep/envs.md` | p2's environments and Radius link, Chord's sync pieces, the sync and hands sketches, open questions | +| `apps/p2/docs/p3-functions/` | the functionality map (partly outdated) | +| `packages/chord/docs/next/` | design, decisions (2026-11-02 meeting summary), app kit and plugin model, theming | diff --git a/packages/chord/docs/next/p3-features/index.html b/packages/chord/docs/next/p3-features/index.html new file mode 100644 index 000000000..352a6d1ed --- /dev/null +++ b/packages/chord/docs/next/p3-features/index.html @@ -0,0 +1,832 @@ + + +p3: Feature Set and Work Packages (prep for the design meeting) + + + + +
+
+
Meeting prep · p3 on Chord v2 · 2026-11-03
+

p3
Features

+

What p2 does today, what p3 should ship first, how hands, sync and the plugin API fit, and a first cut into work packages

+
28feature areas in p2 today
21model-facing tools in p2
78coding-agent extension examples checked against the p3 plugin API
12proposed work packages
+

Written by the redesign session for the meeting on 2026-11-03, from three research passes over p2 +(apps/p2 at b7af55ace), the coding agent's extension examples and p2's environments and Radius code +(/tmp/p3-prep/features.md, extensions.md, envs.md), the owner's notes and Armin's summary of the 2026-11-02 +meeting. Chord v2's slices 1-13 are built (the app kit included); slice 11 (web and TUI) is building. Everything +marked "proposed" is for the meeting to decide.

+
+
+ + +
+

Before we begin

+

p2 grew feature by feature on the phone: 33 plugins, about 65,000 lines, a web shell and a TUI. p3 is its rewrite on +Chord v2, with an app kit that already gives hosts, workers, HTTP with sessions and pairing, plugins with versioned +APIs, files and export. The meeting's job is to decide what p3 ships first, what waits, and how the work splits.

+

Some of it was settled yesterday (Armin's summary): sessions sync to R2 through Radius by commit log, the session index +is a replicated Chord table, every host kind loads plugins through registries, hands connect proactively. The owner +added: voice waits; environments turn around into hands; continuing a local session on Radius is core; subagents and +handoff are in, artifacts are questionable; the plugin API stays small enough to evolve.

+

Ten things to know

+
    +
  1. Proposed base: sessions (with delete), the agent and conversations, subagents, history and handoff, code mode, +shell commands, models and login, prompt resources, workspace (read and review), file index, status and notices, +search through the index, the web shell and the TUI, hands, session sync and continue-on-Radius.
  2. +
  3. Proposed later: voice, terminals (need channels and hands), artifacts, port forwards, chat mode and guest +sharing, MCP and tool_search.
  4. +
  5. Hands replace SSH-dialled daemons: a long-lived daemon dials the server (or Radius), is enrolled once by a +pairing code, and carries p2's existing daemon protocol over one multiplexed WebSocket.
  6. +
  7. The sync handshake needs a driver identity. Two machines writing one session both push "from Radius's last +seq" and silently diverge; an epoch or lease per session (Chord's rule S7) closes it.
  8. +
  9. The index has two writers once a session continues on Radius; three options, one recommended (derive rows from +session stores).
  10. +
  11. Tools in base: read, write, edit, bash, codemode, subagent, team, agent, conversations, +history_*, location tools, rename_session, send_file; artifacts, ports and reload_plugins wait or change.
  12. +
  13. One CLI, no environment variables: p3 serve, p3 tui, p3 hand, p3 owner-link, p3 install; settings as +arguments and files, per the app kit.
  14. +
  15. The plugin API v1 is seven clusters, almost all already existing: Durable's extension, a Conversation +contract, input transforms, environments and models, questions, UI registries, objects for state.
  16. +
  17. Litmus test: all 78 coding-agent extension examples are expressible; Doom needs two small fixes (frame splitting +in the TUI bridge, a path for large plugin assets); streaming frames from a worker and raw terminal ownership do +not fit, by design.
  18. +
  19. Twelve work packages, with p3's skeleton on the app kit and the session/index/sync core first.
  20. +
+

Words used throughout

+

A hand is a daemon on a machine that connects out to a p3 server or to Radius and offers execution (commands, +files, watching, terminals). A driver is the single host allowed to write a session's store at a time; a +replica follows its commits. An epoch numbers driver changes. The index is the table of session metadata +(title, status, idle state) the session list reads. The app kit is Chord's startApp/startWorker layer.

+
+ +
I

p2 Today, and the Proposed Cut

Every feature area, what it is, and where it goes in p3.

+

Sizes are non-test lines in p2. "Base" means p3's first release; "later" means designed for, built after; "drop" means +not carried over.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
AreaWhat it doesp2 sizeProposed
Sessionslist by machine and directory, create, rename, pin, archive, relocate, clone, fork, unread, automatic titles2,186base, plus delete; clone and fork in the web too
Conversations and agenttranscript, send, steer, follow-up, outbox, stop, model and thinking, cost, compaction3,682 + web 2,700base
Subagents, one-shot agents, cross-session messagesnamed background agents, team, agent from scripts, conversations2,182base (owner: "awesome")
History and handoffre-read earlier entries after compaction, hand off with a continuation533base
Code modescripts calling tools as nested durable calls155 + Durablebase (small, Durable owns it)
Shell commands!cmd and !!cmd in the conversation's environmentin chatbase, web too (TUI only today)
Chat modepeople talk without a turn, @pi mentions876later
Sharing and guestsshare links, guest names, revocation1,047later (the app kit's users and invites cover the start)
Devices and owner loginowner links, device cookies684base, replaced by the app kit's sessions and pairing
Models, credentials, provider loginpicker, scope, API keys, OAuth flows3,828base
Settingssettings.json, hot reload87base
Prompt resourcesAGENTS.md, skills, templates, read in the session's environment958base
Workspacefiles, git status, diffs, search, review comments2,655 + web 1,341base (read and review; uploads later)
File index and @ranked file completion242base
Terminalsdetached PTYs, ghostty-vt in the web3,518later (channels, hands)
Environments and locationsSSH environments, directory picker, agents moving themselves2,546base, redesigned as hands
Artifacts and filesattachments, send_file, versioned artifacts with previews1,713attachments and send_file base (app kit Files); artifacts later
Searchsidebar search over titles and messages (FTS5)775base over the index; message text search through the same sync mechanism later
Status and presencebusy, tasks, usage, viewers, worker failures566base (feeds the index)
Logs and noticesowner log viewer, notices487base
Voicerooms, barge-in, speech service3,770 + Pythonlater (owner)
Radius relay and port forwardsbrain WebSocket, up to 50 forwards2,000relay base (as the hands/sync link), forwards later
Web shellPWA, sheets, navigation, reconnect9,252base, rebuilt on slice 11 ("look and feel great")
TUIlauncher, sandbox, 40 commands, themes, keybindings6,720 + launcherbase on slice 11
Dev and reloadsupervisor, dirty marks, reload tool468 + scriptsbase as the app kit's "apply changes"; the supervisor goes
Fake provider, PI_MOBILE_* aliases, P2_DEV_RESUMEtest and legacy leftovers218drop
tool_search, MCPnot installed in p2-later
+

Rough edges p3 should not inherit: features only in the TUI (shell commands, clone, fork, compact, new context), no +session delete, terminals and voice without a TUI side, docs that drifted from code (voice routes, TUI notes), the +plugin loader's module leak on reload (solved by Chord's plugin component).

+
+ +
II

Hands

Environments turned around: the machine connects to the server, not the other way.

+

What p2 does

+

p2's server and workers start a daemon (pi-env) over SSH on demand, one connection per session and per use (four +connection pools: each worker, the directory picker, the connection test, the terminal host). The daemon is +stateless and kills its commands 30 seconds after its connection goes quiet. Its frame protocol (exec, files, watch, +file listing, PTYs) does not depend on SSH.

+

What a hand is (proposed)

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
p2's daemonhand
who dialsthe server, over SSHthe hand, over WSS, to the server or to Radius
needssshd, a key, a network path to the machineoutbound HTTPS only
lifetimeper connectiona service (systemd, launchd, Termux:Boot), reconnecting
installdeployed and verified at each startinstalled once, signed updates
machine identitySSH host keya hand key pair, enrolled once
connectionsone per session and useone per hand and server, multiplexed
+
    +
  • Enrollment: p3 hand enroll <server> shows a code; the owner approves it in the UI (the app kit's device +pairing); the server pins the hand's public key; fingerprints shown on both sides. Revocation in a Hands panel.
  • +
  • Reachability: a hand reaches the owner's own server directly (LAN, own tunnel) or through Radius, which relays +hands to whichever host drives a session (the upsell: no tunnel to set up; Radius can also offer sandboxed machines +and hands of its own).
  • +
  • Protocol: p2's daemon protocol over the WebSocket, unchanged in shape; two changes: several sessions share one +connection (cancellation, handles and backpressure per session), and a disconnect starts a grace period instead of +killing everything (detached commands and PTYs reattach on reconnect; this moves the terminal host's job into the +hand).
  • +
  • Binding: a session's location becomes { hand, cwd }; one server object holds the hand connections and the +HarnessEnvironments entry resolves a hand id to an execution environment; workers reach hands through the server's +links. An SSH machine stays possible as a hand kind the server dials.
  • +
  • Offline: an operation needing an offline hand fails with a clear error the model sees; the hand's status shows +in the session list.
  • +
+
+ +
III

Session Sync and the Index

What was decided yesterday, and the three gaps it leaves.

+

Decided

+

The worker updates the session index from events and sends it to the server; sessions sync to R2 through Radius: +Radius reports per session its last sequence number, pi uploads the commit log from there; R2 holds the SQLite file +and the commit log, a SQLite Durable Object applies them; sessions have an owner id; the index is a Chord table +replicated like a session, its row updated only on metadata, session idle and subagent idle changes.

+

Chord v2 has the pieces: a versioned outbox written in each commit's transaction, export archives, batch apply that +refuses gaps and divergence, export and import per address (app kit). Missing (the sync layer): upload and trim, +epochs, pull-back after a power loss, the split, starting from an archive.

+

Gap 1: who is the driver

+

Two machines both write session X (the laptop resumed it while Radius continued it). Both ask Radius for its last +seq, both upload from there; Radius applies whichever arrives first and refuses the other as "not following". The +copies have now diverged and nobody knows.

+

Proposed: a driver epoch per session, handed out by Radius. An upload names its epoch; an old epoch is refused; +the refused copy becomes a new session (Chord's rule S7: a split, never a rollback, because tools already ran). +"Continue on Radius" is an epoch change: the local driver waits for idle, uploads the tail and the session's files, +Radius bumps the epoch and starts the agent from its store, the local side becomes a replica or closes. Back is the +reverse.

+

Gap 2: two writers of the index

+

When a session continues on Radius, Radius writes its index row, and so does the local server.

+ + + + + + + + + + + + + + + + + + + + + + + +
OptionHowCost
a. one index per writereach host keeps its own index, Radius merges per owner, newest epoch wins per rowa second merge rule
b. rows derived from session storesevery host computes a session's row from the session's own commitsno index sync at all; each host needs the session's commits
c. rows tagged { epoch, seq }Radius keeps the highest per rowrows must carry their source position
+

Recommended: (b) where the session's commits are present (Radius has them all), with (c)'s tag on the rows a local +server writes for sessions it drives, so a list without the session store still sorts correctly.

+

Gap 3: what else moves

+

Files outside the store (uploads, sent files, the app kit's Files) must sync as blobs before the batches naming +them, R2 as their home. Model access and secrets for a session continued on Radius: Radius's own models, or the +owner's provider keys stored on Radius (decide). Local prompt resources (the device's AGENTS.md, skills) must be read +through a hand or synced. Closed sessions have no worker: the server reads a session's outbox without starting it, or +starts it to sync.

+
+ +
IV

Tools Built In

p2's 21 model-facing tools, and which ship with p3.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Toolp2 pluginProposed
read, write, edit, bashcoding-tools (Durable)base
powershellpowershellbase (deferred tool, Windows hands)
codemodecodemodebase
agent (from scripts)agentbase
subagent, teamsubagentsbase
conversationsconversationsbase
history_search, history_read, history_handoffhistorybase
location_envs, location_dirs, location_movelocationsbase, renamed around hands (hands, hand_dirs, move)
rename_sessionsessionsbase
send_filesend-filebase (on the app kit's Files)
artifactartifactslater (owner: maybe not as useful)
portsportslater (with forwards)
reload_pluginsdev-toolsbase as an "apply changes" tool for the owner, not tied to a checkout
tool_searchDurablelater (with MCP)
+

Every tool is a plugin in the app's own root, so any of them can be removed, replaced or added without touching base +code (the app kit's rule: base code is the API, bootstrap and a start-data facet).

+
+ +
V

Running p3

Starting the server, the CLI, themes and keybindings.

+

The CLI (proposed)

+

One binary, p3, and no environment variables (app kit decision); settings are arguments or files in the data +directory.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
CommandDoes
p3 serve [--data-dir <dir>] [--host <addr>] [--port <n>]starts the server (startApp); prints the one-time owner link
p3 owner-linkprints a new owner link for a running server (through the owner token file)
p3 tui [url] [--pair]the sandboxed TUI; on the same machine it uses the owner token file, elsewhere device pairing
p3 hand enroll <url>, p3 hand runenrolls this machine as a hand, runs it (or installs a service)
p3 radius loginsigns the server into Radius (device code)
`p3 install <git:...npm:...>`
p3 export <session> <file>, p3 import <file>the app kit's export per address
+

The data directory defaults to ~/.p3. Its files: settings.json, models.json, auth.json (locked), +AGENTS.md, skills/, prompts/, plugins/ (the user's root), the server store, a directory per session worker, +owner-token, logs/. Plugin-specific configuration moves into plugin objects (documents) instead of more files.

+

Themes and keybindings

+

Themes follow the looks-and-themes proposal (decided 2026-11-02): design tokens as CSS variables on the web, roles in +the TUI, one Themes registry plugins contribute to, one dark and one light choice per user. Keybindings: commands are +registry entries with default keys (p2's TuiCommands with when conditions, 78 binding ids); the user's overrides +live in a preferences document (per user, synced like any data), not in files on each client; the web gets the same +command registry with its own key handling.

+
+ +
VI

The Plugin API

The smallest surface that still expresses everything the coding agent's extensions do.

+

The rule

+

Offer few things, each stable: what an API offers, it must keep (versioned in the import path, @p3/app/v1). Every +cluster below is a registry or a contract that already exists in Chord, Durable or p2; p3 v1 publishes those and +nothing else. Legacy concepts of the old extension API are not carried: CLI flags, an event bus, RPC-mode UI, hasUI +checks, "destructive switch" gates, pi.exec running locally regardless of the environment.

+

Seven clusters

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ClusterHostMechanismCovers
A. The agent's turnworkerDurable's Extension in HarnessExtensions: tools, beforeTool/afterTool, beforeRequest, sections, wraps, onYield, afterRun, beforeCompact20 examples: hello, permission-gate, protected-paths, tool-override, plan-mode, todo, custom-compaction, ...
B. Conversation controlworker objecta Conversation contract: submit, configure, append (typed entries), compact, fork, rename; state: busy, agent, usagesend-user-message, file-trigger, session-name, preset, handoff, qna, ...
C. InputworkerInputTransforms registry (rewrite or answer input before the model)input-transform, inline-bash
D. Environments and modelsworker, serverHarnessEnvironments (ssh, gondolin, hands as kinds), HarnessModels (providers, virtual models), LoginProvidersssh, gondolin, custom providers, jev-router
E. Asking the userserver object, UI renderersQuestions.ask from worker code; QuestionKinds per UI kindpermission-gate, question, questionnaire, timed-confirm, project-trust
F. UItui, webregistries: Commands (slash, palette, keys), ToolRenderers, EntryRenderers, Slots (header, footer, status, widgets), overlays through the UI context, editor extension pointsevery UI-only example, the games
G. State and standing behaviouranylocal objects owning natives, documents for statefile-trigger's watcher, gondolin's VM, game saves
+
// a plugin with a tool, a gate and a renderer: plugins/todo/
+// worker.ts
+self.contribute(HarnessExtensions, { name: "todo", tools: [todoTool], hooks: [hook(ToolTask, { beforeTool: guard })] });
+// tui.ts
+self.contribute(TuiToolRenderers, { todo: { call: (args) => todoCall(args), result: (r) => todoList(r.details) } });
+self.contribute(TuiCommands, { "todo.show": { slash: { name: "todos" }, run: (ui) => ui.overlay("todos", new TodoView(ui)) } });
+// web.tsx: the same registries with React components
+

The litmus test

+

All 78 examples map onto the seven clusters (table in /tmp/p3-prep/extensions.md). The examples that mix agent and +UI split into a worker facet and a tui/web facet sharing a contract file: a tool that wants a dialog asks the +Questions object, which works from any client, several clients or none.

+
    +
  • Snake, Space Invaders, tic-tac-toe: a command plus an overlay in tui.ts and a canvas in web.tsx, engine in a +shared file, saves in a user-keyed document. Fit today.
  • +
  • Doom: fits with two fixes in the TUI: the sandbox bridge caps a frame at 64 KiB and a Doom frame is about 160 KB +(split writes at escape boundaries), and the sandbox has no files or network, so large plugin assets (a 4 MB WAD) +need a documented "asset of my plugin" path (chunked reads through the plugin's server facet).
  • +
  • Does not fit, by design: streaming 35 FPS frames from a worker to clients (objects carry state; needs channels), +engine state surviving an in-place reload (needs an explicit save), raw terminal ownership (interactive-shell +becomes a terminal view; terminal notifications need a launcher action).
  • +
+

What v1 leaves out

+

Editor replacement (extension points only), raw provider stream events and response headers, a fork hook, a +turn_start hook, an event bus. Each has one or two examples; add when a real plugin needs it.

+
+ +
VII

Work Packages

A first cut, in dependency order, for the meeting to rearrange.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#PackageContainsDepends on
1p3 skeletonstartApp with p3's API v1 modules (shared, web, tui, worker), core plugins in the app's root, p3 CLIChord slices 11-13
2Sessions and conversationsDurable on the Chord store, session lifecycle with delete, the conversation contract, subagents, history and handoff, code mode, shell commands1
3Session indexthe index table, worker-to-server updates on metadata and idle changes, the session list, search over it2
4Session sync to Radiusoutbox upload and trim, epochs, pull-back, split, files as blobs, continue on Radius and back2, 3, Radius side
5Radius sideper-session SQLite DO, commit log and SQLite on R2, epochs, the sync handshake, hand relaywith 4
6Handsdaemon as an outbound service, enrollment by pairing, multiplexed protocol, reattach after disconnects, server hand pool, HarnessEnvironments kind, SSH as a hand kind1
7Web shellp2's layout and flows rebuilt on slice 11 ("look and feel great"), themes1, slice 11
8TUIp2's TUI on slice 11's sandbox, frame splitting, plugin assets path, keybindings and themes1, slice 11
9Models and settingscatalog, credentials, provider login flows, settings, prompt resources1
10Workspacefiles, git, diffs, search, review comments, file index and @, through hands6
11Plugin API v1 and docsthe seven clusters published in @p3/app/v1, authoring guide, ported examples as the litmus (hello, permission-gate, todo, plan-mode, snake, doom)1, 2, 7, 8
12Access and statusowner, devices and pairing (app kit), notices, logs, status and presence1
+

Later: voice, terminals (channels and hands), artifacts, port forwards, chat mode and guest sharing, MCP and +tool_search.

+
+ +
∴

Open Questions

To decide in the meeting.

+
    +
  1. The proposed cut: base versus later per area (first table).
  2. +
  3. Driver identity: epochs per session, handed out by Radius, with splits on conflict?
  4. +
  5. The index with two writers: derived rows plus { epoch, seq } tags, or one of the other options?
  6. +
  7. Who syncs a closed session (the server reading its outbox, or starting its worker)?
  8. +
  9. Files outside the store as blobs on R2, uploaded before their batch?
  10. +
  11. Models and secrets for sessions continued on Radius: Radius's own models, or the owner's keys stored there?
  12. +
  13. Hands: through Radius as a relay to the driver, and may one hand serve the local server and Radius at once?
  14. +
  15. Do hands keep commands and terminals alive across disconnects (absorbing p2's terminal host)?
  16. +
  17. Hand install and updates: signed binary, service install, version negotiation.
  18. +
  19. Is SSH kept as a hand kind for machines without a service?
  20. +
  21. Plugin API v1: the seven clusters as listed, and what is explicitly left out?
  22. +
  23. Work package owners and order.
  24. +
+
+ +
A

Sources

+ + + + + + + + + + + + + + + + + + + + + + + + + + + +
SourceWhat
/tmp/p3-prep/features.mdp2's feature areas with files, sizes, tools, rough edges; the CLI, environment variables and data files
/tmp/p3-prep/extensions.mdall 78 extension examples, the capability matrix, the clusters with sketches, the hard cases
/tmp/p3-prep/envs.mdp2's environments and Radius link, Chord's sync pieces, the sync and hands sketches, open questions
apps/p2/docs/p3-functions/the functionality map (partly outdated)
packages/chord/docs/next/design, decisions (2026-11-02 meeting summary), app kit and plugin model, theming
+
+ +

p3 Feature Set and Work Packages · meeting prep · 2026-11-03

+
+ +