Skip to content
View as .md

pi 1.0: target shape

Started 2026-10-03 after upgrading to pi-ai 1.0.1 and pi-coding-agent 1.0.0. Research behind it: three reports (how arbe runs pi today, pi 1.0 as an embedding surface, pi-durable’s fit) summarized here; this doc is the working surface, not the reports.

Shape

thread entry ──▶ director ──▶ bot turn ─┬─▶ in-process loop (backstage) pi agent loop, Absurd step per model call / tool
└─▶ delegate_task `pi` on a machine, session persisted in a thread
▲
one tool registry ─────────────────────────────────┘ (both loops get the same arbe tools)
pi events ──▶ one decoder (core schema) ──▶ thread entries + one outcome decider

A coding run is pi on a machine whose session is persisted in an arbe thread. Stop and resume are first-class.

Target: multiplayer threads

Decided 2026-10-04, inspired by pi-durable’s viewState(), watch() and submit({whenBusy: "steer"}).

  • Many people and clients work with the same thread at once: they watch it, join late, and steer it.
  • A client first gets the current view, then only what changes. A late joiner or a reconnect starts from the current view, not by replaying the whole thread.
  • Two kinds of state. The transcript is the immutable thread stream, finished messages only. The view is what’s happening right now: who’s working, running tools, queued messages, usage, and in principle the in-progress answer. The view isn’t stored on its own: it is folded from the progress signals already in the stream (phases, tool calls without results, steers, run status), so it costs the transcript nothing new. That’s where “writing…” lives, and where typing text would live if we ever want it.
  • Any client can steer a running bot: a message posted mid-reply joins the running work after the current tool call. This already works for coding runs. In-thread replies are next, now possible because they run on pi’s agent loop.
  • The view is exposed through the API, so www and the CLI are thin clients over it.

Decided

  • Two engines: the in-process loop for thread replies, pi as a process for coding runs. Not one.
  • No pi-durable, no chord. The thread is the durable record; a second single-owner log would fight it. Borrow its ideas: replay-safe tools, idempotent submissions.
  • No live typing text (2026-10-03): finished messages land per step, like before; the bot shows “writing…” meanwhile.
  • Coding runs can be stopped and resumed.
  • The interactive chat command is deleted from the CLI (2026-10-03).
  • Coding runs get arbe’s tools, so agents are one kind of thing with one tool set rather than two loops with two.

Open, one at a time

  1. Replies appearing as the bot writes them. Decided 2026-10-03: no live text. Finished messages land per step, like before: per tool round for in-process replies, per message_end for coding runs. The bot shows “writing…” from its dispatch phase, a coding run from its status. The few old threads with typing text keep it unrendered; no legacy machinery.
  2. Coding runs: pi RPC mode in the runner (gives stop, steer, resume) and delete the mirror extension. Done 2026-10-03: the runner drives pi --mode rpc; chats in the child steer it, stop aborts it, a later human message resumes the same session (lossless rebuild on a fresh box). Mirror, run.ts, CodingAgent and ARBE_SANDBOX_PROVIDER deleted, pending/deferred handled in the sandbox decider. Later the same day each run became one backstage task that alone claims, ends and reports it; the runner only reports.
  3. Replace dispatch/tool-loop.ts with pi’s agent loop, with an Absurd step around each model call and tool run. Done 2026-10-03: agent-turn.ts runs in-thread replies on pi-agent-core’s Agent. Each model call and tool run is a checkpoint in the wake task, appends go through a per-turn idempotent producer, the reply lands with its director outcome, and tools mark themselves replay: 'safe' or get an honest “may or may not have run” after a crash. The allow/deny list still hides tools rather than refusing them in beforeToolCall; that hook re-checks the director lease before a tool that changes something. Now possible, not done: per-tool executionMode: 'sequential' for tools sharing a budget, terminate for tools like post_to_thread, and transformContext as the compaction hook.
  4. One outcome decider in core for both engines. Done 2026-10-04: decideAgentOutcome (core/pi/outcome.ts) maps the last assistant message, a stop, the process exit and a funded-key refusal to completed, cancelled or failed with one plain message, for replies and coding runs alike. A killed pi now reads crashed; only the runner’s own time limit reads timed out.
  5. One tool registry exposed to coding runs through the arbe API. Done 2026-10-05: tools marked codingRuns: true reach a run through /api/threads/:id/tools, called by pi’s arbe-tools extension as the delegating bot. The house-files token and its curl recipe are gone.
  6. Deletions riding along: out-of-date bits of pi.md and dispatch.md. Done 2026-10-05: pi.md describes the two engines and what they share (steering, one outcome decider, one tool registry); dispatch.md names the outcome decider and the shared argument repairs. (The stale “redelivery reuses the reply” claims went with item 3; run.ts, CodingAgent, ARBE_SANDBOX_PROVIDER and the unused decoder fields went with item 2.)
  7. Steering in-thread replies. Done 2026-10-04: a human message the director would hand to the running bot is routed to its reply task as an event, taken in after the current tool round, checkpointed there, and listed on the reply’s outcome so the director reads it as answered. Past the last tool round it stays owed.
  8. One “current view” per thread through the API. Done 2026-10-04: reduceThreadNow in core folds the stream into who’s working, running tools, waiting and steered messages, and usage for the current reply or run; GET /api/threads/:id/now serves a snapshot from the director’s decided offset (a coding run’s own start), and the web app, arbe thread now --follow and entries read keep folding the tail. A reply now appends signal.dispatch.steered when it takes a message in. The fold also lists the coding runs a thread delegated until each hands back, seeded from the run records, and clients follow each child’s own view for its tools; GET /api/threads/:id/usage serves the ledger rollup that arbe thread usage, arbe thread now and the web usage page share.

Later, bigger: is arbe a pi-durable application? The blog (2026-10-01) pitches it as a multiplayer harness where one process owns storage and clients attach, steer and fork, with subagents owned by the tool call that started them. That is close to arbe’s own thesis, and backstage is already one daemon. The conflict is who owns the log: today the thread stream is the record and many writers append to it. Adopting it means backstage owns each thread and www and the CLI attach as clients, the shape arbe ran as celld and deleted in August (one-daemon-dispatch.md). Its storage also assumes one exclusive writer with no fencing. What it lacks is choosing which of several bots speaks; each conversation has one agent, so the thread director stays arbe’s own. Worth a spike once pi-durable stops being experimental: a Postgres storage backend, backstage as the harness, threads as conversations. Borrow now regardless: replay-safe tools, idempotent submissions, abort that cascades to owned runs, background compaction, first-write-wins memos for decisions that must survive a crash, and handoff/reset with older messages searchable by a tool as the compaction answer.

Later, not now: pi-ai’s lighter /models entry for the web bundle; an offline Jev experiment for the director’s yes/no questions.