@yaag/extension
v0.27.0
Published
The yaag pi extension: run Orchestration Programs from a pi session.
Readme
@yaag/extension
The yaag pi extension: run Orchestration Programs from a pi session.
It is loaded by pi through jiti, in Node, and bridges to the yaag CLI,
which runs on Bun (ADR-0005, ADR-0015).
Docs
The package ships the user and agent docs in docs/:
docs/getting-started.md— install, prerequisites, first Run.docs/authoring.md— how to write an Orchestration Program.docs/examples.md— eleven example programs, in reading order.docs/cli.md— CLI flags and tool parameters.docs/troubleshooting.md— the errors you can meet.
Prerequisites
Bun is required. The extension itself runs in pi's Node process, but every
Orchestration Program runs on Bun. Bun must be on PATH, or at ~/.bun/bin/bun:
curl -fsSL https://bun.sh/install | bashA missing Bun is reported once, when the extension loads, naming what to
install — never as an ENOENT at the first tool call.
Install
pi install ./packages/extension # from the monorepo rootPi records the local path without copying, so the workspace:* dependency on
the CLI resolves through the monorepo's node_modules. No publish, no bundle,
no version bump.
For a throwaway session instead: pi -e ./packages/extension.
What it provides
yaag_run
| Parameter | Type | Meaning |
|---|---|---|
| file | string? | Path to the Orchestration Program file |
| script | string? | Orchestration Program source text (Inline Program) |
| args | string? | The program's arguments, as a JSON object string |
| background | boolean? | Start the Run in the background and return its Run id |
| record | string? | Write this Run's Cassette artifact to this path |
| resume | string? | Replay a matching Cassette prefix, then continue live; alone it resumes a stored Inline Program |
Give file or script, and not both; a violation is a parameter error raised
before any process starts. A script program can import @yaag/runtime and
typebox only (ADR-0033). The source travels to the CLI on a descriptor, and
never through the process argument list (ps, /proc/<pid>/cmdline), which
every local user can read. The durable Run record keeps the source, so the
record directory is 0700 and each record file is 0600. To resume an inline
Run, give resume and give no file and no script: yaag reads the source
back from the Run record whose Checkpoint path matches, and gives it to the CLI
again, so the program identity check keeps its meaning (ADR-0033). Give script
again only to change the program. A Cassette that you
ask for with record also embeds the source, and it is written owner-only,
with mode 0600 (ADR-0021).
Blocking by default: the call returns when the Run ends, and its content is the
Run's return value. With background: true it returns at once with a short Run
id (r1, r2, …) and the result arrives later as a follow-up message that
triggers a turn. Background Runs may overlap; each has its own id and stop handle.
The model sees only the Run's value (or the id). The user additionally sees the
Run Summary — program, Agents, Asks settled, cost — refreshed after every
Lifecycle Event. Blocking completion and yaag_stop report token usage on pi's
nested-usage channel. Natural background completion is the exception: pi custom
follow-ups have no usage field, so their summaries remain available for rendering
without changing Host Session token or cost totals.
yaag_describe
| Parameter | Type | Meaning |
|---|---|---|
| file | string | Path to the Orchestration Program file |
| exportName | string | Describe this named export as an Agent Definition instead of the program |
With exportName it returns the Definition's declared metadata, any name
deviation, and any entry-level finding. A finding is reported in the result and
is not a failure.
Returns the program's declared { name, description, args } contract verbatim.
Use it to discover a known program's arguments before calling yaag_run; it does
not execute the Run or enumerate programs.
yaag_setup_workspace
| Parameter | Type | Meaning |
|---|---|---|
| dir | string? | Workspace root; omitted uses pi's current working directory |
Use this when an Orchestration Program author sees missing @yaag/runtime editor/type
resolution in their workspace. dir is optional and defaults to pi's current working
directory. Normally run setup once for that workspace, and rerun it when an upgrade
needs to refresh generated declarations. Its content is the CLI's verbatim per-artifact
written/skipped report. Setup is not a Run: it has no Run id, summary, progress, usage,
cassette, or follow-up behavior.
yaag_stop
| Parameter | Type | Meaning |
|---|---|---|
| id | string | The Run id to stop, as returned by yaag_run |
Reaps the Run's Agents and reports what it got through and what it spent: program, Agents seen, Asks settled, cost and tokens. Costs are reported as "at least" when the Summary is incomplete.
/yaag
Opens the yaag menu in the TUI: Programs, Status, Settings.
Programs lists the Runs of this session, and a selected Run opens its
interactive tree as the next level. esc goes up one level and closes the menu
at the root, which is the reliable exit. ctrl+c closes the menu from any
level, but it is a help only: pi gives ctrl+c to app.clear at app level, and
can thus take the byte first. ctrl+q and m in the Run tree
open the Run Control Menu. The menu lists only the actions that the Run permits
at that moment: Pause, Stop, Resume or Reattach, and Cancel. A Run that permits
no action shows no menu, and its footer shows no [m] hint. A live Run keeps
running after you leave the menu.
Status shows the resolved bun executable and CLI entry point, then the counts
of the live, the settled, and the restored Runs. Settings has one entry, Setup
workspace: it creates or refreshes editor type support under .yaag/ in pi's
current working directory and shows the per-artifact written/skipped report in
the pane. It is the user-facing counterpart of yaag_setup_workspace, not a
Run: it has no Run id, summary, progress, usage, cassette, or follow-up
behavior.
Where Orchestration Programs live
By convention, in a .yaag/ directory in your project — but nothing enforces
that location, and file accepts any path. Run yaag_setup_workspace once to create
.yaag/tsconfig.json and refresh .yaag/types/. The CLI vendors its runtime and
TypeBox declarations there, so editors and type checkers work without installing
workspace dependencies; execution separately uses the runtime shipped with the CLI.
Setup creates no program scaffold: authored programs remain yours, separate from its
generated declarations. In this monorepo the convention is
examples/. A program is a user-authored TypeScript file whose
default export is defineRun({ run }). Use file tools to find a known program, then use
yaag_describe({ file }) to discover its declared contract and arguments.
A worked example
.yaag/review.ts:
import { defineRun, prompt } from "@yaag/runtime";
export default defineRun({
name: "review",
description: "Review the working tree from one angle.",
run: async (ctx) => {
const agent = await ctx.spawn({
name: "reviewer",
model: ["anthropic/claude-opus-4:medium", "anthropic/claude-haiku-4"],
thinking: (model) => (model.includes("haiku") ? "low" : "high"),
tools: ["read", "grep"],
disallowedTools: ["yaag_run"],
skills: ["review"],
disallowedSkills: [],
});
return agent.ask(
prompt`
Audit this repo for issues.
Report the top three.
`,
{
maxTurns: 4,
maxToolCalls: 12,
maxDurationMs: 60_000,
wrapUpPrompt: "Give the findings now.",
},
);
},
});Program author controls
prompt\…`dedents static prompt text while preserving interpolated values.tools/disallowedToolsandskills/disallowedSkillsare spawn-level
restrictions: a skill is a portable name, never aSKILL.md` path. Tool allowlists
are applied before denylists; explicit empty allowlists disable flag-controllable
items.
maxTurns, maxToolCalls, and maxDurationMs are per-Ask soft limits. They
steer an Agent to wrap up, then abort only after grace; an ASK_LIMIT rejection
is recoverable and the Handle can be asked again. wrapUpPrompt replaces the
default steering message. This is intentionally different from timeoutMs, the
destructive fallback that rejects with ASK_TIMEOUT and closes the Agent.
model takes one pattern, an ordered list, or a function of the failures so
far; thinking takes a level or a function of the settled model. A pattern can
end with a thinking suffix ("opus-5:medium"), and the suffix wins over
thinking. The fallback rules — trigger classes, retry, termination — are
stated on the types in <program dir>/.yaag/types/runtime/index.d.ts and in
the root README.md; the sequences are
in ../../docs/architecture.md §4 and §6.
The tool call:
{ "file": ".yaag/review.ts", "args": "{\"focus\":\"security\"}" }For this blocking call, what comes back:
- content — the Run's return value, the reviewer's report (this is what the model reads)
- details — the Run Summary, rendered for the user
- usage — the token breakdown: input, output, cache read, cache write, total
The background variant:
{ "file": ".yaag/review.ts", "background": true }returns Run r1 started in the background. immediately; progress keeps
streaming into the fold, and when the Run ends a follow-up message arrives with
its value and final Run Summary in non-model-visible details. Pi's custom
follow-up API has no nested-usage field, so natural completion does not alter
Host Session token or cost totals. yaag_stop({ "id": "r1" }) ends it early
and reports its nested usage through the stop tool result.
Both entry points, one execution path
bun apps/yaag/src/cli.ts run .yaag/review.tsand yaag_run are the same execution path. The extension spawns exactly this
CLI as a child — bun cli.ts run <file> --events-fd 3 --args <json> — and reads
structured Lifecycle Events from descriptor 3 while the CLI's own stderr format
stays unchanged. An Inline Program adds one inbound channel: the extension
writes the source to descriptor 4, closes it, and names it as --eval-fd 4. What you debug in a terminal is what the session runs.
How stopping works
One ladder, taken by every route: pressing Esc during a blocking Run, calling
yaag_stop on a background Run, or closing the session.
- stdin EOF — the extension closes the child's stdin; the CLI unwinds through the runtime's own Agent reap ladder.
- SIGTERM — if the CLI is still there.
- group SIGKILL — only if the CLI ignored both.
It is the runtime's Agent reap ladder (ADR-0008), one process level up.
Docs
Provided tools: yaag_run, yaag_describe, yaag_setup_workspace, yaag_stop.
Provided commands: /yaag.
../../docs/architecture.md— the diagrams../../docs/adr/— why it is built this way- ADR-0018: foreign workspaces use CLI aliases and vendored types — workspace setup and foreign-program support
- ADR-0037: model resolution triggers are read from pi's stderr diagnostic — which failures start a fallback
- ADR-0038: a mid-Ask model swap is a yaag-side match, then
set_model— how a live Agent changes model - ADR-0039: replay adopts the recorded resolved model — why a replay skips the resolution loop
