Call Barry from a bag
Use this when a bag’s code needs Barry itself: a tool that records an event, a
script that lists an identity’s sessions, a service that starts an action. The
Barry object from @barry-rocks/sdk answers for identities, sessions, bags,
actions, events, schedules, and services, with the same calls in every
process. The example is pulse, a bag that holds two tools plus a report
script.
Before you begin
Section titled “Before you begin”- Create a bag.
- Know which process your code runs in. A tool runs inside Barry’s tool host, and a service or a script runs on its own. The difference decides how you connect.
Call Barry from a tool
Section titled “Call Barry from a tool”Barry’s tool host installs every subject Barry answers for, so a tool calls
Barry directly, with no setup. The host names no caller, so these calls act
as the installation’s owner, with full access. To act with a session’s access
instead, call through Barry.as({ kind: "session", identity, sessionId }),
which needs the session’s identity as well as its ID.
import { Barry } from "@barry-rocks/sdk";import { z } from "zod";
export const announce = Barry.Tool({ namespace: "pulse", name: "announce", access: "write", description: "Record an event that shows up in Barry's event feed.", schema: { title: z.string().min(1).max(500).describe("One line on what happened"), body: z.string().optional().describe("Details"), }, handler: async ({ title, body }, context) => { const event = await Barry.recordEvent({ type: "notification", severity: "info", title, ...(body ? { body } : {}), ...(context?.sessionId ? { session: context.sessionId } : {}), }); return { id: event.id, createdAt: event.createdAt }; },});
export const scheduleHealth = Barry.Tool({ namespace: "pulse", name: "schedule_health", access: "read", description: "Say whether the scheduler is alive and which schedules keep failing.", schema: {}, handler: async () => { const [scheduler, schedules] = await Promise.all([Barry.scheduler(), Barry.schedules()]); return { scheduler: scheduler.status, failing: schedules.filter((schedule) => schedule.consecutiveFailures > 0).map((schedule) => schedule.id), }; },});An event’s type is progress, notification, task_finished, or
system_alert, and its severity is info, warn, error, or success.
Connect from a script or a service
Section titled “Connect from a script or a service”Any other process connects first. Barry.connect points every later call at
one Barry over HTTP, and localConnection from @barry-rocks/sdk/services
finds this machine’s Barry and its instance secret. Barry.at returns a
separate Barry for another installation, with its own address and
credential, and leaves the default connection alone.
import { Barry, isBarryError } from "@barry-rocks/sdk";import { localConnection } from "@barry-rocks/sdk/services";
/** This machine's Barry, or another one when BARRY_REMOTE_URL names it. */async function connect(): Promise<Barry> { const remote = process.env.BARRY_REMOTE_URL; if (remote) return Barry.at({ baseUrl: remote, secret: process.env.BARRY_REMOTE_SECRET }); Barry.connect(await localConnection()); return Barry;}
const identity = process.argv[2];if (!identity) { console.error("usage: report.ts <identity>"); process.exit(2);}
const barry = await connect();try { for await (const session of barry.identity(identity).sessions({ active: true })) { console.log(`${session.id} ${session.status} ${session.name}`); } const unread = await barry.events({ unread: true }); console.log(`${unread.length} unread event(s)`);} catch (error) { if (!isBarryError(error)) throw error; console.error(`${error.reason}: ${error.message}${error.hint ? `\n${error.hint}` : ""}`); process.exitCode = 1;}A process that neither hosts Barry nor connects gets a BarryError with the
reason unavailable on its first call. The bag declares the script under
scripts:, so Barry can run it on demand:
name: pulsedescription: Reports on this Barry, its sessions, events, and schedules, by calling Barry from bag code.
scripts: report: description: Print an identity's active sessions and the unread event count. entry: src/report.tsBarry runs a declared script as barry <bag> <script> [args...], so this
one runs as barry pulse report <identity>. It starts the script in the bag’s directory,
passes the words after the script’s name as arguments, and hands the
script’s exit code back as its own. A bag’s command group appears only for a
bag listed under bags: in cli.yaml, in the settings directory that
barry config show prints, so add pulse to that list first.
Barry runs a TypeScript entry through tsx’s loader, which it finds in the
bag’s own node_modules, so the bag lists tsx beside the SDK:
{ "name": "pulse-bag", "private": true, "type": "module", "scripts": { "test": "vitest run" }, "dependencies": { "@barry-rocks/sdk": "link:../barry/sdk", "tsx": "4.23.12", "zod": "4.4.3" }, "devDependencies": { "vitest": "4.1.0" }}Use handles and collections
Section titled “Use handles and collections”Naming a thing returns a handle, such as Barry.identity("bonanza") or
Barry.schedule("standup/sweep"). A handle makes no request until you call a
method on it, and a misspelled name fails on that first call.
A method that returns many items returns a collection. Await it to get every
item as an array, or iterate it with for await to fetch one page at a time:
Barry.identities(), and an identity’ssessions(query)- a session’s
messages(query) Barry.events(query)
The rest of what Barry answers, by subject:
| Subject | Calls |
|---|---|
| Identities | identity(ref) with get, update, traits, grants, tools, session(id), and start(input); createIdentity(input) |
| Sessions | A session handle’s get, send(text), events() for live events, changes, end, and archive |
| Bags | bags(), and bag(name) with tools, store(name), and job(name).run(inputs) |
| Actions | actions(), and action(name) with run(inputs) and runs() |
| Events | events(query) and recordEvent(input) |
| Schedules | schedules(), scheduler(), schedule(id).runs(), scheduleOnce(input), oneOffs(query), and oneOff(id).cancel() |
| Services | services(), and service(key) with get, start, stop, and restart |
scheduleOnce runs an action once at dueAt, with optional inputs,
identity, traits, bounds, from 1 to 5 attempts, and an
idempotencyKey.
Some calls work only in a process that hosts Barry, among them an identity’s
or a session’s grants, a session’s live events, a bag’s tools, and listing
closed sessions. Over HTTP, those fail with the reason unavailable and a
hint.
Handle a refusal
Section titled “Handle a refusal”Every refusal is a BarryError. Its reason is one of invalid,
forbidden, not_found, conflict, or unavailable; its message says what
happened; and its hint, when present, says what to do. Test for it with
isBarryError, which also recognizes an error from another copy of the SDK
bundled into a different bag.
Verify
Section titled “Verify”To call the tools from the terminal, install, pack, and grant the bag as Create a bag describes, and run:
barry install ~/repos/pulsebonanza pack pulsebonanza identity traits add pulsebonanza tool pulse announce "Deployed the docs"bonanza events listThe event list shows Deployed the docs as a notification. To run the script
against this machine’s Barry, run barry pulse report <identity> with your identity’s name: it prints each
active session’s ID, status, and name, then the unread event count. Run as an
identity’s command, such as bonanza pulse report <identity>, a script needs
that identity’s unbounded Bash grant.
If it fails
Section titled “If it fails”- A call fails with
unavailableand says nothing answers. The process doesn’t host Barry and never connected. CallBarry.connectfirst. - A call fails with
not_found. A handle names an identity, a session, or a schedule that doesn’t exist. Check the name. - A call fails with
forbidden. The caller may not do it. A call throughBarry.aswith a session caller has only that session’s identity’s access, and a paired device has only its grant. - Barry answers the script with
Unknown command. The bag isn’t listed incli.yaml. Addpulseunderbags:. - For anything else, see Troubleshooting.
What’s next
Section titled “What’s next”- Test a bag to replace these subjects with fakes.
- The root SDK reference for every type the calls take and return.
- Add a service for a process that calls Barry while it runs.