Skip to content
WIP

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

@barry-rocks/sdk/bags

Types for bags as Barry sees them: the parsed manifest (Bag and the shapes of its apps, hooks, dependencies, stores, and more), plus parseManifest and findManifest. Use it when code needs to read a bag.yaml; the configuration reference documents each key.

import { … } from "@barry-rocks/sdk/bags";

A run: block, normalized. Paths are still relative to the action directory.

Property Type
args string[]
command string
entry? string
workingDirectory? string

A fully loaded bag with resolved paths and capabilities

Property Type Description
actionsDirs string[] Dirs holding this bag’s actions (<bagDir>/actions). Twin of skillsDirs.
apps BagApp & object[] GUI applications declared by this bag
builtin boolean -
dependencies BagDependency[] -
description string -
hooks ResolvedBagHook[] Agent hooks declared by this bag, resolved to runnable commands
infra? BagInfra Infrastructure declared by this bag
instructionsDirs string[] Dirs holding this bag’s instructions (<bagDir>/instructions).
jobs? WorkContract & object & object[] Jobs declared by this bag. dir is the bag directory their paths resolve in.
manifest BagManifest | null -
mcpServers Record<string, BagMcpServer> -
name string -
requires BagRequirement & object[] Capability requirements, tagged with the required bag’s name
schedules BagSchedule & object[] Schedules declared by this bag. dir is the bag directory inline actions run in.
services BagService & object[] Long-running services declared by this bag
skillsDirs string[] -
slashCommands BagSlashCommand[] -
source BagSource -
tools BagToolMeta[] -
toolsError? string Why this bag’s tool code could not be built or imported, when it could not. Set instead of throwing, so one broken bag degrades itself rather than every load it is part of. Everything the manifest declares (services, schedules, skills, composition) is still present; only tools is unknown. Unknown, not empty: trait derivation must not guess namespaces for this bag, and nothing may prune grants on the strength of its absence.
traits BagTrait[] -

A GUI application declared by a bag (apps: in bag.yaml).

Distinct from a service: an app is built, installed as a bundle, and launched by the user, who may quit it. A service that exits has failed; an app that exits has not.

Property Type Description
build BagAppBuild -
bundleId? string -
capabilities? BagAppCapabilities -
description string -
device? boolean iOS only: whether this app can be signed and installed onto a device.
displayName? string -
id? string -
keepAlive? boolean -
loginItem? boolean -
name string -
output string -
platform "macos" | "ios" -
protocolVersion? number -
urlSchemes? string[] -

Property Type Description
approvalReview? boolean Barry AG-UI extension capabilities.
attachments? boolean -
gating? boolean -
listener? boolean -
replay? boolean -
streaming? boolean Legacy App Protocol v1 capabilities.

CLI-delegated auth for a bag (auth: in bag.yaml).

Some vendors ship their own OAuth in their CLI (e.g. temporal cloud login). barry bag auth <name> runs check first (exit 0 → already authenticated), then runs command interactively so the user can complete the browser flow. Credentials stay in the vendor CLI’s own store — no vault/barry env plumbing.

Property Type Description
args? string[] -
check? object Optional command that exits 0 when already authenticated
check.args? string[] -
check.command string -
command string -

A named bound as a manifest defines it.

Property Type
bound Bound
description string

Where a composed sub-bag’s code comes from.

Exactly one of repo or path — enforced by the schema, because an entry with neither is a declaration that names no source and would otherwise parse into “resolve from the registry”, silently behaving like a bare name.

Property Type Description
path? string Filesystem path, relative to the declaring bag’s directory.
ref? string Branch or tag to track when rev is absent.
repo? string Git remote: owner/name shorthand, or any URL git can clone.
rev? string Exact commit to check out. Reproducible; what a submodule gitlink pins.
subdir? string Directory inside the repo that holds the bag, when the clone root is not the bag itself — one bag in a monorepo of them. Omitted, the clone root is the bag, which is the common case. Deliberately not path: that already means “a sibling bag on local disk”, an alternative to cloning rather than a refinement of it. One key meaning two unrelated things depending on whether repo sits beside it would make {repo, path} and {path} behave nothing alike.

CLI configuration for a bag.

Optional — only needed when the bag wants a different CLI command name than its manifest name. Which bags actually appear in barry -h is controlled by the user’s ~/.config/barry/cli.yaml, not the manifest.

Property Type Description
alias? string Alternate CLI command group name (default: bag name). Used as: barry <alias> <tool>

One harness credential’s default source. Vault-only — see BagManifest.credentials.

Property Type
field "username" | "password" | "notes" | "uri"
item string
source "vault"

A bag’s fallback notifier — see default_notifier on the manifest.

Property Type Description
target? string Optional destination. Usually omitted: a default notifier that needs a target is not usable until someone configures it, which defeats the point.
tool string Tool to call. Must be provided by this bag and usable without a target.

Property Type Description
install? string Install command shown when the binary is missing (e.g. “brew install uv”)
name string Binary name looked up on PATH
optional? boolean -
package? string Host package key, which may differ from its executable name.
reason? string What the bag needs it for

An agent hook declared in a bag manifest (hooks: in bag.yaml)

Property Type Description
args? string[] Extra argv appended to the command — carries the hook’s mode (e.g. session-tracker’s start)
cursorEvents? string[] Override the default neutral→Cursor event mapping for this hook
cursorMatcher? string Cursor matcher (substring semantics, e.g. "npm " on beforeShellExecution)
event BagHookEvent -
matcher? string Claude tool-name matcher (PreToolUse/PostToolUse); omitted = match all
run string Script path relative to the bag directory
timeout? number Per-invocation timeout in seconds

What a hosting provider bag can do (provides: in bag.yaml), in vendor-neutral words: give services local names, and/or carry tunnel routes at the listed reaches behind the listed gates.

Property Type
local? Record<string, never>
tunnel? object
tunnel.gates "access"[]
tunnel.reach ("private" | "public")[]

Infrastructure-as-code a bag owns (infra: in bag.yaml). Today only Terraform is supported. barry bag infra <name> plan|apply runs terraform in the declared root with the declared variables set.

Property Type Description
provider "terraform" -
root string Bag-relative path to the terraform root directory
variables? string[] Env var names terraform needs — validated by doctor, set by barry bag infra

What a bag declares when it wants something RUN.

Shared by services, scripts and app builds, which had repeated these three fields over and over. Everything else about them differs — a service carries launchd lifecycle and network identity, a script nothing at all — so only the genuinely common part lives here. (Scheduled work is an action on a schedule, which has its own run: shape.)

Services normalize literals into named values; scripts retain KEY=VALUE entries. Their representation belongs to each launch consumer.

Distinct from the agents’ LaunchPlan for the same kind of reason: that one carries resolved values, temp dirs to clean up, and a reserved session id, none of which exist in a manifest.

Property Type Description
args? string[] -
command string -
description string -
entry? string Bag-relative entry; command is the normalized interpreter placeholder until launch.
workingDirectory? string Relative to the bag directory; defaults to the bag dir.

Property Type Description
apps Record<string, BagApp> GUI applications this bag ships
auth? BagAuthCommand -
bags? string[] Sub-bags this bag includes — auto-enabled when this bag is enabled
bagSources? Record<string, BagChildSource> Where sub-bags named in bags come from, for those that say. Keyed by the same name that appears in bags, so composition stays a single list and this only answers “where do I fetch it from”. A name in bags with no entry here resolves from the registry as it always has. The distinction is load-bearing at install time: a name alone is best-effort widening (the bag may not exist yet, so it is skipped with a warning), while an entry here is an unambiguous claim that the child exists at that source — so failing to fetch it fails the install.
barryHooks? BarryHook[] Record hooks this bag declares (hooks.barry)
bounds? Record<string, BagBoundDefinition> Named bounds this bag defines, keyed by name (bounds: at the manifest’s top level). A trait refers to one by name in its own bounds: list. Distinct from a trait’s inline bound:, which restricts that one trait and has no name for anything else to reuse.
cli? BagCliConfig CLI exposure configuration — how this bag’s tools appear as barry <group> <tool> commands
constants? Record<string, unknown> -
credentials? Record<string, BagCredentialDeclaration> Credentials a HARNESS bag’s sessions authenticate with, as a resolution plan: env var name -> where the value comes from when the identity does not define the variable itself. The identity’s own definition wins; this map is the default. Declared here because a claim a picker or the gate can read without loading adapter code is the one that holds. Vault-only: a manifest is shared code, and an inline constant in shared code is either a placeholder (vendor rows have placeholder for that) or a secret committed to the repo.
default_notifier? BagDefaultNotifier A notifier this bag offers when the barry has not configured one. Progress notifications resolve in precedence order: an explicit notify_tool argument, then the barry’s own status_notify, then a bag default, then nothing. The last two exist for different reasons — the barry’s setting is a user’s deliberate choice and always wins, while this is a bag saying “if you have not picked anything, I can carry it.” The tool named here must be one this bag actually provides, and must work without a target — a default that needs configuring is not a default.
dependencies BagDependency[] -
description string -
extends? string The plain name of the bag this extension adds to.
hooks? BagHook[] Agent hooks this bag declares (hooks.agent, or the legacy bare array)
infra? BagInfra Infrastructure-as-code this bag owns (terraform root + variables)
instructions? string Guidance injected into the session’s system prompt when this bag’s tools are active. Use it to steer the agent toward the bag’s structured tools — e.g. the git bag tells the agent to use git_* tools, not shell git.
jobs? Record<string, BagJob> Work this bag ships for Barry to run (jobs:). Absent when it declares none.
mcpServers Record<string, BagMcpServer> -
name string -
permissions? BagPermission[] macOS privacy permissions its code needs.
promptsEntry? BagPromptsEntry -
provides? BagHostingCapability Hosting this bag can provide (provides:), if it is a hosting provider
requires? Record<string, BagRequirement> Capabilities of other bags this bag calls (requires: in bag.yaml)
schedules Record<string, BagSchedule> Actions this bag runs on a trigger (schedules:), run by the scheduler service
scripts Record<string, BagScript> On-demand commands this bag ships (barry <bag> <script>)
services Record<string, BagService> Long-running services this bag declares (launchd-managed)
slashCommands? BagSlashCommandsEntry Slash commands this bag provides for the Slack server
storeRestores? Record<string, { entry: string; }> -
stores? Record<string, { engine: "sqlite"; migrations: string; owned_by: "identity" | "node"; }> -
storeSnapshots? Record<string, { entry: string; }> -
tools BagToolMeta[] -
toolsEntry? BagToolsEntry -
toolVisibility? Record<string, "enabled" | "deferred" | "disabled"> -
traits Record<string, Omit<BagTrait, "name">> -
vendors? Vendor[] -
version? string The bag’s own release (e.g. “v0.1”), if it declares one.

MCP server definition within a bag

Property Type Description
args? string[] -
command? string -
env? string[] -
sessionScoped? boolean Give each Barry session its own connection to this server. Bag connections are normally pooled by bagName:credentialHash and shared process-wide, which is right for stateless request/response servers (an issue tracker, a docs search). It is wrong for a server that holds a live artifact between calls — a browser tab, a running app — because every session then drives the same one: navigating in one session moves the page under another, and whatever is typed or logged into is visible across all of them. Costs one server process per session, so only set it when the server is genuinely stateful.
type? "http" -
url? string -

A macOS privacy permission the bag’s code needs, granted by a person in System Settings › Privacy & Security. Declared so barry bag doctor can say what to grant and to which program before a tool fails on a silent denial.

Property Type Description
app? string For automation: the app it sends Apple events to, by name (“Pages”).
kind "automation" | "accessibility" | "screen-recording" | "full-disk-access" -
reason string What the bag needs it for.

A bag’s computed prompt contributor (prompts: { entry }).

Property Type
entry string

Immutable view of one registry resolution, shared by runtime consumers.

Property Type
bags readonly Bag[]
byName ReadonlyMap<string, Bag>
registry Readonly<BagRegistry>

A capability requirement declared in a bag manifest (requires: in bag.yaml)

Property Type Description
allowRemote? boolean A remote deployment registry entry satisfies this requirement
optional? boolean Unmet: warn and load anyway, instead of dropping the bag’s tools
reason? string Why — surfaced verbatim in the unmet-requirement error
resource? string Which of the target bag’s services: this bag calls; omitted = just needs the bag registered

A schedule, normalized.

Property Type
bounds? string[]
catchUp "none" | "once"
description? string
enabled boolean
identity? string
inputs? Record<string, unknown>
overlap "queue" | "skip"
payload SchedulePayload
traits? string[]
trigger ScheduleTrigger

A command a bag ships, run on demand as barry <bag> <script> (scripts: in bag.yaml). Nothing installs it, so it carries no launchd bookkeeping; to run one on a timer, make it a run: action on a schedule.

Property Type Description Inherited from
args? string[] - BagLaunch.args
command string - BagLaunch.command
description string - BagLaunch.description
entry? string Bag-relative entry; command is the normalized interpreter placeholder until launch. BagLaunch.entry
env? string[] Non-secret KEY=VALUE literals. Credentials belong under secrets. -
secrets? string[] - -
workingDirectory? string Relative to the bag directory; defaults to the bag dir. BagLaunch.workingDirectory

What a bag declares when it wants something RUN.

Shared by services, scripts and app builds, which had repeated these three fields over and over. Everything else about them differs — a service carries launchd lifecycle and network identity, a script nothing at all — so only the genuinely common part lives here. (Scheduled work is an action on a schedule, which has its own run: shape.)

Services normalize literals into named values; scripts retain KEY=VALUE entries. Their representation belongs to each launch consumer.

Distinct from the agents’ LaunchPlan for the same kind of reason: that one carries resolved values, temp dirs to clean up, and a reserved session id, none of which exist in a manifest.

Property Type Description Inherited from
args? string[] - BagLaunch.args
autostart? "login" | "manual" Who starts this: the machine at login (the default), or something else. A service RUNS CONTINUOUSLY by definition, so this is the only part of its lifecycle a bag still chooses. manual covers both a service another process bootstraps (coffee’s supervisor, started by its own tools) and one that is simply parked until needed (file-stream) — the same case, since neither wants the machine starting it. -
command string - BagLaunch.command
description string - BagLaunch.description
entry? string Bag-relative entry; command is the normalized interpreter placeholder until launch. BagLaunch.entry
env? LiteralEnvValue[] Authored non-secret process literals. -
health? string Readiness probe path, e.g. “/health”. -
localOnly? boolean Its security assumes callers on this Mac, so no tunnel may route to it. Only a local name may. -
optionalSecrets? string[] Secrets the service runs without (optional-secrets:). -
secrets? string[] - -
serves? "http" The protocol this service speaks. http: it listens on the loopback port the service management assigns (injected as PORT), or on its socket. -
socket? string Unix socket path, relative to the state directory for user: services and to the bag directory otherwise, instead of an assigned port: kernel-enforced socket permissions are the point, so a TCP listener alongside would reopen what the socket closes. -
user? string macOS service account this runs as (e.g. “_barryvault”). Absent means the invoking user, installed as a LaunchAgent. Prepare with barry setup, then install with sudo barry setup --system. The privileged pass creates the account and installs root-owned built code as a LaunchDaemon. Its socket lives under the service’s state directory. Ordinary setup and refresh never run it as the invoking user. -
workingDirectory? string Relative to the bag directory; defaults to the bag dir. BagLaunch.workingDirectory

A SLACK slash command declared in a bag manifest — /oncall typed into Slack, dispatched to a handler exported from the entry module.

NOT a coding-agent slash command. Those come from actions declaring slash_command: true and are generated per session (see @barry-rocks/sdk/internal/actions action-commands). The two are unrelated despite the shared word, and the resemblance has already misled one reader into calling this dead scaffolding.

It is live: the slack-app bag imports each declaring bag’s entry module at startup (slack-app/src/handlers/bag-commands.ts), registers one route per command name (routes/commands.ts), and publishes them to Slack via generate-manifest.ts. ~/repos/bags/vantage-slack ships /oncall and /sentry this way. Nothing in THIS repo reads slashCommands — the consumer is a bag, which is why a monorepo-only grep makes it look unused.

Property Type
description string
name string

Slack slash commands entry in a bag manifest (slash-commands: in bag.yaml)

Property Type Description
commands BagSlashCommand[] -
entry string Module exporting one handler per declared command name.

Tool metadata declared in a bag manifest

Property Type Description
access BagAccess -
contractHash? string Read from the tool’s code (toolContractHash); absent for manifest and remote tools.
description? string What the tool does, from its definition; absent for manifest and remote tools.
namespace string -
toolName string -

Conventional local tool module discovered from the bag directory.

Property Type
entry string

Trait declared in a bag manifest

Property Type Description
access BagAccess -
bags string[] Bag/namespace names this trait grants. Resolved to namespaces at the session-scope boundary.
bound? Bound Restriction declared inline on the trait rather than referenced by name. Lets a bag outside this repo ship its own bound without a row in the framework’s bounds table. Merged with any named bounds when the session’s effective bound is built.
boundNames string[] Named bounds whose restrictions this trait carries, resolved against the bounds table when the session’s effective bound is built. Empty for auto-traits: a generated trait grants everything its bag provides, and narrowing it is an authoring decision, never a derived one.
description string -
harnessTraits? Record<string, string> Select one included trait by the running harness.
includes? string[] -
instructions string[] Instruction names this trait points at. Resolved through the instruction catalog at session start; the prose itself lives in an instructions/ file.
name string -
nativeTools? Record<string, string[]> -
skills string[] Skill names (directory basenames under the bag’s skills/ dirs) granted by this trait

A hook on a Barry record, declared under hooks.barry in bag.yaml.

Property Type Description
args? string[] Extra argv appended to the command — carries the hook’s mode
event "event-created" -
run string Script path relative to the bag directory
timeout? number Per-invocation timeout in seconds

Property Type
name string
source "value"
value string

Registry entry for a local bag

Property Type Description
access? BagAccessLevel -
disabled? boolean Deprecated use access: "disabled" — kept for back-compat
git? object Git remote that path was cloned from, when Barry fetched it. Provenance, exactly like npm above: the bag is still an ordinary local checkout — same loading, building and traits — and this records where it came from so a vanished path can be re-cloned and bag doctor can check the checkout still matches what the manifest declared. rev is the pinned commit when one was requested; ref the branch or tag otherwise. Both are recorded as resolved, not as written, so drift from the manifest is detectable.
git.ref? string -
git.repo string -
git.rev? string -
npm? string npm package specifier that owns path (e.g. @acme/my-bag). Stored so the registry can re-resolve a bag whose absolute path vanished after pnpm install reshuffled node_modules.
path string -
type "local" -

Registry entry for a remote bag (MCP server or command)

Property Type Description
access? BagAccessLevel -
args? string[] -
command? string -
disabled? boolean Deprecated use access: "disabled" — kept for back-compat
env? string[] -
resources? boolean When true, connect to MCP server and discover barry:// resources
session-scoped? boolean Connect per session instead of once at startup. Mirrors the manifest’s session-scoped key (BagMcpServer.sessionScoped) for registry entries, which have no manifest to declare it in. Without this a credentialless remote bag is connected eagerly and its process lives as long as the MCP server, whether or not any session uses it.
tools? BagToolMeta[] Inline tool metadata for trait-based access control
type "remote" -
url? string -

A bag hook resolved to a runnable command (absolute script path + runner)

Property Type Description
bag string Owning bag, for diagnostics
command string Full command string, e.g. tsx /abs/path/index.ts start
cursorEvents? string[] -
cursorMatcher? string -
event BagHookEvent -
matcher? string -
timeout? number -

The contract as the rest of Barry reads it.

Property Type Description
inputSchema? Record<string, unknown> -
outputSchema? Record<string, unknown> -
preflight? ContractPreflight -
requires object -
requires.bins string[] -
requires.labels? Record<string, string> -
requires.secrets string[] -
requires.tools string[] -
timeout? number Run timeout in seconds, when declared.
validate? ContractValidate -

ActionAgent = object

The manifest’s agent: block — which coding agent and model a detached run prefers. Both optional and resolved independently; see the schema for why there is no model-provider field.

optional agent?: HarnessId

optional model?: string


ActionBody = { job?: undefined; prompt: string; run?: undefined; } | { env?: LiteralEnvValue[]; job?: undefined; prompt?: undefined; run: ActionRunSpec; secrets?: string[]; } | { job: string; prompt?: undefined; run?: undefined; }

How an action’s work is done: by an agent following prompt, or by Barry running run. A union so every consumer of prompt has to say what it does with an action that has none, instead of rendering an empty string.

{ job?: undefined; prompt: string; run?: undefined; }

Name Type Description
job? undefined -
prompt string Prompt body, already resolved from the file form when that was used.
run? undefined -

{ env?: LiteralEnvValue[]; job?: undefined; prompt?: undefined; run: ActionRunSpec; secrets?: string[]; }

Name Type Description
env? LiteralEnvValue[] Declared env for the process, resolved at run time.
job? undefined -
prompt? undefined -
run ActionRunSpec -
secrets? string[] -

{ job: string; prompt?: undefined; run?: undefined; }

Name Type Description
job string The job this action runs, as written: name or bag:name[@version].
prompt? undefined -
run? undefined -

ActionContext = "session" | "none"


ActionManifest = ActionBody & object

Name Type Description
agent? ActionAgent -
commandDescription? string Menu subtitle for the slash command. Falls back to description.
commandHint? string Argument hint shown during / autocomplete.
context ActionContext Caller-context requirement; session unless the manifest says none.
description string -
inputSchema? Record<string, unknown> JSON Schema for caller-supplied inputs (object root, named properties). Absent = the action takes no declared inputs.
instructions string[] Names of shared instructions to prepend, in declared order.
name string -
outputSchema? Record<string, unknown> JSON Schema for the declared output. Absent = the action declares none.
preflight? ActionPreflight Pre-run reachability probe. Absent = the action claims nothing.
requires object -
requires.bins string[] -
requires.secrets string[] -
requires.tools string[] -
slashCommand boolean Expose as a /name slash command where the provider supports one.
steps ActionStep[] Child actions, in declared order. Empty for an ordinary action — a non-empty array is what makes this one a composite, so callers test length rather than needing a separate flag that could disagree with it.
timeout? number Run timeout in seconds, when declared.
validate? ActionValidate -
version? string -

ActionPreflight = ContractPreflight


ActionStep = object

One child of a composite action.

An object rather than a bare string so a step can grow fields — input mapping, a run-only-if condition — without a breaking format change. Today action is the only one, and every step runs.

action: string

Child action, by bare name or bag:name. Resolved by the catalog.


ActionValidate = ContractValidate


BagAccess = "read" | "readwrite"


BagAccessLevel = "enabled" | "deferred" | "disabled"

A bag’s visibility in a session, set on its registry entry:

  • enabled — tools appear in the MCP tools/list normally (default)
  • deferred — tools are hidden from tools/list but stay callable and discoverable via the tool_search tool
  • disabled — the bag is off entirely (supersedes the legacy disabled bool)

BagAppBuild = string | { configuration: string; project: string; scheme: string; }

Package script name or an explicit Xcode build.


BagHookEvent = "session-start" | "session-end" | "user-prompt" | "stop" | "pre-tool" | "post-tool"

Neutral hook event vocabulary — mapped per provider in sdk/src/hooks/hooks.ts


BagJob = WorkContract & object

A job, normalized: its contract in the camelCase every reader uses.

Name Type
bounds? string[]
description? string
runs JobRuns
version? string

BagRegistry = Record<string, BagSource>

The bag registry: every installed bag by name (config.db, as its snapshot projects it)


BagResource = ServiceEntry

One registered resource: how a client reaches it.


BagResourceKind = ServiceEntry["kind"]


BagSource = LocalBagSource | RemoteBagSource


BarryHookEvent = "event-created"

Records a bag can hook. Only events, because events have one write path that already announces after commit — see sdk/src/tools/hooks-schema.ts for why errors reach hooks as events rather than directly.


ContractPreflight = object

command: string

optional hint?: string

optional timeout?: number


ContractValidate = object

optional command?: string

optional files_exist?: string[]

optional timeout?: number


JobRuns = { env?: LiteralEnvValue[]; main: string; secrets?: string[]; using: "node"; workingDirectory?: string; } | { command: string[]; env?: LiteralEnvValue[]; secrets?: string[]; using: "process"; workingDirectory?: string; } | { action: string; using: "action"; }


SchedulePayload = { kind: "ref"; ref: string; } | { kind: "inline"; manifest: ActionManifest; } | { kind: "job"; ref: string; }

What a schedule runs: an action by reference, an inline action, or a job by reference.


ScheduleTrigger = { expression: string; kind: "cron"; timezone?: string; } | { kind: "at"; time: string; } | { kind: "login"; }

{ expression: string; kind: "cron"; timezone?: string; }


{ kind: "at"; time: string; }


{ kind: "login"; }

Once per verified operating-system login event.


SchemaValidationResult = { ok: true; } | { errors: string[]; ok: false; }

const BoundDefinitionSchema: ZodObject<{ bound: ZodObject<{ bash: ZodOptional<ZodObject<{ deny: ZodOptional<ZodArray<ZodString>>; denyPrograms: ZodOptional<ZodArray<ZodString>>; }, $strict>>; deniedAccess: ZodOptional<ZodArray<ZodString>>; deniedTools: ZodOptional<ZodArray<ZodString>>; description: ZodOptional<ZodString>; files: ZodOptional<ZodObject<{ allowOnly: ZodOptional<ZodArray<ZodString>>; deny: ZodOptional<ZodArray<ZodString>>; }, $strict>>; name: ZodOptional<ZodString>; nativeTools: ZodOptional<ZodRecord<ZodString, ZodArray<ZodString>>>; network: ZodOptional<ZodObject<{ actions: ZodOptional<ZodArray<ZodEnum<{ all: …; dns: …; git:fetch: …; git:push: …; http:read: …; http:write: …; read: …; ssh:read: …; ssh:write: …; write: …; }>>>; allowDomains: ZodOptional<ZodArray<ZodString>>; domains: ZodOptional<ZodArray<ZodString>>; enforce: ZodOptional<ZodEnum<{ guard: "guard"; sandbox: "sandbox"; }>>; }, $strict>>; nodes: ZodOptional<ZodObject<{ allow: ZodOptional<ZodArray<ZodString>>; deny: ZodOptional<ZodArray<ZodString>>; }, $strict>>; }, $strict>; description: ZodString; }, $strict>

A named bound a manifest defines at its top level. Strict for the same reason as TraitSchema: a misspelled key would otherwise parse clean and restrict nothing. Identity files define bounds with the same fields.


const TraitSchema: ZodObject<{ access: ZodOptional<ZodEnum<{ read: "read"; readwrite: "readwrite"; write: "write"; }>>; bags: ZodOptional<ZodArray<ZodString>>; bound: ZodOptional<ZodObject<{ bash: ZodOptional<ZodObject<{ deny: ZodOptional<ZodArray<ZodString>>; denyPrograms: ZodOptional<ZodArray<ZodString>>; }, $strict>>; deniedAccess: ZodOptional<ZodArray<ZodString>>; deniedTools: ZodOptional<ZodArray<ZodString>>; description: ZodOptional<ZodString>; files: ZodOptional<ZodObject<{ allowOnly: ZodOptional<ZodArray<ZodString>>; deny: ZodOptional<ZodArray<ZodString>>; }, $strict>>; name: ZodOptional<ZodString>; nativeTools: ZodOptional<ZodRecord<ZodString, ZodArray<ZodString>>>; network: ZodOptional<ZodObject<{ actions: ZodOptional<ZodArray<ZodEnum<…>>>; allowDomains: ZodOptional<ZodArray<ZodString>>; domains: ZodOptional<ZodArray<ZodString>>; enforce: ZodOptional<ZodEnum<{ guard: …; sandbox: …; }>>; }, $strict>>; nodes: ZodOptional<ZodObject<{ allow: ZodOptional<ZodArray<ZodString>>; deny: ZodOptional<ZodArray<ZodString>>; }, $strict>>; }, $strict>>; bounds: ZodOptional<ZodArray<ZodString>>; description: ZodString; harnessTraits: ZodOptional<ZodRecord<ZodString, ZodString>>; includes: ZodOptional<ZodArray<ZodString>>; instructions: ZodOptional<ZodArray<ZodString>>; namespaces: ZodOptional<ZodArray<ZodString>>; nativeTools: ZodOptional<ZodRecord<ZodString, ZodArray<ZodString>>>; skills: ZodOptional<ZodArray<ZodString>>; }, $strict>

Unknown keys are rejected, like every sibling schema here. Until now this one alone was permissive despite comments claiming otherwise, so a trait that misspelled a restriction key — bound for bounds, or the reverse — parsed clean and silently unrestricted.

assertCompilableSchema(schema): void

Throws if the schema itself does not compile. Callers that accept schemas from manifests should invoke this at parse time so a broken schema fails the manifest’s own validation instead of the first live run.

Parameter Type
schema Record<string, unknown>

void


bagServiceUrl(bag, name, path?): string

Require this instance’s registered service address, with no fallback; see docs/decisions/0017-sdk-discovery-and-wire-contracts.md.

Parameter Type
bag string
name string
path? string

string


clearManifestCache(): void

void


extensionName(parent, name): string

The registered name of an add-on to one bag.

Parameter Type
parent string
name string

string


findBagResource(bag, name, path?): ServiceEntry | null

The resource, or null when it is not registered.

Parameter Type
bag string
name string
path? string

ServiceEntry | null


findBagResourceUrl(bag, name, path?): string | null

The resource’s URL, or null when unregistered or not HTTP-reachable.

Parameter Type
bag string
name string
path? string

string | null


findManifest(bagDir): string | null

Parameter Type
bagDir string

string | null


getBagResourceRegistryPath(): string

string


getSkillsDirs(bagDir): string[]

Parameter Type
bagDir string

string[]


normalizeTrait(d): Omit<BagTrait, "name">

Parameter Type Description
d { access?: "read" | "write" | "readwrite"; bags?: string[]; bound?: { bash?: { deny?: string[]; denyPrograms?: string[]; }; deniedAccess?: string[]; deniedTools?: string[]; description?: string; files?: { allowOnly?: string[]; deny?: string[]; }; name?: string; nativeTools?: Record<string, string[]>; network?: { actions?: ("read" | "write" | "all" | "git:push" | "git:fetch" | "http:write" | "http:read" | "ssh:write" | "ssh:read" | "dns")[]; allowDomains?: string[]; domains?: string[]; enforce?: "guard" | "sandbox"; }; nodes?: { allow?: string[]; deny?: string[]; }; }; bounds?: string[]; description: string; harnessTraits?: Record<string, string>; includes?: string[]; instructions?: string[]; namespaces?: string[]; nativeTools?: Record<string, string[]>; skills?: string[]; } -
d.access? "read" | "write" | "readwrite" -
d.bags? string[] Bag/namespace names — accepts both bags and legacy namespaces.
d.bound? { bash?: { deny?: string[]; denyPrograms?: string[]; }; deniedAccess?: string[]; deniedTools?: string[]; description?: string; files?: { allowOnly?: string[]; deny?: string[]; }; name?: string; nativeTools?: Record<string, string[]>; network?: { actions?: ("read" | "write" | "all" | "git:push" | "git:fetch" | "http:write" | "http:read" | "ssh:write" | "ssh:read" | "dns")[]; allowDomains?: string[]; domains?: string[]; enforce?: "guard" | "sandbox"; }; nodes?: { allow?: string[]; deny?: string[]; }; } Restriction declared inline, rather than referenced by name, so an external bag can ship an opinionated bound without a row in this framework’s config. Validated against the real bound shape — a permissive z.record() would let a misspelled denyPrograms parse into a bound that denies nothing and looks, from every angle, exactly like a working one. A restriction that fails open is worse than one that fails to parse.
d.bound.bash? { deny?: string[]; denyPrograms?: string[]; } -
d.bound.bash.deny? string[] -
d.bound.bash.denyPrograms? string[] -
d.bound.deniedAccess? string[] -
d.bound.deniedTools? string[] -
d.bound.description? string -
d.bound.files? { allowOnly?: string[]; deny?: string[]; } -
d.bound.files.allowOnly? string[] -
d.bound.files.deny? string[] -
d.bound.name? string -
d.bound.nativeTools? Record<string, string[]> -
d.bound.network? { actions?: ("read" | "write" | "all" | "git:push" | "git:fetch" | "http:write" | "http:read" | "ssh:write" | "ssh:read" | "dns")[]; allowDomains?: string[]; domains?: string[]; enforce?: "guard" | "sandbox"; } -
d.bound.network.actions? ("read" | "write" | "all" | "git:push" | "git:fetch" | "http:write" | "http:read" | "ssh:write" | "ssh:read" | "dns")[] -
d.bound.network.allowDomains? string[] -
d.bound.network.domains? string[] -
d.bound.network.enforce? "guard" | "sandbox" -
d.bound.nodes? { allow?: string[]; deny?: string[]; } -
d.bound.nodes.allow? string[] -
d.bound.nodes.deny? string[] -
d.bounds? string[] Named bounds whose restrictions this trait carries. Read-only is composed this way — the grant plus bounds: [readonly] — which is exactly what the generated <bag>:read twin is. A manifest trait named read replaces that twin, for a bag whose read-only variant needs to differ. The legacy scopes: spelling was dropped here. It was documented as permanent on the premise that external bag.yaml files are unreachable and unsweepable, so no moment could exist when every bag was known converted. The premise was wrong: every bag.yaml on disk was swept and exactly one still said scopes: (the slack bag, since converted). The sweep is also repeatable — registered-manifests.test.ts parses every registered local bag, so a straggler fails the suite instead of failing silently in a running MCP server. config/*.yaml keeps its legacy readers, because its gitignored *.local.yaml twins genuinely cannot be swept.
d.description string -
d.harnessTraits? Record<string, string> -
d.includes? string[] -
d.instructions? string[] Instruction names this trait points at. Names, not prose — the body lives in an instructions/ file and resolves through the instruction catalog, so the text is authored once and stays under the prompt budgets.
d.namespaces? string[] -
d.nativeTools? Record<string, string[]> -
d.skills? string[] -

Omit<BagTrait, "name">


parentOf(bag): string | null

The parent of an extension, or null for a plain bag.

Parameter Type
bag string

string | null


parseManifest(bagDir): BagManifest | null

Parameter Type
bagDir string

BagManifest | null


parseManifestObject(raw, label?, bagDir?, options?): BagManifest

bagDir is where inline schedule actions resolve their relative paths. Omitted for a manifest with no directory, where an inline action naming a file cannot resolve and fails as a missing file.

Parameter Type Default value
raw unknown undefined
label string "manifest"
bagDir string ""
options { source?: "local" | "remote"; } {}
options.source? "local" | "remote" undefined

BagManifest


parseManifestObjectSafe(raw, label?, options?): object

Non-throwing variant for untrusted input.

Remote manifests must not be able to break discovery: a third-party server running a newer (or simply wrong) manifest shape should degrade to “no manifest” with a reportable reason, not abort the whole bag load.

Parameter Type Default value
raw unknown undefined
label string "manifest"
options { source?: "local" | "remote"; } {}
options.source? "local" | "remote" undefined

object

Name Type
error string | null
manifest BagManifest | null

readBagResourceRegistry(path?): Record<string, BagResource>

Every registered resource, or none when there is no readable registry.

Parameter Type
path? string

Record<string, BagResource>


readRawManifest(bagDir): Record<string, unknown> | null

The raw manifest object as written on disk, before normalization.

Used when serving a bag over MCP: the wire format is the author’s bag.yaml as JSON, so it round-trips through the same strict schema on the other side. Re-emitting the normalized form would not, because normalization renames keys (mcp-servers -> mcpServers).

Parameter Type
bagDir string

Record<string, unknown> | null


registeredName(manifest): string

Parameter Type
manifest { extends?: string; name: string; }
manifest.extends? string
manifest.name string

string


resolveBagAccess(source): BagAccessLevel

Resolve a bag’s effective access level, honoring the legacy disabled boolean for back-compat. An explicit access field wins.

Parameter Type
source BagSource

BagAccessLevel


resourceKey(bag, name): string

<bag>.<name> — the key consumers look up.

Parameter Type
bag string
name string

string


validateAgainstSchema(schema, value): SchemaValidationResult

Validate a value against a JSON Schema.

Errors are pre-formatted strings rather than Ajv’s error objects: every consumer here feeds them back to an agent or an HTTP 422 body, and Ajv’s internal shape is neither stable API nor legible prose.

Parameter Type
schema Record<string, unknown>
value unknown

SchemaValidationResult