Skip to content
WIP

This guide is a work in progress. Barry is also a work in progress.

The control layer

Barry describes itself as two things: an SDK for building agent capabilities, and “a control layer, for enforcing tool & network access per identity and session.” This page explains the second half. It names the services that make up the control layer, says what sits outside it, and then follows one tool call from the moment you start a session to the moment a handler runs, with a pointer to the code at each step.

The control layer is the set of resident services that decide what a session may do and then hold it to that decision. Each one is a launchd job declared in its bag’s bag.yaml under services:, and each runs as its own process.

  • identities.identities manages identities, traits, bounds, and device credentials. The other services check each request’s credential, the instance secret or a scoped device credential, against its store.
  • sessions.api owns the session lifecycle and its records. A launch records its session here before the harness connects, and hosted sessions resolve their egress sandbox here.
  • mcp.server serves every bag’s tools to a harness through one MCP server, filtered and checked per session.
  • schedules.scheduler fires the schedules every registered bag declares and records their runs.
  • workflows.runtime owns workflow history, executes activities, serves the runs console, and decides which worker may claim each activity.
  • vault.api serves secrets over a unix socket as its own operating-system user, so a credential resolves without the daemon sharing your account.

Other bags can declare services too. barry status lists every service with its state, port, and health, and Services and stores covers how to start, stop, and inspect them.

Two things sit outside the control layer, and the boundary matters because the control layer can only enforce what passes through it.

Bag code. A bag’s manifest declares intent, and the host supplies the runtime behavior. Bag code must not import the engine or the host’s loading and authentication machinery, as the host contract states. A tool handler owns its domain behavior, and Barry evaluates authorization before the handler runs. When a handler receives the list of tools a session can call, that list describes capabilities; it grants nothing.

Harness behavior. Harness adapters receive an effective session configuration and own what the harness does with it. The module ownership table draws the same line: service management doesn’t own detached session harnesses. A harness’s native tools, such as its own shell and file tools, run inside the harness and never reach mcp.server. The control layer reaches them only through the session configuration Barry writes and the hooks a harness offers, which is why the adapters reference lists what each harness and mode can enforce.

The trace follows a session you start from the command line, with an MCP tool call that reaches a bag’s handler.

  1. You start a session. <identity> session start resolves the session’s grants: the identity’s traits and bounds plus any you pass with --traits or --bound. From those grants it derives the native-tool policy for the chosen harness and mode, and it refuses to launch when a bound would need to take back a native tool the mode can’t control. The steps live in launch-context.ts.
  2. Barry records the session first. The launch posts a session row to sessions.api at /api/v1/sessions/start, carrying the session ID, the identity, the selected bounds, and the harness. If you passed --bound and the row can’t land, the launch stops rather than run unbounded.
  3. Barry writes the harness’s MCP configuration. The barry server entry points at http://127.0.0.1:<port>/mcp?sessionId=<id> and carries BARRY_SECRET as a bearer header. Namespaces that split onto their own endpoint get /mcp/ns/<namespace>?sessionId=<id>. Before any adapter starts work, the SDK applies the native ceiling: it denies every native tool the grants and mode don’t permit, forces a strict MCP configuration so the harness loads no unscoped vendor servers, and refuses raw adapter arguments that could override the policy. <identity> session start --relax lifts the strict configuration and merges your user-level MCP servers into the session. Those servers don’t pass through Barry’s MCP server, so its tool filter and guards never see their calls.
  4. The harness connects, and mcp.server authenticates it. The server accepts the instance secret or a workflow session credential, and refuses a device credential. The session ID in the URL selects which policy applies; it authenticates nothing. The server checks that the session exists and belongs to the installation’s owner, and refuses a connection that names an unknown session. See transport-authorization.ts.
  5. The server resolves the session’s tools. resolveSessionTools in session-tools-resolver.ts reads the session row, resolves the identity’s credentials, and applies the identity’s tool visibility. When resolution fails, the server serves no tools and retries on a backoff.
  6. The server computes the grant. resolveSessionBound in session-bound.ts asks for the same grants every launch path composes. A connection with no session resolves to the default-trait grant. For a workflow session, it intersects the grant with the ceiling the run’s approval recorded, so a trait revoked since then narrows the run.
  7. Tools filter by qualified ID. filterToolsQualified in filter-tools.ts keeps a tool only when a trait grants it, its access level fits, and no denial, write restriction, or network action rule removes it. It returns namespace:name IDs, because two bags can export a tool with the same name, and a bare name would admit both. A file restriction also withdraws Barry’s Bash tool.
  8. Guards wrap the survivors. applyBoundGuards in tool-guards.ts wraps each handler with the bound’s file, shell, and network checks. A file guard canonicalizes each path parameter before it compares it with the deny and allowlist patterns.
  9. Every call asserts its grant. Before a handler runs, assertToolAllowed in execution-context.ts checks the node against the bound’s node rules and the tool’s qualified ID against the session’s allowed set. The server also revalidates the connection’s credential on each call, and closes the connection when access changes revoke it.
  10. The handler runs with its context. The server opens the session’s store context, so an identity-scoped store reads and writes that identity’s database, and passes the handler only the secrets its definition declares, plus the session ID.

Native tool calls take a separate path, because the harness runs them. The ceiling from step 3 sets which native tools exist in the session at all, and native-policy.ts decides which ones a bound takes back: a shell restriction takes back the shell, a file restriction takes back the shell and file tools, and so on.

Where a harness offers a hook, Barry adds a gate that rules on each call. Some examples:

  • In Claude Code’s hosted mode, a PreToolUse hook passes only granted native tools and the session’s MCP tools, and denies a call that gets no ruling before its deadline.
  • In Codex, a launcher runs the policy script and turns a missing script, a crash, a timeout, or a malformed answer into an explicit deny.
  • In Cursor, the hook script passes granted native tools and the session’s MCP servers, and denies on malformed input.
  • In Pi’s hosted mode, an extension blocks any tool outside the native allowance and the session’s MCP servers.

Claude Code’s guest and instrumented modes run the vendor’s interface with no Barry PreToolUse hook, so the ceiling acts there as a static restriction, not a per-call ruling.

A bound with network.enforce: sandbox asks for egress enforcement outside the agent’s reach. In Claude Code’s hosted mode, sdk-sandbox.ts runs the agent’s whole process tree behind a proxy that filters outbound traffic by hostname. An absent allowlist permits no outbound host, the sandbox fails rather than run unsandboxed when it can’t start, and the agent can’t opt a command out of it.

The proxy filters by host, not by HTTP method, so it can’t tell a read from a write to the same host. A bound that denies only some network actions still relies on the guards from step 8, and a hosted session that asks the sandbox to enforce such a split refuses to start. The bounds reference lists which fields each layer enforces.