blueclaw

Architecture

blueclaw is split into three parts that compile against each other.

blueclaw is split into three parts that compile against each other.

PartOwnsWhere
Host (blueclaw)connectors, policy, identity, POSIX isolation, task store, approvals, tool catalog, capabilities, memory, deliverythis repository
Harnessthe agent loop: run a turn and report what happenedbluecollar at .dependency/bluecollar, or an external agent through internal/acpharness and internal/cliharness
Contractthe types both sides compile against, and the harness portagentcontract and toolcontract in the bluecollar module

bluecollar is a separate repository pinned as a submodule and pulled in through a replace directive in go.mod. agentcontract travels with it because both sides need identical types. Building with -tags nobundledharness leaves the loop out of the binary; agent.harness.name must then name an external harness, and a test in cmd/blueclaw fails if the loop creeps back in.

  chat platform (via chatd) / HTTP / ACP client
            |
            v
  blueclaw daemon
    connectors · intake · policy · task store · approvals · tool catalog · POSIX projection
            |
            +-- agentcontract.Harness --+-- bluecollar (Go, in process)
            |                           +-- ACP or CLI agent, started as the requester
            |
            +-- tool execution --> blueclaw-posix-helper --> the requester's UID, GID and groups

The harness port

type Harness interface {
	RunTurn(context.Context, AgentTurnRequest) (AgentTurnResult, error)
}

The host opens the task run and hands the harness an ExistingTaskRunID to settle, so a first turn is recorded the same way whichever loop ran it. Everything else the host needs (events, cancellation, run lookup) it takes from the task store.

Deciding whether an inbound message becomes a task at all is host policy. One call to the decision model answers every closed question about a message: who it is addressed to, whether it follows on from a running task, and how the turn should be routed. The answer is memoized on the inbound event (internal/connectors/intake_decision.go, implemented by bluecollar's intake package).

The path of one message

  1. Ingress. A connector persists the raw event, resolves the sender to a person in the policy, and refuses accounts the policy does not know.
  2. Decision. Addressing is one of four outcomes: ignore, react only, reply, or react and reply. A message that is only an image is described by a vision model first.
  3. Busy routing. If the person already has an active task, the decision's busyRoute is one of status, steer, replace, cancel, new_task or unrelated.
  4. Launch. TaskLauncher.Launch (internal/agentruntime/task_launcher.go) runs nine steps, each recorded as an event so a failure names its step: resolve the requester's email, resolve the active circle, build the conversation's artifact manifest, provision the requester's workspace, build the tool set, audit the tool registry, load memory, carry out an approved call, run the turn.
  5. Delivery. The result is enqueued in the connector outbox and delivered.

Choosing a harness

agent.harness.name selects the loop, and internal/harnessselection is the only place a harness is named.

NameWhat runs
bluecollar (default)the bundled in-process loop
acpany Agent Client Protocol agent at agentCommandPath
claude-codethe claude CLI in headless mode
codexthe codex CLI
antigravitythe agy CLI

An external harness changes where the agent runs, how it reaches tools, and how the outcome is known. It starts inside the requester's POSIX identity. It reaches the catalog over MCP at /harness/tool-catalog with a session token revoked when the turn ends; agents that advertise MCP over HTTP get the endpoint directly, and the rest get a stdio server (the daemon binary in mcp-tool-catalog mode) that proxies to it. Because an ACP turn ending or a CLI exiting says nothing about whether the work was done, internal/turnoutcome decides the status from the agent's final message and the catalog tools that actually succeeded.

internal/acpharness also refuses ACP's own filesystem and terminal methods, which pushes file and shell work onto the catalog. Tools an agent runs inside its own process are answered yes and recorded as harness.tool_permitted or harness.tool_refused; the POSIX identity is the limit on those.

Serving ACP

blueclaw can also be the agent. Started with --acp-socket <path>, it serves ACP on that unix socket (recreated at start, mode 0600). --inbound acp makes ACP the only path that admits a message, and POST /connectors/<platform>/events then answers 409. See ACP sessions.

On this page