Worker Interaction System
Worker Interaction System
Section titled “Worker Interaction System”The Worker Interaction System enables real-time interaction between users and running AI workers. It consists of three components: a sidecar inside the sandbox, a renderer on the user’s terminal, and a control channel that bridges them through the control plane.
Architecture
Section titled “Architecture”USER'S MACHINE CONTROL PLANE SANDBOX+-----------------------+ +------------------+ +--------------------+| | | | | || arpi attach w1 |<-- WS --->| SDK event | | agent (--print) || +-------------------+ | events | store (PG) | | | || | bubbletea TUI | | +ctrl | + NATS pub/sub |<- HTTP -| arpi-sidecar || | (renderer) | | | + control API | | - parse stdout || +-------------------+ | | | | - emit events || | +------------------+ | - relay control |+-----------------------+ | - monitor proc | +--------------------+Two Interaction Modes
Section titled “Two Interaction Modes”Interactive (arpi attach <worker-id>)
Section titled “Interactive (arpi attach <worker-id>)”- Renderer connects to WebSocket event stream (
/v1/workers/{id}/sdk-events/stream) - SDK events render as TUI widgets: streaming text, tool cards, collapsible results, progress bar
- When the agent needs tool approval, a
ControlRequestflows to the renderer as a modal prompt - User approves/denies via
POST /v1/workers/{id}/control - Sidecar polls
GET /v1/workers/{id}/control/pendingand relays the decision to agent stdin - Detach with
ctrl+d— worker keeps running
Autonomous (headless)
Section titled “Autonomous (headless)”- Sidecar runs with
autonomous: truein its config PermissionEngineauto-approves tools per theauto_approvelist- SDK events flow to the store for observability
- User reviews later:
arpi attach --read-only w1(replay from cursor 0)
Components
Section titled “Components”Sidecar (arpi-sidecar)
Section titled “Sidecar (arpi-sidecar)”Go binary deployed inside the sandbox at spawn time. Acts as the process manager for the agent.
Location: server/cmd/arpi-sidecar/
Responsibilities:
- Launch agent subprocess with adapter-specific args
- Parse agent stdout line-by-line through a pluggable
Adapter - Emit normalized SDK events via
POST /v1/workers/{id}/sdk-events - Monitor agent process lifecycle (heartbeat, exit detection)
- Poll for control responses and relay to agent stdin
- Apply permission decisions (auto-approve, deny, escalate to human)
Adapter interface:
type Adapter interface { ParseLine(line []byte) ([]*SDKEvent, error) FormatControlResponse(resp ControlResponse) []byte LaunchArgs(agentCmd string) []string}Two built-in adapters:
claude-code— Parses Claude Code’s--print --output-format stream-jsonNDJSON output. Mapsassistant,tool_use,tool_result,resultmessage types to SDK events.generic— Wraps each stdout line as aSystemEvent. Works with any agent.
Config (deployed as /etc/arpi/sidecar.json):
{ "adapter": "claude-code", "worker_id": "abc-123", "control_plane_url": "https://api.arpi.sh", "auth_token": "...", "autonomous": false, "permissions": { "auto_approve": ["Read", "Glob", "Grep"], "deny": [], "ask": ["Write", "Edit", "Bash"] }, "poll_interval_ms": 500}Deployment: The sidecar binary is cross-compiled and embedded in the arpid server binary via //go:embed. During spawn, deploySidecar uploads the binary and config to the sandbox after the credential proxy is deployed (Step 6.5 in the spawn pipeline).
Renderer (bubbletea TUI)
Section titled “Renderer (bubbletea TUI)”Integrated into the arpi CLI binary. Launched by arpi attach <worker-id>.
Location: cli/internal/renderer/
SDK event rendering:
| Event Type | Widget |
|---|---|
assistant | Streaming text block |
tool_use | Bordered card: [tool_name] input_summary |
tool_result | First 3 lines of output, error indicator |
result | Final status with icon |
progress | Status bar (tokens, cost) |
system | Level-colored dimmed line |
control_request | Modal prompt: Allow [tool]? (y/n) |
Key bindings:
ctrl+d/ctrl+c— detach (worker keeps running)y/n— approve/deny pending tool approvalup/down/pgup/pgdown— scroll event historyq— quit (when no prompt active)
Flags:
--read-only— replay events without control channel (auto-set for completed/stopped/failed workers)--raw— raw PTY proxy (not yet implemented, pending OpenSandbox PTY support)
Control Channel
Section titled “Control Channel”Bidirectional flow using the existing SDK event infrastructure.
Request flow (agent needs approval):
agent stdout → sidecar adapter → POST /v1/workers/{id}/sdk-events (type: control_request) → sdk.Store.Insert() → WebSocket stream → renderer shows modal promptResponse flow (user decides):
user presses y/n → POST /v1/workers/{id}/control → sdk.Store.Insert(control_response) → NATS publish → sidecar polls GET /v1/workers/{id}/control/pending?after_id={cursor} → adapter.FormatControlResponse() → agent stdinThe control channel reuses the sdk_events Postgres table — control_request and control_response are SDK event types. Pending responses use cursor-based pagination (after_id parameter) to prevent re-delivery.
Template Configuration
Section titled “Template Configuration”Templates opt into the sidecar via a [sidecar] TOML section:
[sidecar]adapter = "claude-code" # "claude-code" | "codex" | "generic"auto_approve = ["Read", "Glob", "Grep", "LS"]deny = []ask = ["Write", "Edit", "Bash"]Templates without a [sidecar] section skip sidecar deployment entirely. The permission engine uses three lists:
auto_approve— tools silently alloweddeny— tools silently rejectedask— tools requiring human approval (auto-approved in autonomous mode)
Tools not in any list default to “ask” (or “allow” in autonomous mode).
API Endpoints
Section titled “API Endpoints”SDK Events
Section titled “SDK Events”| Method | Path | Description |
|---|---|---|
POST | /v1/workers/{id}/sdk-events | Report SDK event (sidecar → server) |
GET | /v1/workers/{id}/sdk-events | List events (cursor-based) |
WS | /v1/workers/{id}/sdk-events/stream | Stream events (WebSocket, polling) |
Control Channel
Section titled “Control Channel”| Method | Path | Description |
|---|---|---|
POST | /v1/workers/{id}/control | Submit control response (renderer → server) |
GET | /v1/workers/{id}/control/pending | Poll pending responses (server → sidecar) |
SDK Event Types
Section titled “SDK Event Types”Eight normalized event types, defined in proto/arpi/v1/sdk_event.proto:
| Type | Direction | Description |
|---|---|---|
assistant | agent → user | Text output from the agent |
tool_use | agent → user | Agent invoking a tool |
tool_result | agent → user | Tool execution result |
result | agent → user | Agent completed its turn |
progress | agent → user | Token/cost counters |
system | agent → user | System-level notification |
control_request | agent → user | Permission needed for a tool |
control_response | user → agent | Permission decision |
Spawn Pipeline Integration
Section titled “Spawn Pipeline Integration”The sidecar is deployed as Step 6.5 in the spawn workflow, between the credential proxy (Step 6) and finalize (Step 7):
- Select embedded binary for target arch (linux/amd64)
- Upload to
/usr/local/bin/arpi-sidecar - Read auth token from
/var/run/arpi/token(written by cred-proxy step) - Build config from template
[sidecar]section - Upload config to
/etc/arpi/sidecar.json - Start sidecar in background:
ARPI_SIDECAR_CONFIG=/etc/arpi/sidecar.json arpi-sidecar
The sidecar then launches the agent as a subprocess and manages its lifecycle.
Security
Section titled “Security”- Sidecar auth: Uses the same per-worker bearer token as the credential proxy. Token is generated during spawn and stored at
/var/run/arpi/token. - No NATS from sandbox: The sidecar communicates with the control plane exclusively via HTTP. NATS access is not exposed to the sandbox network, maintaining the two-wall security boundary.
- Config via Upload: Sidecar config is written via the compute
UploadAPI, not shell interpolation, preventing injection attacks. - Ownership validation: Control endpoints validate worker existence before accepting requests.
Server
Section titled “Server”server/cmd/arpi-sidecar/— sidecar binary (8 files)server/internal/sidecar/types/types.go— shared wire typesserver/internal/sdk/control.go— control query methodsserver/internal/api/control.go— control HTTP endpointsserver/internal/api/sdk_events.go— SDK event HTTP/WS endpoints (modified)server/internal/spawn/deploy_sidecar.go— spawn pipeline stepserver/internal/spawn/sidecar_embed.go— embedded binary
cli/internal/renderer/— bubbletea TUI (model, widgets)cli/internal/api/events.go— WebSocket + control clientcli/cmd/attach.go— cobra command
proto/arpi/v1/sdk_event.proto— SDK event type definitions