mirror of
https://github.com/badlogic/pi-mono.git
synced 2026-11-05 23:37:25 +00:00
A manager on the p2 server owns every MCP connection and process. Session workers hold one view each and lease the locations their conversations work at; the manager runs the owner's mcp.json servers there (stdio per session and location, or shared; HTTP shared by default) and offers their tools as deferred Durable tools found through tool_search. Stdio servers on an environment start through its pi-env daemon, one connection per environment shared by its instances, through cmd.exe on Windows. A project's .pi/mcp.json, read by the worker at each location, may add stdio servers that run there and switch the owner's servers on or off; the manager refuses everything else. A lease gets servers only on environments its session works on.
178 lines
12 KiB
Markdown
178 lines
12 KiB
Markdown
# p2 handoff
|
||
|
||
For whoever continues p2, human or agent, starting cold. It assumes you know pi and this repository, but not the
|
||
history of p2. Everything you need is in the repository; nothing depends on earlier conversations.
|
||
|
||
## What p2 is
|
||
|
||
p2 (`apps/p2`) is Pi Mobile rebuilt on **Chord**, an object runtime (Chord v2: `@earendil-works/chord`,
|
||
`packages/chord`; p2 runs on Chord v2 only, see `docs/p2-notes.md` **Chord v2**; Chord v1 is deleted).
|
||
Every feature is a plugin (`apps/p2/plugins/*`) with facets per process: `server.ts`, `worker.ts` (one worker per
|
||
session, running Durable), `web.tsx` and `tui.ts`. There is no manifest and no dependency list: p2 discovers plugins
|
||
by listing their facet files and loads them alphabetically. Chord resolves dependencies by contract name, so their
|
||
order does not matter; a missing provider fails loudly, naming the consumer. Compatible backend reloads preserve
|
||
unchanged state and resources; a successful reload publishes rebuilt web and TUI bundles.
|
||
|
||
p2 replaced Pi Mobile (formerly `apps/mobile`, removed; see git history). Its web UI matches Pi Mobile's (same DOM,
|
||
classes, styles); the data layer is Chord objects instead of Pi Mobile's hand-written protocol. On top of Pi Mobile,
|
||
p2 has hot reload with explicit reload/restart controls, logs and owner notices, chat mode with replies and `@pi`
|
||
mentions, owner sign-in links, an isolated TUI with pi's look and keys, and security hardening (CSP, restricted guest
|
||
web assets, WebSocket limits).
|
||
|
||
p2 lives in `earendil-works/pi` at `apps/p2` and keeps the name p2.
|
||
|
||
## State at handoff
|
||
|
||
| Where | What | Port | Data |
|
||
| --- | --- | --- | --- |
|
||
| Phone (Termux, Node 26) | p2, the daily driver; Pi Mobile's sessions were imported when it replaced Pi Mobile | 8787 | `~/.pi/p2` |
|
||
| slayer (`slayer.marioslab.io`) | p2 in dev mode, public at https://p2.mariozechner.at | 8788 | `~/.pi/p2` (test data) |
|
||
|
||
Done: everything Pi Mobile did (checked by a four-part audit; what differs on purpose is under Decisions in
|
||
`docs/p2-notes.md`), the switch of the phone, the TUI port phases P0–P7 and P9–P11, and Chord in `packages/chord`
|
||
replacing the old facet/service runtime and the `pi-client`, `pi-protocol` and `pi-server` packages (pi
|
||
`6db4070f1`). Open items are under [Open work](#open-work).
|
||
|
||
## The phone
|
||
|
||
- **Checkout:** `~/pi-private`, branch `private`. Old data: `~/.pi/mobile` (keep it: imported history refers to
|
||
attachments there), `~/.pi/p2.parallel` and `~/.pi/p2.trial-oct10` (leftovers, deletable).
|
||
- **p2** runs in tmux session `p2` from `~/pi-private/apps/p2` (as planned for the switch; confirm with `tmux ls`
|
||
and `ps`):
|
||
```sh
|
||
PORT=8787 P2_ALLOWED_HOSTS=pim.ngrok.pro P2_PUBLIC_URL=https://pim.ngrok.pro npm run dev
|
||
```
|
||
Update: `git pull`, `npm install --ignore-scripts` at the root if dependencies changed, then **Restart** in the app or
|
||
`npm run reload -- --restart` (plugin changes: **Reload**).
|
||
- **Tunnel:** ngrok as a termux-services (runit) service, `$PREFIX/var/service/ngrok/run`, forwarding
|
||
`https://pim.ngrok.pro` to port 8787 (`sv status|restart $PREFIX/var/service/ngrok`; logs in
|
||
`$PREFIX/var/log/ngrok/current`).
|
||
- **Radius:** not configured (no `radius.json`); `npm run radius:login` enables it.
|
||
- **No boot scripts.** Run `termux-wake-lock` so Android keeps the processes alive.
|
||
|
||
## Running p2
|
||
|
||
```sh
|
||
cd apps/p2
|
||
npm run dev # dev mode: apply plugin changes, including web facets, with Reload or `npm run reload`
|
||
npm start # without the dev supervisor
|
||
P2_FAKE=1 npm start # fake model, no provider needed
|
||
npm run owner-link [url] # one-time owner sign-in link for another device (run on the machine itself)
|
||
npm run tui [url] # TUI (Node >= 26.5.1); on loopback it signs in as owner automatically
|
||
npm run reload [plugins] [--restart]
|
||
npm run check # Biome + tsc
|
||
npm test # full suite; TUI tests need Node 26, browser tests need CHROMIUM_BIN
|
||
```
|
||
|
||
Environment variables: `docs/p2-notes.md` (Running). Configuration in the data dir: `models.json`, `settings.json`
|
||
(incl. `codemode`, default on in p2), `envs.json`, `mcp.json`, `auth.json`, `radius.json`, `forwards.json`.
|
||
|
||
**Sign-in.** Requests from the device itself are the owner. Other devices get owner rights with a link from `npm
|
||
run owner-link` (single use, 10 minutes, long-lived signed cookie). Guests join through share links. Requests other
|
||
websites make never carry owner rights. Sign out per device or all devices in the Devices sheet.
|
||
|
||
**Dev mode.** A file watcher only marks plugins dirty; reloads are explicit (button, `npm run reload`, the
|
||
owner-only `reload_plugins` tool) because a watcher can't tell when an agent has finished a multi-file edit.
|
||
Runtime code (`src/hosts`, `src/runtime`, workspace packages such as `packages/chord`) needs a restart; the "Server
|
||
code changed" pill appears only when a module the server actually loaded changed.
|
||
|
||
**slayer.** `~/p2.mariozechner.at/` holds `run.sh` (starts `npm run dev` from the deploy worktree `~/p2-live`),
|
||
`deploy.sh` (moves `~/p2-live` to `origin/private` and restarts), `bridge.mjs` (unix socket for Caddy →
|
||
127.0.0.1:8788), `Caddyfile` and `docker-compose.yml` (Caddy only marks requests as proxied; p2 authenticates on its
|
||
own). It runs in tmux session `p2`. Development happens in `~/pi-private`; `~/p2-verify` is a scratch worktree for
|
||
verifying an agent's files on their own before committing. Node 24: `~/node-v24.4.1-linux-x64/bin`; Node 26 (TUI):
|
||
`~/.local/bin/node26`. Browser tests: `CHROMIUM_BIN=/home/badlogic/.local/bin/chromium-docker`.
|
||
|
||
## Architecture in five minutes
|
||
|
||
- **Chord v2** (`packages/chord`, `docs/design.md` there): objects per (type, params, principal) placed on host kinds
|
||
(`Server`, `Session({ id })`), JSON Schema contracts, held references, documents (`defineState`) and derives,
|
||
natives (`self.open`), keyed registries, binary links over WebSocket and IPC with routes (browser → server →
|
||
worker), the workers component (`createWorkers`) and the plugins component (`createPlugins`: build, load,
|
||
rollout). How p2 uses it: `docs/p2-notes.md` **Chord v2**.
|
||
- **Hosts** (`src/hosts`): `server.ts` (HTTP, auth, WebSocket admission), `web.ts` (web serving), `bundles.ts`
|
||
(content-named web and TUI esbuild bundles), `worker.ts` (session worker),
|
||
`tui*.ts` (launcher, sandbox, filter, grants), `dev*.ts` (dev supervisor).
|
||
- **Runtime** (`src/runtime`): shared contracts and helpers (principals, worker and Harness, Durable registry,
|
||
extension switches, input entries, settings, logs, fetch).
|
||
- **Plugins**: chat, chat-mode, sessions, subagents, agent (one-shot agents), codemode, coding-tools, prompt,
|
||
models, login, search, status, drafts, files, send-file, artifacts, workspace, file-index, terminals, envs, mcp, ports,
|
||
radius, sharing, devices, proxy-marker, logs, dev-tools, powershell, web-shell. Before writing one, read Chord's
|
||
tutorial (`packages/chord/docs/tutorial/`) and plugin guide (`packages/chord/docs/plugin-authoring.md`), then an
|
||
existing p2 plugin of the same shape.
|
||
- **Locations:** every conversation works at `{ env, cwd }`: the main one at the session's, subagents spawned with a
|
||
cwd at their own (on the caller's environment), the others following the session. A session's location changes in
|
||
place: relocating an idle running worker fences it, updates the catalog and moves the main conversation and its
|
||
followers without a restart; subagents with their own location stay. Plugins resolve a conversation's location
|
||
and environment through `Harness.environments`, the session's through `WorkerProcess.location`
|
||
(`docs/p2-notes.md`, Agent locations and Relocation).
|
||
- **Durable** (`packages/durable`) runs the agent: conversations, inputs as custom entries (`p2.input`,
|
||
`chat.mention`), display-only writes placed immediately, code mode (QuickJS sandbox; scripts call tools as
|
||
`tools.<name>`, one-shot agents via `tools.agent`), compaction with visible failures, deferred tools and
|
||
`tool_search` (MCP tools are deferred).
|
||
|
||
## The TUI
|
||
|
||
`npm run tui [url]` (Node ≥ 26.5.1). The launcher owns the terminal, the connection and sign-in; all TUI code (pi-tui
|
||
and plugin facets) comes from the server as one bundle and runs in a worker that drops every Node permission first.
|
||
Output passes a strict escape-sequence allowlist; privileged actions (copy, open URL, download, external editor,
|
||
attach, saving preferences, sign-out) need launcher grants, mostly with a launcher-drawn consent screen. Preferences
|
||
(keybindings, theme, display settings) are the visitor's own (`~/.pi/agent`, `~/.pi/p2-tui/prefs.json`). Threat
|
||
model: `DEV.md` and `README.md` (TUI isolation). Design and as-built notes: `docs/p2-tui-port.md`.
|
||
|
||
## Rules
|
||
|
||
From `AGENTS.md` and this project's history:
|
||
|
||
- **One tree.** p2, the packages, root config and scripts change together in `earendil-works/pi`. After p2 changes
|
||
run `npm run check` both at the root and in `apps/p2`.
|
||
- **Commit only verified states.** When several agents edit one tree, copy an agent's files into a separate worktree
|
||
on top of `HEAD`, run `npm run check` and the full `npm test` there, and commit exactly that content
|
||
(`git hash-object -w` and `git update-index`). A browser test that times out under load and passes on its own is
|
||
acceptable; anything else is not. Lockfile commits need `PI_ALLOW_LOCKFILE_CHANGE=1`; npm 11 strips `libc` fields,
|
||
revert that noise. Never `--no-verify`.
|
||
- **Deploy only pushed commits** (`~/p2.mariozechner.at/deploy.sh` on slayer).
|
||
- **Port from the diff.** Changes copied from elsewhere start from the commit (`git show <hash>`), never from a
|
||
summary; briefs to agents name the commit and the files.
|
||
- **No hand tests on real sessions.** Use throwaway sessions under `/tmp`, or a separate server on another port with
|
||
a temporary data dir. Never print cookies, tokens or share links.
|
||
- **Check `pwd`** before working in a subagent, and spawn subagents with an explicit `cwd`.
|
||
- **`pkill -f` matches its own command line.** Kill by PID or by `/proc/<pid>/cwd` instead (it once killed the live
|
||
tmux session).
|
||
- TypeScript: no `any`, erasable syntax only, top-level imports only (except the plugin loader's dynamic import).
|
||
Use Opus-class models for documentation and tutorials.
|
||
|
||
## Open work
|
||
|
||
1. **The phone's daemon binary:** p2's file index uses the pi-env daemon's `listFiles` when the packaged daemon has
|
||
it, else the old git/walk index. slayer has a fresh build in `packages/env/bin/pi-env-linux-x64` (static musl;
|
||
built with `RUSTFLAGS="-C linker=rust-lld -C link-self-contained=yes" cargo build --release --locked --target
|
||
x86_64-unknown-linux-musl` in `packages/env/daemon`). On the phone: `cargo build --release --locked` there and copy
|
||
it to `packages/env/bin/pi-env-android-arm64/pi-env`. The next pi-env release ships all targets through CI.
|
||
2. **TUI:** launcher images, a filtered terminal relay, a "trust like SSH" mode, web tool cards
|
||
(`docs/p2-tui-port.md` §10). Fork from subagent conversations; fork/clone, shell and templates have no web UI yet
|
||
beyond rendering.
|
||
3. **Cleanup leftovers:** two HTTP helpers were not merged because their semantics differ (byte ranges: files
|
||
serves `bytes=-` whole, workspace answers 416; body readers: sharing answers 400 past the limit, devices 413);
|
||
decide one behavior each.
|
||
4. **MCP:** `plugins/mcp` (WP6 and WP7 of `docs/mcp-durable.md`: owner `mcp.json`, host stdio and HTTP, deferred
|
||
tools, `tool_search`, servers on environments, project `.pi/mcp.json`); OAuth (WP8) and code mode namespaces (WP9)
|
||
are open.
|
||
5. **npm:** deprecate `@earendil-works/pi-client`, `pi-protocol` and `pi-server` (1.1.0), which no longer exist.
|
||
6. `docs/p2-notes.md` TODO: the TypeScript 5.9.3 pin (Termux), an isolating plugin loader, sandboxing third-party
|
||
facets.
|
||
7. **Slow test suite:** about 11 minutes serially; files could run in parallel once their isolation is fixed. The
|
||
chat-mode long-press reply browser test times out under load.
|
||
8. **After the Chord v2 port:** the TUI on Chord's sandbox (C2); no per-link call limit any more (owner decision;
|
||
Chord v2 has none). Chord v1 and the old-page shim are deleted (C1).
|
||
|
||
## Documents
|
||
|
||
| File | Content |
|
||
| --- | --- |
|
||
| `README.md`, `DEV.md` | running, deployment behind a proxy, TUI sign-in and isolation |
|
||
| `docs/p2-notes.md` | decisions, configuration, plugin-by-plugin notes |
|
||
| `docs/p2-tui-port.md` | TUI design, as-built notes, open phases |
|
||
| `docs/workflows.md` | code mode workflows with one-shot agents |
|
||
| `docs/remote-envs.md` | SSH environments |
|
||
| `packages/chord/docs/` | Chord v2's design, slices and guides; `tutorial/` for writing plugins |
|