diff --git a/README.md b/README.md index 12c4f78..3a29ecf 100644 --- a/README.md +++ b/README.md @@ -19,15 +19,10 @@ gh skills clone githubnext/rig ```ts import { agent, - addons, configureAgent, defineTool, - oncePerAgent, p, - repair, s, - steering, - timeout, } from "rig"; ``` @@ -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 @@ -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`. @@ -227,7 +218,7 @@ 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) { @@ -235,58 +226,6 @@ if (log.enabled) { } ``` -## 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: @@ -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 diff --git a/scripts/haiku.integration.test.ts b/scripts/haiku.integration.test.ts index 779739a..8c440ec 100644 --- a/scripts/haiku.integration.test.ts +++ b/scripts/haiku.integration.test.ts @@ -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, diff --git a/skills/rig/SKILL.md b/skills/rig/SKILL.md index a439c2c..4996715 100644 --- a/skills/rig/SKILL.md +++ b/skills/rig/SKILL.md @@ -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. @@ -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 @@ -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 @@ -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. diff --git a/skills/rig/addons.ts b/skills/rig/addons.ts deleted file mode 100644 index ed2d4f5..0000000 --- a/skills/rig/addons.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { addons, oncePerAgent, repair, steering, timeout } from "./rig.ts"; -export type { AgentRegistration, SteeringOptions, TimeoutOptions } from "./rig.ts"; diff --git a/skills/rig/references/agent-api.md b/skills/rig/references/agent-api.md index c85d5ed..d3aac78 100644 --- a/skills/rig/references/agent-api.md +++ b/skills/rig/references/agent-api.md @@ -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 @@ -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 @@ -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. diff --git a/skills/rig/references/composition.md b/skills/rig/references/composition.md index 087f894..b6392a2 100644 --- a/skills/rig/references/composition.md +++ b/skills/rig/references/composition.md @@ -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 @@ -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 @@ -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. diff --git a/skills/rig/references/runtime.md b/skills/rig/references/runtime.md index 177937e..513bfe1 100644 --- a/skills/rig/references/runtime.md +++ b/skills/rig/references/runtime.md @@ -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 @@ -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 diff --git a/skills/rig/rig.ts b/skills/rig/rig.ts index 8650865..78839ce 100644 --- a/skills/rig/rig.ts +++ b/skills/rig/rig.ts @@ -9,12 +9,10 @@ * T:AgentInputValue type input accepting raw values or PromptIntent/PromptBuilder at any nesting level * T:Simplify type flattens intersection types for display * T:ValidationResult type {ok:true}|{ok:false;error:string} - * T:AgentSpec type {name,description,input,output,prompt,addons?,maxTurns?,agents?} agent declaration; agents? enables sub-agent delegation - * T:AgentFn type callable agent with .use(addons) and .spec property + * T:AgentSpec type {name,description,input,output,prompt,maxTurns?,agents?} agent declaration; agents? enables sub-agent delegation + * T:AgentFn type callable agent with .spec property * T:AgentFactory type (options:AgentOptions)=>Agent|Promise * T:Agent interface {ask(input,opts?):Promise,close():Promise} - * T:AgentAddon type middleware (ctx,next)=>Promise; ctx exposes spec,turn,prompt,output,completed,nextPrompt - * T:AgentAddonContext type context passed to each addon in the chain * T:AgentDefinitionFactory type typeof agent (for passing agent constructor as value) * T:AgentError class error carrying kind,agent,turn,response,schema,schemaText fields * T:Tool type ToolConfig+name; created by defineTool @@ -675,74 +673,9 @@ const debugAgentTurn = debug("agent:turn"); const debugAgentResponse = debug("agent:response"); const debugAgentError = debug("agent:error"); const debugAgentComplete = debug("agent:complete"); -const debugAgentRetry = debug("agent:retry"); const debugAgentFailure = debug("agent:failure"); const debugAgentClose = debug("agent:close"); -export type AgentAddonContext = { - spec: NormalizedAgentSpec; - agent: Agent; - input: unknown; - outputSchema: Schema; - signal: AbortSignal | undefined; - turn: number; - maxTurns: number; - prompt: string; - response?: string; - completed: boolean; - output?: unknown; - nextPrompt?: string; - error?: unknown; -}; -/** - * Middleware function that wraps each agent turn. Addons are called in - * declaration order; each must call `await next()` to continue the chain (or - * the terminal `runtimeAgent.ask`). - * - * **Control-flow fields** — set on `context` after `await next()` returns: - * - `context.completed = true` + `context.output` — short-circuit; return - * `output` immediately without further turns. - * - `context.nextPrompt` — replace the prompt for the next turn; the harness - * loops back to turn N+1 with the new prompt. - * - `context.error` — abort the agent and rethrow this value as the error. - * - Leave all fields unchanged to let the harness parse and validate - * `context.response` with the declared output schema as normal. - * - * @example - * // Custom repair addon: retry up to maxTurns with the schema error appended - * const repairAddon: AgentAddon = async (ctx, next) => { - * await next(); - * if (ctx.completed || ctx.response === undefined) return; - * const analysis = analyzeResponse(ctx.response, ctx.outputSchema, ctx.spec.name, ctx.turn); - * if (analysis.ok) { - * ctx.completed = true; - * ctx.output = analysis.output; - * } else if (ctx.turn < ctx.maxTurns) { - * ctx.nextPrompt = defaultRepairPrompt(ctx.spec, analysis.error); - * } else { - * ctx.error = analysis.error; - * } - * }; - */ -export type AgentAddon = ( - context: AgentAddonContext, - next: () => Promise, -) => void | Promise; -/** Options for the {@link steering} addon. */ -export type SteeringOptions = { - /** Warning message appended to the prompt on the last available turn. Defaults to a generic "you are running out of turns" notice. */ - message?: string; -}; -/** Options for the {@link timeout} addon. */ -export type TimeoutOptions = { - /** Maximum milliseconds to wait for a single agent turn before raising an `AbortError`. */ - timeout: number; -}; -/** Callback invoked once per unique {@link Agent} instance when {@link oncePerAgent} is used. */ -export type AgentRegistration = ( - agent: Agent, - context: AgentAddonContext, -) => void | Promise; export type ToolHandler = (args: TArgs) => unknown | Promise; export type ToolParameters = Schema | Record; export type Tool = ToolConfig & { name: string }; @@ -779,10 +712,8 @@ export type AgentSpec>; /** Optional system message forwarded to the underlying engine session. */ @@ -829,16 +760,6 @@ export type AgentFn = ((input: AgentInputValu /** The fully normalized spec used to construct this agent. */ spec: NormalizedAgentSpec; _namespace: string; - /** - * Appends one or more addon middleware to this agent and returns the same - * `AgentFn`. Addons are applied in the order they are registered and wrap - * each turn's ask/response cycle. - * - * @example - * const worker = agent({ model: "mini", instructions: "..." }); - * worker.use(timeout({ timeout: 5_000 })); - */ - use: (addons: AgentAddon | AgentAddon[]) => AgentFn; }; export type PromptIntentOptions = { @@ -1373,84 +1294,6 @@ export class AgentError extends Error { } } -const DEFAULT_STEERING_WARNING = "You are running out of turns. This is your final attempt before reaching the turn limit. Please correct your output now."; - -export function steering(options: SteeringOptions = {}): AgentAddon { - const message = options.message ?? DEFAULT_STEERING_WARNING; - return async (context, next) => { - await next(); - if (context.nextPrompt && context.turn + 1 === context.maxTurns) { - context.nextPrompt = `${context.nextPrompt}\n${message}`; - } - }; -} - -export function repair(): AgentAddon { - return async (context, next) => { - await next(); - if (context.completed || context.error !== undefined || context.nextPrompt !== undefined) { - return; - } - if (context.response === undefined) { - return; - } - const analysis = analyzeResponse(context.response, context.outputSchema, context.spec.name, context.turn); - if (analysis.ok) { - context.completed = true; - context.output = analysis.output; - return; - } - if (context.turn >= context.maxTurns) { - context.error = analysis.error; - return; - } - context.nextPrompt = defaultRepairPrompt(context.spec, analysis.error); - }; -} - -export function timeout(options: TimeoutOptions): AgentAddon { - return async (context, next) => { - context.signal = timeoutSignal(context.signal, options.timeout); - await next(); - }; -} - -export function oncePerAgent(register: AgentRegistration): AgentAddon { - const seen = new WeakSet(); - return async (context, next) => { - if (!seen.has(context.agent)) { - await register(context.agent, context); - seen.add(context.agent); - } - await next(); - }; -} - -/** - * Namespace of built-in addon factories. Import and combine these to customize - * agent behaviour without modifying the core harness. - * - * | Addon | Purpose | - * |---|---| - * | `repair` | Re-prompts when the model returns invalid JSON or fails schema validation (up to `maxTurns`). Built in by default. | - * | `steering` | Appends a warning to the prompt on the last turn so the model knows it must correct output now. | - * | `timeout` | Cancels the in-flight turn via `AbortSignal` after the given number of milliseconds. | - * | `oncePerAgent` | Runs a registration callback exactly once per unique `Agent` instance (e.g. to register tools). | - * - * @example - * import { addons, agent, s } from "rig"; - * const a = agent({ - * output: s.object({ answer: s.string }), - * addons: [addons.steering(), addons.timeout({ timeout: 30_000 })], - * }); - */ -export const addons = { - oncePerAgent, - timeout, - repair, - steering, -}; - let currentAgentFactory: AgentFactory = defaultAgentFactory(); /** @@ -1876,56 +1719,20 @@ export function agent(spec: AgentSpec): AgentFn { let failure: unknown; try { - for (let turn = 1; turn <= runtime.maxTurns; turn += 1) { - throwIfAborted(runtime.signal); - debugAgentTurn({ agent: normalizedSpec.name, turn, prompt }); - const context: AgentAddonContext = { - spec: normalizedSpec, - agent: runtimeAgent, - input: normalizedInput, - outputSchema, - signal: runtime.signal, - turn, - maxTurns: runtime.maxTurns, - prompt, - completed: false, - }; - - await runAgentAddons(runtime.addons, context, async () => { - lastResponse = await runtimeAgent.ask( - context.prompt, - context.signal ? { signal: context.signal } : undefined, - ); - context.response = lastResponse; - debugAgentResponse({ agent: normalizedSpec.name, turn, response: lastResponse }); - }); - - if (context.error !== undefined) { - debugAgentError({ agent: normalizedSpec.name, turn, error: context.error }); - throw context.error; - } - if (context.completed) { - debugAgentComplete({ agent: normalizedSpec.name, turn, output: context.output }); - return context.output; - } - if (context.nextPrompt !== undefined) { - debugAgentRetry({ agent: normalizedSpec.name, turn, nextTurn: turn + 1 }); - prompt = context.nextPrompt; - continue; - } - if (context.response !== undefined) { - const analysis = analyzeResponse(context.response, context.outputSchema, context.spec.name, context.turn); - if (analysis.ok) { - debugAgentComplete({ agent: normalizedSpec.name, turn, output: analysis.output }); - return analysis.output; - } - debugAgentError({ agent: normalizedSpec.name, turn, error: analysis.error }); - throw analysis.error; - } - throw new Error( - `Agent ${normalizedSpec.name}: addons must set context.output with context.completed=true or context.nextPrompt for turn ${turn}.`, - ); + throwIfAborted(runtime.signal); + debugAgentTurn({ agent: normalizedSpec.name, turn: 1, prompt }); + lastResponse = await runtimeAgent.ask( + prompt, + runtime.signal ? { signal: runtime.signal } : undefined, + ); + debugAgentResponse({ agent: normalizedSpec.name, turn: 1, response: lastResponse }); + const analysis = analyzeResponse(lastResponse, outputSchema, normalizedSpec.name, 1); + if (analysis.ok) { + debugAgentComplete({ agent: normalizedSpec.name, turn: 1, output: analysis.output }); + return analysis.output; } + debugAgentError({ agent: normalizedSpec.name, turn: 1, error: analysis.error }); + throw analysis.error; } catch (error) { failure = error; debugAgentFailure({ agent: normalizedSpec.name, error }); @@ -1947,8 +1754,6 @@ export function agent(spec: AgentSpec): AgentFn { } } } - - throw new Error(`Agent ${normalizedSpec.name} failed after ${runtime.maxTurns} turns. Last response:\n${lastResponse}`); }; const fn = (async (input: unknown, options: CallOptions = {}) => invoke(input, options)) as AgentFn; @@ -1960,13 +1765,6 @@ export function agent(spec: AgentSpec): AgentFn { fn.outputShape = outputSchema; fn.spec = normalizedSpec; fn._namespace = normalizedSpec.name; - fn.use = (addons) => { - normalizedSpec.addons = [ - ...normalizeAddons(normalizedSpec.addons), - ...normalizeAddons(addons), - ]; - return fn; - }; return fn; } @@ -1992,7 +1790,6 @@ function normalizeSpec(specOrName: AgentSpec): NormalizedAgentSpec, options: CallOp model: string; maxTurns: number; signal: AbortSignal | undefined; - addons: AgentAddon[]; systemMessage: unknown; tools: Tool[] | undefined; } { @@ -2524,48 +2320,11 @@ function resolveCallRuntime(spec: NormalizedAgentSpec, options: CallOp model: options.model ?? spec.model ?? "small", maxTurns: options.maxTurns ?? spec.maxTurns ?? 4, signal: timeoutSignal(options.signal, options.timeout), - addons: normalizeAddons(spec.addons), systemMessage: spec.systemMessage, tools: spec.tools, }; } -function normalizeAddons(addons?: AgentAddon | AgentAddon[]): AgentAddon[] { - if (!addons) { - return []; - } - const items = Array.isArray(addons) ? [...addons] : [addons]; - for (let i = 0; i < items.length; i++) { - const addon = items[i]; - if (typeof addon !== "function") { - const got = addon === null ? "null" : typeof addon; - throw new Error(`Agent addon entries must be functions (entry at index ${i} is ${got}).`); - } - } - return items; -} - -async function runAgentAddons( - addons: AgentAddon[], - context: AgentAddonContext, - terminal: () => Promise, -): Promise { - let index = -1; - const dispatch = async (current: number): Promise => { - if (current <= index) { - throw new Error(`Agent ${context.spec.name} addon at index ${current} called next() multiple times.`); - } - index = current; - const addon = addons[current]; - if (addon === undefined) { - await terminal(); - return; - } - await addon(context, () => dispatch(current + 1)); - }; - await dispatch(0); -} - function isSchema(value: unknown): value is Schema { return !!value && (typeof value === "object" || typeof value === "function") && SCHEMA_SYMBOL in value; } diff --git a/skills/rig/samples/100-workspace-config-drift-2.md b/skills/rig/samples/100-workspace-config-drift-2.md index f3bb763..25ff2bc 100644 --- a/skills/rig/samples/100-workspace-config-drift-2.md +++ b/skills/rig/samples/100-workspace-config-drift-2.md @@ -1,7 +1,7 @@ # 100 - Workspace Config Drift 2 ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const parseJson = defineTool("parseJson", { description: "Parse a JSON string and return parsed object or error", @@ -25,7 +25,6 @@ const workspaceConfigDrift = agent({ })), tools: [parseJson], maxTurns: 4, - addons: repair(), }); export default workspaceConfigDrift; diff --git a/skills/rig/samples/101-commit-format-suggester-2.md b/skills/rig/samples/101-commit-format-suggester-2.md index 2735d8b..3155f45 100644 --- a/skills/rig/samples/101-commit-format-suggester-2.md +++ b/skills/rig/samples/101-commit-format-suggester-2.md @@ -1,7 +1,7 @@ # 101 - Commit Format Suggester 2 ```rig -import { agent, p, s, repair, steering } from "rig"; +import { agent, p, s } from "rig"; // Agent role: suggest conventional commit format for recent git commit messages. const commitFormatSuggester = agent({ @@ -14,7 +14,6 @@ const commitFormatSuggester = agent({ category: s.enum("feat", "fix", "chore", "docs", "test", "refactor", "style"), })), maxTurns: 4, - addons: [steering({ message: "Every commit must have a suggested message in conventional format." }), repair()], }); export default commitFormatSuggester; diff --git a/skills/rig/samples/103-hotspot-file-analyzer-2.md b/skills/rig/samples/103-hotspot-file-analyzer-2.md index 6d485b3..c9471f2 100644 --- a/skills/rig/samples/103-hotspot-file-analyzer-2.md +++ b/skills/rig/samples/103-hotspot-file-analyzer-2.md @@ -1,7 +1,7 @@ # 103 - Hotspot File Analyzer 2 ```rig -import { agent, p, s, steering } from "rig"; +import { agent, p, s } from "rig"; // Agent role: identify hot-spot files by measuring commit churn and contributor spread. const hotspotFileAnalyzer = agent({ @@ -12,7 +12,6 @@ const hotspotFileAnalyzer = agent({ topContributors: s.array(s.string), })), maxTurns: 5, - addons: steering({ message: "Every file entry must have a numeric churnScore 0-100 and at least one topContributor." }), }); export default hotspotFileAnalyzer; diff --git a/skills/rig/samples/105-import-cycle-detector-2.md b/skills/rig/samples/105-import-cycle-detector-2.md index 6998474..c206c42 100644 --- a/skills/rig/samples/105-import-cycle-detector-2.md +++ b/skills/rig/samples/105-import-cycle-detector-2.md @@ -1,7 +1,7 @@ # 105 - Import Cycle Detector 2 ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; // Agent role: detect circular import cycles in TypeScript source files. const importCycleDetector = agent({ @@ -15,7 +15,6 @@ const importCycleDetector = agent({ })), }), maxTurns: 5, - addons: repair(), }); export default importCycleDetector; diff --git a/skills/rig/samples/107-git-rename-tracker.md b/skills/rig/samples/107-git-rename-tracker.md index 04d3a91..e49d813 100644 --- a/skills/rig/samples/107-git-rename-tracker.md +++ b/skills/rig/samples/107-git-rename-tracker.md @@ -1,7 +1,7 @@ # 107 - Git Rename Tracker ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; // Agent role: track file renames in git history and produce a structured rename log. const gitRenameTracker = agent({ @@ -14,7 +14,6 @@ const gitRenameTracker = agent({ newPath: s.string, })), maxTurns: 4, - addons: repair(), }); export default gitRenameTracker; diff --git a/skills/rig/samples/110-workspace-config-drift-3.md b/skills/rig/samples/110-workspace-config-drift-3.md index a520157..9b1f0b5 100644 --- a/skills/rig/samples/110-workspace-config-drift-3.md +++ b/skills/rig/samples/110-workspace-config-drift-3.md @@ -1,13 +1,12 @@ # 110 - Workspace Config Drift 3 ```rig -import { agent, defineTool, p, s, repair } from "rig"; +import { agent, defineTool, p, s } from "rig"; // Agent role: detect drift in workspace config files against a baseline. const workspaceConfigDrift = agent({ model: "mini", maxTurns: 6, - addons: repair(), input: s.object({ baselineFile: s.path, }), diff --git a/skills/rig/samples/111-conventional-commit-suggester.md b/skills/rig/samples/111-conventional-commit-suggester.md index 1aaf6ff..b1c6d2f 100644 --- a/skills/rig/samples/111-conventional-commit-suggester.md +++ b/skills/rig/samples/111-conventional-commit-suggester.md @@ -1,13 +1,12 @@ # 111 - Conventional Commit Suggester ```rig -import { agent, p, s, repair, steering } from "rig"; +import { agent, p, s } from "rig"; // Agent role: suggest conventional-format rewrites for recent git commit messages. const conventionalCommitSuggester = agent({ model: "mini", maxTurns: 6, - addons: [steering({ message: "Ensure each suggestion follows the conventional commits spec: (?): using imperative mood." }), repair()], instructions: p`Review the recent git log and suggest conventional commit message rewrites: ${p.bash("git log --oneline -20")} diff --git a/skills/rig/samples/113-git-hotspot-analyzer.md b/skills/rig/samples/113-git-hotspot-analyzer.md index 5f39d33..f7e5069 100644 --- a/skills/rig/samples/113-git-hotspot-analyzer.md +++ b/skills/rig/samples/113-git-hotspot-analyzer.md @@ -1,12 +1,11 @@ # 113 - Git Hotspot Analyzer ```rig -import { agent, p, s, steering } from "rig"; +import { agent, p, s } from "rig"; // Agent role: identify hot-spot files by git churn and top contributors. const gitHotspotAnalyzer = agent({ model: "mini", - addons: steering({ message: "Focus only on the top N files by change frequency. Skip binary files and node_modules." }), input: s.object({ topN: s.int, }), diff --git a/skills/rig/samples/115-import-cycle-detector.md b/skills/rig/samples/115-import-cycle-detector.md index f955d72..a0f946b 100644 --- a/skills/rig/samples/115-import-cycle-detector.md +++ b/skills/rig/samples/115-import-cycle-detector.md @@ -1,13 +1,12 @@ # 115 - Import Cycle Detector ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; // Agent role: detect circular import cycles in the TypeScript project. const importCycleDetector = agent({ model: "mini", maxTurns: 6, - addons: repair(), instructions: p`Detect circular imports in this TypeScript project. TypeScript config: diff --git a/skills/rig/samples/118-pr-review-checklist.md b/skills/rig/samples/118-pr-review-checklist.md index d1dd661..226a0d7 100644 --- a/skills/rig/samples/118-pr-review-checklist.md +++ b/skills/rig/samples/118-pr-review-checklist.md @@ -1,13 +1,12 @@ # 118 - Pr Review Checklist ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; // Agent role: generate a PR review checklist from the recent git diff. const prReviewChecklist = agent({ model: "mini", maxTurns: 6, - addons: repair(), instructions: p`Generate a PR review checklist based on the changes in this branch. Changed files: diff --git a/skills/rig/samples/120-git-hotspot-v2.md b/skills/rig/samples/120-git-hotspot-v2.md index 8dfe02c..ba80a46 100644 --- a/skills/rig/samples/120-git-hotspot-v2.md +++ b/skills/rig/samples/120-git-hotspot-v2.md @@ -1,7 +1,7 @@ # 120 - Git Hotspot V2 ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const getFileCommitCount = defineTool("getFileCommitCount", { description: "Count how many commits touched a specific file", @@ -22,7 +22,6 @@ const gitHotspotAnalyzer = agent({ name: "gitHotspotAnalyzer", model: "small", maxTurns: 3, - addons: repair(), instructions: p`Analyze the git history to identify file hot-spots. Changed files: ${p.bash("git log --follow --name-only --format='' -- . 2>/dev/null | sort | uniq -c | sort -rn | head -30")} diff --git a/skills/rig/samples/122-import-cycle-detector.md b/skills/rig/samples/122-import-cycle-detector.md index d38880a..2b8430a 100644 --- a/skills/rig/samples/122-import-cycle-detector.md +++ b/skills/rig/samples/122-import-cycle-detector.md @@ -1,14 +1,13 @@ # 122 - Import Cycle Detector ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; // Agent role: detect import cycles in a TypeScript project and assess their severity const importCycleDetector = agent({ name: "importCycleDetector", model: "small", maxTurns: 3, - addons: repair(), instructions: p`Detect circular imports in this TypeScript project. Madge circular analysis: ${p.bash("npx madge --circular --extensions ts . 2>/dev/null || echo 'madge not available, use heuristic analysis'")} diff --git a/skills/rig/samples/125-test-coverage-mapper.md b/skills/rig/samples/125-test-coverage-mapper.md index bc62255..7a607af 100644 --- a/skills/rig/samples/125-test-coverage-mapper.md +++ b/skills/rig/samples/125-test-coverage-mapper.md @@ -1,7 +1,7 @@ # 125 - Test Coverage Mapper ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const findTestFile = defineTool("findTestFile", { description: "Find the best matching test file for a given source file using filename heuristics", @@ -26,7 +26,6 @@ const testCoverageMapper = agent({ name: "testCoverageMapper", model: "small", maxTurns: 2, - addons: repair(), instructions: p`Map source files to their corresponding test files using filename heuristics. Source files: ${p.bash("find . -type f -name '*.ts' -not -name '*.test.ts' -not -name '*.spec.ts' -not -path '*/node_modules/*' | head -50")} diff --git a/skills/rig/samples/126-tsconfig-options-auditor.md b/skills/rig/samples/126-tsconfig-options-auditor.md index eca1d8e..140c480 100644 --- a/skills/rig/samples/126-tsconfig-options-auditor.md +++ b/skills/rig/samples/126-tsconfig-options-auditor.md @@ -1,7 +1,7 @@ # 126 - Tsconfig Options Auditor ```rig -import { agent, p, s, defineTool, steering, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const checkOption = defineTool("checkOption", { description: "Check whether a tsconfig compiler option matches the recommended value", @@ -19,7 +19,6 @@ const checkOption = defineTool("checkOption", { const tsconfigOptionsAuditor = agent({ name: "tsconfigOptionsAuditor", model: "small", - addons: [steering(), repair()], instructions: p`Audit TypeScript compiler options in this project against best-practice recommendations. tsconfig.json: ${p.readOptional("tsconfig.json", "{}")} diff --git a/skills/rig/samples/128-npm-script-dep-mapper.md b/skills/rig/samples/128-npm-script-dep-mapper.md index 6462b30..cc6a886 100644 --- a/skills/rig/samples/128-npm-script-dep-mapper.md +++ b/skills/rig/samples/128-npm-script-dep-mapper.md @@ -1,7 +1,7 @@ # 128 - Npm Script Dep Mapper ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const parseScriptDeps = defineTool("parseScriptDeps", { description: "Parse pre/post hooks and npm run references for a script", @@ -27,7 +27,6 @@ const npmScriptDepMapper = agent({ name: "npmScriptDepMapper", model: "small", maxTurns: 2, - addons: repair(), instructions: p`Map npm script dependencies by analyzing the package.json scripts. package.json: ${p.read("package.json")} diff --git a/skills/rig/samples/129-markdown-link-checker.md b/skills/rig/samples/129-markdown-link-checker.md index 91348a1..1bb37e1 100644 --- a/skills/rig/samples/129-markdown-link-checker.md +++ b/skills/rig/samples/129-markdown-link-checker.md @@ -1,7 +1,7 @@ # 129 - Markdown Link Checker ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const checkUrl = defineTool("checkUrl", { description: "Check HTTP status of a URL using curl", @@ -24,7 +24,6 @@ const checkUrl = defineTool("checkUrl", { const markdownLinkChecker = agent({ name: "markdownLinkChecker", model: "small", - addons: repair(), instructions: p`Find and check HTTP links in markdown files. Markdown files: ${p.bash("find . -name '*.md' -not -path '*/node_modules/*' | head -20")} diff --git a/skills/rig/samples/130-git-hotspot-analyzer.md b/skills/rig/samples/130-git-hotspot-analyzer.md index 6954881..65c3853 100644 --- a/skills/rig/samples/130-git-hotspot-analyzer.md +++ b/skills/rig/samples/130-git-hotspot-analyzer.md @@ -1,7 +1,7 @@ # 130 - Git Hotspot Analyzer ```rig -import { agent, p, s, steering } from "rig"; +import { agent, p, s } from "rig"; // Agent role: analyze which files are hot-spots by measuring commit churn and top contributors. const gitHotspotAnalyzer = agent({ @@ -12,7 +12,6 @@ const gitHotspotAnalyzer = agent({ topContributors: s.array(s.string), })), maxTurns: 5, - addons: [steering({ message: "Every file must have a numeric churnScore between 0 and 100 and at least one topContributor entry." })], }); export default gitHotspotAnalyzer; diff --git a/skills/rig/samples/132-import-cycle-detector.md b/skills/rig/samples/132-import-cycle-detector.md index e1a31a9..1f4c558 100644 --- a/skills/rig/samples/132-import-cycle-detector.md +++ b/skills/rig/samples/132-import-cycle-detector.md @@ -1,13 +1,12 @@ # 132 - Import Cycle Detector ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; // Agent role: detect circular import cycles in the TypeScript project and classify severity. const importCycleDetector = agent({ model: "small", maxTurns: 6, - addons: [repair()], instructions: p`Detect circular imports in this TypeScript project. TypeScript config: ${p.bash("cat tsconfig.json 2>/dev/null || echo '{}'")} diff --git a/skills/rig/samples/135-test-coverage-mapper.md b/skills/rig/samples/135-test-coverage-mapper.md index 74d0c70..8ab2b2c 100644 --- a/skills/rig/samples/135-test-coverage-mapper.md +++ b/skills/rig/samples/135-test-coverage-mapper.md @@ -1,7 +1,7 @@ # 135 - Test Coverage Mapper ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const matchTestFile = defineTool("matchTestFile", { description: "Find matching test files for a source file using filename heuristics", @@ -19,7 +19,6 @@ const matchTestFile = defineTool("matchTestFile", { const testCoverageMapper = agent({ model: "small", maxTurns: 4, - addons: [repair()], instructions: p`Map source files to their test counterparts. Source files: ${p.bash("find . -type f -name '*.ts' -not -name '*.test.ts' -not -name '*.spec.ts' -not -path '*/node_modules/*' | head -50")} diff --git a/skills/rig/samples/139-markdown-frontmatter-checker.md b/skills/rig/samples/139-markdown-frontmatter-checker.md index 1bbc8b5..5ab3f54 100644 --- a/skills/rig/samples/139-markdown-frontmatter-checker.md +++ b/skills/rig/samples/139-markdown-frontmatter-checker.md @@ -1,7 +1,7 @@ # 139 - Markdown Frontmatter Checker ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; import { readFileSync, existsSync } from "node:fs"; const parseFrontmatter = defineTool("parseFrontmatter", { @@ -26,7 +26,6 @@ const parseFrontmatter = defineTool("parseFrontmatter", { const markdownFrontmatterChecker = agent({ model: "small", maxTurns: 4, - addons: [repair()], instructions: p`Check markdown files for required YAML frontmatter fields. Markdown files: ${p.bash("find . -name '*.md' -not -path '*/node_modules/*' | head -50")} diff --git a/skills/rig/samples/140-git-hotspot-analyzer.md b/skills/rig/samples/140-git-hotspot-analyzer.md index 42d7b14..a98bfce 100644 --- a/skills/rig/samples/140-git-hotspot-analyzer.md +++ b/skills/rig/samples/140-git-hotspot-analyzer.md @@ -1,7 +1,7 @@ # 140 - Git Hotspot Analyzer ```rig -import { agent, p, s, defineTool, steering } from "rig"; +import { agent, p, s, defineTool } from "rig"; const scoreFile = defineTool("scoreFile", { description: "Compute a churn score from commit count for a file.", @@ -26,7 +26,6 @@ ${p.bash("git shortlog -sn --no-merges -- . | head -10")} Use the scoreFile tool to compute a churnScore for each file. Return s.record output keyed by file path with churnScore, commitCount, and topContributors.`, tools: [scoreFile], - addons: steering({ message: "Ensure all top files appear in the output keyed by their path." }), output: s.record( s.object({ churnScore: s.number, diff --git a/skills/rig/samples/142-import-cycle-detector.md b/skills/rig/samples/142-import-cycle-detector.md index 5529fcf..8f5f2c1 100644 --- a/skills/rig/samples/142-import-cycle-detector.md +++ b/skills/rig/samples/142-import-cycle-detector.md @@ -1,7 +1,7 @@ # 142 - Import Cycle Detector ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; // Agent role: Detect import cycles in TypeScript source using madge and classify severity. const importCycleDetector = agent({ @@ -21,7 +21,6 @@ For each cycle found, classify severity: - low: short cycle between utility files Return the structured output with hasCycles, cycles array, and totalCycles count.`, - addons: repair(), output: s.object({ hasCycles: s.boolean, cycles: s.array( diff --git a/skills/rig/samples/145-test-coverage-mapper.md b/skills/rig/samples/145-test-coverage-mapper.md index 4237241..1414e69 100644 --- a/skills/rig/samples/145-test-coverage-mapper.md +++ b/skills/rig/samples/145-test-coverage-mapper.md @@ -1,7 +1,7 @@ # 145 - Test Coverage Mapper ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const matchTestFile = defineTool("matchTestFile", { description: "Heuristically find test files matching a source file by name.", @@ -41,7 +41,6 @@ Classify coverage: Return s.record output keyed by source file path.`, tools: [matchTestFile], - addons: repair(), output: s.record( s.object({ coverage: s.enum("covered", "uncovered", "partial"), diff --git a/skills/rig/samples/149-dotfile-inventory.md b/skills/rig/samples/149-dotfile-inventory.md index d54af8b..3a19a6a 100644 --- a/skills/rig/samples/149-dotfile-inventory.md +++ b/skills/rig/samples/149-dotfile-inventory.md @@ -1,7 +1,7 @@ # 149 - Dotfile Inventory ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; // Agent role: Discover and categorize dotfiles in the workspace root and nearby directories. const dotfileInventory = agent({ @@ -23,7 +23,6 @@ For each dotfile, determine its purpose and category: - other: anything else Return dotfiles record (keyed by filename) with purpose and category, totalFound, and categorySummary (record of counts per category).`, - addons: repair(), output: s.object({ dotfiles: s.record( s.object({ diff --git a/skills/rig/samples/150-git-hotspot-analyzer-v3.md b/skills/rig/samples/150-git-hotspot-analyzer-v3.md index f89655e..8d0646e 100644 --- a/skills/rig/samples/150-git-hotspot-analyzer-v3.md +++ b/skills/rig/samples/150-git-hotspot-analyzer-v3.md @@ -1,12 +1,11 @@ # 150 - Git Hotspot Analyzer V3 ```rig -import { agent, defineTool, p, s, steering } from "rig"; +import { agent, defineTool, p, s } from "rig"; // Agent role: identify hot-spot files by git churn and classify risk level. const gitHotspotAnalyzerV3 = agent({ model: "small", - addons: steering({ message: "Focus on files that appear most frequently. Assign riskLevel based on churnScore: >20=critical, >10=high, >5=medium, else low." }), instructions: p`Analyze git history to find frequently changed files and classify their risk. Recently changed files (with churn counts): diff --git a/skills/rig/samples/152-import-cycle-detector-v3.md b/skills/rig/samples/152-import-cycle-detector-v3.md index 9d0a6c6..e340e88 100644 --- a/skills/rig/samples/152-import-cycle-detector-v3.md +++ b/skills/rig/samples/152-import-cycle-detector-v3.md @@ -1,13 +1,12 @@ # 152 - Import Cycle Detector V3 ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; // Agent role: detect circular import cycles in TypeScript source and classify severity. const importCycleDetectorV3 = agent({ model: "small", maxTurns: 3, - addons: repair(), instructions: p`Detect circular import cycles in this TypeScript project. Circular dependency analysis: diff --git a/skills/rig/samples/155-test-coverage-mapper-v2.md b/skills/rig/samples/155-test-coverage-mapper-v2.md index 1b42a34..f8609a0 100644 --- a/skills/rig/samples/155-test-coverage-mapper-v2.md +++ b/skills/rig/samples/155-test-coverage-mapper-v2.md @@ -1,13 +1,12 @@ # 155 - Test Coverage Mapper V2 ```rig -import { agent, defineTool, p, s, repair } from "rig"; +import { agent, defineTool, p, s } from "rig"; // Agent role: map source files to their test files using filename heuristics. const testCoverageMapperV2 = agent({ model: "small", maxTurns: 2, - addons: repair(), instructions: p`Map TypeScript source files to their corresponding test files. Source files (non-test TypeScript): diff --git a/skills/rig/samples/156-regex-pattern-tester.md b/skills/rig/samples/156-regex-pattern-tester.md index 3707746..1e05f52 100644 --- a/skills/rig/samples/156-regex-pattern-tester.md +++ b/skills/rig/samples/156-regex-pattern-tester.md @@ -1,12 +1,11 @@ # 156 - Regex Pattern Tester ```rig -import { agent, defineTool, p, s, repair } from "rig"; +import { agent, defineTool, p, s } from "rig"; // Agent role: run regex patterns against test cases and report pass/fail results. const regexPatternTester = agent({ model: "small", - addons: repair(), input: s.object({ patterns: s.array( s.object({ diff --git a/skills/rig/samples/157-commit-churn-classifier.md b/skills/rig/samples/157-commit-churn-classifier.md index bbfb5d4..545d999 100644 --- a/skills/rig/samples/157-commit-churn-classifier.md +++ b/skills/rig/samples/157-commit-churn-classifier.md @@ -1,12 +1,11 @@ # 157 - Commit Churn Classifier ```rig -import { agent, p, s, steering } from "rig"; +import { agent, p, s } from "rig"; // Agent role: classify files by commit churn frequency and assign a risk level. const commitChurnClassifier = agent({ model: "small", - addons: steering({ message: "Be precise: assign riskLevel based on churnCount: >20=critical, >10=volatile, >5=active, else stable." }), instructions: p`Classify repository files by how frequently they are committed (churn). File churn counts from last 100 commits (count file): diff --git a/skills/rig/samples/160-dead-code-detector.md b/skills/rig/samples/160-dead-code-detector.md index c4bd7a7..339ee8b 100644 --- a/skills/rig/samples/160-dead-code-detector.md +++ b/skills/rig/samples/160-dead-code-detector.md @@ -2,7 +2,6 @@ ```rig import { agent, defineTool, p, s } from "rig"; -import { repair } from "rig/addons"; // Agent role: detect dead TypeScript exports by scanning for exported symbols and estimating usage counts. const deadCodeDetector = agent({ @@ -34,7 +33,6 @@ Use the estimateUsage tool for each exported symbol name to count how many times }, }), ], - addons: [repair()], output: s.object({ symbols: s.record(s.object({ usageCount: s.int, diff --git a/skills/rig/samples/164-stale-dependency-detector.md b/skills/rig/samples/164-stale-dependency-detector.md index 8b695ec..1ab701c 100644 --- a/skills/rig/samples/164-stale-dependency-detector.md +++ b/skills/rig/samples/164-stale-dependency-detector.md @@ -2,7 +2,6 @@ ```rig import { agent, defineTool, p, s } from "rig"; -import { repair } from "rig/addons"; // Agent role: detect stale npm dependencies by comparing installed versions with latest published versions. const staleDependencyDetector = agent({ @@ -30,7 +29,6 @@ Use the classifyDrift tool for each outdated package to classify the version dri }, }), ], - addons: [repair()], output: s.object({ packages: s.array(s.object({ name: s.string, diff --git a/skills/rig/samples/167-makefile-target-extractor.md b/skills/rig/samples/167-makefile-target-extractor.md index 00342f5..a18df28 100644 --- a/skills/rig/samples/167-makefile-target-extractor.md +++ b/skills/rig/samples/167-makefile-target-extractor.md @@ -2,7 +2,6 @@ ```rig import { agent, defineTool, p, s } from "rig"; -import { repair } from "rig/addons"; // Agent role: extract and classify Makefile targets into phony vs real file targets with descriptions. const makefileTargetExtractor = agent({ @@ -31,7 +30,6 @@ Use the parseTargets tool to extract target names, then classify each. Phony tar }, }), ], - addons: [repair()], output: s.object({ targets: s.array(s.object({ name: s.string, diff --git a/skills/rig/samples/169-server-uptime-log-parser.md b/skills/rig/samples/169-server-uptime-log-parser.md index 45610be..b7af804 100644 --- a/skills/rig/samples/169-server-uptime-log-parser.md +++ b/skills/rig/samples/169-server-uptime-log-parser.md @@ -2,7 +2,6 @@ ```rig import { agent, defineTool, p, s } from "rig"; -import { steering } from "rig/addons"; // Agent role: parse server uptime logs to extract start/stop/crash events and compute uptime statistics. const serverUptimeLogParser = agent({ @@ -27,9 +26,6 @@ Use the parseLogLine tool to classify each log entry as a start, stop, crash, or }, }), ], - addons: [ - steering({ message: "If you detect multiple crashes within an hour, flag this as a crash loop anomaly in the output." }), - ], output: s.object({ uptimeEvents: s.array(s.object({ event: s.enum("start", "stop", "crash", "restart"), diff --git a/skills/rig/samples/170-stale-dep-detector.md b/skills/rig/samples/170-stale-dep-detector.md index bb2683e..6a8914f 100644 --- a/skills/rig/samples/170-stale-dep-detector.md +++ b/skills/rig/samples/170-stale-dep-detector.md @@ -1,7 +1,7 @@ # 170 - Stale Dep Detector ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; import { defineTool } from "rig"; const classifyDrift = defineTool("classifyDrift", { @@ -22,7 +22,6 @@ const classifyDrift = defineTool("classifyDrift", { const staleDependencyDetector = agent({ model: "small", maxTurns: 3, - addons: repair(), tools: [classifyDrift], instructions: p`Analyze outdated npm packages using ${p.bash("npm outdated --json 2>/dev/null || echo '{}'")} and the manifest ${p.read("package.json")}. For each outdated package, use the classifyDrift tool to determine drift level. Compute overallRisk: "critical" if any major drift, "moderate" if any minor drift, else "safe".`, output: s.object({ diff --git a/skills/rig/samples/172-service-health-probe.md b/skills/rig/samples/172-service-health-probe.md index cfbe268..6b68721 100644 --- a/skills/rig/samples/172-service-health-probe.md +++ b/skills/rig/samples/172-service-health-probe.md @@ -1,12 +1,11 @@ # 172 - Service Health Probe ```rig -import { agent, p, s, steering } from "rig"; +import { agent, p, s } from "rig"; // Agent role: probe service health endpoints and classify status for each URL. const serviceHealthProbe = agent({ model: "small", - addons: steering({ message: "For unexpected or missing HTTP codes, classify status as 'unknown' rather than failing." }), input: s.array(s.string), instructions: p`For each URL in the input array, run a curl probe using p.bashEach. Use ${p.bash("echo 'probe each URL with: curl -o /dev/null -s -w \\'%{http_code}\\' --max-time 5'")} as a reference. For each URL in the input, probe it and classify its status: "up" (2xx), "down" (4xx/5xx/connection refused), "slow" (timeout), "unknown" (other). Compute healthyCount and overallStatus.`, output: s.object({ diff --git a/skills/rig/samples/178-import-style-checker.md b/skills/rig/samples/178-import-style-checker.md index a09a676..9448673 100644 --- a/skills/rig/samples/178-import-style-checker.md +++ b/skills/rig/samples/178-import-style-checker.md @@ -1,7 +1,7 @@ # 178 - Import Style Checker ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; import { defineTool } from "rig"; const classifyStyle = defineTool("classifyStyle", { @@ -19,7 +19,6 @@ const classifyStyle = defineTool("classifyStyle", { const importStyleChecker = agent({ model: "small", maxTurns: 3, - addons: repair(), tools: [classifyStyle], instructions: p`Find JS files: ${p.bash("find . -name '*.js' -not -path '*/node_modules/*' 2>/dev/null | head -30")}. For each file run both grep counts in one call: ${p.bash("find . -name '*.js' -not -path '*/node_modules/*' 2>/dev/null | head -30 | xargs -I{} sh -c 'echo {}:$(grep -c \"require(\" {} 2>/dev/null || echo 0):$(grep -c \"^import \" {} 2>/dev/null || echo 0)'")}. Use classifyStyle tool with the counts for each file. Aggregate summary counts by style.`, output: s.object({ diff --git a/skills/rig/samples/179-git-tag-semver-validator.md b/skills/rig/samples/179-git-tag-semver-validator.md index df54964..54e29f8 100644 --- a/skills/rig/samples/179-git-tag-semver-validator.md +++ b/skills/rig/samples/179-git-tag-semver-validator.md @@ -1,7 +1,7 @@ # 179 - Git Tag Semver Validator ```rig -import { agent, p, s, steering } from "rig"; +import { agent, p, s } from "rig"; import { defineTool } from "rig"; const parseSemver = defineTool("parseSemver", { @@ -22,7 +22,6 @@ const parseSemver = defineTool("parseSemver", { // Agent role: validate all git tags against semver format and identify the latest valid tag. const gitTagSemverValidator = agent({ model: "small", - addons: steering({ message: "For tags with date-based or non-semver formats, mark valid as false and omit the parsed field." }), tools: [parseSemver], instructions: p`List all git tags: ${p.bash("git tag --list 2>/dev/null || true")}. For each tag, call the parseSemver tool to check if it conforms to semver (e.g. v1.2.3 or 1.2.3). Count invalid tags. Identify the latest valid semver tag by finding the highest version number.`, output: s.object({ diff --git a/skills/rig/samples/181-service-health-probe.md b/skills/rig/samples/181-service-health-probe.md index 307c520..b277b19 100644 --- a/skills/rig/samples/181-service-health-probe.md +++ b/skills/rig/samples/181-service-health-probe.md @@ -1,12 +1,11 @@ # 181 - Service Health Probe ```rig -import { agent, p, s, steering } from "rig"; +import { agent, p, s } from "rig"; // Agent role: probe each URL endpoint and classify its health status. const serviceHealthProbe = agent({ model: "small", - addons: [steering({ message: "For any URL that times out or returns a non-standard code, classify as 'unknown' rather than failing." })], input: s.array(s.string), instructions: p`For each URL in the input, probe it using curl: ${p.bash("echo 'example: curl -o /dev/null -s -w \\'%{http_code}\\' --max-time 5 '")}. Run a separate curl command for each input URL. Classify each: 'up' (2xx), 'down' (4xx/5xx/refused), 'slow' (timeout after 5s), 'unknown' (other). Count healthyCount and determine overallStatus: 'all-healthy' if all up, 'critical' if more than half are down, otherwise 'degraded'.`, output: s.object({ diff --git a/skills/rig/samples/187-lock-file-drift-detector.md b/skills/rig/samples/187-lock-file-drift-detector.md index 108c3ae..9aa617e 100644 --- a/skills/rig/samples/187-lock-file-drift-detector.md +++ b/skills/rig/samples/187-lock-file-drift-detector.md @@ -1,7 +1,7 @@ # 187 - Lock File Drift Detector ```rig -import { agent, defineTool, p, s, repair } from "rig"; +import { agent, defineTool, p, s } from "rig"; const crossCheckVersions = defineTool("crossCheckVersions", { description: "Cross-check declared package.json versions against package-lock.json resolved versions", @@ -38,7 +38,6 @@ const crossCheckVersions = defineTool("crossCheckVersions", { // Agent role: detect version drift between package.json declarations and package-lock.json resolved versions. const lockFileDriftDetector = agent({ model: "small", - addons: [repair()], tools: [crossCheckVersions], instructions: p`Read package.json: ${p.bash("cat package.json 2>/dev/null || echo '{}'")} and package-lock.json: ${p.bash("cat package-lock.json 2>/dev/null || echo '{}'")}. Call crossCheckVersions with both file contents to find drifted packages. Return driftedPackages list, total driftCount, and whether all packages are in sync.`, output: s.object({ diff --git a/skills/rig/samples/188-git-conflict-marker-scanner.md b/skills/rig/samples/188-git-conflict-marker-scanner.md index 283500a..e9c2e42 100644 --- a/skills/rig/samples/188-git-conflict-marker-scanner.md +++ b/skills/rig/samples/188-git-conflict-marker-scanner.md @@ -1,12 +1,11 @@ # 188 - Git Conflict Marker Scanner ```rig -import { agent, p, s, steering } from "rig"; +import { agent, p, s } from "rig"; // Agent role: scan the repository for git conflict markers and report affected files. const gitConflictMarkerScanner = agent({ model: "small", - addons: [steering({ message: "Check all text file types including .ts, .js, .json, .md, .yaml. Report every line where a conflict marker appears." })], instructions: p`Scan for git conflict markers in tracked files: ${p.bash("grep -rn '<<<<<<< \\|=======$\\|>>>>>>> ' --include='*.ts' --include='*.js' --include='*.json' --include='*.md' --include='*.yaml' --include='*.yml' . 2>/dev/null | grep -v node_modules | head -100 || echo ''")}. Also check all text files: ${p.bash("grep -rn '^<<<<<<< ' . 2>/dev/null | grep -v node_modules | grep -v '.git' | head -50 || echo ''")}. For each match, extract the file path, line number, and which marker it is. Deduplicate affected file paths. Return all conflict locations, the unique affected files list, total count, and whether the repo is clean.`, output: s.object({ conflicts: s.array(s.object({ diff --git a/skills/rig/samples/49-timeout-signal-helper.md b/skills/rig/samples/49-timeout-signal-helper.md index 6eaa2df..7fb57de 100644 --- a/skills/rig/samples/49-timeout-signal-helper.md +++ b/skills/rig/samples/49-timeout-signal-helper.md @@ -1,12 +1,11 @@ # 49 - Timeout Signal Helper ```rig -import { agent, s, timeout } from "rig"; -// Agent role: return a short response before the timeout expires. +import { agent } from "rig"; +// Agent role: return a short response. const worker = agent({ model: "mini", - instructions: "Return a short response before the timeout expires.", - addons: timeout({ timeout: 5_000 }), + instructions: "Return a short response.", }); export default worker; ``` diff --git a/skills/rig/samples/55-file-change-lint-middleware.md b/skills/rig/samples/55-file-change-lint-middleware.md deleted file mode 100644 index 4950c05..0000000 --- a/skills/rig/samples/55-file-change-lint-middleware.md +++ /dev/null @@ -1,27 +0,0 @@ -# 55 - File Change Lint Middleware - -```rig -import { agent, s, type AgentAddon } from "rig"; -import { $ } from "zx"; - -const fingerprint = async () => { - const { exitCode, stdout } = await $`git status --porcelain`.nothrow(); - return exitCode === 0 ? stdout.trim() : ""; -}; -const lintOnFileChange = (runLint: () => Promise): AgentAddon => async (_context, next) => { - const before = await fingerprint(); - await next(); - const after = await fingerprint(); - if (before !== after) await runLint(); -}; - -// Agent role: update files and run linting after file changes. -const fileChangeMiddleware = agent({ - model: "mini", - instructions: "Update files when needed, then summarize the change.", - output: s.object({ changed: s.boolean, summary: s.string }), - addons: lintOnFileChange(() => $`npm run typecheck`), -}); - -export default fileChangeMiddleware; -``` diff --git a/skills/rig/samples/66-ci-workflow-health.md b/skills/rig/samples/66-ci-workflow-health.md index e64e056..907c879 100644 --- a/skills/rig/samples/66-ci-workflow-health.md +++ b/skills/rig/samples/66-ci-workflow-health.md @@ -1,7 +1,7 @@ # 66 - CI Workflow Health Analyzer ```rig -import { agent, defineTool, p, s, repair } from "rig"; +import { agent, defineTool, p, s } from "rig"; const parseYamlSteps = defineTool("parse_yaml_steps", { description: "Extract step names and uses from a YAML workflow file.", @@ -20,7 +20,6 @@ const parseYamlSteps = defineTool("parse_yaml_steps", { const ciWorkflowAnalyzer = agent({ model: "mini", maxTurns: 3, - addons: repair(), instructions: p`Scan ${p.glob(".github/workflows/*.yml")} and ${p.bashRaw`find .github/workflows -name '*.yaml' 2>/dev/null`} for workflow health issues. Use parse_yaml_steps to inspect individual files.`, output: s.object({ issues: s.array(s.object({ workflow: s.nonEmptyString, severity: s.enum("info", "warning", "error"), message: s.string, fix: s.optional(s.string) })), diff --git a/skills/rig/samples/68-changelog-generator.md b/skills/rig/samples/68-changelog-generator.md index 0142cf3..7ccf02e 100644 --- a/skills/rig/samples/68-changelog-generator.md +++ b/skills/rig/samples/68-changelog-generator.md @@ -1,7 +1,7 @@ # 68 - Changelog Generator ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const validateSemver = defineTool("validateSemver", { description: "Validate that a bump type is major, minor, or patch", @@ -27,7 +27,6 @@ const changelogGenerator = agent({ }), tools: [validateSemver], maxTurns: 6, - addons: repair(), }); export default changelogGenerator; diff --git a/skills/rig/samples/71-package-script-health.md b/skills/rig/samples/71-package-script-health.md index e098aa4..7ddf3ac 100644 --- a/skills/rig/samples/71-package-script-health.md +++ b/skills/rig/samples/71-package-script-health.md @@ -1,7 +1,7 @@ # 71 - Package Script Health ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const validateScriptName = defineTool("validateScriptName", { description: "Validate that a package.json script name follows conventional naming (lowercase, hyphens only)", @@ -27,7 +27,6 @@ const packageScriptHealth = agent({ }), tools: [validateScriptName], maxTurns: 6, - addons: repair(), }); export default packageScriptHealth; diff --git a/skills/rig/samples/73-git-branch-pruner.md b/skills/rig/samples/73-git-branch-pruner.md index dca604e..4ac4f5a 100644 --- a/skills/rig/samples/73-git-branch-pruner.md +++ b/skills/rig/samples/73-git-branch-pruner.md @@ -1,7 +1,7 @@ # 73 - Git Branch Pruner ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; // Agent role: identify git branches that are candidates for pruning based on merge status and last commit date. const gitBranchPruner = agent({ @@ -18,7 +18,6 @@ const gitBranchPruner = agent({ pruneCount: s.int, }), maxTurns: 6, - addons: repair(), }); export default gitBranchPruner; diff --git a/skills/rig/samples/74-prettier-eslint-compat.md b/skills/rig/samples/74-prettier-eslint-compat.md index 37a4ece..8966c80 100644 --- a/skills/rig/samples/74-prettier-eslint-compat.md +++ b/skills/rig/samples/74-prettier-eslint-compat.md @@ -1,7 +1,7 @@ # 74 - Prettier Eslint Compat ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const detectConflicts = defineTool("detectConflicts", { description: "Detect rule conflicts between Prettier and ESLint configs", @@ -41,7 +41,6 @@ const prettierEslintCompat = agent({ }), tools: [detectConflicts], maxTurns: 6, - addons: repair(), }); export default prettierEslintCompat; diff --git a/skills/rig/samples/76-commit-msg-rewriter.md b/skills/rig/samples/76-commit-msg-rewriter.md index a556660..40ea49a 100644 --- a/skills/rig/samples/76-commit-msg-rewriter.md +++ b/skills/rig/samples/76-commit-msg-rewriter.md @@ -1,7 +1,7 @@ # 76 - Commit Msg Rewriter ```rig -import { agent, p, s, steering, repair } from "rig"; +import { agent, p, s } from "rig"; // Agent role: rewrite recent git commit messages into conventional commit format with imperative mood. const commitMsgRewriter = agent({ @@ -17,7 +17,6 @@ const commitMsgRewriter = agent({ markdown: s.string, }), maxTurns: 6, - addons: [steering({ message: "Use imperative mood and conventional commit prefixes. Be consistent." }), repair()], }); export default commitMsgRewriter; diff --git a/skills/rig/samples/78-build-log-analyzer.md b/skills/rig/samples/78-build-log-analyzer.md index a66fd98..189f0ac 100644 --- a/skills/rig/samples/78-build-log-analyzer.md +++ b/skills/rig/samples/78-build-log-analyzer.md @@ -1,7 +1,7 @@ # 78 - Build Log Analyzer ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; // Agent role: run the build and analyze the output for errors, warnings, and success status. const buildLogAnalyzer = agent({ @@ -17,7 +17,6 @@ const buildLogAnalyzer = agent({ summary: s.string, }), maxTurns: 5, - addons: repair(), }); export default buildLogAnalyzer; diff --git a/skills/rig/samples/82-workspace-config-drift.md b/skills/rig/samples/82-workspace-config-drift.md index 535787b..5f6fa4d 100644 --- a/skills/rig/samples/82-workspace-config-drift.md +++ b/skills/rig/samples/82-workspace-config-drift.md @@ -1,7 +1,7 @@ # 82 - Workspace Config Drift ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const parseJson = defineTool("parseJson", { description: "Parse a JSON string and return it, or report a parse error", @@ -26,7 +26,6 @@ const workspaceConfigDrift = agent({ })), tools: [parseJson], maxTurns: 4, - addons: repair(), }); export default workspaceConfigDrift; diff --git a/skills/rig/samples/83-commit-format-suggester.md b/skills/rig/samples/83-commit-format-suggester.md index cc69c8d..0ac44c9 100644 --- a/skills/rig/samples/83-commit-format-suggester.md +++ b/skills/rig/samples/83-commit-format-suggester.md @@ -1,7 +1,7 @@ # 83 - Commit Format Suggester ```rig -import { agent, p, s, repair, steering } from "rig"; +import { agent, p, s } from "rig"; // Agent role: review recent git commits and suggest conventional-format rewrites for each one. const commitFormatSuggester = agent({ @@ -14,7 +14,6 @@ const commitFormatSuggester = agent({ category: s.enum("feat", "fix", "chore", "docs", "test", "refactor", "style"), })), maxTurns: 5, - addons: [steering(), repair()], }); export default commitFormatSuggester; diff --git a/skills/rig/samples/86-npm-audit-simplifier.md b/skills/rig/samples/86-npm-audit-simplifier.md index 7c3fc3a..2e57771 100644 --- a/skills/rig/samples/86-npm-audit-simplifier.md +++ b/skills/rig/samples/86-npm-audit-simplifier.md @@ -1,7 +1,7 @@ # 86 - Npm Audit Simplifier ```rig -import { agent, p, s, repair } from "rig"; +import { agent, p, s } from "rig"; // Agent role: run npm audit, parse the JSON output, and produce a simplified vulnerability report. const npmAuditSimplifier = agent({ @@ -13,7 +13,6 @@ const npmAuditSimplifier = agent({ recommendation: s.string, }), maxTurns: 5, - addons: repair(), }); export default npmAuditSimplifier; diff --git a/skills/rig/samples/90-workspace-config-drift.md b/skills/rig/samples/90-workspace-config-drift.md index 06fb5e2..a6d882a 100644 --- a/skills/rig/samples/90-workspace-config-drift.md +++ b/skills/rig/samples/90-workspace-config-drift.md @@ -1,7 +1,7 @@ # 90 - Workspace Config Drift ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const parseJson = defineTool("parseJson", { description: "Parse a JSON string and return it, or report a parse error", @@ -26,7 +26,6 @@ const workspaceConfigDrift = agent({ })), tools: [parseJson], maxTurns: 4, - addons: repair(), }); export default workspaceConfigDrift; diff --git a/skills/rig/samples/91-commit-format-suggester.md b/skills/rig/samples/91-commit-format-suggester.md index 223a57a..a1e1d26 100644 --- a/skills/rig/samples/91-commit-format-suggester.md +++ b/skills/rig/samples/91-commit-format-suggester.md @@ -1,7 +1,7 @@ # 91 - Commit Format Suggester ```rig -import { agent, p, s, repair, steering } from "rig"; +import { agent, p, s } from "rig"; // Agent role: review recent git commits and suggest conventional-format rewrites for each one. const commitFormatSuggester = agent({ @@ -14,7 +14,6 @@ const commitFormatSuggester = agent({ category: s.enum("feat", "fix", "chore", "docs", "test", "refactor", "style"), })), maxTurns: 5, - addons: [steering(), repair()], }); export default commitFormatSuggester; diff --git a/skills/rig/samples/93-hotspot-file-analyzer.md b/skills/rig/samples/93-hotspot-file-analyzer.md index c9d1daa..9812fa8 100644 --- a/skills/rig/samples/93-hotspot-file-analyzer.md +++ b/skills/rig/samples/93-hotspot-file-analyzer.md @@ -1,7 +1,7 @@ # 93 - Hotspot File Analyzer ```rig -import { agent, p, s, steering } from "rig"; +import { agent, p, s } from "rig"; // Agent role: analyze which source files are hot-spots by measuring churn and top contributors. const hotspotFileAnalyzer = agent({ @@ -13,7 +13,6 @@ const hotspotFileAnalyzer = agent({ riskLevel: s.enum("low", "medium", "high"), })), maxTurns: 5, - addons: steering({ message: "Ensure every file entry has a numeric churnScore and at least one topContributor." }), }); export default hotspotFileAnalyzer; diff --git a/skills/rig/samples/94-ts-interface-conflict-checker.md b/skills/rig/samples/94-ts-interface-conflict-checker.md index fab4faf..753ff1e 100644 --- a/skills/rig/samples/94-ts-interface-conflict-checker.md +++ b/skills/rig/samples/94-ts-interface-conflict-checker.md @@ -1,7 +1,7 @@ # 94 - Ts Interface Conflict Checker ```rig -import { agent, p, s, defineTool, repair } from "rig"; +import { agent, p, s, defineTool } from "rig"; const scanInterfaces = defineTool("scanInterfaces", { description: "Scan a TypeScript file for exported interface names using grep", @@ -42,7 +42,6 @@ const tsInterfaceConflictChecker = agent({ }), tools: [scanInterfaces], maxTurns: 6, - addons: repair(), }); export default tsInterfaceConflictChecker; diff --git a/skills/rig/samples/96-test-naming-enforcer.md b/skills/rig/samples/96-test-naming-enforcer.md index e58a06a..e4ec2c2 100644 --- a/skills/rig/samples/96-test-naming-enforcer.md +++ b/skills/rig/samples/96-test-naming-enforcer.md @@ -1,7 +1,7 @@ # 96 - Test Naming Enforcer ```rig -import { agent, p, s, steering } from "rig"; +import { agent, p, s } from "rig"; // Agent role: audit test file naming conventions and report files that violate the standard pattern. const testNamingEnforcer = agent({ @@ -15,7 +15,6 @@ const testNamingEnforcer = agent({ allConform: s.boolean, }), maxTurns: 5, - addons: steering({ message: "Ensure every discovered test file has an entry in files and allConform is a boolean." }), }); export default testNamingEnforcer; diff --git a/src/addons.ts b/src/addons.ts deleted file mode 100644 index 982a005..0000000 --- a/src/addons.ts +++ /dev/null @@ -1,2 +0,0 @@ -export { addons, oncePerAgent, repair, steering, timeout } from "../skills/rig/rig.ts"; -export type { AgentRegistration, SteeringOptions, TimeoutOptions } from "../skills/rig/rig.ts"; diff --git a/src/rig.test.ts b/src/rig.test.ts index a25c693..2fc5391 100644 --- a/src/rig.test.ts +++ b/src/rig.test.ts @@ -59,7 +59,7 @@ vi.mock("@github/copilot-sdk", () => ({ RuntimeConnection: { forUri: mocks.forUri, forStdio: mocks.forStdio }, })); -import { AgentError, PromptBuilder, agent, analyzeResponse, configureAgent, copilotEngine, debug, defineTool, oncePerAgent, p, repair, s, steering, timeout, toJsonSchema } from "rig"; +import { AgentError, PromptBuilder, agent, analyzeResponse, configureAgent, copilotEngine, debug, defineTool, p, s, toJsonSchema } from "rig"; import type { Tool } from "rig"; beforeEach(() => { @@ -407,130 +407,6 @@ describe("agent invocation", () => { }); }); - it("exposes the SDK-neutral agent through an addon", async () => { - const addon = vi.fn(async (context, next) => { - await next(); - expect(context.agent).toMatchObject({ - ask: expect.any(Function), - close: expect.any(Function), - }); - }); - mocks.setSendAndWaitImpl(async () => ({ text: "hello world" })); - - const greet = agent({ - name: "greeter", - input: s.object({ text: s.string }), - output: s.object({ text: s.string }), - addons: addon, - }); - - await expect(greet({ text: "Hi" })).resolves.toEqual({ text: "hello world" }); - expect(addon).toHaveBeenCalledTimes(1); - }); - - it("applies default addons from agent spec", async () => { - const addon = vi.fn(async (_context, next) => { - await next(); - }); - mocks.setSendAndWaitImpl(async () => ({ text: "hello world" })); - - const greet = agent({ - name: "greeter", - input: s.object({ text: s.string }), - output: s.object({ text: s.string }), - addons: addon, - }); - - await expect(greet({ text: "Hi" })).resolves.toEqual({ text: "hello world" }); - expect(addon).toHaveBeenCalledTimes(1); - }); - - it("supports express-like addon registration with use()", async () => { - const order: number[] = []; - const first = vi.fn(async (_context, next) => { - order.push(1); - await next(); - }); - const second = vi.fn(async (_context, next) => { - order.push(2); - await next(); - }); - mocks.setSendAndWaitImpl(async () => ({ text: "hello world" })); - - const greet = agent({ - name: "greeter", - input: s.object({ text: s.string }), - output: s.object({ text: s.string }), - }); - - expect(greet.use(first).use(second)).toBe(greet); - await expect(greet({ text: "Hi" })).resolves.toEqual({ text: "hello world" }); - expect(order).toEqual([1, 2]); - expect(first).toHaveBeenCalledTimes(1); - expect(second).toHaveBeenCalledTimes(1); - }); - - it("validates addons passed to use()", () => { - const greet = agent({ - name: "greeter", - input: s.object({ text: s.string }), - output: s.object({ text: s.string }), - }); - - expect(() => greet.use([null as unknown as any] as any)).toThrow( - "Agent addon entries must be functions (entry at index 0 is null).", - ); - }); - - it("disconnects the session when an addon throws", async () => { - const addon = vi.fn(() => { - throw new Error("hook failed"); - }); - const greet = agent({ - name: "greeter", - input: s.object({ text: s.string }), - output: s.object({ text: s.string }), - addons: addon, - }); - - await expect(greet({ text: "Hi" })).rejects.toThrow("hook failed"); - expect(mocks.disconnectSession).toHaveBeenCalledTimes(1); - }); - - it("starts with no repair addon by default", async () => { - mocks.setSendAndWaitImpl(async () => "not json"); - - const strict = agent({ - name: "strict", - maxTurns: 2, - }); - - await expect(strict("go")).rejects.toBeInstanceOf(AgentError); - await expect(strict("go")).rejects.toMatchObject({ kind: "parse", turn: 1 }); - }); - - it("retries invalid JSON with the repair addon", async () => { - const prompts: string[] = []; - let calls = 0; - - mocks.setSendAndWaitImpl(async ({ prompt }) => { - prompts.push(prompt); - calls += 1; - return calls === 1 ? "not json" : JSON.stringify("repaired"); - }); - - const repairable = agent({ - name: "repairable", - addons: repair(), - maxTurns: 2, - }); - - await expect(repairable("go")).resolves.toBe("repaired"); - expect(prompts).toHaveLength(2); - expect(prompts[1]).toContain(" { mocks.setSendAndWaitImpl(async () => "```json\n\"hello\"\n```"); @@ -561,34 +437,6 @@ describe("agent invocation", () => { await expect(lister("go")).resolves.toEqual(["alpha", "beta"]); }); - it("retries validation failures with addon-customized repair prompts", async () => { - const prompts: string[] = []; - let calls = 0; - - mocks.setSendAndWaitImpl(async ({ prompt }) => { - prompts.push(prompt); - calls += 1; - return calls === 1 ? { wrong: true } : JSON.stringify("fixed"); - }); - - const repairable = agent({ - name: "repairable", - addons: [ - async (context, next) => { - await next(); - if (context.nextPrompt) { - context.nextPrompt = `please fix: ${context.nextPrompt}`; - } - }, - repair(), - ], - maxTurns: 2, - }); - - await expect(repairable("go")).resolves.toBe("fixed"); - expect(prompts[1]).toContain("please fix"); - }); - it("throws AgentError after the final invalid turn", async () => { mocks.setSendAndWaitImpl(async () => "not json"); @@ -730,19 +578,6 @@ describe("agent invocation", () => { await expect(slow("go", { timeout: 50 })).rejects.toThrow(/Timed out/); }); - it("supports timeout as an addon", async () => { - mocks.setSendAndWaitImpl(async ({ signal }) => { - await new Promise((_, reject) => { - signal?.addEventListener("abort", () => reject(signal.reason), { once: true }); - setTimeout(() => reject(new Error("should have aborted")), 5000); - }); - return ""; - }); - - const slow = agent({ name: "timeout-test", addons: timeout({ timeout: 50 }) }); - await expect(slow("go")).rejects.toThrow(/Timed out/); - }); - it("inlines prompt intents and omits top-level prompt metadata", async () => { const prompts: string[] = []; @@ -797,136 +632,6 @@ describe("agent invocation", () => { expect(prompts[0]).toContain("before answering."); }); - it("supports addons that steer retries near max turns", async () => { - const prompts: string[] = []; - let calls = 0; - - mocks.setSendAndWaitImpl(async ({ prompt }) => { - prompts.push(prompt); - calls += 1; - if (calls === 1) { - return "not json"; - } - return prompt.includes("running out of turns") - ? JSON.stringify("recovered") - : "still not json"; - }); - - const steerable = agent({ - name: "steerable", - maxTurns: 2, - addons: [ - async (context, next) => { - await next(); - if (context.nextPrompt && context.turn === context.maxTurns - 1) { - context.nextPrompt = `${context.nextPrompt}\nAdd a short correction because you are running out of turns.`; - } - }, - repair(), - ], - }); - - await expect(steerable("go")).resolves.toBe("recovered"); - expect(prompts).toHaveLength(2); - expect(prompts[1]).toContain("running out of turns"); - }); - - it("exports a steering addon that warns near max turns", async () => { - const prompts: string[] = []; - let calls = 0; - - mocks.setSendAndWaitImpl(async ({ prompt }) => { - prompts.push(prompt); - calls += 1; - if (calls === 1) { - return "not json"; - } - return prompt.includes("final attempt before reaching the turn limit") - ? JSON.stringify("recovered") - : "still not json"; - }); - - const steerable = agent({ - name: "steerable", - maxTurns: 2, - addons: [steering(), repair()], - }); - - await expect(steerable("go")).resolves.toBe("recovered"); - expect(prompts).toHaveLength(2); - expect(prompts[1]).toContain("final attempt before reaching the turn limit"); - }); - - it("supports addons that validate snippets inline", async () => { - let calls = 0; - mocks.setSendAndWaitImpl(async () => { - calls += 1; - return calls === 1 - ? JSON.stringify({ code: "const x = 1;" }) - : JSON.stringify({ code: "```ts\nconst x = 1;\n```" }); - }); - - const snippetGuard = agent({ - name: "snippet-guard", - maxTurns: 2, - output: s.object({ code: s.string }), - addons: [ - async (context, next) => { - await next(); - if (!context.nextPrompt && context.output && typeof context.output === "object") { - const code = (context.output as { code?: unknown }).code; - if (typeof code === "string" && !code.includes("```")) { - context.completed = false; - context.output = undefined; - context.nextPrompt = "Return the same payload but wrap code in a fenced markdown block."; - } - } - }, - repair(), - ], - }); - - await expect(snippetGuard("go")).resolves.toEqual({ code: "```ts\nconst x = 1;\n```" }); - }); - - it("rejects non-function addon entries", async () => { - mocks.setSendAndWaitImpl(async () => JSON.stringify("ok")); - const guarded = agent({ name: "guarded", addons: [null as unknown as any] as any }); - await expect(guarded("go")).rejects.toThrow( - "Agent addon entries must be functions (entry at index 0 is null).", - ); - }); - - it("registers with the runtime agent once per call", async () => { - let turns = 0; - const register = vi.fn(); - mocks.setSendAndWaitImpl(async () => { - turns += 1; - return turns === 1 ? "not json" : JSON.stringify("hello world"); - }); - - const review = agent({ - name: "review", - maxTurns: 2, - addons: [ - oncePerAgent(async (runtimeAgent, context) => { - register(runtimeAgent, context.turn); - }), - repair(), - ], - }); - - await expect(review("go")).resolves.toBe("hello world"); - expect(register).toHaveBeenCalledTimes(1); - expect(register).toHaveBeenCalledWith( - expect.objectContaining({ - ask: expect.any(Function), - close: expect.any(Function), - }), - 1, - ); - }); - it("renders schema descriptions for discovery", async () => { const prompts: string[] = []; diff --git a/src/samples/49-timeout-signal-helper.ts b/src/samples/49-timeout-signal-helper.ts index 5a1aa19..bcfcc66 100644 --- a/src/samples/49-timeout-signal-helper.ts +++ b/src/samples/49-timeout-signal-helper.ts @@ -1,11 +1,10 @@ -import { agent, timeout } from "rig"; +import { agent } from "rig"; -// Agent role: return a short response in output.text. +// Agent role: return a short response within a time budget. const worker = agent({ model: "mini", - instructions: `Return a short response in output.text.`, - addons: timeout({ timeout: 5_000 }), + instructions: `Return a short response.`, }); export default worker; diff --git a/src/samples/55-file-change-lint-middleware.ts b/src/samples/55-file-change-lint-middleware.ts deleted file mode 100644 index ba54da9..0000000 --- a/src/samples/55-file-change-lint-middleware.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { agent, s } from "rig"; -import type { AgentAddon } from "rig"; -import { $ } from "zx"; - -async function workspaceFingerprint(): Promise { - const { exitCode, stdout } = await $`git status --porcelain`.nothrow(); - return exitCode === 0 ? stdout.trim() : ""; -} - -function lintOnFileChange(runLint: () => Promise): AgentAddon { - return async (_context, next) => { - const before = await workspaceFingerprint(); - await next(); - const after = await workspaceFingerprint(); - if (before !== after) { - await runLint(); - } - }; -} - -// Agent role: apply workspace changes and trigger linting when files changed. -const fileChangeMiddleware = agent({ - model: "mini", - instructions: "Update files when needed, then summarize the change.", - output: s.object({ - changed: s.boolean, - summary: s.string, - }), - addons: lintOnFileChange(() => $`npm run typecheck`), -}); - -await fileChangeMiddleware("Inspect the workspace and apply a small fix if needed."); - -export default fileChangeMiddleware; diff --git a/src/samples/57-complex-integration-sonnet.ts b/src/samples/57-complex-integration-sonnet.ts index 223f051..96e3cc6 100644 --- a/src/samples/57-complex-integration-sonnet.ts +++ b/src/samples/57-complex-integration-sonnet.ts @@ -1,4 +1,4 @@ -import { agent, defineTool, p, s, oncePerAgent, steering, timeout } from "rig"; +import { agent, defineTool, p, s } from "rig"; const summarizeText = defineTool<{ text: string }>("summarize_text", { description: "Create a concise summary from text.", @@ -48,11 +48,6 @@ const complexIntegration = agent({ }), tools: [summarizeText], agents: { planner }, - addons: [ - oncePerAgent(async () => {}), - timeout({ timeout: 45_000 }), - steering(), - ], instructions: p` Build a compact execution brief for the user topic. Use repository context from ${p.read("README.md")} and workspace state from ${p.bash("git status --short")}.