Picophony
Picophony is an additive, first-party Rust layer for driving a headless Pi agent over Pi's documented JSON-RPC mode (pi --mode rpc). It is a new mode, not a replacement: it never modifies the existing tmux/pi launch, messaging, or attach paths used by managed agent runtimes. Pi-equivalence is preserved by construction — Picophony runs the real pi binary with the caller's program/args/env (same flags, same plugin ecosystem) and only consumes Pi's published RPC and session-file contracts. Pi remains the sole writer of session files.
Tracked under epic.
Why Picophony
The managed runtime launches pi inside a Herdr workspace or tmux session and attaches operators to that pane. Picophony adds a second, structured way to drive the same pi process: a typed JSON-RPC channel plus a read-only session reader. That structured seam is what makes a single agent core renderable across native and web UIs — the standalone pico TUI, the caco TUI and daemon, the Apple and Android companion apps, and the browser — without each surface reimplementing Pi orchestration.
Architecture
Picophony is one shared core crate (caco-picophony) plus a thin standalone binary (pico):
protocol— typed RPC commands, responses, events, and extension-UI dialog types (the wire model).session— a read-only, forward-tolerant Pi session JSONL reader. Picophony never writes session files.PiBackend— the backend-swap seam. The shipping backend isRpcSubprocessBackend; a future in-process SDK backend can implement the same trait without changing callers.RpcSubprocessBackend— spawns a stock Pi RPC JSONL program, forwards typed RPC commands, and demultiplexes stdout into typed RPC events. Ordinary Pico launches usepi --mode rpc ....JsonlFramer— strict LF-only JSONL line framing per Pi's RPC rules.- host broker, unix socket, and attach client — the default tokio runtime layer that lets multiple local viewers attach to one driven agent.
The pico binary
pico is a standalone ratatui TUI that drives a headless pi --mode rpc underneath — a “nicer pi” usable with no Cacophony integration at all. The caco daemon and TUI consume the same caco-picophony library separately, so the standalone tool and the managed surfaces share one rendering core.
Conversation recovery
Use /help or composer completion to discover recovery commands. These are client-side controls, not model prompts, and remain usable above 100% context or after failed compaction.
/treeshows Pi entries, parent links and the active leaf, including abandoned branches. Long displays offer/tree <cursor>; browsing changes nothing./rewind <user-entry-id>forks a new session containing context before that user message. The original remains intact; no selected prompt, discarded tail or summary is submitted. Stable IDs distinguish duplicate messages./newstarts empty without compaction and preserves old history./sessionshows active session ID/file and the latest context reading./resume <session-file>switches to preserved history without a model request.
Rewind, new and resume clear queued steering/follow-up input before requesting the change. Extensions may cancel; Pico reports cancellation and keeps the current view. Only a confirmed change clears stale transcript/context state and refreshes messages, statistics and identity. The previous file is shown for returning with /resume. Cancel browsing by not submitting a rewind command.
Pi remains the only session writer. After a standalone restart, use the new backend-confirmed file with pico --session <file>, not the original launch path. Managed recreate --fresh and automatic relaunch selection have a separately tracked regression; Running is not proof of a fresh conversation. Verify session ID/file and context, especially for empty sessions which Pi may not yet have written to disk.
The shared composer/reducer exposes these controls to TUI, FFI/JNI and WASM consumers. Run python3 scripts/test-pico-recovery.py /absolute/path/to/pi with Pi 0.84.4 or compatible RPC for isolated real-runtime coverage: over-limit context, compaction failure, active and abandoned branch forks, a model/read-tool turn, fresh recovery, preserved history, switching and reopening the confirmed file.
Cargo features
caco-picophony keeps its surface bindings behind cargo features so each consumer compiles only what it needs.
| Feature | Default | Purpose |
|---|---|---|
runtime | yes | tokio runtime: subprocess backend, host broker, unix socket, attach client |
ratatui | no | shared ratatui AgentView renderer (the pico binary and caco-tui) |
ws | no | WebSocket session source: attach over the daemon agent session endpoint for remote/mesh attach |
ffi | no | C ABI surface for native bindings (Apple C ABI plus the symbol base for Android JNI shims) |
wasm | no | wasm-bindgen web binding (the PicoView JS class) |
Building with --no-default-features yields the pure protocol + view core (serde-only, no tokio) for wasm32 targets.
Cross-surface consumers
One core, many front-ends:
- Standalone
picoTUI — thepicobinary. - caco TUI and daemon — the Rust dashboards consume
caco-picophonydirectly. - Apple (iOS / macOS / watchOS) — via the C ABI (
ffi) and thecaco-embed-iosembedding crate. - Android — via the JNI shim built on the same C ABI symbol base.
- Web — via the
wasmPicoViewJS class.
Surface integrations are landing incrementally under the Picophony epic; not every front-end exposes the full agent experience yet. See the companion app guides for per-platform status.
Relationship to managed Pi agents
Picophony is strictly additive. Managed pi, pico, and on-demand worker agents retain their existing lifecycle, and Picophony never rewrites Pi's session files. The structured RPC channel is an alternative driver for surfaces that need a typed, renderable agent stream rather than a raw terminal pane.
Logical pi-daemon agents are intentionally different: they have no tmux pane or per-agent Pi process, and they do not speak the Pico protocol.
Each is the AHP session ahp-session:/<agent id> on its node's pi-daemon 1.0 host (pi_daemon.ahp).
/api/v1/agents/<id>/culture and /session both pipe the viewer to that session exactly as /culture does for Paratenic agents: the owning daemon creates the session if needed, holds the host bearer, and limits the pipe to the agent's own session and chat.
The TUI and Android open these agents in their Culture view; the web Pico pane speaks AHP for them (ahp-chat-view.js).
Closing a viewer closes only its pipe, never the resident session.
A raw terminal attach remains refused; there is no fallback that silently spawns a new local Pi runtime.
Pi Daemon host capacity
agents.pi_daemon (or the selected node's complete override) configures the separately supervised shared host. Cacophony remains a client: these settings become neutral host CLI flags, not per-agent implementation state. A node-local object replaces the global fallback; add limits to the existing object rather than dropping its socket, path or security declarations.
An explicit desktop/server profile can add:
max_sessions: 512
max_concurrent_turns: 256
max_session_queue_depth: 4
max_connections: 1024
max_api_connections: 1024
max_rpc_hubs: 512
max_rpc_replay_bytes: 2097152
max_rpc_total_replay_bytes: 1073741824
max_residency_leases: 2048
idle_session_ttl_ms: 1800000
recovery_open_timeout_ms: 30000
recovery_total_timeout_ms: 600000
node_max_old_space_size_mb: 16384
These settings are optional; omission preserves standalone defaults. Zero is permitted only for queue depth and idle eviction, and forwarded numbers must be JavaScript safe integers. The aggregate RPC replay budget must cover the hub count multiplied by the per-hub bytes, including omitted defaults. Raising only hubs above 32 without a matching budget is rejected at config validation, not deferred to a crash loop.
A synchronous wake holds a Unix connection while waiting, including queue time. Allow for active and queued workers plus control traffic; HTTP/WebSocket connections have a separate allowance. RPC hubs and residency leases concern attachments, not all headless wakes. max_line_bytes and max_events_per_request remain client decoding/retention bounds. Agent spawn ceilings and heavy build/test admission remain independent.
The CLI launch plan, native supervisor and capability preflight use one capacity argument mapping. New configured flags must appear in the selected host's help before an explicit restart can stop anything. Original session controls remain compatible with old incomplete help output. The new API/RPC/lease settings require Pi Daemon's PD-020 support; released 0.3.20 is too old. Do not silently omit requested settings to start an older host.
node_max_old_space_size_mb appends a numeric --max-old-space-size option to NODE_OPTIONS for the Pi Daemon service launch. Existing options are preserved, with the explicit heap setting taking precedence, without mutating the supervisor or sibling-service environment. It is not a logical-session environment declaration; Node child tools may inherit it normally. Old-space is not total RSS or preallocated memory: native allocations, buffers and tools require headroom. The native launcher already raises the child's file-descriptor allowance; no CPU quota or tool/build concurrency increase is implied.
After compatible binaries and source are installed, apply through the existing lifecycle owner during an approved safe restart window. Inspect the running service: Unix handshake/status report resident, turn, queue and lease limits; /v1/capabilities reports RPC budgets; startup capacity records also include Unix and HTTP connection limits. Preserve accepted and indeterminate tickets rather than resending prompts after timeouts or restarts. Config validation never restarts or reconfigures a running host.