pi-daemon1.3

Log 06 · Clients

Reach it directly.

pi-daemon is a complete agent host. Culture connects to it like any other host, pi-client puts TUI Pi in front of any session on any host, Paratenic can republish it beside the Pi you run in a terminal, editors speak ACP to it, and applications drive their agents with the same protocols. Every client sees the same sessions, live.

01Culture

One more host in Culture

Culture Web connected to a pi-daemon host named Atlas: five sessions in the catalogue, one marked Input; the Release notes chat shows the user's prompt, collapsed reasoning, a completed read tool call and a bulleted answer
Culture Web, connected straight to pi-daemon. The session read the changelog with its read tool before answering.
# ~/.config/culture/config.yaml
hosts:
  - id: atlas
    label: Atlas
    url: ws://127.0.0.1:7463/ahp
    token_file: ~/.local/state/pi-daemon/api-token
    default_working_directory: file:///home/me/src

Culture rereads the token file on every connection and requires it to be owner-only, so pointing it at the daemon’s own file works when both run as you. Both speak AHP 0.8.0; the daemon refuses any other version, so upgrade them together.

Culture Web split view: Release notes on the left; on the right Publish build 1056, where the agent's ask_user call waits on a question card asking which channel to publish to, with Decline and Submit
A question from ask_user: any client may answer, and the first answer wins.
Culture Web showing Clean the build, where a bash call to remove the build directory is pending confirmation
A bash call held for approval by its profile’s requireApproval.
Culture in a terminal: the catalogue lists five sessions on Atlas; the Publish build 1056 pane shows the reasoning, the ask_user call running, and an INPUT NEEDED card with three numbered choices
The same question in Culture’s terminal client.
  • Profiles are agentsEach profile is an agent named after it. A new session asks for a cwd under its roots, a model and a thinking level, filled with the profile’s defaults.
  • Sessions are sessionsEach pi-daemon session is one Culture session; its forks, side chats and subagents are its chats. History pages from the store; a running turn streams.
  • State that persistsRead and archive marks, the default chat and drafts are kept by the daemon across restarts; deleting a session deletes its store.
  • No Paratenic conventionsParatenic’s turn metadata, feed resource and identity hints are optional extras; pi-daemon does not provide them, and Culture works without them.

02pi-client

TUI Pi, detached from the agent

pi-client (also pi-daemon attach) is the Pi you run in a terminal, with the agent taken out of it. It draws with TUI Pi’s own components, theme, keybindings and tool renderers, reading your Pi settings.json and keybindings.json; the agent lives in a durable session on a host. Quit, close the lid, attach from another machine: the session never noticed.

pi-client's session picker listing eight sessions across two hosts, atlas and vega, each with its state: busy, waiting or done; a sidebar on the right groups the same sessions by host
Every host’s sessions in one picker, live: ? waiting for an answer, a spinner while working, done when finished.
pi-client and its hosts pi-client on one machine, left, holds one connection per host it knows: this machine's own daemon, pi-daemon on atlas and on vega, and an application's /chord endpoint behind a proxy, such as one agent of a coordinator. Each connection opens when the picker or sidebar needs it, is released when idle, and an unreachable host is retried every 30 seconds without stopping the others. Every host's sessions appear live in one picker and one sidebar. pi-clientone picker, one sidebarTUI Pi’s look and keys this machineits own pi-daemon atlaspi-daemon vegaunreachable · retried every 30 s an application’s agent…/agents/a1/chord /chord one /chord connection per host, opened when needed
Fig. 6.1One connection per host, opened when needed; one bad host never stops the others.
pi-client attached to Release notes on atlas: the prompt, the reasoning, a read tool call and the answer, with the sessions sidebar on the right
A session in TUI Pi’s own rendering; Alt+1–9 switches sessions from the sidebar.
pi-client on Publish build 1056: the agent's ask_user call, and its question with three choices shown where the editor usually is
An agent’s question, asked in place of the editor, as Pi asks it.
// ~/.pi/agent/settings.json — TUI Pi’s own file; pi-client reads its "client" section
{
  "client": {
    "hosts": [
      { "name": "atlas", "url": "http://atlas.example.ts.net:7463", "tokenFile": "~/.config/pi-daemon/atlas.token" },
      { "name": "vega",  "url": "http://vega.example.ts.net:7463",  "tokenFile": "~/.config/pi-daemon/vega.token" }
    ],
    "defaultHost": "atlas",
    "sidebar": { "enabled": true },
    "notifications": { "bell": true }
  }
}
  • Every host at onceOne file serves every machine: the local daemon is found among client.hosts by its URL. The picker and the sidebar list every host’s sessions live; --host NAME, HOST/SESSION and /new @host choose where.
  • An agent connector--url takes any /chord endpoint, including one behind an application’s proxy — wss://…/agents/a1/chord — so you can drop into one agent of a coordinator with nothing else installed.
  • Pi’s keys and commandsEnter steers while the agent works, Alt+Enter queues a follow-up, Esc aborts. Queued inputs show whole above the editor, taking at most a third of the pane (the wheel scrolls back through more); what an abort withdraws becomes the conversation’s draft, which the editor shares with every other client of it, Culture included. /model, /thinking, /compact, /fork, /new, /name, /sessions, /tasks, the profile’s module commands and ! shell commands.
  • Exactly once, always connectedEvery submission carries a request id reused on retry, so it runs once even across a host restart. The connection comes back by itself from fresh values, and typing never waits on the network.

pi-client is growing into a session aggregator for a whole fleet: today it shows and switches between every host’s sessions in one place, with a bell when another session starts waiting or finishes. It speaks only /chord, so anything that serves it — a daemon, or an application’s proxy — is a host it can join.

03From elsewhere

A phone, a laptop, another node

RouteDaemonCulture
tailnetapi.bind = the tailnet address, with allowInsecureHttpws://100.x.y.z:7463/ahp with allow_plaintext_non_loopback: true
TLS proxyloopback bind; the proxy forwards upgrades and the Authorization headerwss://host.example/ahp

Every client of an instance shares its bearer and sees all its sessions. A client that must not see some sessions needs its own instance, with its own state directory, port and bearer.

04Paratenic

Beside the Pi you run in a terminal

Paratenic is an AHP host for Pi sessions that are not durable — a pi in tmux, say. Its connect ahp connector republishes another AHP host’s sessions next to them, so one Culture connection shows both pools. pi-daemon is the usual upstream.

pi-daemon beside terminal Pi, through Paratenic Culture holds one AHP connection to Paratenic. Paratenic carries a Pi running in a terminal through its extension and backend socket, and, through paratenic connect ahp, republishes pi-daemon's sessions: the connector speaks standard AHP 0.8 to pi-daemon's /ahp and registers each profile as provider pi-daemon:<profile> over one backend connection. Culture can equally connect to pi-daemon directly, shown dashed. Below, one turn's identity: Culture's turn t7 keeps its id through the connector, whose client id is stable, so pi-daemon's Durable request id is ahp:paratenic-upstream-pi-daemon:t7 and a resent turn runs once. Cultureone connection, both pools Paratenicthe AHP host for Pi sessions Pi in a terminalextension · backend socket paratenic connect ahpprovider pi-daemon:<profile> pi-daemon/ahp · its own stores AHP socket backend AHP 0.8 or directly ONE TURN’S IDENTITY Culture turn t7 connector: same turn id t7 Durable request ahp:paratenic-upstream-pi-daemon:t7
Fig. 6.2The connector keeps the turn id and a stable client id, so pi-daemon’s request id is the same on every resend.
paratenic connect ahp \
  --url ws://127.0.0.1:7463/ahp --provider-id pi-daemon \
  --token-file ~/.local/state/pi-daemon/api-token
# or, in Nix: services.paratenic.upstreams.pi-daemon = { url = …; tokenFile = …; };
ChooseWhen
Culture → pi-daemonpi-daemon’s sessions are all you need on that node. Fewer moving parts, and every daemon feature, terminals and changesets included, reaches Culture as the daemon serves it.
Culture → Paratenic → pi-daemonyou also run Pi in terminals and want one host for both pools. The connector relays turns, steering and queued messages, cancel, model and thinking, questions, chats, forks and history.

Paratenic adds its own guarantees on the way: a command whose upstream outcome is unknown is reported indeterminate and never replayed, and after any restart it rebuilds each relayed chat from pi-daemon’s snapshot, keyed by turn id, so nothing is lost or doubled. See Paratenic’s join paths for its side of the link.

05Applications

A profile plus an AHP client

An application delivering a message to its agent The application ensures its agent's session with createSession on a URI it chooses, for its profile and a cwd under the profile's roots; retrying succeeds without creating anything. It subscribes to the root chat and starts a turn whose id is its own message id; pi-daemon submits it to Durable under ahp:<clientId>:<messageId>, so a resend after a restart is rejected rather than run twice. Inside the worker, the application's own extension module can hold tools in a beforeTool hook while the application has the agent paused. The answer streams as chat actions; an ask_user question arrives as chat/inputRequested and anyone may answer it; the turn's end state is the delivery status. createSession ahp-session:/agent-7profile myapp · cwd1 ok — again: nothing new2 chat/turnStarted · turnId m-423 submit · ahp:myapp:m-424 beforeTool: paused?5 not paused: run it6 chat/* as the answer streams7 chat/turnComplete = delivered8 timers, schedules, deadlines: the application’s own applicationits own AHP clientapplicationits own AHP client /ahppi-daemon/ahppi-daemon workerits profile’sworkerits profile’s its modulein the workerits modulein the worker
Fig. 6.3The message id is the turn id; the turn’s end state is the delivery status. pi-daemon keeps neither tickets nor receipts.
The application needsIt does
an agent that existsidempotent createSession on a URI it chooses; a title in the config names it
to deliver a messagea turn whose id is the message id; a busy agent takes it as a steering or queued message
heartbeats, schedules, batchingits own timers, sending ordinary turns
a deadlineits own timer, then chat/turnCancelled
to pause an agentits extension module blocks tools in beforeTool
per-agent credentialsa file beside each agent’s directory, found by sessionEnvFiles
delivery statusthe turn’s end: chat/turnComplete, chat/turnCancelled or chat/error

Keep the AHP client id stable across the application’s restarts: it is half of every request id. Culture sees the application’s sessions live, so a person can watch an agent or answer its question while the application drives it.

The full application guide

06Editors

ACP for the editor you already use

Point an editor’s ACP client at ws://127.0.0.1:7463/acp/personal with the bearer as Authorization. It gets new and loaded sessions of that profile, prompts streamed as session updates, cancel, and the agent’s questions as permission requests. The session it creates is an ordinary pi-daemon session, so it also appears in Culture.

What ACP maps to

07JavaScript and /web

Durable’s own views, for Node and the browser

import { ChordClient } from "@harryaskham/pi-daemon";

const chord = await ChordClient.connect({ url: "http://127.0.0.1:7463", token });
const done = await chord.call("conversation.submit", {
  sessionId, requestId: messageId, content: "Summarise today’s changes", wait: true,
});
pi-daemon's own /web page: a list of sessions with their titles
/web: the session list.
pi-daemon's /web page showing the Release notes conversation with its prompt, the read tool call and the answer, and a composer
A conversation, live, with send, steer and abort.

/web is deliberately small: a window onto the same Durable views for when Culture is not to hand. Log in once with the bearer; the cookie lasts 12 hours and only opens /chord on the same origin.