Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
69 changes: 4 additions & 65 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,10 @@ gh skills clone githubnext/rig
```ts
import {
agent,
addons,
configureAgent,
defineTool,
oncePerAgent,
p,
repair,
s,
steering,
timeout,
} from "rig";
```

Expand All @@ -38,9 +33,6 @@ import {
- ``p`...` `` also works in `instructions` to embed prompt intents directly: `` instructions: p`Review ${p.bash("git status")}` ``.
- `configureAgent(factory)` selects the SDK adapter used to instantiate runtime agents.
- `defineTool(name, config)` creates an SDK-neutral tool definition and accepts rig `s.*` schemas for `parameters`.
- `addons` accepts express-like `(context, next)` turn addons for steering, inline validation, and runtime agent access.
- `rig` starts with no default addons.
- `rig` exports addon helpers: `oncePerAgent`, `repair`, `steering`, `timeout`, and `addons.{oncePerAgent,repair,steering,timeout}`.
- `p\`...\`` returns a prompt builder and renders intent values when coerced to string; prefer `${p.read(...)}` / `${p.bash(...)}` when the context source is already known.

## Embedding in markdown
Expand Down Expand Up @@ -207,7 +199,6 @@ Use these samples to quickly gauge how well `rig` supports increasingly agentic

- Default model: `small`
- Default max turns: `4`
- No addons are loaded by default (including repair/retry behavior)

Per call, you can override `model`, `timeout`, `maxTurns`, and `signal`.

Expand All @@ -227,66 +218,14 @@ Custom integrations can use the same logger. Details are evaluated only when the
```ts
import { debug } from "rig";

const log = debug("my-addon:result");
const log = debug("my-component:result");
log({ status: "started" });
log(() => ({ result: expensiveResult() }));
if (log.enabled) {
// Optional setup for debug-only instrumentation.
}
```

## Addons

Each agent call runs a per-turn addon chain:

```ts
const steerFinalTurn = async (context, next) => {
await next();
if (context.nextPrompt && context.turn === context.maxTurns - 1) {
context.nextPrompt = `${context.nextPrompt}\nYou are running out of turns. Return corrected JSON now.`;
}
};

const review = agent({
maxTurns: 3,
addons: steerFinalTurn,
});
```

`context` includes `prompt`, `response`, `turn`, `maxTurns`, `signal`, `output`, `nextPrompt`, `error`, and `completed`.

For the common retry flow with last-turn steering or stable default timeouts, opt into addons:

```ts
const review = agent({
maxTurns: 3,
addons: [timeout({ timeout: 30_000 }), steering(), repair()],
});
```

Use `oncePerAgent(...)` to register with the runtime agent once:

```ts
const review = agent({
addons: oncePerAgent((runtimeAgent) => {
// Register adapter-specific behavior here.
}),
});
```

Per-turn addons receive `context.agent`, and you can also register addons after creating the agent:

```ts
const timingAddon = async (context, next) => {
await next();
};

const review = agent({});
review.use(timingAddon);
```

`agent.use()` accepts **only** `AgentAddon | AgentAddon[]`. It does not accept spec fields or call-time overrides — passing `maxTurns`, `model`, or similar is a type error. Put stable defaults in the agent spec; pass per-run overrides at call time.

## Agent implementations

Rig runs only against a minimal interface:
Expand Down Expand Up @@ -326,11 +265,11 @@ configureAgent(geminiEngine());

If you do not call `configureAgent(...)`, Rig now auto-selects a default engine from environment variables: `COPILOT_SDK_URI` → `copilotEngine()`, then `RIG_ENGINE` (`copilot` | `anthropic` | `codex` | `gemini`) if set, then `ANTHROPIC_API_KEY` → `anthropicEngine()`, `OPENAI_API_KEY` → `codexEngine()`, `GEMINI_API_KEY`/`GOOGLE_API_KEY` → `geminiEngine()`, otherwise `copilotEngine()`.

`piEngine()` uses the maintained `@earendil-works/pi-agent-core` package and requires the provider for model lookup. `anthropicEngine()` uses `@anthropic-ai/sdk`. Both adapters preserve conversation state across repair turns and map Rig tools to their SDK tool runners.
`piEngine()` uses the maintained `@earendil-works/pi-agent-core` package and requires the provider for model lookup. `anthropicEngine()` uses `@anthropic-ai/sdk`. Both adapters preserve conversation state and map Rig tools to their SDK tool runners.

`codexEngine()` uses `@openai/codex-sdk`, preserves its thread across repair turns, and accepts Codex client options plus thread options under `thread`. Rig system messages become Codex developer instructions. The Codex SDK does not expose custom tool registration, so the adapter rejects agents with Rig tools.
`codexEngine()` uses `@openai/codex-sdk`, preserves its thread across turns, and accepts Codex client options plus thread options under `thread`. Rig system messages become Codex developer instructions. The Codex SDK does not expose custom tool registration, so the adapter rejects agents with Rig tools.

`geminiEngine()` runs the installed `gemini` executable in headless JSON mode and resumes its session across repair turns. It accepts `command`, `cwd`, additional CLI `args`, environment variables, and an optional `approvalMode`. Rig system messages are prepended to the first prompt. The Gemini CLI does not expose custom tool registration through its command line, so the adapter rejects agents with Rig tools.
`geminiEngine()` runs the installed `gemini` executable in headless JSON mode and resumes its session across turns. It accepts `command`, `cwd`, additional CLI `args`, environment variables, and an optional `approvalMode`. Rig system messages are prepended to the first prompt. The Gemini CLI does not expose custom tool registration through its command line, so the adapter rejects agents with Rig tools.

## Copilot SDK adapter

Expand Down
2 changes: 1 addition & 1 deletion scripts/haiku.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -89,7 +89,7 @@ describe("rig runtime integration", () => {
);

itWithToken(
"runs a complex sonnet sample with tools, addons, intents, and subagent wiring",
"runs a complex sonnet sample with tools, intents, and subagent wiring",
async () => {
const stdout = await runIntegrationSample(
complexSamplePath,
Expand Down
18 changes: 7 additions & 11 deletions skills/rig/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export default reviewDiff;

## Construction rules

1. Use one `import { ... } from "rig"` statement (including addons such as `repair`/`steering`) and `agent({ ... })`; add a `// Agent role: ...` comment above every agent.
1. Use one `import { ... } from "rig"` statement and `agent({ ... })`; add a `// Agent role: ...` comment above every agent.
2. Set examples to `model: "large"`, `"mini"`, or `"nano"`.
3. Omit `input` and `output` when the default free-form `s.string` schemas are enough; otherwise use explicit `s.*` schemas.
4. Put known workspace context directly in `p\`...\`` with `${p.read(...)}` or `${p.bash(...)}`. Add `input` only for values supplied by the caller.
Expand All @@ -45,14 +45,12 @@ export default reviewDiff;

## Put settings in the right place

| Concern | Location | Addon args |
|---------|----------|------------|
| `name`, `instructions`, `input`, `output`, tools, stable `model`/`maxTurns` | `agent({ ... })` | n/a |
| Per-run `model`, `maxTurns`, `timeout`, `signal` | `myAgent(input, { ... })` | n/a |
| Stable addons | `addons` in the spec | `steering({ message? })`, `timeout({ timeout })`, `repair()` (no args) |
| Additional addons | `agent.use(addon)` | Same signatures as spec addons |
| Concern | Location |
|---------|----------|
| `name`, `instructions`, `input`, `output`, tools, stable `model`/`maxTurns` | `agent({ ... })` |
| Per-run `model`, `maxTurns`, `timeout`, `signal` | `myAgent(input, { ... })` |

Defaults are model `small`, `maxTurns: 4`, no addons, and name `"agent"`. `agent.use()` accepts only addons.
Defaults are model `small`, `maxTurns: 4`, and name `"agent"`.

## Choose schemas deliberately

Expand Down Expand Up @@ -109,8 +107,6 @@ For `p.readOptional` fallbacks, pass a value the model can parse in context (for
- Define tools with `defineTool(name, { description, parameters: s.object(...), handler })`; schema-based handler arguments are inferred and tools default to `skipPermission: true`. `s.unknown` is valid in tool parameters when the tool needs to compare or echo arbitrary JSON-like values, for example `parameters: s.object({ currentValue: s.unknown, recommendedValue: s.unknown })`. Handlers can be sync or async and return a string or any JSON-serializable value; return plain JS values (do not `JSON.stringify`) because Rig serializes non-string results automatically. Async handlers may import Node built-ins with `await import("node:child_process")`. Destructure only handler fields you use. Use `defineTool` for external operations and deterministic transforms that support reasoning — not to replace the core classification or judgment step with in-process TypeScript logic.
- `agents` must be a named object — `agents: { extractor }` — never an array (`agents: [extractor]` is a type error). Attach every declared subagent to the exported root's graph.
- There is no chain or loop primitive; give the coordinator explicit delegation instructions and require one combined output.
- Automatic parse/schema repair uses `repair()` from `rig`; `repair()` takes no arguments. Put retries on the agent spec: `maxTurns: 3, addons: repair()`. Do not pass retries to `repair()` (`addons: repair({ maxTurns: 3 })` is invalid).
- `addons` accepts a single addon or an array. Use `addons: repair()` for one addon, and `addons: [steering(), repair()]` when combining. **`steering()` must come before `repair()` in the array** — write `[steering(), repair()]`, not `[repair(), steering()]`. `steering()` (default warning) or `steering({ message: "..." })` (custom text) should run before `repair()` to append a last-chance instruction on the final retry. `oncePerAgent(callback)` invokes the callback once per runtime agent instance — its internal `WeakSet` already deduplicates, so no external tracking is needed.

## Runnable markdown

Expand Down Expand Up @@ -147,6 +143,6 @@ Read only the reference needed for the current task:

- [Agent API and schemas](references/agent-api.md) — spec fields, schema overloads, tools, and call-time options.
- [Prompt intents](references/prompt-intents.md) — helper semantics, writes, dynamic paths, and failures.
- [Composition and addons](references/composition.md) — subagents, coordinator patterns, repair, and addon lifecycle.
- [Composition](references/composition.md) — subagents and coordinator patterns.
- [Linting](references/linting.md) — custom Rig ESLint rules, fixes, and rule scaffolding.
- [Running and engines](references/runtime.md) — inline/file launch modes, typechecking, stdin coercion, and SDK adapters.
2 changes: 0 additions & 2 deletions skills/rig/addons.ts

This file was deleted.

18 changes: 6 additions & 12 deletions skills/rig/references/agent-api.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,17 @@ Use `agent({ name, ... })` as the only declaration form. `name` is optional and
| `input` | Input schema; defaults to `s.string` |
| `output` | Output schema; defaults to `s.string` |
| `model` | Default model for calls |
| `maxTurns` | Total turn budget, including the initial attempt |
| `addons` | Per-turn steering, validation, and retry behavior |
| `maxTurns` | Total turn budget |
| `agents` | Named subagents exposed to the harness |
| `tools` | Function-calling tools created with `defineTool` or compatible plain objects |

### Setting placement

| Setting | Spec | Call-time | `.use(addon)` |
|---------|------|-----------|---------------|
| `name`, `instructions`, `input`, `output`, `agents`, `tools` | yes | — | — |
| `model`, `maxTurns` | default | override | — |
| `timeout`, `signal` | — | yes | — |
| `addons` | stable addons | — | additional addons |

`agent.use()` accepts only `AgentAddon | AgentAddon[]`; passing spec fields or invocation options is a type error.
| Setting | Spec | Call-time |
|---------|------|-----------|
| `name`, `instructions`, `input`, `output`, `agents`, `tools` | yes | — |
| `model`, `maxTurns` | default | override |
| `timeout`, `signal` | — | yes |

### Defaults

Expand All @@ -37,7 +33,6 @@ Use `agent({ name, ... })` as the only declaration form. `name` is optional and
| Input/output | `s.string` |
| Model | `small` |
| Max turns | `4` |
| Addons | none |

## Schema helpers

Expand Down Expand Up @@ -219,6 +214,5 @@ Use only the current API:
- `agent({ name, ... })`
- `p.*` and ``p`...` `` from `rig`
- `s.*` for explicit schemas
- `oncePerAgent`, `repair()`, `steering`, and `timeout` from `rig`

Do not add deprecated hooks, alternate schema syntaxes, or compatibility bridges.
98 changes: 3 additions & 95 deletions skills/rig/references/composition.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Composition and addons
# Composition

Read this reference when a root agent delegates work, coordinates a dynamic set, or needs retry/addon behavior.
Read this reference when a root agent delegates work or coordinates a dynamic set.

## Named subagents

Expand Down Expand Up @@ -124,7 +124,7 @@ const coordinator = agent({
export default coordinator;
```

For large lists the model may process a subset. Give it an adequate turn budget, explicit completeness requirements, and repair when structured completeness matters.
For large lists the model may process a subset. Give it an adequate turn budget and explicit completeness requirements.

## Runnable markdown task harness

Expand All @@ -135,95 +135,3 @@ When a task asks for runnable markdown:
- define one default-exported root with no required external input
- do not call the root in the snippet
- keep every subagent attached to the root graph

## Repair

Rig starts with no addons. `maxTurns` is only the total budget; automatic parse/schema correction requires `repair()`:

```ts
import { agent, repair } from "rig";

// Agent role: return a valid concise summary.
const summarize = agent({
model: "mini",
maxTurns: 3,
addons: repair(),
});

export default summarize;
```

`repair()` takes no options. The budget includes the initial attempt and all retries — for example, `maxTurns: 3` means one initial attempt plus two repair retries. Configure `maxTurns` on the agent spec; a call-time value can override it.

```ts
// wrong: repair() does not accept maxTurns
addons: repair({ maxTurns: 3 });

// right: put maxTurns on the agent spec
maxTurns: 3,
addons: repair();
```

## Final-turn steering

`steering()` appends a last-chance warning to the final retry prompt produced by `repair`. Put it before `repair` so it can observe the repair prompt as the addon chain unwinds:

```ts
import { agent, repair, steering } from "rig";

// Agent role: return a valid concise summary with final-turn steering.
const summarize = agent({
model: "mini",
maxTurns: 3,
addons: [steering(), repair()],
});

export default summarize;
```

```ts
// wrong: steering runs too late here
addons: [repair(), steering()];

// right: steering wraps repair
addons: [steering(), repair()];
```

Use `repair()` alone when the validation error is enough guidance. Pass custom warning text in an options object, as in `steering({ message: "Return valid JSON now." })`; a positional string is invalid. Do not use `steering()` without `repair()`, because it only augments prompts generated by repair.

## One-time runtime registration

`oncePerAgent(register)` invokes its callback exactly once per runtime agent instance — not once per turn and not once per retry. The callback receives `(agent: Agent, context: AgentAddonContext)`. Use it for one-time initialization such as registering a tool adapter or constructing a client:

```ts
import { agent, oncePerAgent, repair, s } from "rig";

// Agent role: answer after one-time runtime initialization.
const qa = agent({
model: "mini",
addons: [
oncePerAgent(async (runtimeAgent) => {
// e.g. register a tool adapter on the runtime agent once
await runtimeAgent.ask("initialize");
}),
repair(),
],
});

export default qa;
```

`oncePerAgent` tracks initialization via its own internal `WeakSet`; do not add an external `WeakSet` to track the same thing. Repair retries reuse the same runtime agent, so the registration still runs once.

Both `addons: singleAddon` and `addons: [addon1, addon2]` are valid. Prefer the array form when combining addons.

## Addon lifecycle

Per-turn addon context contains:

- `prompt`, `response`, `output`
- `turn`, `maxTurns`
- `signal`, `agent`
- `nextPrompt`, `error`, `completed`

Put stable addons in the agent spec. Add later behavior with `agent.use(addon)`; `.use()` does not accept model, turn, timeout, or signal options.
6 changes: 3 additions & 3 deletions skills/rig/references/runtime.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ interface Agent {
}
```

Register a factory with `configureAgent(factory)`. Rig creates one adapter instance per invocation and preserves it across repair turns.
Register a factory with `configureAgent(factory)`. Rig creates one adapter instance per invocation.

## Included engines

Expand Down Expand Up @@ -95,8 +95,8 @@ configureAgent(geminiEngine());
- `copilotEngine()` uses the Copilot SDK HTTP transport by default; launcher `--server` selects stdio.
- `piEngine({ provider })` uses `@earendil-works/pi-agent-core` and requires a provider for model lookup.
- `anthropicEngine()` uses `@anthropic-ai/sdk` and reads `ANTHROPIC_API_KEY`.
- `codexEngine(options)` uses `@openai/codex-sdk`, accepts thread options under `thread`, preserves the thread across repair turns, and maps Rig system messages to developer instructions. It rejects Rig tools because the SDK does not expose custom tool registration.
- `geminiEngine(options)` runs an installed Gemini CLI in headless JSON mode and resumes its session across repair turns. It accepts `command`, `cwd`, CLI `args`, environment variables, and `approvalMode`; it rejects Rig tools because the CLI does not expose registration.
- `codexEngine(options)` uses `@openai/codex-sdk`, accepts thread options under `thread`, preserves the thread across turns, and maps Rig system messages to developer instructions. It rejects Rig tools because the SDK does not expose custom tool registration.
- `geminiEngine(options)` runs an installed Gemini CLI in headless JSON mode and resumes its session across turns. It accepts `command`, `cwd`, CLI `args`, environment variables, and `approvalMode`; it rejects Rig tools because the CLI does not expose registration.

## Operational conventions

Expand Down
Loading