Introduction

OverviewArchitectureAgent Experience

The product

This repository

Structure

Usage

Other

Guides

Connect a Remote Agent

The intended end-to-end runbook for enrolling a machine, minting a token, claiming a task and writing results back

Nothing in this guide works yet

There is no MCP endpoint, no /api/v1 REST surface, no Machine, Agent, AgentToken, Task or Run table. Following these steps today fails at step one. This page documents the intended runbook so the design can be reviewed and built against. Every command below is a proposed shape, not a working call.

OPB Brain does not execute anything. The work happens on a remote workstation running Claude Code or Codex, which reads context out of OPB Brain and writes status, events and artifacts back. This runbook is the full loop for one such workstation.

1. Enroll the machine

A Machine is a physical or remote workstation that can host a runtime. Enrollment records its identity and capabilities so tasks can be routed to it.

curl -X POST https://api.opb.brain/api/v1/machines \
  -H "Authorization: Bearer $OPB_ADMIN_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "hostname": "workstation-01",
    "os": "linux",
    "arch": "x86_64",
    "connection": "mcp",
    "capabilities": ["rust", "next.js", "browser"]
  }'

The response carries the machine id (mch_01J…) and its heartbeatTtl. A machine silent past that TTL marks its agents offline and releases their leases.

2. Mint an AgentToken

Tokens are scoped per agent and per machine so one can be revoked without touching the others. The token value is returned once, at creation, and is hashed at rest. There is no way to read it back.

curl -X POST https://api.opb.brain/api/v1/agent-tokens \
  -H "Authorization: Bearer $OPB_ADMIN_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{
    "agentId": "agt_01J...",
    "machineId": "mch_01J...",
    "scopes": ["tasks:read", "tasks:claim", "runs:write", "artifacts:write"],
    "expiresAt": "2026-12-31T23:59:59Z"
  }'

Store it on the workstation, never in the repository:

export OPB_AGENT_TOKEN="opb_at_..."

The token carries the agent's toolAllowlist. The MCP server refuses tools outside it rather than hiding them, so a call to a disallowed tool returns an error, not silence.

3. Point Claude Code at the MCP endpoint

The MCP server is a streamable-HTTP endpoint at /mcp, authenticated with the AgentToken.

claude mcp add --transport http opb-brain https://api.opb.brain/mcp \
  --header "Authorization: Bearer $OPB_AGENT_TOKEN"

Confirm the identity the server resolved before doing anything else:

> use the opb-brain whoami tool

whoami returns the calling agent, its capabilities, its tool allowlist and any leases it already holds. If it returns leases, the session is a resume, not a fresh start. Go to step 8.

4. Read the rules instead of guessing them

The specs are exposed as MCP resources, so the rules the server enforces are the rules the agent can read:

spec://domain-model      entities, state machine, invariants
spec://state-machine     legal task transitions
spec://conventions       ids, slugs, tenancy, versioning, provenance
spec://graphql-schema    the committed SDL

Canonical workflows ship as MCP prompts: start-work, hand-off, report-blocked, close-out. Run the prompt rather than reinventing the sequence.

5. Claim a task

tasks_claim is an atomic claim of the next ready task. The server picks the highest-priority ready task matching the capability filter, sets status=in_progress, creates a Run, and returns a lease with a TTL plus the full working context: brief, acceptance criteria, and the contextRefs the agent must read before starting.

> use tasks_claim with capabilities ["rust"] and projectId "prj_01J..."

The REST equivalent, for a runtime without MCP:

curl -X POST https://api.opb.brain/api/v1/tasks/claim \
  -H "Authorization: Bearer $OPB_AGENT_TOKEN" \
  -H "Idempotency-Key: $(uuidgen)" \
  -H "Content-Type: application/json" \
  -d '{"agentId": "agt_01J...", "capabilities": ["rust"]}'

Two agents claiming at the same moment can never both win. The claim is a single conditional update.

6. Heartbeat while working

Renew the lease at least every leaseTtl / 3, with a one-line human-readable status and a progress percentage.

> use runs_heartbeat with runId "run_01J...", progress 40,
  status "cargo test green; wiring the CLI flag"

A runner that stops heartbeating is assumed dead, not done. The lease expires, the Run is marked abandoned, the task returns to todo and attemptCount increments. Nothing is lost and nothing double-runs.

7. Write back as you go

  • comments_post on any decision a human would want to see, and on every blocker. A comment with blocking: true and no answer moves the task to blocked.
  • artifacts_attach for the diff, the PR link, and any screenshot proving the work.
  • approvals_request before a destructive or outward-facing action. The task sits in blocked until a human decides.
  • documents_upsert for anything the next agent would otherwise have to rediscover.

8. Resume after a crash

No human help required, and no in-memory state:

whoami → open leases → tasks_get → events_stream?since=<lastEventId> → back to work

events_stream is the append-only timeline. Replaying from the last event id the session saw reconstructs everything that happened while it was gone.

9. Close the run out honestly

runs_finish records the true outcome: the exit reason, usage, and the PR url. The acceptance criteria checkboxes must reflect what was actually verified.

> use runs_finish with runId "run_01J...", outcome "succeeded",
  prUrl "https://github.com/org/repo/pull/128"

A failed run reported as failed is worth more than a green lie. Report failed with the exit reason and post a comment saying what blocked it.

The state machine will refuse dishonest shortcuts anyway:

  • in_review requires an externalRef of kind github_pr.
  • ready_to_merge can only be set by an actor other than the one who set in_review. The implementer never promotes its own work.
  • done requires every acceptance criterion checked.
  • Every transition writes an Event. There are no exceptions.

An illegal transition returns 422 invalid_transition with the legal alternatives in the body, so the next call is always knowable from the last failure.