rig
Minimal TypeScript harness for typed agents in sandboxed workflows and runnable rig markdown fences.
Use this file for construction defaults. Load only the focused reference named by the task; do not read every reference preemptively.
Canonical program
import { agent, p, s } from "rig";
// Agent role: review the current diff and return prioritized findings.
const reviewDiff = agent({
model: "small",
instructions: p`Review ${p.bash("git diff -- .")} and return only the declared output.`,
output: s.object({
summary: s.string,
risk: s.enum("low", "medium", "high"),
findings: s.array(s.object({
file: s.path,
line: s.optional(s.int),
message: s.string,
})),
}),
});
export default reviewDiff;
Construction rules
- Import current APIs from
"rig" once and define agents with agent({ ... }) or workflows with workflow({ ... }).
- Add a
// Agent role: ... comment above each agent() and a // Workflow role: ... comment above each workflow().
- Omit
input/output when free-form strings suffice; otherwise use explicit s.* schemas.
- Put known workspace context in
p`...` with p.read, p.bash, or another intent. Use input only for caller-supplied values.
- Keep outputs strict and small; prefer
s.enum, s.literal, s.path, and s.int when they express the contract.
- Add narrow, named subagents only when delegation helps; attach them as
agents: { name }.
- Export exactly one root value — an
agent or a workflow. Do not invoke it or print its result in generated programs.
Defaults: name: "agent", model: "small", maxTurns: 4, string input/output, and no addons.
High-frequency decisions
| Need |
Choose |
| Known required/optional file |
p.read(path) / p.readOptional(path, fallback?) (prefer this over `cat ... |
| Several known files |
p.readAll(["path/a.ts", "path/b.ts"]) (explicit array of literal paths) |
| Static shell command |
p.bash(command); use p.bashRaw`...` for literal backslashes |
| Caller-supplied path(s) |
p.readInput(field) / p.readAllInput(field) with s.path schemas |
| Discover workspace paths |
p.glob(pattern) returns paths only; then delegate one path at a time to a subagent using p.readInput("path") (there is no p.readAll(globPattern) overload) |
| Persist generated output |
p.writeOutput(field, path) or p.writeInput(pathField, outputField) |
| String-keyed map |
s.record(value); keys are always string — do not wrap in s.object; use s.record(s.int) for count maps |
| URL and file-path fields |
s.url for URIs, s.path for paths, and wrappers like s.array(s.path) for path lists |
| Numeric schema choice |
s.int for counts/line numbers; s.number for measurements and ratios |
| Optional versus nullable |
s.optional(shape) for omission; s.nullable(shape) for explicit null |
| Deterministic TypeScript fan-out |
workflow({ meta, input?, body }) + export default; use call, pipeline, parallel, until inside body |
| One-off prompt inside a workflow |
call.text(prompt) for a string, call.json(prompt, schema) for structured output |
| Reusable workflow step |
Define an agent({ input, output }) and call(worker, input, { label, phase }) |
| Phase or log from an agent program |
Import phase / log from rig and call them at top level; the launcher runs every program inside a workflow |
Ambient call outside body |
Import call from "rig/globals"; it routes through the active workflow context automatically. Do not import from "rig/globals" unless you need it — this avoids polluting non-workflow code. |
| Custom model-callable operation |
defineTool(name, { description, parameters, handler }) |
| Structured-output retries |
maxTurns on the agent plus addons: [repair()] |
| Retry with final-turn warning |
addons: [steering(), repair()] in that order |
Prompt intents are declarative instructions, not in-process operations. Prefer file intents over cat and workspace paths over large in-memory strings.
Composition invariants
agents is a named object, never an array; every subagent must be reachable from the exported root.
- There is no chain or loop primitive. Tell the coordinator what to delegate, in what order, and what combined output to return.
defineTool uses the two-argument config form. Use s.object({ ... }) for object-shaped parameters — plain { key: s.string } loses handler arg type inference. Arrow callbacks in handlers must have explicit type annotations: .map((line: string) => ...).
repair() takes no arguments. Turn budgets belong on the agent spec or invocation. Repair turns are parse/schema retry turns; a steering turn is only the last warning prepended to the final repair retry.
- Stable settings belong in
agent({ ... }); per-run model, maxTurns, timeout, and signal belong on invocation; agent.use() accepts only addons.
- Valid
agent() fields: name, instructions, input, output, model, maxTurns, addons, agents, systemMessage, tools. Misspelled keys (e.g. instructions2) are silently dropped; the linter flags them.
- Handler functions that return string literals must use
as const to preserve the literal type for enum schema comparison. Example: return "stable" as const.
Runnable output
For runnable markdown, emit exactly one fenced rig block with one default-exported root (agent or workflow) and no required external input. Never call the root inside the fence. Add a // Agent role: ... comment above each agent() and a // Workflow role: ... comment above each workflow().
Before running generated TypeScript:
node skills/rig/eslint/lint.js program.ts
cat program.ts | node skills/rig/rig.ts --typecheck
Final check
- Known context uses
p.*; caller data uses input.
- Schemas use only current
s.* helpers and constrain important output.
- Every import, addon, tool, and helper follows the current API.
- Every subagent is named, reachable, and narrowly scoped.
- The program has one default export (an
agent or a workflow) and no console.log.
- Linting and typechecking pass.
Focused references
Read only when the task needs the listed detail:
- Agent API and schemas — spec fields, schema overloads, tools, and invocation options.
- Prompt intents — complete helper semantics, dynamic inputs, writes, and failure behavior.
- Composition and addons — delegation patterns, dynamic sets, repair, steering, and addon lifecycle.
- Dynamic workflows — bounded fan-out, failure semantics, limits, budget, events, and convergence loops.
- Claude workflow conversion — mapping Claude Code dynamic-workflow scripts onto rig primitives, including model selection and the Anthropic engine.
- Running and engines — markdown/file launch modes, typechecking, Agentic Workflows, and SDK adapters.
- Linting — linter usage, autofixes, rules, and rule development.
1---2name: rig3description: Minimal agent cli harness for defining harnesses in prompts as rig markdown fences.4license: MIT5---6
7# rig
8
9Minimal TypeScript harness for typed agents in sandboxed workflows and runnable `rig` markdown fences.
10
11Use this file for construction defaults. Load only the focused reference named by the task; do not read every reference preemptively.
12
13## Canonical program
14
15```ts
16import { agent, p, s } from "rig";
17
18// Agent role: review the current diff and return prioritized findings.
19const reviewDiff = agent({
20 model: "small",
21 instructions: p`Review ${p.bash("git diff -- .")} and return only the declared output.`,
22 output: s.object({
23 summary: s.string,
24 risk: s.enum("low", "medium", "high"),
25 findings: s.array(s.object({
26 file: s.path,
27 line: s.optional(s.int),
28 message: s.string,
29 })),
30 }),
31});
32
33export default reviewDiff;
34```
35
36## Construction rules
37
381. Import current APIs from `"rig"` once and define agents with `agent({ ... })` or workflows with `workflow({ ... })`.
392. Add a `// Agent role: ...` comment above each `agent()` and a `// Workflow role: ...` comment above each `workflow()`.
403. Omit `input`/`output` when free-form strings suffice; otherwise use explicit `s.*` schemas.
414. Put known workspace context in ``p`...` `` with `p.read`, `p.bash`, or another intent. Use `input` only for caller-supplied values.
425. Keep outputs strict and small; prefer `s.enum`, `s.literal`, `s.path`, and `s.int` when they express the contract.
436. Add narrow, named subagents only when delegation helps; attach them as `agents: { name }`.
447. Export exactly one root value — an `agent` or a `workflow`. Do not invoke it or print its result in generated programs.
45
46Defaults: `name: "agent"`, `model: "small"`, `maxTurns: 4`, string input/output, and no addons.
47
48## High-frequency decisions
49
50| Need | Choose |
51|------|--------|
52| Known required/optional file | `p.read(path)` / `p.readOptional(path, fallback?)` (prefer this over `cat ... || echo ...`) |
53| Several known files | `p.readAll(["path/a.ts", "path/b.ts"])` (explicit array of literal paths) |
54| Static shell command | `p.bash(command)`; use ``p.bashRaw`...` `` for literal backslashes |
55| Caller-supplied path(s) | `p.readInput(field)` / `p.readAllInput(field)` with `s.path` schemas |
56| Discover workspace paths | `p.glob(pattern)` returns paths only; then delegate one path at a time to a subagent using `p.readInput("path")` (there is no `p.readAll(globPattern)` overload) |
57| Persist generated output | `p.writeOutput(field, path)` or `p.writeInput(pathField, outputField)` |
58| String-keyed map | `s.record(value)`; keys are always `string` — do not wrap in `s.object`; use `s.record(s.int)` for count maps |
59| URL and file-path fields | `s.url` for URIs, `s.path` for paths, and wrappers like `s.array(s.path)` for path lists |
60| Numeric schema choice | `s.int` for counts/line numbers; `s.number` for measurements and ratios |
61| Optional versus nullable | `s.optional(shape)` for omission; `s.nullable(shape)` for explicit `null` |
62| Deterministic TypeScript fan-out | `workflow({ meta, input?, body })` + `export default`; use `call`, `pipeline`, `parallel`, `until` inside `body` |
63| One-off prompt inside a workflow | `call.text(prompt)` for a string, `call.json(prompt, schema)` for structured output |
64| Reusable workflow step | Define an `agent({ input, output })` and `call(worker, input, { label, phase })` |
65| Phase or log from an agent program | Import `phase` / `log` from `rig` and call them at top level; the launcher runs every program inside a workflow |
66| Ambient `call` outside `body` | Import `call` from `"rig/globals"`; it routes through the active workflow context automatically. Do not import from `"rig/globals"` unless you need it — this avoids polluting non-workflow code. |
67| Custom model-callable operation | `defineTool(name, { description, parameters, handler })` |
68| Structured-output retries | `maxTurns` on the agent plus `addons: [repair()]` |
69| Retry with final-turn warning | `addons: [steering(), repair()]` in that order |
70
71Prompt intents are declarative instructions, not in-process operations. Prefer file intents over `cat` and workspace paths over large in-memory strings.
72
73## Composition invariants
74
75- `agents` is a named object, never an array; every subagent must be reachable from the exported root.
76- There is no chain or loop primitive. Tell the coordinator what to delegate, in what order, and what combined output to return.
77- `defineTool` uses the two-argument config form. Use `s.object({ ... })` for object-shaped parameters — plain `{ key: s.string }` loses handler arg type inference. Arrow callbacks in handlers must have explicit type annotations: `.map((line: string) => ...)`.
78- `repair()` takes no arguments. Turn budgets belong on the agent spec or invocation. Repair turns are parse/schema retry turns; a steering turn is only the last warning prepended to the final repair retry.
79- Stable settings belong in `agent({ ... })`; per-run `model`, `maxTurns`, `timeout`, and `signal` belong on invocation; `agent.use()` accepts only addons.
80- Valid `agent()` fields: `name`, `instructions`, `input`, `output`, `model`, `maxTurns`, `addons`, `agents`, `systemMessage`, `tools`. Misspelled keys (e.g. `instructions2`) are silently dropped; the linter flags them.
81- Handler functions that return string literals must use `as const` to preserve the literal type for enum schema comparison. Example: `return "stable" as const`.
82
83## Runnable output
84
85For runnable markdown, emit exactly one fenced `rig` block with one default-exported root (`agent` or `workflow`) and no required external input. Never call the root inside the fence. Add a `// Agent role: ...` comment above each `agent()` and a `// Workflow role: ...` comment above each `workflow()`.
86
87Before running generated TypeScript:
88
89```bash
90node skills/rig/eslint/lint.js program.ts
91cat program.ts | node skills/rig/rig.ts --typecheck
92```
93
94## Final check
95
96- Known context uses `p.*`; caller data uses `input`.
97- Schemas use only current `s.*` helpers and constrain important output.
98- Every import, addon, tool, and helper follows the current API.
99- Every subagent is named, reachable, and narrowly scoped.
100- The program has one default export (an `agent` or a `workflow`) and no `console.log`.
101- Linting and typechecking pass.
102
103## Focused references
104
105Read only when the task needs the listed detail:
106
107- [Agent API and schemas](references/agent-api.md) — spec fields, schema overloads, tools, and invocation options.
108- [Prompt intents](references/prompt-intents.md) — complete helper semantics, dynamic inputs, writes, and failure behavior.
109- [Composition and addons](references/composition.md) — delegation patterns, dynamic sets, repair, steering, and addon lifecycle.
110- [Dynamic workflows](references/dynamic-workflows.md) — bounded fan-out, failure semantics, limits, budget, events, and convergence loops.
111- [Claude workflow conversion](references/claude-workflow-conversion.md) — mapping Claude Code dynamic-workflow scripts onto rig primitives, including model selection and the Anthropic engine.
112- [Running and engines](references/runtime.md) — markdown/file launch modes, typechecking, Agentic Workflows, and SDK adapters.
113- [Linting](references/linting.md) — linter usage, autofixes, rules, and rule development.