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.
What the control layer is
Section titled “What the control layer is”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.identitiesmanages 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.apiowns 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.serverserves every bag’s tools to a harness through one MCP server, filtered and checked per session.schedules.schedulerfires the schedules every registered bag declares and records their runs.workflows.runtimeowns workflow history, executes activities, serves the runs console, and decides which worker may claim each activity.vault.apiserves 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.
What the control layer isn’t
Section titled “What the control layer isn’t”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.
One tool call, end to end
Section titled “One tool call, end to end”The trace follows a session you start from the command line, with an MCP tool call that reaches a bag’s handler.
- You start a session.
<identity> session startresolves the session’s grants: the identity’s traits and bounds plus any you pass with--traitsor--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 inlaunch-context.ts. - Barry records the session first. The launch posts a session row to
sessions.apiat/api/v1/sessions/start, carrying the session ID, the identity, the selected bounds, and the harness. If you passed--boundand the row can’t land, the launch stops rather than run unbounded. - Barry writes the harness’s MCP configuration. The
barryserver entry points athttp://127.0.0.1:<port>/mcp?sessionId=<id>and carriesBARRY_SECRETas 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 --relaxlifts 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. - The harness connects, and
mcp.serverauthenticates 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. Seetransport-authorization.ts. - The server resolves the session’s tools.
resolveSessionToolsinsession-tools-resolver.tsreads 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. - The server computes the grant.
resolveSessionBoundinsession-bound.tsasks 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. - Tools filter by qualified ID.
filterToolsQualifiedinfilter-tools.tskeeps a tool only when a trait grants it, its access level fits, and no denial, write restriction, or network action rule removes it. It returnsnamespace:nameIDs, 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. - Guards wrap the survivors.
applyBoundGuardsintool-guards.tswraps 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. - Every call asserts its grant. Before a handler runs,
assertToolAllowedinexecution-context.tschecks 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. - 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 tools and hook gates
Section titled “Native tools and hook gates”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
PreToolUsehook 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.
The egress sandbox
Section titled “The egress sandbox”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.