@yaag/extension

The yaag pi extension: run Orchestration Programs from a pi session.

Packages

Package details

extension

Install @yaag/extension from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@yaag/extension
Package
@yaag/extension
Version
0.28.1
Published
Sep 14, 2026
Downloads
4,063/mo · 965/wk
Author
dzag
License
unknown
Types
extension
Size
523.8 KB
Dependencies
5 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./src/index.ts"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

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/:

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 | bash

A 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 root

Pi 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.ts

and 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.

  1. stdin EOF — the extension closes the child's stdin; the CLI unwinds through the runtime's own Agent reap ladder.
  2. SIGTERM — if the CLI is still there.
  3. 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.