4.2 KiB
Operations
Layout
cmd/byre:byre serveruns the server;byre rundrives one Claude session in the current directory from the terminal.api/openapi.yamlis the API source of truth.go generate ./internal/apiregeneratesinternal/api/api.gen.go, whose types are also the store's model types.internal/store: SQLite (WAL). Every mutation and its event commit in one transaction; events are published to SSE subscribers inseqorder.internal/server: HTTP handlers, identity, SSE.internal/git: worktree create and remove.internal/agents: the agent session manager. It owns the running Claude processes and records their activity as events.internal/claude: the Claude Code stream-json client (seedocs/claude-protocol.md).internal/report: thereportMCP tool and the Stop hook that requires it each turn.web/: the SPA, embedded into the binary.byre serve --devserves./webfrom disk instead (run from the repo root).
Data
State lives in the byre directory under Go's os.UserConfigDir() (on macOS, ~/Library/Application Support/byre): byre.db and worktrees/<project-id>/<task-id>. Task branches are byre/<task-id> in the project's repo.
Running as a service
The listen address is listenAddr in cmd/byre/serve.go; it binds loopback only.
go install ./cmd/byre
cp deploy/run.fc.byre.plist ~/Library/LaunchAgents/
launchctl bootstrap gui/$(id -u) ~/Library/LaunchAgents/run.fc.byre.plist
tailscale serve --bg --https=<port from listenAddr> http://<listenAddr>
byre gets its own HTTPS port on the machine's ts.net name rather than a path under another service: the SPA and API use absolute paths, which break under a path prefix. tailscale serve status shows the URL and what else the machine serves.
After rebuilding: go install ./cmd/byre && launchctl kickstart -k gui/$(id -u)/run.fc.byre. Logs go to ~/Library/Logs/byre.log.
Clients reach tailscale serve over HTTP/2; it proxies to byre over HTTP/1.1 and passes SSE through unbuffered.
Identity
tailscale serve adds Tailscale-User-Login and Tailscale-User-Name to proxied requests. The server accepts API requests only from loopback and only with both headers; anything else gets 401 or 403. The SPA's static files need no identity. Users are created on first request; each sets their commit identity with PUT /api/me.
To call the API locally without Tailscale, send the headers yourself:
curl -H 'Tailscale-User-Login: you@example.com' -H 'Tailscale-User-Name: You' http://<listenAddr>/api/me
Agent sessions
POST /api/tasks/{id}/sessions starts a Claude session in the task's worktree and sends the prompt as its first turn; POST /api/sessions/{id}/messages starts later turns and POST /api/sessions/{id}/interrupt ends the current one. A task has at most one session that isn't completed. The session's commits use the git identity of the user who created it, so that user must have set one with PUT /api/me.
byre chooses the Claude session ID (vendor_session_id) up front, so every session can be resumed. Processes start on demand: the first message after a restart resumes the session with --resume. Sessions are running from the moment a turn is sent until its result arrives; on startup, every session still marked running was cut off mid-turn, and byre resumes it and sends a message telling the agent to re-check the working tree. Stopping the server kills all agent processes. Completing a task kills its sessions and marks them completed.
Agent activity becomes events with agent_session_id set: user.message, agent.message, agent.thinking, agent.tool_call, agent.tool_result, agent.report, turn.ended, and session.state. Anything else Claude Code emits (rate-limit status, task progress, state changes) is recorded unchanged as agent.vendor.
Events
GET /api/events is server-sent events: id is the event's seq, data the Event JSON. Reconnecting with Last-Event-ID (EventSource does this itself) or ?after= replays everything after that seq before going live. A client that falls further behind than its subscriber buffer (subscribe in internal/server/hub.go) is disconnected and catches up by reconnecting.