Skip to content

Architecture

Repowire is a local-first routing daemon plus thin transport adapters for each agent runtime.

Agent runtime
  ├─ Go hooks + native inbox + stdio identity shim (Claude Code)
  ├─ Go hooks + stdio identity shim (Gemini)
  ├─ App Server bridge + MCP (Codex)
  ├─ plugin + WebSocket (OpenCode)
  ├─ extension (Pi)
  └─ Channel bridge / ACP transport (Claude Code experimental)
Go HTTP/WebSocket/MCP daemon on 127.0.0.1:8377
Dashboard, Telegram, Slack, orchestrator peers, relay, and other peers

The daemon is the single routing hub. It does not care whether a peer arrived through hooks, an OpenCode plugin, the Pi extension path, a bot, relay traffic, or experimental channel/ACP delivery. Every peer is represented in the registry and routes messages through the same core message layer.

Runtime-side components that translate a native session API into this common protocol are called bridges. Their lifecycle follows the runtime: Codex needs a supervised companion, while Claude Channels, OpenCode, and Pi load their bridge inside the agent session.

Repowire architecture diagram

Core modules

Area Files Responsibility
CLI and installers daemon-go/cli/ Setup, runtime installation, service/peer/job/schedule/session commands, embedded client assets
Configuration daemon-go/config/ YAML/default/environment loading for daemon, spawn, relay, MCP, and experiments
Daemon routes daemon-go/hub/ HTTP/WebSocket server, dashboard, history/timeline, daemon endpoints, and /mcp
Peer state daemon-go/peer/ Typed identity registry, lifecycle FSM, circles, roles, lazy repair, and contradictions
Message routing daemon-go/service/delivery.go, daemon-go/service/router.go, daemon-go/service/transport.go, daemon-go/service/acp.go ACP-before-WebSocket delivery, receipts, queuing, and transport lifecycle
Ask lifecycle daemon-go/service/ask_tracker.go, daemon-go/hub/routes_ask_lifecycle.go Open asks, reminders, structured answers, and ack/reply delivery
Sessions and spawn daemon-go/service/session_control.go, daemon-go/service/spawn_service.go Resume prevalidation, destructive pane proof, executor acquisition, and controls
Schedules and jobs daemon-go/service/scheduler.go, daemon-go/service/job_runner.go, daemon-go/service/job_completion.go, daemon-go/state/ Deadline-driven schedules, durable work, turn completion, executor-death failure, SQLite state
Hooks daemon-go/hooks/ Runtime adapters, ws-hook supervision, Claude inbox/tmux delivery, chat extraction, remote approval
Codex bridge daemon-go/codexbridge/ App Server lifecycle, native thread steering, and chat events
MCP daemon-go/hub/routes_mcp*.go, daemon-go/mcpstdio/ Complete daemon-owned HTTP tool surface plus the per-runtime identity proxy
Control surfaces web/, daemon-go/mobile/ Dashboard and native Telegram/Slack human peers
Relay daemon-go/relayserver/, daemon-go/relay/ Native hosted server plus the daemon's outbound tunnel client

Transports

Hooks + MCP

Claude Code and Gemini use native Go lifecycle hooks for registration, status, and chat extraction. Their repowire mcp process resolves a daemon-minted runtime certificate and proxies tool JSON-RPC to /mcp, where all 31 tool implementations live.

Claude Code 2.1.224+ receives messages through its per-session native inbox; the same WebSocket hook retains tmux injection as a compatibility fallback. Gemini still uses tmux injection. Stop-hook reminders resurface unacked asks. The identity shim lazily registers on tool calls, and the daemon ignores a claimed identity header unless its certificate proof is current and bound to that peer.

Codex App Server

The separately supervised Codex companion owns the default Unix control socket and translates App Server threads into ordinary mesh peers. thread/started registers a peer before its first prompt; turn and item notifications drive status and dashboard chat events. Inbound delivery uses turn/steer for an active turn or turn/start for an idle thread. Restarting the routing daemon does not restart App Server or the Codex TUI.

If a hook-backed orchestrator reconnects after daemon restart or WebSocket churn without carrying its prior peer_id, the daemon may reclaim the existing offline identity when the role, display name, circle, backend, and path match unambiguously. Queued notifications for that peer are replayed over the renewed WebSocket before falling back to Stop-hook or CLI draining.

OpenCode plugin and Pi extension

OpenCode does not expose the same hook shape, so Repowire installs a TypeScript plugin. The plugin holds a WebSocket connection to the daemon and bridges OpenCode session events into the same peer/message model. Pi uses Repowire's extension path when setup detects the pi CLI or config.

Claude Channel bridge / ACP transport

repowire setup --experimental-channels installs the embedded TypeScript Claude Channel bridge. Messages arrive as <channel source="repowire"> tags. The HTTP-backed MCP identity shim remains installed for stable tools. The Go daemon also contains the experiment-gated ACP subprocess client and maps ACP permission requests onto the shared blocking-question path. Channel mode still requires Claude Code support, claude.ai login, and bun.

Relay

The daemon connects outbound to the hosted relay over WSS. The relay tunnels dashboard HTTP/SSE calls and bridges WebSocket traffic without requiring inbound access to the user's machine.

Lazy repair

Repowire avoids polling loops. Liveness repair, persistence flushes, and ghost cleanup are piggy-backed on user-visible requests, bounded by cooldowns. The design rule is: repair when needed, not on timers.

When lazy repair detects a self-inconsistent peer (e.g. online but no live WebSocket, a missing pane, or a dead agent pid) it emits a peer_contradiction event once per transition, so silent failures surface in the dashboard stream. repowire peer doctor <peer> is the explicit, operator-triggered counterpart: it runs the same reconciliation, then reports identity, inbound reachability, pending asks, and contradictions on demand. repowire peer rehook <peer> is the non-destructive recovery (re-establish the inbound ws-hook without killing the pane). A delivery trace ledger (repowire trace <id>) records per-message ask/notify stages for post-hoc "where did it go" inspection, persisted in a dedicated SQLite table rather than the bounded dashboard event buffer.

v0.14 session-native direction

The current stable surface is peer-oriented, but the v0.14 architecture train is moving toward a session-first mesh:

  • Sessions become the durable unit of work.
  • Peers remain runtime executors.
  • Ask/notify delivery now goes through a delivery service plus transport router; WebSocket hooks, experimental ACP, relay, and future transports continue moving toward transport-neutral routing.
  • The dashboard currently shows a selected peer/session timeline, merging Claude Code or Codex local history where available with realtime events.
  • The first session-targeted control routes resolve repowire_session_id bindings to an active executor or explicit resume capability status.
  • Broader composer actions, scheduling, approval handling, and backend/model controls move toward the same shared session command surface.

This is a roadmap. Current routes and tools still expose peers, circles, asks, notifications, and schedules. The ask/notify delivery-service and transport-router extraction has landed, but ACP remains experimental and not every route/control path is transport-neutral yet.

Knowledge graph

graphify-out/GRAPH_REPORT.md summarizes the codebase graph. The current report identifies the main hubs as AgentType, Config, PeerRegistry, MessageRouter, and WebSocketTransport, with communities around daemon routing, CLI/setup, channel installer, Telegram, attachments, hook normalization, relay auth, and peer lifecycle.

Keep generated graph JSON and cache files out of prose docs. Link or summarize the report when useful; do not paste large graph artifacts into README or hand-written docs.