# agent.run() + Pi

Run typed AgentRun nodes with [Pi](https://github.com/earendil-works/pi), or describe a workflow and retain a validated candidate. Requires Node 22.19 or newer. Pi is optional for the DSL; this adapter explicitly depends on it. Installing `@agentrun/dsl` alone does not install Pi. Installing `@agentrun/pi` brings the full Pi coding-agent SDK and its transitive provider SDKs, including AWS, Google, Anthropic, and OpenAI clients; it is a materially larger dependency graph than the core. Review the release dependency inventory for the exact locked versions.

This is an unpublished release candidate; the installation command below applies after publication. From the source workspace, use `node packages/pi/dist/cli.js` instead of `npx agentrun-pi`.

```sh
npm install @agentrun/dsl @agentrun/pi @earendil-works/pi-coding-agent
```

## First run: no account or key

From the source workspace after `npm ci` and `npm run build`:

```sh
node examples/pi.mjs --run
```

This authors a count-extraction workflow, retains its JSON and review under `candidates/pi-example/`, checks three fixed host fixtures, then explicitly runs the retained candidate on a separate input. The final output is `{"count":3}`. Remove `--run` to stop after authoring and fixture acceptance. Each invocation creates a new candidate directory; it never activates a workflow or replaces an earlier one.

The default uses a scripted session through the real runner and author APIs. It makes no model calls and demonstrates integration and retention, not model quality. Read [the complete example](../../examples/pi.mjs) to see the candidate, fixtures, and explicit run boundary.

## Use your configured Pi runtime

If Pi is already configured, use its saved default without entering a model identifier:

```sh
node examples/pi.mjs --config ./examples/pi-config.mjs --run
```

The supplied config reads Pi's saved global and trusted project settings. It does not change them, select a fallback, or refresh the model catalog over the network. If no default is saved or it is unavailable, it stops with a configuration error. Establish access and save a default in Pi first; installing AgentRun does not create provider access.

For an existing host agent, `--config ./pi.config.mjs` may instead load a trusted module default-exporting your `PiRunnerOptions`: a configured SDK `ModelRuntime` instance and its already-selected `model`, with optional `maxTurns`, `maxSubmissions`, `timeoutMs`, and explicit `tools`. Do not include `sessionFactory` for a live SDK session. Importing a config module executes its code.

The live example spends model calls on authoring, three fixture evaluations per accepted attempt, and the explicit run. A failed fixture returns feedback to the author. This is a small acceptance demonstration, not a model-quality benchmark.

The CLI uses the same caller-owned configuration:

```sh
npx agentrun-pi author --config ./pi.config.mjs --out ./candidates --inputs text \
  'Extract a numeric count from text into {count: number}'
npx agentrun-pi validate ./candidates/candidate-.../001.json
```

The author retains each candidate and its validation feedback in a unique directory. It does not execute or activate the workflow. With a reviewed candidate and `input.json` such as `{"text":"There are three apples"}`:

```sh
npx agentrun-pi run --config ./pi.config.mjs ./candidate.json ./input.json
```

`run` executes trusted workflow code. `validate` checks structure and may evaluate code probes; review executable nodes before invoking it on an unknown workflow. The author rejects generated code, effects and artifacts before validation unless the host explicitly sets `allowExecutableCandidates: true`.

## Embed the runner

```js
import { runWorkflow } from '@agentrun/dsl';
import { createPiRunner } from '@agentrun/pi';
import options from './pi.config.mjs';

const result = await runWorkflow(workflow, input, {
  runNode: createPiRunner(options),
  // Supply runJudge separately to use Jev nodes or semantic verification.
});
```

LLM `instructions` are literal text; `{text}` inside them stays `{text}`. Refer to “the text field in the JSON input” and declare `requires: ["text"]`. The runtime provides state as the JSON user message. Placeholder substitution applies to designated fields such as `judge.state` and `call.args`.

The model submits `{value: result}` through a dedicated tool. The adapter checks the output schema and calls the engine's review hook. Schema and semantic rejection feedback goes back to the same session. Review exceptions propagate with their original evidence. A turn limit, submission limit, timeout, or cancellation throws `PiRunError`; no rejected draft is returned as success.

No filesystem or shell tools, workspace instructions, extensions, prompt templates or skills load automatically. Supply `tools` explicitly as Pi `ToolDefinition[]`; workflow node tool lists can only narrow that set. Model and authentication objects are required. SDK retries and compaction are disabled so the declared turn budget remains observable. The caller's timeout bounds asynchronous setup and execution even when an injected adapter ignores cancellation. Custom tools must honor their cancellation signal; cancellation cannot undo an external effect, and an uncooperative tool may continue after the caller receives an error. Do not blindly retry such effects.

## Author with an independent acceptance check

```js
import { authorWorkflow } from '@agentrun/pi';

const candidate = await authorWorkflow({
  request: 'Extract a numeric count from text',
  outputDir: './candidates',
  inputKeys: ['text'],
  pi: options,
  maxCandidates: 4,
  acceptance: async workflow => {
    // Return diagnostics from your own fixed tests; [] accepts.
    return checkAgainstYourFixtures(workflow);
  },
});
console.log(candidate.path, candidate.checks);
```

The author has only the submission tool and cannot edit your acceptance tests. Without `acceptance`, the result is labeled `structural`, which does not establish correct behavior. A supplied acceptance callback receives a separate candidate copy. Store rubric text in `rubricSections`; every supplied section must be referenced by every generated LLM node, including nodes in child workflows. This is a conservative author policy: it does not infer which subset of a rubric a node needs. With supplied sections, the author rejects Jev judge/pick/sift/route nodes, semantic `ask` predicates, and `verify` clauses because they require a separately reviewed question contract. Ordinary schema properties and input data named `node`, `verify`, or `predicate` are data and do not trigger that policy. At runtime supply that same authoritative rubric through the DSL's `deps.sop`.

Sessions live in memory. Candidate directories retain request text, rubric text, workflow versions, and feedback; choose a storage location appropriate for that data. `sessionFactory` provides a deterministic test seam, and `onEvent` lets the host observe Pi lifecycle events. These adapter events do not replace the DSL's workflow events.

The accompanying `skills/author/SKILL.md` describes the authoring contract for other agents. The packaged CLI imports a trusted caller-owned config module rather than adding its own provider configuration format.
