Skip to content
View as .md

pi

Arbe’s LLM driver is @earendil-works’s pi-mono stack. At runtime we use pi-ai, pi-agent-core and the pi CLI binary (pi-coding-agent). Upstream READMEs are the source of truth for each API — read one with gh api repos/earendil-works/pi-mono/contents/packages/<pkg>/README.md --jq .content | base64 -d (<pkg>: ai, agent, coding-agent).

  • pi-ai — one LLM API across providers (Anthropic, OpenAI, Google, …), and arbe’s provider boundary: import @earendil-works/pi-ai/built-in provider factories, never a provider SDK like @anthropic-ai/sdk. A Context is plain serialisable data, so a conversation can move between providers mid-run.
  • pi-agent-core — the tool-calling loop over pi-ai. In-thread replies run on its Agent.
  • pi-coding-agent — the pi CLI binary. Coding runs drive it in RPC mode on the box. To resume on a fresh box, arbe writes a pi session file (version: 3) rebuilt from the thread. Its entries form an id/parentId tree, and compaction replaces old entries with one summary, so a rebuild that skips an entry type silently drops conversation state.

Two engines, one per kind of work:

bot reply (Absurd dispatch task) pi-agent-core Agent, a checkpointed step per model call and tool (packages/core/dispatch/agent-turn.ts), run in the backstage
delegated coding run arbe-pi-runner drives `pi --mode rpc` on the box → thread

Both share the rest. A human message sent while either works steers it: the reply takes it in after its current tool round, pi after its current step. Both end through one decider, decideAgentOutcome (core/pi/outcome.ts), which reads the last assistant message, a stop, the process exit or a refused key as completed, cancelled or failed with one plain message. Both call the same arbe tools: a reply directly, a coding run through pi’s arbe-tools extension and /api/threads/:id/tools, as the bot that delegated it (dispatch). Env-bound replies reach the sandbox through run_command; a coding run is stopped, steered and resumed as described in daytona runtime.

Sessions and decoding

Arbe owns the per-thread session directory, scoped by arbe thread id: arbe-sessions/<threadId>/ under the sandbox’s home. A resumed run on the same box passes --continue; a fresh box gets a session rebuilt from the thread and --session.

pi’s JSON wire decodes through decodePiEvent in packages/core/pi/events.ts into pi.assistant, pi.tool_result and pi.compaction envelopes, plus signal.thread.pi_retry when pi retries a failed model call (the failed attempt’s pi.assistant is dropped). arbe-pi-runner reads the same wire from pi’s RPC stdout in the box and posts it to the thread; a run ends at agent_settled, not agent_end, since pi can still retry, compact or take a queued steer after agent_end.

There is no live typing text. Each finished message lands as it completes: an in-process reply per tool round, a coding run per pi message_end; meanwhile the thread’s current view shows the bot’s phase (“writing…”) and any tool still running, for a reply and a coding run alike. A few old threads still hold typing text (pi.message_start, pi.message_update, pi.chunk); it parses and is never shown.

pi is a process boundary

@arbe/core and @arbe/cli must not import pi-coding-agent at runtime. Its module init reads its own package.json, which fails under bun build --compile, and its heavy deps (a TUI, WASM, zip/file-type/glob, and more) would balloon every @arbe/core import. So treat pi as a process boundary and decode its wire or file output at arbe-owned seams.

Auth

OPENROUTER_API_KEY is the base key for in-process bot replies and sandbox pi turns. Direct provider keys such as ANTHROPIC_API_KEY work only when the chosen model ref uses that provider. Both are server-side only: the browser talks to route handlers, never pi-ai. Bots use model refs (agent.model, defaulting to DEFAULT_BOT_MODEL in @arbe/core/schemas/agent-model). A person who connected their ChatGPT plan runs their own turns on it, through pi-ai’s OpenAI provider with a plan access token (pi auth).

Code: packages/core/pi/events.ts, packages/core/pi/models.ts, packages/core/dispatch/. Proof: bun run apps/www/scripts/prove-pi-ai.ts. See system/dispatch, system/auth, system/secrets.