rig is a minimal TypeScript agent harness skill designed to run inside sandboxed agentic workflows embedded in markdown.
npm install github:pelikhan/rig#v0.0.8Or install the skill for Copilot coding agent:
gh skills clone pelikhan/rigimport {
agent,
defineTool,
p,
s,
} from "rig";
import { addons, oncePerSession, repair, steering, timeout } from "rig/addons";agent(spec)creates a typed agent function.s.*defines input/output schemas. Omitinput/outputwhen free-form strings are enough.p.*creates declarative prompt intents for prompt templates or inputs.p()andp`...`create a prompt builder withvar,write, andregionprimitives for assembling prompts.p`...`also works ininstructionsto embed prompt intents directly:instructions: p`Review ${p.bash("git status")}`.defineTool(name, config)matches the Copilot SDK helper and accepts rigs.*schemas forparameters.addonsaccepts express-like(context, next)turn addons for steering, inline validation, and Copilot session access.rigstarts with no default addons.rig/addonsprovides optional addon helpers:oncePerSession,repair,steering,timeout, andaddons.{oncePerSession,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.
Use rig code fences in markdown files to define runnable harness programs:
```rig
export default "Summarize this repository.";
```Extract the rig fence and run it with:
awk '/^```rig$/{in_block=1;next}/^```$/{if(in_block){exit}}in_block' ./program.md | node skills/rig/rig.tsimport { agent, p, s } from "rig";
// Agent role: extract package scripts and summarize what they do.
const extractScripts = agent({
model: "nano",
instructions: p`Read ${p.read("package.json")} and summarize the package scripts. Use ${p.bash("find src -name '*.ts' -type f | sort")} only to call out source files that look relevant.`,
output: s.object({
scriptsByName: s.record(s.string),
summary: s.string,
relatedFiles: s.array(s.string),
}),
});
export default extractScripts;When the context already lives in the workspace, prefer intent templates like the example above over adding input fields just to shuttle shell output or file contents. Favor p.read("path") over p.bash("cat path"), and let the harness work from files instead of assembling large in-memory strings first.
s.string
s.string("description")
s.number
s.boolean
s.unknown
s.array(item, "description")
s.object(fields, "description")
s.record(value, "description")
s.enum(...values)
s.enum(values, "description")
s.optional(shape)
s.optional(shape, "description")Use declarative s.* helpers for every schema node.
s.* values serialize directly as JSON Schema, so no separate conversion step is needed.
Rig renders these declarations as JSON Schema in prompt schema blocks.
Implicit object literals, trailing-underscore optional fields, and {"*": ...} record sugar are not supported.
Prompt intents for shell and file operations are optimized for sandboxed agentic workflows. They assume the harness is already running with the required constraints and protections, so the generated instructions tell the agent to execute the action directly instead of adding extra permission prompts.
p.bash("git status --short")
p.bash("npm test")
p.read("README.md")
p.write("README.md", "# Updated\n")
const reviewWorkspace = agent({
instructions: p`Review ${p.read("README.md")} against ${p.bash("git status --short")}.`,
});const b = p();
const repo = b.var("repo", "rig");
b.write("Summarize repository ", repo, ".\n");
b.write("Start by checking ", b.bash("git status --short"), ".\n");
b.region("ts", "type Summary = { text: string };");
const prompt = b.toString();Register custom tools directly on an agent using the same shape as @github/copilot-sdk:
const lookupIssue = defineTool("lookup_issue", {
description: "Look up an issue by id.",
parameters: s.object({
issue: s.string,
}),
handler: async ({ issue }) => `Issue ${issue}`,
});
const triage = agent({
instructions: "Use lookup_issue before answering.",
tools: [lookupIssue],
});Rig defaults agent tools to skipPermission: true, and you can also place plain tool objects in tools; rig will convert s.* parameter schemas into JSON Schema before creating the Copilot session.
Use these samples to quickly gauge how well rig supports increasingly agentic workflows:
skills/rig/samples/20-issue-reproducer.md— chained diagnose/fix flowskills/rig/samples/36-subagent-delegation.md— delegation between focused agentsskills/rig/samples/47-prompt-intents.md— prompt intents embedded directly in prompt templatesskills/rig/samples/50-end-to-end-release-agent.md— multi-step release planning workflowdocs/rig-syntax-genaiscript-comparison.md— comparison of rig syntax with 10 GitHub GenAIScript sample ports
- Default model:
gpt-4.1 - Default max turns:
4 - No addons are loaded by default (including repair/retry behavior)
Per call, you can override model, timeout, maxTurns, and signal.
Each agent call runs a per-turn addon chain:
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:
const review = agent({
maxTurns: 3,
addons: [timeout({ timeout: 30_000 }), steering(), repair],
});For direct SDK access, use oncePerSession(...) to register with the session once:
const review = agent({
addons: oncePerSession((session) => {
session.on?.((event) => {
// custom event handling
});
}),
});Per-turn addons still receive context.session directly, and you can also register addons after creating the agent:
const timingAddon = async (context, next) => {
await next();
};
const review = agent({});
review.use(timingAddon);rig is specialized for Copilot SDK sessions inside sandboxed agentic workflows.
By default it connects to an already-running Copilot server via HTTP (COPILOT_SDK_URI, then localhost:7777).
Pass --server to spawn the server over stdio when launching a program.
Run node skills/rig/rig.ts --help for CLI usage; the launcher also accepts common help aliases such as -h, help, /help, and /?.
For runnable programs, you can pipe a rig program directly on stdin (assumes the Copilot server is already running):
cat <<'RIG' | node skills/rig/rig.ts
import { p } from "rig";
export default p`Summarize this repository and include highlights from ${p.read("README.md")}.`;
RIGInline stdin programs run a default-exported root program with no required external input and write the result to stdout. Export either an agent, a string, or a prompt builder. If export default is omitted, the harness defaults to the first const/let/var name = agent(...) assignment.
import { agent, p, s } from "rig" is optional in inline mode because the harness injects it when missing.
Inline mode accepts root agents that either omit input, use input: s.object({}), or rely on the default input: s.string (which is invoked with "").
Pass --server to start the Copilot server automatically as part of the run:
cat ./program.ts | node skills/rig/rig.ts --serverPass --typecheck to typecheck the rig program before execution:
cat ./program.ts | node skills/rig/rig.ts --typecheckTo run a root program from a program file, export the root value as the default export and pass input on stdin:
echo "Summarize this repository" | node skills/rig/rig.ts src/program.tsProgram-file mode also supports --typecheck:
echo "Summarize this repository" | node skills/rig/rig.ts src/program.ts --typecheckFor program-file mode stdin coercion:
- if root input schema is
string, stdin is passed as raw text - if root input schema is an object containing
text, stdin is passed as{ text: "<stdin>" } - otherwise stdin must be valid JSON for the declared input schema
Copilot SDK lifecycle events and rig request events are logged to stderr as JSONL.
npm test
npm run typecheck