Policy resolution
This page explains how Barry turns an identity, the traits a session selects, and every bound in play into one effective policy: the list of tools the session sees and the bound that guards each call. Knowing the order and the merge rules lets you predict a session’s tools before you start it, and explains why selecting one read-only trait can make a whole session read-only.
One pass, two results
Section titled “One pass, two results”Barry computes a session’s policy in a single pass, in the identities bag’s
sessionPolicy.
The pass produces two things:
- The trait set, which decides which tools the session may see, and at what access.
- The effective bound, which takes tools, write access, paths, commands, and network destinations back from that set.
The MCP tool filter, the call-time guards, the native tool ceiling, and the egress sandbox all read the same result, so they can’t disagree about what a session may do.
Collect the traits
Section titled “Collect the traits”Barry gathers traits from three sources and keeps each name once:
- Base defaults. The base identity’s traits:
artifacts,bash,filesystem,instructions, andsessions. An identity can turn one off by listing it as optional. - Identity traits. The traits in the identity’s
traits:list. - Session traits. The traits you select when the session starts, such as
with
--traits.
A trait you select explicitly must exist, or the session refuses to start. A base default this installation can’t resolve grants nothing: Barry drops it and records a warning rather than failing the session.
Barry then expands the set. A trait’s includes: pulls in other traits, and
Barry rejects a cycle. A trait with per-harness policies expands to the branch
for the harness the session runs, and fails when it has no branch for that
harness.
Packed bags aren’t grants
Section titled “Packed bags aren’t grants”Packing a bag doesn’t put its trait in the set. The base identity sets
bag_grant: session, the default, which means a packed bag’s tools reach a
session only when a trait selects them. An identity that sets
bag_grant: identity adds the automatic trait of each of its packed root bags
to every session.
Automatic traits
Section titled “Automatic traits”A root bag that provides tool namespaces or skills gets two automatic traits:
<bag>grants every namespace and skill the bag provides atreadwrite, including those of the bags it lists underbags:.<bag>:readincludes<bag>and carries thereadonlybound. Because a bound applies to the whole session, selecting<bag>:readmakes the entire session read-only, not only that bag’s tools.
Some bags get no automatic trait: a bag with only services or only actions, and
a bag that extends another with extends:. Those bags reach a session through
traits that someone wrote by hand. A trait in bag.yaml with the bag’s own
name, or with the name read, replaces the matching automatic trait.
ADR 0045
records this rule.
Resolve grants
Section titled “Resolve grants”Barry resolves the expanded traits into grants, keyed both by namespace
and by qualified tool id. Traits only add, so the grants union. When two traits grant
the same namespace at different access levels, the more permissive one wins:
readwrite beats read. An explicit grant for one tool takes precedence over
its namespace’s grant.
Barry decides every tool by its qualified id, namespace:tool, never by its
bare name. Two bags can both export a tool named status, and a grant for one
never admits the other.
ADR 0037
records the reason. Prefer qualified ids in a trait’s tools: list; a bare name
still works, matches that name in every namespace, and logs a deprecation
warning.
Merge the bounds
Section titled “Merge the bounds”Barry builds the effective bound by merging, in this order:
- Every bound a selected trait carries (inline bounds and named ones).
- The base identity’s required bounds. The bounds
baseships, such asreadonlyandno-network, are optional, so they apply only when something selects them. - The identity’s named bounds, from its
bounds:list. - The named bounds the session selects, such as with
--bound. - The identity’s inline
bound:. - The session’s inline bound.
The order doesn’t change the result, because every rule moves toward more restriction:
- Denials union. A tool, access level, path, command, or network action that any source denies stays denied.
- Allowlists intersect.
files.allowOnlyandnetwork.allowDomainskeep only the entries every source permits. - The strictest enforcement wins.
network.enforce: sandboxbeatsguard.
A named bound that doesn’t exist stops the session from starting. A
network.allowDomains list in the merged bound requires
network.enforce: sandbox, because only the egress sandbox can enforce an
allowlist.
Filter the tools
Section titled “Filter the tools”Barry keeps a tool only when every check passes:
- A trait grants its namespace or its qualified id.
- The bound doesn’t deny its namespace or name in
deniedTools. A bound with file rules also withholds thebashtool. - A write tool needs a
readwritegrant, and the bound mustn’t deny write access to it. - The bound’s network actions don’t imply denying it.
What survives becomes the session’s tool list. The MCP server lists only those tools, so the model never sees the rest.
Sessions without a resolved identity
Section titled “Sessions without a resolved identity”A request that reaches the MCP server without a session resolves under an anonymous scope. It gets only the default-trait grant, filtered the same way. When resolution fails for any reason, such as a session that names an identity that doesn’t exist, the MCP server serves no tools rather than every tool. ADR 0018 records both rules.
A worked example
Section titled “A worked example”Suppose an ops identity packs two bags, github and slack, each with one
read tool and one write tool:
| Tool | Access |
|---|---|
github:list_issues |
read |
github:create_issue |
write |
slack:read_channel |
read |
slack:post_message |
write |
The identity grants slack to every session and requires one bound:
name: opsbags: [github, slack]traits: [slack]bounds: [no-network-write]You start a session as ops and select one more trait:
<identity> session start --traits github:readBarry resolves it in four steps:
- Traits. The base defaults,
slackfrom the identity, andgithub:readfrom the session.github:readexpands to includegithub. - Grants.
githubandslackboth grant their namespaces atreadwrite. - Bound.
github:readcarriesreadonly, which denies theBash,Write, andEdittools, denies write access, and denies network writes. The identity’sno-network-writealso denies network writes. The union equalsreadonly. - Filter. Write access is now denied session-wide, so both write tools drop out.
The session sees two of the four tools:
| Tool | Result | Why |
|---|---|---|
github:list_issues |
Listed | Granted through github:read |
github:create_issue |
Withheld | readonly denies write access |
slack:read_channel |
Listed | Granted through slack |
slack:post_message |
Withheld | readonly applies to the whole session |
The default traits pass through the same filter, so the session keeps the
filesystem’s read tools and loses its write tools and bash. To post to Slack
while reading GitHub, select github instead of github:read and add a bound
of your own that denies github:create_issue.
Related
Section titled “Related”Primitives defines each term this page uses, and Bounds lists every bound field. Session modes and guarantees explains how much of this policy each harness lets Barry enforce.