mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
docs(chord): p3 feature set and work packages, meeting prep
This commit is contained in:
parent
cf74b67653
commit
e64f313bd6
2 changed files with 1161 additions and 0 deletions
329
packages/chord/docs/next/p3-features/book.md
Normal file
329
packages/chord/docs/next/p3-features/book.md
Normal file
|
|
@ -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 <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.
|
||||
|
||||
# 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 <dir>] [--host <addr>] [--port <n>]` | 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 <url>`, `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 <git:...|npm:...>` | installs a plugin into the user's root |
|
||||
| `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.
|
||||
|
||||
# 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 |
|
||||
832
packages/chord/docs/next/p3-features/index.html
Normal file
832
packages/chord/docs/next/p3-features/index.html
Normal file
|
|
@ -0,0 +1,832 @@
|
|||
<!doctype html>
|
||||
<html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
|
||||
<title>p3: Feature Set and Work Packages (prep for the design meeting)</title>
|
||||
<style>
|
||||
|
||||
:root{--paper:#fbf8f2;--ink:#2a2723;--muted:#7b7468;--rule:#e3dbcc;--accent:#b4472f;--blue:#2e6a8a;--sand:#c2b18c;--soft:#f4eee2}
|
||||
*{box-sizing:border-box}
|
||||
html{background:#efe9dd}
|
||||
body{margin:0;color:var(--ink);font:19px/1.68 'Iowan Old Style','Palatino Linotype',Palatino,'Book Antiqua',Georgia,serif;
|
||||
-webkit-font-smoothing:antialiased;text-rendering:optimizeLegibility}
|
||||
.book{max-width:860px;margin:0 auto;background:var(--paper);box-shadow:0 0 0 1px #e1d9c8,0 30px 80px -40px rgba(60,40,10,.35);padding:0 0 4rem}
|
||||
.inner{padding:0 90px}
|
||||
h1,h2,h3{font-weight:400;line-height:1.15}
|
||||
h2{font-size:2.1rem;margin:.2em 0 .3em;letter-spacing:-.01em}
|
||||
h3{font-size:1.05rem;text-transform:uppercase;letter-spacing:.14em;margin:2.4em 0 .6em;color:var(--ink);font-weight:600;
|
||||
font-family:'Avenir Next','Helvetica Neue',sans-serif}
|
||||
p{margin:0 0 1.05em;hyphens:auto;text-align:left}
|
||||
code{font:.8em/1 'SF Mono',Menlo,Consolas,monospace;background:#f1ebde;padding:.1em .35em;border-radius:3px}
|
||||
pre{font:.74rem/1.55 'SF Mono',Menlo,Consolas,monospace;background:#f4eee2;border-left:3px solid var(--sand);padding:1rem 1.2rem;margin:1.4rem 0 1.8rem;
|
||||
overflow-x:auto;border-radius:0 4px 4px 0;white-space:pre;tab-size:2}
|
||||
pre code{background:none;padding:0;font-size:1em}
|
||||
em{font-style:italic}
|
||||
a{color:var(--blue)}
|
||||
.cover{min-height:92vh;display:flex;flex-direction:column;justify-content:center;padding:90px;background:
|
||||
radial-gradient(circle at 85% 15%,rgba(180,71,47,.13),transparent 40%),radial-gradient(circle at 10% 90%,rgba(46,106,138,.12),transparent 45%),var(--paper);
|
||||
border-bottom:1px solid var(--rule)}
|
||||
.kicker{font:600 .78rem 'Avenir Next','Helvetica Neue',sans-serif;letter-spacing:.22em;text-transform:uppercase;color:var(--accent)}
|
||||
.cover h1{font-size:5.2rem;line-height:.98;margin:.35em 0 .25em;letter-spacing:-.025em}
|
||||
.subtitle{font-size:1.45rem;font-style:italic;color:#4a453e;max-width:30em;text-align:left}
|
||||
.cover-stats,.stats{display:flex;flex-wrap:wrap;gap:1.2rem 2.4rem;margin:2.2rem 0}
|
||||
.stats{margin:1.6rem 0 1.8rem;padding:1.1rem 0;border-top:1px solid var(--rule);border-bottom:1px solid var(--rule)}
|
||||
.stat b{display:block;font-size:2.1rem;font-weight:400;line-height:1.1;font-variant-numeric:lining-nums}
|
||||
.stat span{font:.78rem/1.3 'Avenir Next','Helvetica Neue',sans-serif;color:var(--muted);letter-spacing:.03em;display:block;max-width:14em}
|
||||
.cover-note{font-size:.9rem;color:var(--muted);max-width:34em;text-align:left;margin-top:2rem}
|
||||
.toc{padding:4rem 0 2rem}
|
||||
.toc ol{list-style:none;padding:0;margin:1rem 0;columns:2;column-gap:3rem}
|
||||
.toc li{padding:.3rem 0;border-bottom:1px dotted var(--rule);break-inside:avoid}
|
||||
.toc a{color:var(--ink);text-decoration:none}
|
||||
.toc a:hover{color:var(--accent)}
|
||||
.preface{padding-top:2rem}
|
||||
.findings{padding-left:1.4em}
|
||||
.findings li{margin:.55em 0;padding-left:.3em}
|
||||
.findings li::marker{color:var(--accent);font-weight:600}
|
||||
.chapter{padding-top:4.5rem;margin-top:3rem;border-top:1px solid var(--rule)}
|
||||
.opener{text-align:center;margin-bottom:2.6rem}
|
||||
.numeral{font-size:4.2rem;color:var(--accent);line-height:1;font-variant:small-caps;letter-spacing:.05em}
|
||||
.opener h2{font-size:2.5rem;margin:.25em 0}
|
||||
.lede{font-style:italic;color:#5a544a;font-size:1.12rem;max-width:30em;margin:0 auto;text-align:center}
|
||||
.dropcap::first-letter{float:left;font-size:4.1em;line-height:.82;padding:.06em .09em 0 0;color:var(--accent)}
|
||||
.fig{margin:2rem 0 2.2rem}
|
||||
.fig.wide{margin-left:-50px;margin-right:-50px}
|
||||
.fig svg{display:block;margin:0 auto}
|
||||
figcaption{font:.8rem/1.45 'Avenir Next','Helvetica Neue',sans-serif;color:var(--muted);margin-top:.7rem;padding-left:.8rem;border-left:2px solid var(--rule)}
|
||||
.fignum{color:var(--ink);font-weight:600}
|
||||
.note{font:.86rem/1.55 'Avenir Next','Helvetica Neue',sans-serif;color:#5b5549;background:#f4eee2;border-left:3px solid var(--sand);
|
||||
padding:.9rem 1.1rem;margin:1.8rem 0;border-radius:0 4px 4px 0}
|
||||
.note.review{border-left-color:var(--accent)}
|
||||
.note b.tag{font-weight:600;letter-spacing:.12em;text-transform:uppercase;font-size:.72rem;color:var(--accent);display:block;margin-bottom:.3rem}
|
||||
.note code{font-size:.85em}
|
||||
table{width:100%;border-collapse:collapse;margin:1.4rem 0 1.8rem;font:.8rem/1.4 'Avenir Next','Helvetica Neue',sans-serif}
|
||||
th{text-align:left;font-weight:600;color:var(--muted);text-transform:uppercase;letter-spacing:.06em;font-size:.68rem;border-bottom:1.5px solid var(--ink);padding:.45rem .5rem}
|
||||
td{padding:.42rem .5rem;border-bottom:1px solid var(--rule);vertical-align:top}
|
||||
td code{font-size:.92em}
|
||||
table.num td:not(:first-child),table.num th:not(:first-child){text-align:right}
|
||||
.epilogue p{font-size:1.04em}
|
||||
.appendix{font-size:.92em}
|
||||
.appendix ul,.chapter ul{padding-left:1.2em}
|
||||
.appendix li,.chapter li{margin:.4em 0}
|
||||
.colophon{text-align:center;font:.78rem 'Avenir Next','Helvetica Neue',sans-serif;color:var(--muted);margin-top:4rem;letter-spacing:.05em}
|
||||
@media (max-width:760px){.inner{padding:0 22px}.cover{padding:40px 22px}.cover h1{font-size:3.2rem}.fig.wide{margin-left:0;margin-right:0}
|
||||
.toc ol{columns:1}p{text-align:left}body{font-size:17px}}
|
||||
@media print{html{background:#fff}.book{box-shadow:none}.chapter{break-before:page}.fig{break-inside:avoid}}
|
||||
.transcript p b{font:600 .78rem 'Avenir Next','Helvetica Neue',sans-serif;letter-spacing:.08em;text-transform:uppercase;color:var(--accent);margin-right:.3em}
|
||||
pre .tk-c{color:#8d8576;font-style:italic}pre .tk-s{color:#5f7a3d}pre .tk-n{color:#a0662a}pre .tk-k{color:#b4472f}pre .tk-t{color:#2e6a8a}pre .tk-f{color:#7a3f6a}
|
||||
h4{font:600 .82rem 'Avenir Next','Helvetica Neue',sans-serif;letter-spacing:.06em;margin:1.8em 0 .5em;color:var(--muted)}
|
||||
.inner ul,.inner ol:not(.findings){padding-left:1.2em}
|
||||
.inner li{margin:.35em 0}
|
||||
blockquote{margin:1.4rem 0;padding:.2rem 0 .2rem 1.2rem;border-left:3px solid var(--rule);color:#5a544a;font-style:italic}
|
||||
.fig svg text{dominant-baseline:auto}
|
||||
.fig .replay{font:600 .7rem 'Avenir Next','Helvetica Neue',sans-serif;letter-spacing:.1em;text-transform:uppercase;color:var(--accent);
|
||||
background:none;border:1px solid var(--rule);border-radius:3px;padding:.2rem .55rem;margin:0 0 0 1rem;cursor:pointer;float:right}
|
||||
.fig .replay:hover{border-color:var(--accent)}
|
||||
.anim.armed .step{opacity:0}
|
||||
.anim.armed.play .step{animation:stepIn .45s ease-out both;animation-delay:calc(var(--i) * var(--pace, .45s))}
|
||||
.anim.armed.play .step.edge{animation-name:edgeIn}
|
||||
@keyframes stepIn{from{opacity:0;transform:translateY(4px)}to{opacity:1;transform:none}}
|
||||
@keyframes edgeIn{from{opacity:0}to{opacity:1}}
|
||||
.steps{margin:2rem 0 2.2rem;border:1px solid var(--rule);border-radius:6px;background:#fffdf8}
|
||||
.steps-head{display:flex;align-items:center;gap:.8rem;padding:.6rem 1rem;border-bottom:1px solid var(--rule);
|
||||
font:600 .72rem 'Avenir Next','Helvetica Neue',sans-serif;letter-spacing:.12em;text-transform:uppercase;color:var(--accent)}
|
||||
.steps-head .title{flex:1}
|
||||
.steps-head .count{color:var(--muted);letter-spacing:.06em}
|
||||
.steps-head button{font:600 .72rem 'Avenir Next','Helvetica Neue',sans-serif;color:var(--ink);background:var(--soft);border:1px solid var(--rule);
|
||||
border-radius:3px;padding:.25rem .65rem;cursor:pointer}
|
||||
.steps-head button:disabled{opacity:.35;cursor:default}
|
||||
.step-panel{padding:1rem 1.2rem .4rem}
|
||||
.step-panel>h4:first-child{margin-top:.2rem;color:var(--ink)}
|
||||
.steps.js .step-panel{display:none}
|
||||
.steps.js .step-panel.on{display:block}
|
||||
.steps:not(.js) .steps-head button,.steps:not(.js) .steps-head .count{display:none}
|
||||
.steps:not(.js) .step-panel+.step-panel{border-top:1px dashed var(--rule)}
|
||||
.fig figcaption{display:flow-root}
|
||||
.fig svg{max-width:100%;height:auto}
|
||||
table{max-width:100%}
|
||||
@media (max-width:760px){.inner{padding:0 20px}.cover{padding:48px 22px}.cover h1{font-size:3.2rem}.fig.wide{margin-left:0;margin-right:0}table{display:block;overflow-x:auto}.toc ol{columns:1}}
|
||||
</style>
|
||||
|
||||
|
||||
</head>
|
||||
<body><div class="book">
|
||||
<section class="cover">
|
||||
<div class="kicker">Meeting prep · p3 on Chord v2 · 2026-11-03</div>
|
||||
<h1>p3<br>Features</h1>
|
||||
<p class="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</p>
|
||||
<div class="cover-stats"><div class="stat"><b>28</b><span>feature areas in p2 today</span></div><div class="stat"><b>21</b><span>model-facing tools in p2</span></div><div class="stat"><b>78</b><span>coding-agent extension examples checked against the p3 plugin API</span></div><div class="stat"><b>12</b><span>proposed work packages</span></div></div>
|
||||
<div class="cover-note"><p>Written by the redesign session for the meeting on 2026-11-03, from three research passes over p2
|
||||
(<code>apps/p2</code> at <code>b7af55ace</code>), the coding agent's extension examples and p2's environments and Radius code
|
||||
(<code>/tmp/p3-prep/features.md</code>, <code>extensions.md</code>, <code>envs.md</code>), 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.</p>
|
||||
</div>
|
||||
</section><div class="inner">
|
||||
<nav class="toc"><h2>Contents</h2><ol>
|
||||
<li><a href="#before-we-begin">Before we begin</a></li>
|
||||
<li><a href="#features">I. p2 Today, and the Proposed Cut</a></li>
|
||||
<li><a href="#hands">II. Hands</a></li>
|
||||
<li><a href="#sync">III. Session Sync and the Index</a></li>
|
||||
<li><a href="#tools">IV. Tools Built In</a></li>
|
||||
<li><a href="#running">V. Running p3</a></li>
|
||||
<li><a href="#api">VI. The Plugin API</a></li>
|
||||
<li><a href="#packages">VII. Work Packages</a></li>
|
||||
<li><a href="#open-questions">Open Questions</a></li>
|
||||
<li><a href="#sources">Appendix A: Sources</a></li>
|
||||
</ol></nav>
|
||||
|
||||
<section class="preface" id="before-we-begin">
|
||||
<h2>Before we begin</h2>
|
||||
<p class="dropcap">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.</p>
|
||||
<p>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.</p>
|
||||
<h3 id="ten-things-to-know">Ten things to know</h3>
|
||||
<ol class="findings">
|
||||
<li><strong>Proposed base:</strong> 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.</li>
|
||||
<li><strong>Proposed later:</strong> voice, terminals (need channels and hands), artifacts, port forwards, chat mode and guest
|
||||
sharing, MCP and <code>tool_search</code>.</li>
|
||||
<li><strong>Hands replace SSH-dialled daemons:</strong> 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.</li>
|
||||
<li><strong>The sync handshake needs a driver identity.</strong> 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.</li>
|
||||
<li><strong>The index has two writers</strong> once a session continues on Radius; three options, one recommended (derive rows from
|
||||
session stores).</li>
|
||||
<li><strong>Tools in base:</strong> <code>read</code>, <code>write</code>, <code>edit</code>, <code>bash</code>, <code>codemode</code>, <code>subagent</code>, <code>team</code>, <code>agent</code>, <code>conversations</code>,
|
||||
<code>history_*</code>, location tools, <code>rename_session</code>, <code>send_file</code>; artifacts, ports and <code>reload_plugins</code> wait or change.</li>
|
||||
<li><strong>One CLI, no environment variables:</strong> <code>p3 serve</code>, <code>p3 tui</code>, <code>p3 hand</code>, <code>p3 owner-link</code>, <code>p3 install</code>; settings as
|
||||
arguments and files, per the app kit.</li>
|
||||
<li><strong>The plugin API v1 is seven clusters,</strong> almost all already existing: Durable's extension, a <code>Conversation</code>
|
||||
contract, input transforms, environments and models, questions, UI registries, objects for state.</li>
|
||||
<li><strong>Litmus test:</strong> 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.</li>
|
||||
<li><strong>Twelve work packages,</strong> with p3's skeleton on the app kit and the session/index/sync core first.</li>
|
||||
</ol>
|
||||
<h3 id="words-used-throughout">Words used throughout</h3>
|
||||
<p>A <strong>hand</strong> is a daemon on a machine that connects out to a p3 server or to Radius and offers execution (commands,
|
||||
files, watching, terminals). A <strong>driver</strong> is the single host allowed to write a session's store at a time; a
|
||||
<strong>replica</strong> follows its commits. An <strong>epoch</strong> numbers driver changes. The <strong>index</strong> is the table of session metadata
|
||||
(title, status, idle state) the session list reads. The <strong>app kit</strong> is Chord's <code>startApp</code>/<code>startWorker</code> layer.</p>
|
||||
</section>
|
||||
|
||||
<section class="chapter" id="features"><header class="opener"><div class="numeral">I</div><h2>p2 Today, and the Proposed Cut</h2><p class="lede">Every feature area, what it is, and where it goes in p3.</p></header>
|
||||
<p class="dropcap">Sizes are non-test lines in p2. "Base" means p3's first release; "later" means designed for, built after; "drop" means
|
||||
not carried over.</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Area</th>
|
||||
<th>What it does</th>
|
||||
<th align="right">p2 size</th>
|
||||
<th>Proposed</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td>Sessions</td>
|
||||
<td>list by machine and directory, create, rename, pin, archive, relocate, clone, fork, unread, automatic titles</td>
|
||||
<td align="right">2,186</td>
|
||||
<td><strong>base</strong>, plus delete; clone and fork in the web too</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Conversations and agent</td>
|
||||
<td>transcript, send, steer, follow-up, outbox, stop, model and thinking, cost, compaction</td>
|
||||
<td align="right">3,682 + web 2,700</td>
|
||||
<td><strong>base</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Subagents, one-shot agents, cross-session messages</td>
|
||||
<td>named background agents, <code>team</code>, <code>agent</code> from scripts, <code>conversations</code></td>
|
||||
<td align="right">2,182</td>
|
||||
<td><strong>base</strong> (owner: "awesome")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>History and handoff</td>
|
||||
<td>re-read earlier entries after compaction, hand off with a continuation</td>
|
||||
<td align="right">533</td>
|
||||
<td><strong>base</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Code mode</td>
|
||||
<td>scripts calling tools as nested durable calls</td>
|
||||
<td align="right">155 + Durable</td>
|
||||
<td><strong>base</strong> (small, Durable owns it)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Shell commands</td>
|
||||
<td><code>!cmd</code> and <code>!!cmd</code> in the conversation's environment</td>
|
||||
<td align="right">in chat</td>
|
||||
<td><strong>base</strong>, web too (TUI only today)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Chat mode</td>
|
||||
<td>people talk without a turn, <code>@pi</code> mentions</td>
|
||||
<td align="right">876</td>
|
||||
<td>later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Sharing and guests</td>
|
||||
<td>share links, guest names, revocation</td>
|
||||
<td align="right">1,047</td>
|
||||
<td>later (the app kit's users and invites cover the start)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Devices and owner login</td>
|
||||
<td>owner links, device cookies</td>
|
||||
<td align="right">684</td>
|
||||
<td><strong>base</strong>, replaced by the app kit's sessions and pairing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Models, credentials, provider login</td>
|
||||
<td>picker, scope, API keys, OAuth flows</td>
|
||||
<td align="right">3,828</td>
|
||||
<td><strong>base</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Settings</td>
|
||||
<td><code>settings.json</code>, hot reload</td>
|
||||
<td align="right">87</td>
|
||||
<td><strong>base</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Prompt resources</td>
|
||||
<td>AGENTS.md, skills, templates, read in the session's environment</td>
|
||||
<td align="right">958</td>
|
||||
<td><strong>base</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Workspace</td>
|
||||
<td>files, git status, diffs, search, review comments</td>
|
||||
<td align="right">2,655 + web 1,341</td>
|
||||
<td><strong>base</strong> (read and review; uploads later)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>File index and <code>@</code></td>
|
||||
<td>ranked file completion</td>
|
||||
<td align="right">242</td>
|
||||
<td><strong>base</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Terminals</td>
|
||||
<td>detached PTYs, ghostty-vt in the web</td>
|
||||
<td align="right">3,518</td>
|
||||
<td>later (channels, hands)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Environments and locations</td>
|
||||
<td>SSH environments, directory picker, agents moving themselves</td>
|
||||
<td align="right">2,546</td>
|
||||
<td><strong>base, redesigned as hands</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Artifacts and files</td>
|
||||
<td>attachments, <code>send_file</code>, versioned artifacts with previews</td>
|
||||
<td align="right">1,713</td>
|
||||
<td>attachments and <code>send_file</code> base (app kit <code>Files</code>); artifacts later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Search</td>
|
||||
<td>sidebar search over titles and messages (FTS5)</td>
|
||||
<td align="right">775</td>
|
||||
<td><strong>base</strong> over the index; message text search through the same sync mechanism later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Status and presence</td>
|
||||
<td>busy, tasks, usage, viewers, worker failures</td>
|
||||
<td align="right">566</td>
|
||||
<td><strong>base</strong> (feeds the index)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Logs and notices</td>
|
||||
<td>owner log viewer, notices</td>
|
||||
<td align="right">487</td>
|
||||
<td><strong>base</strong></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Voice</td>
|
||||
<td>rooms, barge-in, speech service</td>
|
||||
<td align="right">3,770 + Python</td>
|
||||
<td>later (owner)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Radius relay and port forwards</td>
|
||||
<td>brain WebSocket, up to 50 forwards</td>
|
||||
<td align="right">2,000</td>
|
||||
<td>relay <strong>base</strong> (as the hands/sync link), forwards later</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Web shell</td>
|
||||
<td>PWA, sheets, navigation, reconnect</td>
|
||||
<td align="right">9,252</td>
|
||||
<td><strong>base</strong>, rebuilt on slice 11 ("look and feel great")</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>TUI</td>
|
||||
<td>launcher, sandbox, 40 commands, themes, keybindings</td>
|
||||
<td align="right">6,720 + launcher</td>
|
||||
<td><strong>base</strong> on slice 11</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Dev and reload</td>
|
||||
<td>supervisor, dirty marks, reload tool</td>
|
||||
<td align="right">468 + scripts</td>
|
||||
<td><strong>base</strong> as the app kit's "apply changes"; the supervisor goes</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>Fake provider, <code>PI_MOBILE_*</code> aliases, <code>P2_DEV_RESUME</code></td>
|
||||
<td>test and legacy leftovers</td>
|
||||
<td align="right">218</td>
|
||||
<td>drop</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>tool_search</code>, MCP</td>
|
||||
<td>not installed in p2</td>
|
||||
<td align="right">-</td>
|
||||
<td>later</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>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).</p>
|
||||
</section>
|
||||
|
||||
<section class="chapter" id="hands"><header class="opener"><div class="numeral">II</div><h2>Hands</h2><p class="lede">Environments turned around: the machine connects to the server, not the other way.</p></header>
|
||||
<h3 id="what-p2-does">What p2 does</h3>
|
||||
<p>p2's server and workers start a daemon (<code>pi-env</code>) 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.</p>
|
||||
<h3 id="what-a-hand-is-proposed">What a hand is (proposed)</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th>p2's daemon</th>
|
||||
<th>hand</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td>who dials</td>
|
||||
<td>the server, over SSH</td>
|
||||
<td>the hand, over WSS, to the server or to Radius</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>needs</td>
|
||||
<td>sshd, a key, a network path to the machine</td>
|
||||
<td>outbound HTTPS only</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>lifetime</td>
|
||||
<td>per connection</td>
|
||||
<td>a service (systemd, launchd, Termux:Boot), reconnecting</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>install</td>
|
||||
<td>deployed and verified at each start</td>
|
||||
<td>installed once, signed updates</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>machine identity</td>
|
||||
<td>SSH host key</td>
|
||||
<td>a hand key pair, enrolled once</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>connections</td>
|
||||
<td>one per session and use</td>
|
||||
<td>one per hand and server, multiplexed</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<ul>
|
||||
<li><strong>Enrollment:</strong> <code>p3 hand enroll <server></code> 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.</li>
|
||||
<li><strong>Reachability:</strong> 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).</li>
|
||||
<li><strong>Protocol:</strong> 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).</li>
|
||||
<li><strong>Binding:</strong> a session's location becomes <code>{ hand, cwd }</code>; one server object holds the hand connections and the
|
||||
<code>HarnessEnvironments</code> 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.</li>
|
||||
<li><strong>Offline:</strong> an operation needing an offline hand fails with a clear error the model sees; the hand's status shows
|
||||
in the session list.</li>
|
||||
</ul>
|
||||
</section>
|
||||
|
||||
<section class="chapter" id="sync"><header class="opener"><div class="numeral">III</div><h2>Session Sync and the Index</h2><p class="lede">What was decided yesterday, and the three gaps it leaves.</p></header>
|
||||
<h3 id="decided">Decided</h3>
|
||||
<p>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.</p>
|
||||
<p>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.</p>
|
||||
<h3 id="gap-1-who-is-the-driver">Gap 1: who is the driver</h3>
|
||||
<p>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.</p>
|
||||
<p><strong>Proposed:</strong> 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.</p>
|
||||
<h3 id="gap-2-two-writers-of-the-index">Gap 2: two writers of the index</h3>
|
||||
<p>When a session continues on Radius, Radius writes its index row, and so does the local server.</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Option</th>
|
||||
<th>How</th>
|
||||
<th>Cost</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td>a. one index per writer</td>
|
||||
<td>each host keeps its own index, Radius merges per owner, newest epoch wins per row</td>
|
||||
<td>a second merge rule</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>b. rows derived from session stores</td>
|
||||
<td>every host computes a session's row from the session's own commits</td>
|
||||
<td>no index sync at all; each host needs the session's commits</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>c. rows tagged <code>{ epoch, seq }</code></td>
|
||||
<td>Radius keeps the highest per row</td>
|
||||
<td>rows must carry their source position</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p><strong>Recommended:</strong> (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.</p>
|
||||
<h3 id="gap-3-what-else-moves">Gap 3: what else moves</h3>
|
||||
<p>Files outside the store (uploads, sent files, the app kit's <code>Files</code>) 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 <code>AGENTS.md</code>, 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.</p>
|
||||
</section>
|
||||
|
||||
<section class="chapter" id="tools"><header class="opener"><div class="numeral">IV</div><h2>Tools Built In</h2><p class="lede">p2's 21 model-facing tools, and which ship with p3.</p></header>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Tool</th>
|
||||
<th>p2 plugin</th>
|
||||
<th>Proposed</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td><code>read</code>, <code>write</code>, <code>edit</code>, <code>bash</code></td>
|
||||
<td>coding-tools (Durable)</td>
|
||||
<td>base</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>powershell</code></td>
|
||||
<td>powershell</td>
|
||||
<td>base (deferred tool, Windows hands)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>codemode</code></td>
|
||||
<td>codemode</td>
|
||||
<td>base</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>agent</code> (from scripts)</td>
|
||||
<td>agent</td>
|
||||
<td>base</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>subagent</code>, <code>team</code></td>
|
||||
<td>subagents</td>
|
||||
<td>base</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>conversations</code></td>
|
||||
<td>conversations</td>
|
||||
<td>base</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>history_search</code>, <code>history_read</code>, <code>history_handoff</code></td>
|
||||
<td>history</td>
|
||||
<td>base</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>location_envs</code>, <code>location_dirs</code>, <code>location_move</code></td>
|
||||
<td>locations</td>
|
||||
<td>base, renamed around hands (<code>hands</code>, <code>hand_dirs</code>, <code>move</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>rename_session</code></td>
|
||||
<td>sessions</td>
|
||||
<td>base</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>send_file</code></td>
|
||||
<td>send-file</td>
|
||||
<td>base (on the app kit's <code>Files</code>)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>artifact</code></td>
|
||||
<td>artifacts</td>
|
||||
<td>later (owner: maybe not as useful)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>ports</code></td>
|
||||
<td>ports</td>
|
||||
<td>later (with forwards)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>reload_plugins</code></td>
|
||||
<td>dev-tools</td>
|
||||
<td>base as an "apply changes" tool for the owner, not tied to a checkout</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>tool_search</code></td>
|
||||
<td>Durable</td>
|
||||
<td>later (with MCP)</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>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).</p>
|
||||
</section>
|
||||
|
||||
<section class="chapter" id="running"><header class="opener"><div class="numeral">V</div><h2>Running p3</h2><p class="lede">Starting the server, the CLI, themes and keybindings.</p></header>
|
||||
<h3 id="the-cli-proposed">The CLI (proposed)</h3>
|
||||
<p>One binary, <code>p3</code>, and no environment variables (app kit decision); settings are arguments or files in the data
|
||||
directory.</p>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Command</th>
|
||||
<th>Does</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td><code>p3 serve [--data-dir <dir>] [--host <addr>] [--port <n>]</code></td>
|
||||
<td>starts the server (<code>startApp</code>); prints the one-time owner link</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>p3 owner-link</code></td>
|
||||
<td>prints a new owner link for a running server (through the owner token file)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>p3 tui [url] [--pair]</code></td>
|
||||
<td>the sandboxed TUI; on the same machine it uses the owner token file, elsewhere device pairing</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>p3 hand enroll <url></code>, <code>p3 hand run</code></td>
|
||||
<td>enrolls this machine as a hand, runs it (or installs a service)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>p3 radius login</code></td>
|
||||
<td>signs the server into Radius (device code)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>`p3 install <git:...</td>
|
||||
<td>npm:...>`</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>p3 export <session> <file></code>, <code>p3 import <file></code></td>
|
||||
<td>the app kit's export per address</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>The data directory defaults to <code>~/.p3</code>. Its files: <code>settings.json</code>, <code>models.json</code>, <code>auth.json</code> (locked),
|
||||
<code>AGENTS.md</code>, <code>skills/</code>, <code>prompts/</code>, <code>plugins/</code> (the user's root), the server store, a directory per session worker,
|
||||
<code>owner-token</code>, <code>logs/</code>. Plugin-specific configuration moves into plugin objects (documents) instead of more files.</p>
|
||||
<h3 id="themes-and-keybindings">Themes and keybindings</h3>
|
||||
<p>Themes follow the looks-and-themes proposal (decided 2026-11-02): design tokens as CSS variables on the web, roles in
|
||||
the TUI, one <code>Themes</code> registry plugins contribute to, one dark and one light choice per user. Keybindings: commands are
|
||||
registry entries with default keys (p2's <code>TuiCommands</code> with <code>when</code> 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.</p>
|
||||
</section>
|
||||
|
||||
<section class="chapter" id="api"><header class="opener"><div class="numeral">VI</div><h2>The Plugin API</h2><p class="lede">The smallest surface that still expresses everything the coding agent's extensions do.</p></header>
|
||||
<h3 id="the-rule">The rule</h3>
|
||||
<p>Offer few things, each stable: what an API offers, it must keep (versioned in the import path, <code>@p3/app/v1</code>). 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, <code>hasUI</code>
|
||||
checks, "destructive switch" gates, <code>pi.exec</code> running locally regardless of the environment.</p>
|
||||
<h3 id="seven-clusters">Seven clusters</h3>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Cluster</th>
|
||||
<th>Host</th>
|
||||
<th>Mechanism</th>
|
||||
<th>Covers</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td>A. The agent's turn</td>
|
||||
<td>worker</td>
|
||||
<td>Durable's <code>Extension</code> in <code>HarnessExtensions</code>: tools, <code>beforeTool</code>/<code>afterTool</code>, <code>beforeRequest</code>, sections, wraps, <code>onYield</code>, <code>afterRun</code>, <code>beforeCompact</code></td>
|
||||
<td>20 examples: hello, permission-gate, protected-paths, tool-override, plan-mode, todo, custom-compaction, ...</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>B. Conversation control</td>
|
||||
<td>worker object</td>
|
||||
<td>a <code>Conversation</code> contract: <code>submit</code>, <code>configure</code>, <code>append</code> (typed entries), <code>compact</code>, <code>fork</code>, <code>rename</code>; state: busy, agent, usage</td>
|
||||
<td>send-user-message, file-trigger, session-name, preset, handoff, qna, ...</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>C. Input</td>
|
||||
<td>worker</td>
|
||||
<td><code>InputTransforms</code> registry (rewrite or answer input before the model)</td>
|
||||
<td>input-transform, inline-bash</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>D. Environments and models</td>
|
||||
<td>worker, server</td>
|
||||
<td><code>HarnessEnvironments</code> (ssh, gondolin, hands as kinds), <code>HarnessModels</code> (providers, virtual models), <code>LoginProviders</code></td>
|
||||
<td>ssh, gondolin, custom providers, jev-router</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>E. Asking the user</td>
|
||||
<td>server object, UI renderers</td>
|
||||
<td><code>Questions.ask</code> from worker code; <code>QuestionKinds</code> per UI kind</td>
|
||||
<td>permission-gate, question, questionnaire, timed-confirm, project-trust</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>F. UI</td>
|
||||
<td>tui, web</td>
|
||||
<td>registries: <code>Commands</code> (slash, palette, keys), <code>ToolRenderers</code>, <code>EntryRenderers</code>, <code>Slots</code> (header, footer, status, widgets), overlays through the UI context, editor extension points</td>
|
||||
<td>every UI-only example, the games</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>G. State and standing behaviour</td>
|
||||
<td>any</td>
|
||||
<td><code>local</code> objects owning natives, documents for state</td>
|
||||
<td>file-trigger's watcher, gondolin's VM, game saves</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<pre><code><span class="tk-c">// a plugin with a tool, a gate and a renderer: plugins/todo/</span>
|
||||
<span class="tk-c">// worker.ts</span>
|
||||
self.<span class="tk-f">contribute</span>(<span class="tk-t">HarnessExtensions</span>, { name: <span class="tk-s">"todo"</span>, tools: [todoTool], hooks: [<span class="tk-f">hook</span>(<span class="tk-t">ToolTask</span>, { beforeTool: guard })] });
|
||||
<span class="tk-c">// tui.ts</span>
|
||||
self.<span class="tk-f">contribute</span>(<span class="tk-t">TuiToolRenderers</span>, { todo: { call: (args) => <span class="tk-f">todoCall</span>(args), result: (r) => <span class="tk-f">todoList</span>(r.details) } });
|
||||
self.<span class="tk-f">contribute</span>(<span class="tk-t">TuiCommands</span>, { <span class="tk-s">"todo.show"</span>: { slash: { name: <span class="tk-s">"todos"</span> }, run: (ui) => ui.<span class="tk-f">overlay</span>(<span class="tk-s">"todos"</span>, <span class="tk-k">new</span> <span class="tk-t">TodoView</span>(ui)) } });
|
||||
<span class="tk-c">// web.tsx: the same registries with React components</span></code></pre>
|
||||
<h3 id="the-litmus-test">The litmus test</h3>
|
||||
<p>All 78 examples map onto the seven clusters (table in <code>/tmp/p3-prep/extensions.md</code>). 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
|
||||
<code>Questions</code> object, which works from any client, several clients or none.</p>
|
||||
<ul>
|
||||
<li><strong>Snake, Space Invaders, tic-tac-toe:</strong> a command plus an overlay in <code>tui.ts</code> and a canvas in <code>web.tsx</code>, engine in a
|
||||
shared file, saves in a user-keyed document. Fit today.</li>
|
||||
<li><strong>Doom:</strong> 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).</li>
|
||||
<li><strong>Does not fit, by design:</strong> 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 (<code>interactive-shell</code>
|
||||
becomes a terminal view; terminal notifications need a launcher action).</li>
|
||||
</ul>
|
||||
<h3 id="what-v1-leaves-out">What v1 leaves out</h3>
|
||||
<p>Editor replacement (extension points only), raw provider stream events and response headers, a fork hook, a
|
||||
<code>turn_start</code> hook, an event bus. Each has one or two examples; add when a real plugin needs it.</p>
|
||||
</section>
|
||||
|
||||
<section class="chapter" id="packages"><header class="opener"><div class="numeral">VII</div><h2>Work Packages</h2><p class="lede">A first cut, in dependency order, for the meeting to rearrange.</p></header>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Package</th>
|
||||
<th>Contains</th>
|
||||
<th>Depends on</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td>1</td>
|
||||
<td>p3 skeleton</td>
|
||||
<td><code>startApp</code> with p3's API v1 modules (shared, web, tui, worker), core plugins in the app's root, <code>p3</code> CLI</td>
|
||||
<td>Chord slices 11-13</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>2</td>
|
||||
<td>Sessions and conversations</td>
|
||||
<td>Durable on the Chord store, session lifecycle with delete, the conversation contract, subagents, history and handoff, code mode, shell commands</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>3</td>
|
||||
<td>Session index</td>
|
||||
<td>the index table, worker-to-server updates on metadata and idle changes, the session list, search over it</td>
|
||||
<td>2</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>4</td>
|
||||
<td>Session sync to Radius</td>
|
||||
<td>outbox upload and trim, epochs, pull-back, split, files as blobs, continue on Radius and back</td>
|
||||
<td>2, 3, Radius side</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>5</td>
|
||||
<td>Radius side</td>
|
||||
<td>per-session SQLite DO, commit log and SQLite on R2, epochs, the sync handshake, hand relay</td>
|
||||
<td>with 4</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>6</td>
|
||||
<td>Hands</td>
|
||||
<td>daemon as an outbound service, enrollment by pairing, multiplexed protocol, reattach after disconnects, server hand pool, <code>HarnessEnvironments</code> kind, SSH as a hand kind</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>7</td>
|
||||
<td>Web shell</td>
|
||||
<td>p2's layout and flows rebuilt on slice 11 ("look and feel great"), themes</td>
|
||||
<td>1, slice 11</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>8</td>
|
||||
<td>TUI</td>
|
||||
<td>p2's TUI on slice 11's sandbox, frame splitting, plugin assets path, keybindings and themes</td>
|
||||
<td>1, slice 11</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>9</td>
|
||||
<td>Models and settings</td>
|
||||
<td>catalog, credentials, provider login flows, settings, prompt resources</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>10</td>
|
||||
<td>Workspace</td>
|
||||
<td>files, git, diffs, search, review comments, file index and <code>@</code>, through hands</td>
|
||||
<td>6</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>11</td>
|
||||
<td>Plugin API v1 and docs</td>
|
||||
<td>the seven clusters published in <code>@p3/app/v1</code>, authoring guide, ported examples as the litmus (hello, permission-gate, todo, plan-mode, snake, doom)</td>
|
||||
<td>1, 2, 7, 8</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>12</td>
|
||||
<td>Access and status</td>
|
||||
<td>owner, devices and pairing (app kit), notices, logs, status and presence</td>
|
||||
<td>1</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
<p>Later: voice, terminals (channels and hands), artifacts, port forwards, chat mode and guest sharing, MCP and
|
||||
<code>tool_search</code>.</p>
|
||||
</section>
|
||||
|
||||
<section class="chapter epilogue" id="open-questions"><header class="opener"><div class="numeral">∴</div><h2>Open Questions</h2><p class="lede">To decide in the meeting.</p></header>
|
||||
<ol>
|
||||
<li>The proposed cut: base versus later per area (first table).</li>
|
||||
<li>Driver identity: epochs per session, handed out by Radius, with splits on conflict?</li>
|
||||
<li>The index with two writers: derived rows plus <code>{ epoch, seq }</code> tags, or one of the other options?</li>
|
||||
<li>Who syncs a closed session (the server reading its outbox, or starting its worker)?</li>
|
||||
<li>Files outside the store as blobs on R2, uploaded before their batch?</li>
|
||||
<li>Models and secrets for sessions continued on Radius: Radius's own models, or the owner's keys stored there?</li>
|
||||
<li>Hands: through Radius as a relay to the driver, and may one hand serve the local server and Radius at once?</li>
|
||||
<li>Do hands keep commands and terminals alive across disconnects (absorbing p2's terminal host)?</li>
|
||||
<li>Hand install and updates: signed binary, service install, version negotiation.</li>
|
||||
<li>Is SSH kept as a hand kind for machines without a service?</li>
|
||||
<li>Plugin API v1: the seven clusters as listed, and what is explicitly left out?</li>
|
||||
<li>Work package owners and order.</li>
|
||||
</ol>
|
||||
</section>
|
||||
|
||||
<section class="chapter appendix" id="sources"><header class="opener"><div class="numeral">A</div><h2>Sources</h2></header>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Source</th>
|
||||
<th>What</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody><tr>
|
||||
<td><code>/tmp/p3-prep/features.md</code></td>
|
||||
<td>p2's feature areas with files, sizes, tools, rough edges; the CLI, environment variables and data files</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>/tmp/p3-prep/extensions.md</code></td>
|
||||
<td>all 78 extension examples, the capability matrix, the clusters with sketches, the hard cases</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>/tmp/p3-prep/envs.md</code></td>
|
||||
<td>p2's environments and Radius link, Chord's sync pieces, the sync and hands sketches, open questions</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>apps/p2/docs/p3-functions/</code></td>
|
||||
<td>the functionality map (partly outdated)</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><code>packages/chord/docs/next/</code></td>
|
||||
<td>design, decisions (2026-11-02 meeting summary), app kit and plugin model, theming</td>
|
||||
</tr>
|
||||
</tbody></table>
|
||||
</section>
|
||||
|
||||
<p class="colophon">p3 Feature Set and Work Packages · meeting prep · 2026-11-03</p>
|
||||
</div></div>
|
||||
|
||||
</body></html>
|
||||
Loading…
Add table
Add a link
Reference in a new issue