# Control plane

How arbe orchestrates execution, organized around the thread. The CLI has no local database: thread state is read and written through the arbe HTTP API; Postgres owns current state, the thread's durable stream owns history. Local pi sessions (`arbe chat`) live under `.arbe/pi-sessions/<threadId>/` as pi's scratch — the arbe stream is still the durable truth. Status lives on the thread.

```
arbe thread create <parent> [--env <env>]      → create a thread (--env gives its bots a sandbox to reach; no trigger yet)
arbe thread entries create <ref> "<msg>"       → thread/agent ref; POST fires dispatch
arbe thread entries read <id>                  → tail live; exits on a dispatch terminal (non-zero on failed)
arbe thread result <id>                        → the last assistant message
arbe thread diagnose <id>                      → classify the last-dispatch stage
arbe thread list                               → threads in the active house

arbe chat [msg]                                → local pi via the per-thread session-dir; re-enters on the same thread
arbe x -e <env> -- <argv…>                     → one-off command on the env's sandbox (not a thread)
```

Authed threads are portable: when you `arbe auth login`, every thread is inspectable from any device through the API. Unauthed = local-only; an unauthenticated CLI has no threads to list. Auth is the consent boundary. The API is the single point of authority — threads from CLI, web, DO alarm, or webhook all go through one creation path that writes the thread + stream before execution starts. No surface creates durable authenticated thread state independently.

Code: `apps/cli/src/commands/thread.ts`, `packages/core/threads.ts`.<br>
See [threads](./threads.md), [system/dispatch](./dispatch.md), [daytona runtime](./sandbox-daytona.md), [system/tasks](../tasks.md).
