Flows
How key ops cross package/runtime boundaries. Read surfaces for capability map; this doc show motion.
Message dispatch
Human or bot post message to room. Message land in durable stream, then agents in room get chance respond.
sequenceDiagram
participant Caller as Caller (browser / CLI / bot)
participant WWW as www API
participant DS as Durable Stream
participant Dispatch as Agent Dispatch
participant DO as ArbeAgent DO
participant LLM as Claude API
Caller->>WWW: POST /api/streams/:scopeId
WWW->>WWW: checkPermission(agent, scope, W)
WWW->>DS: append message
DS-->>Caller: 200 OK
WWW--)Dispatch: dispatchToAgents(roomId, message)
Note over Dispatch: fire-and-forget via waitUntil()
Dispatch->>Dispatch: list agents with R on scope
Dispatch->>Dispatch: exclude message author
loop Each eligible agent
Dispatch->>DO: activate(event)
DO->>DO: check cooldown, relevance gate
alt Agent decides to respond
DO->>DS: read last N messages
DO->>LLM: messages + system prompt
LLM-->>DO: response
DO->>DS: append reply (bypasses dispatch)
DO->>DO: log activation(outcome: replied)
else Agent skips
DO->>DO: log activation(outcome: skipped, reason)
end
end
Run: web dispatch
User dispatch prompt from web UI. Run created, then Durable Object drive sandbox session to completion.
sequenceDiagram
participant Browser
participant WWW as www API
participant DB as Postgres
participant RS as Run Stream (Durable)
participant Disp as ArbeDispatcher DO
participant Sandbox as Sprite (Fly.io)
participant OC as opencode server
Browser->>WWW: POST /api/runs/dispatch {prompt, house_id}
WWW->>DB: insert run (status: queued)
WWW->>RS: create run stream
WWW-->>Browser: 202 {runId, streamId}
WWW--)Disp: dispatch(runId, prompt, sprite)
Note over Disp: fire-and-forget via waitUntil()
Disp->>Disp: resolve secrets for scope
Disp->>Sandbox: sync secrets
Disp->>RS: append status.changed → dispatching
Disp->>Sandbox: POST /session/create
Sandbox->>OC: create session
OC-->>Sandbox: sessionId
Sandbox-->>Disp: sessionId
Disp->>RS: append session.started
Disp->>Sandbox: POST /session/prompt {message}
loop Poll until terminal
Disp->>Sandbox: GET /session/:id
Sandbox-->>Disp: session state
Note over Disp: check for completion, error, permission request
end
Disp->>RS: append session.finished
Disp->>RS: append result
Disp->>DB: update run (status: completed/failed)
Run: CLI dispatch
CLI dispatch to sandbox directly via sprite connection. Session observer map opencode SDK events to run lifecycle events, then sync to remote.
sequenceDiagram
participant User
participant CLI as arbe CLI
participant Local as Local SQLite
participant Sprite as Sprite (Fly.io)
participant OC as opencode server
participant WWW as www API
participant DB as Postgres
participant RS as Run/Session Streams
User->>CLI: arbe do "fix the bug"
CLI->>CLI: resolve sandbox connection
CLI->>Local: write run (status: queued)
CLI->>Sprite: POST /session/create
Sprite->>OC: create session
OC-->>CLI: sessionId
CLI->>CLI: createSessionObserver()
CLI->>Sprite: POST /session/prompt
loop Stream events from opencode
Sprite-->>CLI: SSE events
CLI->>CLI: observer maps to run events
CLI->>Local: update run state
end
CLI->>Local: write run (status: completed/failed)
Note over CLI: Sync to remote
CLI->>CLI: buildSessionPayload()
CLI->>CLI: runToPayload()
CLI->>WWW: POST /api/runs {run + sessions + messages}
WWW->>DB: upsert run, sessions, messages
WWW->>RS: append run stream events
WWW->>RS: append session stream events
Permission resolution
Every permissioned op check agent access by walking up parent chain. Room inherit from house. Mode = bitmask: R=1, W=2, X=4.
flowchart TD
A[checkPermission\nagentId, scopeId, requiredMode] --> B{Direct permission\non scopeId?}
B -->|Yes| C{hasMode?\npermission.mode & required}
C -->|Yes| D[✓ Allowed]
C -->|No| E[✗ Denied]
B -->|No| F{scopeId has\nparent_id?}
F -->|Yes| G[Walk to parent_id]
G --> H{Permission\non parent?}
H -->|Yes| C
H -->|No| I{Parent has\nparent_id?}
I -->|Yes| G
I -->|No| E
F -->|No| E
style D fill:#2d5,stroke:#1a3,color:#fff
style E fill:#d33,stroke:#a22,color:#fff
Session sync
After CLI session end, arbe sync full session data to remote so visible in web UI and to other surfaces.
flowchart LR
subgraph Local [Local / Sandbox]
OC[opencode SQLite] --> BP[buildSessionPayload]
Run[Run file] --> RP[runToPayload]
end
subgraph Payload [Wire format]
BP --> Sync[ArbeRunSync]
RP --> Sync
end
subgraph Remote [Remote]
Sync --> API[POST /api/runs]
API --> PG[(Postgres)]
API --> RStream[Run stream]
API --> SStream[Session stream]
end
Agent activation pipeline
When dispatch fire, ArbeAgent Durable Object decide whether respond. Decision path inside DO.
flowchart TD
E[Activation event arrives] --> Dup{Dedup check:\nsame message ID\nalready processed?}
Dup -->|Yes| Skip1[Skip: dedup]
Dup -->|No| Cool{Cooldown check:\nlast reply < threshold?}
Cool -->|In cooldown| Skip2[Skip: cooldown]
Cool -->|OK| Mode{Trigger mode?}
Mode -->|mention| Mention{Message mentions\nthis agent?}
Mention -->|No| Skip3[Skip: not mentioned]
Mention -->|Yes| Respond
Mode -->|ambient| Rel{Relevance gate:\nLLM decides if\nworth responding}
Rel -->|Not relevant| Skip4[Skip: relevance]
Rel -->|Relevant| Respond
Respond[Build context:\nlast N messages +\nhouse context +\nsystem prompt] --> LLM[Call Claude API]
LLM --> Reply[Append reply\nto stream]
Reply --> Log[Log activation:\noutcome, tokens, duration]
Skip1 --> LogSkip[Log activation:\nskipped + reason]
Skip2 --> LogSkip
Skip3 --> LogSkip
Skip4 --> LogSkip
style Respond fill:#2d5,stroke:#1a3,color:#fff
style Skip1 fill:#666,stroke:#444,color:#fff
style Skip2 fill:#666,stroke:#444,color:#fff
style Skip3 fill:#666,stroke:#444,color:#fff
style Skip4 fill:#666,stroke:#444,color:#fff