# agent.run() + Pi Describe a task in Pi, inspect the workflow, run it with your current model, and save its definition for new input. Requires Node 22.19+ and Pi 1.0.4 to below 1.4 (CI tests 1.0.4, 1.1.0, 1.2.0 and 1.3.0). Pi 1.4 is not supported yet; the next release widens the range. Pin the peers you install to `@1.3.0` (the install lines below do): an unpinned install of Pi's packages now resolves 1.4.0, which this release does not support, and an unpinned `@earendil-works/pi-durable` or `@earendil-works/chord` fails with `ERESOLVE` against this package's peer range. ## Install If you do not have Pi, install it first; 1.3.0 is the newest version CI tests: ```sh npm install -g @earendil-works/pi-coding-agent@1.3.0 ``` Then, in your project: ```sh pi install npm:@parcha/agentrun-pi@beta -l pi --offline ``` `pi --offline` opens Pi's terminal interface, so run it in an interactive terminal; with no terminal attached it prints nothing and exits. `-l` writes project settings in `.pi/settings.json`. Omit it to install for all your Pi sessions. `--offline` skips startup downloads; it does not disable model calls. No AgentRun checkout or build is needed. On first launch, Pi asks "Trust project folder?" before loading the extension, with five choices: Trust, Trust parent folder, Trust (this session only), Do not trust, and Do not trust (this session only). Choose Trust, or Trust (this session only), to load it. If Pi is already open, use `/reload`. The current draft is restored from a persisted Pi session branch when available; named definition saves are stored separately. With no model configured, `pi --offline` prints two warnings at startup: `Warning: fd not found. Offline mode enabled, skipping download.` (the optional `fd` tool is not installed and `--offline` skips fetching it) and `Warning: No models available. Use /login to log into a provider via OAuth or API key.` Both are expected here and the scripted demo needs neither. The `[Skills]` list shows `agentrun-author` and the `[Extensions]` list shows `@parcha/agentrun-pi` when the extension loaded. For live tasks, configure model access in Pi first. AgentRun uses Pi's current model and authentication; it needs no separate model configuration. The scripted demo below works without provider access. Try the bundled example inside Pi: ```text /agentrun demo ``` No Pi session at hand? The same scripted workflow runs from a shell, with no key and no network: ```sh npx @parcha/agentrun-pi@beta demo ``` It prints each sub-question with its answer, then `Status: complete`, the answer and the scripted call counts, and exits 0. `npx @parcha/agentrun-pi@beta demo empty` runs the missing-evidence path and prints `Status: escalated`. The demo shows the workflow graph, progress, and a completed result with call counts. It uses scripted responses over fictional sources. It makes no model calls and does not test model quality. Run `/agentrun status` to check the loaded skill and Pi host version. Status names the supported range, 1.0.4 to below 1.4, and flags a host outside it without blocking it. Missing Pi or Jev access does not prevent the scripted demo. To repeat it, run `/agentrun run` or `/agentrun demo`. A scripted demo stays scripted on rerun, including its missing-evidence path. Use `/agentrun demo live` to switch to real Pi and Jev calls. Workflows authored by Pi use real adapters. For an offline Pi startup, use `pi --offline`. Without that flag, Pi may download optional command-line tools on first launch. This is separate from the scripted demo, which makes no network calls. ## Preview, run, save, reuse **This section needs a configured model.** Authoring a workflow from a task is a model call, so the preview below, and the `input`, `run`, `save` and `load` steps after it, are for a Pi with a model selected. With no model, `/agentrun ` answers `AgentRun requires an active Pi model. Use /login to connect a provider, then /model to select it. Try /agentrun demo without model access.` and `/agentrun demo` (above) is the one workflow you have: `/agentrun input`, `run` and `save` act on that scripted demo, not on a release review. ```text /agentrun Preview a reusable workflow that identifies breaking changes and migration actions in supplied release notes. Do not run it yet. ``` The command loads the `agentrun-author` skill. Its source ships in `@parcha/agentrun-dsl`; this package registers it rendered with the Pi host rules. A preview validates and displays the graph without running it. Ask Pi to run when ready, or use `/agentrun run`. You can also invoke `/skill:agentrun-author ` directly. Open `/agentrun` to inspect the graph in the native terminal UI. Select a stage to see its contract, instructions or criteria, and observed evidence. The Actions page offers run, input, edit, save, load, and history. Arrow keys navigate, Enter inspects, `a` opens Actions, and Escape goes back or closes the view. Closing the inspector does not cancel execution; non-TUI hosts receive a text view. After inspecting a definition, save it and reuse it in a new Pi session in the same project: ```text /agentrun input The release note says requests now require X-Workspace. /agentrun run /agentrun save release-review ``` ```text /agentrun load release-review /agentrun input The next release removes /events; clients must use /v2/events. /agentrun run ``` Loading starts with empty input and does not execute. A save retains the definition, not input or execution permission. Scripted demo adapters are not saved: loading a saved demo uses live adapters and requires the corresponding model and Jev access. Loading a saved definition starts a new run with the current input and model. A run that was interrupted continues with `/agentrun run` (see the durable run below). | Command | What happens | | --- | --- | | `/agentrun ` | Ask Pi to author, edit, or run; say when you want a preview only. | | `/agentrun help` | List commands and demo modes. | | `/agentrun` | Inspect the current workflow, or show help if there is none. | | `/agentrun input ` | Set new input without changing the definition or running it. | | `/agentrun run` | Repeat the current workflow with its last supplied input. Scripted demos stay scripted; authored workflows use real adapters. | | `/agentrun dry-run` | Run the current workflow on synthetic values, with no model, Jev or tool call. | | `/agentrun save ` | Save an immutable, project-local definition revision. | | `/agentrun load [digest]` | Load the newest or exact revision; input starts empty. | | `/agentrun list` | List saved revisions. | | `/agentrun history` | Inspect retained run receipts on this Pi branch. | | `/agentrun status` | Check skill discovery, active Pi selection, and Jev configuration without making provider calls. | | `/agentrun stop` | Request cancellation of the running workflow. | A progress widget shows active steps and the number finished. Results distinguish completion, escalation, failure, and interruption. Decision records show supplied evidence, criteria, answer, and route, not hidden reasoning or proof of correctness. Pi 1.1.0 cannot export a fresh session containing only slash-command results. To save an offline research receipt, run [`examples/research-live.mjs`](https://github.com/Parcha-ai/agentrun/blob/main/examples/research-live.mjs) from a checkout of the repository (the `examples/` directory is in the repository, not in the npm package): `node examples/research-live.mjs --out research-result.json`. This runs the scripted example again and writes its result; it does not export the Pi session. A live run is durable. Its journal is kept in `.pi/agentrun/runs//journal.json`, and `/agentrun run` after an interruption (`/agentrun stop`, a closed session, a killed process) or a failure continues the same run: committed steps are answered from the journal, a tool call that completed is answered from its receipt and is not made again, and the next step runs. The run is bound to its workflow, input, SOP text and tool names; a change to any of them starts a new run. A tool call that was in flight when the run stopped has an unknown outcome and is never made again: the resumed run fails naming it, and the person who knows whether it happened reconciles it before a new run. An agent step that was interrupted starts over from its first turn. A run that completed or escalated is not resumed; `/agentrun run` starts a new one. Scripted demos keep no journal. One workflow runs at a time. Named definitions are durable across sessions in the same project. Pi run receipts belong to the current branch and may remain in memory until Pi persists its session; saving a definition does not save a receipt. A host that does not run the extension can save, load and list the same definitions with `@parcha/agentrun-pi/workflows`, which loads nothing from pi: ```ts import { WorkflowStore } from '@parcha/agentrun-pi/workflows'; const store = new WorkflowStore(projectDirectory); // records go to /.pi/agentrun/workflows// const saved = await store.save('release-review', workflow); // an immutable revision, named by its SHA-256 const again = await store.load('release-review'); // the last one saved; pass a digest for an exact revision const revisions = await store.list(); // every revision, newest first ``` The argument is a project directory, not the workflows directory: the extension keeps its records for `ctx.cwd` in the same place, so a host that passes that directory and the extension read each other's records. Storage needs real directories and regular files (a symbolic link in the path or among the records fails with `WorkflowStoreError` code `unsafe_path`), and every load checks the record's metadata and the SHA-256 of its workflow. A `/` directory copied into the workflows directory loads. A saved workflow is a procedure, not authority: the host still checks it against its current capabilities before it runs. ### Demo modes | Command | What it demonstrates | | --- | --- | | `/agentrun demo` | Scripted successful execution over the bundled fictional sources, without model calls. | | `/agentrun demo empty` | Scripted insufficient-evidence path, without model calls. | | `/agentrun demo live` | Real Pi agent steps and Jev system one decisions over the same fictional sources. | | `/agentrun triage` | A second scripted workflow, support-ticket triage over a fictional ticket, without model calls. | Live mode uses your active Pi model and requires configured TypeSafe access for Jev. It still does not search the web or prove accuracy on real research. See the [Jev adapter configuration](https://github.com/Parcha-ai/agentrun/tree/main/packages/jev#readme). Live calls can incur usage charges; scripted mode needs no provider access. ## Tools and execution By default, the extension exposes currently active Pi built-in `read`, `grep`, `find`, and `ls` tools. A workflow can narrow that set; it cannot enable a tool that the host has not supplied. The additional `search` tool reads only the bundled fictional demo sources. The user must issue `/agentrun run --trusted` for each run that needs code nodes or active built-in `bash`, `edit`, or `write` tools. This permits local, unsandboxed execution; authorization does not carry into the next run. A model cannot grant itself that permission with a tool argument. A dry run (`/agentrun dry-run`, the inspector's Dry-run action, or the tool's `dry-run` action, which also takes an `input`) runs the inspected workflow on synthetic values: agent and judge answers and tool results are synthesized from their schemas, so it makes no model, Jev or tool call, writes no run journal and leaves the last run's receipt as it was. Its `status` is `passed`, `failed` (`problems` say why), or `not_run`: a check could not run, for example a schema the generator cannot satisfy (`pattern`, `format`), a synthetic answer that escalated, or SOP text that was not supplied, and `skipped` says which. A `not_run` dry run proves nothing. A workflow with code nodes is `not_run` unless the user issues `/agentrun dry-run --trusted`, which runs its code in this process for that dry run only; a code node that throws is reported by its label, without what it threw. A dry run stops after 10,000 synthetic steps, or at `/agentrun stop`, as `not_run`. A workflow that names `sopSection`s needs SOP text. `/agentrun sop ` reads a Markdown file inside the project (up to 512 KiB) and checks that it has a `##
` heading for each section the workflow names; `/agentrun sop` shows what is set. The file's digest is kept with the session, and a run is refused if the file changed since it was supplied. A host that passes `sop` to `createAgentRunExtension` supplies the text itself and `/agentrun sop` is not used. Custom extension tools and the outer Pi session's permission hooks are **not inherited**, including in trusted runs. A configured host can explicitly supply its own tool definitions as described below. Shell/executor effect transports and artifact delivery are unavailable in this extension; declared tool effects must use an available tool. A direct `call` to a Pi built-in returns a Pi tool result. Its `content` is an array of text/image blocks; `details` may be absent from the workflow's JSON result. Declare that result shape in the output schema. Custom `PiToolDefinition` implementations follow Pi's SDK contract and return a `details` field; use `details: undefined` when there is no extra data. Only the demo `search` tool returns `{sources}` directly. A tool-only workflow does not create an agent session. Jev is needed only for system one decisions. Jev design guidance is bundled with the author skill and returned by `describe` under `authoring.jev`, including for hosts without a file-reading tool. No separate TypeSafe skill install is required for DSL authoring. The [source-read example](../dsl/skills/author/examples/read-source-decision.json) preserves original tool evidence through a Jev decision and deterministic projection; its fictional tests demonstrate wiring, not measured semantic accuracy. Explicit `tools: []` disables tools for an agent node; omission inherits the host allowlist. Inspection checks nonexecuting host admission as well as DSL structure; `describe` also exposes structural limits. Runtime contract failures provide safe error codes, stages and schema problems for repair. Unknown provider failures remain opaque; provider bodies and arbitrary code exception text are not included in reports. Recognized Jev response/transport failures use fixed `jev_*` categories, and a Pi session response failure uses `model_response_failed`, with the failing node label. Pi runner stops use `pi_timeout`, `pi_turn_limit`, `pi_submission_limit`, `pi_no_submission`, or `pi_aborted`, with a fixed message explaining the next step. Operator cancellation of the workflow still reports `cancelled`. Jev reports may also include a fixed `error.reason` identifying the rejected response invariant, or a numeric HTTP `error.status`. A recognized token-capacity rejection reports `max_tokens_exceeded`; this is not a byte-to-token estimate or permission to truncate evidence. Provider text remains private. These diagnostics add no retry and do not infer an unobserved provider cause. Use `/agentrun stop` to cancel a slash-command run; pressing Escape is not a guaranteed cancellation path. Stopping signals admitted tools, but already-started effects may still finish. Check their results before retrying; a stop does not undo a write. Displayed workflow counts include direct tool calls, system one decisions, and model steps. They do not include tools called inside an agent step or measure tokens, requests, or spending. Hosts can count child tool attempts through `onToolAttempt`. Agent steps capture the active Pi model when the run starts and keep that selection for the run. Request-count and deadline limits bound execution; they are not token or spending caps. The native model-facing interface starts with `agentrun` and `action: "describe"` for available tool schemas and configuration presence, then `action: "inspect"` and a complete `workflow`, followed by `action: "run"` and `input`. There is no outer `submit` tool: `submit` belongs to the internal child runner and the SDK author described below. ### Configure the native extension from a host Hosts that own a restricted corpus, an approved Jev transport, or shared accounting can configure the same native extension. This is a sketch, not a runnable sample: `approvedToolsFor`, `makeHostJudge` and `sharedBudget` stand for your own host's code. See [Integrate with your application](../../docs/host-integration.md) for what a host supplies. ```js import { createAgentRunExtension } from '@parcha/agentrun-pi/extension'; // These definitions and callbacks belong to the embedding host. export default createAgentRunExtension({ hostTools: ctx => approvedToolsFor(ctx), createJudge: ({ signal }) => makeHostJudge({ signal }), onToolAttempt: () => sharedBudget.admit('tool'), }); ``` All options are optional; the default export retains ordinary Pi setup. `hostTools` replaces the complete built-in and demo tool inventory, including during trusted runs. `describe` reports the supplied schemas. Direct calls to these tools return `{content, details?}` even if a supplied tool is named `search`. Definitions refresh from the execution context before each run. Tool names must be unique; `agentrun` and `submit` are reserved. `createJudge` supplies the runner for workflow Jev nodes and is called only when the graph requires one. A failed factory never falls back to ambient configuration. The host receives cancellation signals and owns transport configuration and accounting. `onToolAttempt` is synchronous admission: it runs before each direct tool call and each native child tool attempt, including invalid arguments, unknown tools, and `submit`. Throwing prevents the attempted effect. Outer authoring calls are counted by their own host. `describe.limits` reports the configured workflow deadline, model-request, judge-call and tool-attempt ceilings, and per-node turn, submission, and timeout limits. The callback can impose a shared budget across authoring and execution. Hosts may supply `runtimeLimits` to lower a finite limit or explicitly disable it with `null`: `deadlineMs`, `modelRequests`, `judgeCalls`, `toolAttempts`, `nodeMaxTurns`, `nodeMaxSubmissions`, and `nodeTimeoutMs`. Omitted values keep existing finite defaults. Disabling all seven removes extension inference/time admission caps while preserving operator cancellation, schema validation, graph structure, concurrency, and trace/data guards. This is host configuration; neither a workflow nor an author tool argument can change it. Tool-specific timeouts and provider limits are separate host/tool contracts. Trace retention is **not an execution budget**. A run keeps recent sanitized events within a bounded in-memory report and continues when that retention fills. The report's `trace` counts received, retained, dropped and rejected events/bytes; `traceTruncated` discloses incomplete retained history, not incomplete output. Individual events still have plain-JSON, byte, depth and value-count safety guards. The SDK service configures those separately with `maxTraceBytes` (retention) and `maxEventBytes` (individual safety). For complete host-owned observation, configure `onWorkflowEvent(frame)`. Each frame has `workflowDigest`, a registration-local `runOrdinal`, a per-run `sequence`, and an isolated sanitized `event`. Valid events arrive even after report retention fills, including cancellation cleanup before run closure. This callback cannot grant authority or change execution. It is not a durable-store guarantee: the host must record write failures, drain asynchronous work, and check received sequences against report counters before claiming a complete journal. Callback errors are nonfatal to the workflow. Do not store source material without an appropriate host-owned storage policy. An operator may select a different active Pi model between inspection and execution; child agent steps capture that execution selection. The workflow cannot choose a replacement model or configure host callbacks. Code still requires the per-run trusted command and remains unsandboxed: a restricted tool inventory is not a sandbox for executable code. The host must review executable candidates before granting that authority. ## SDK: embed the runner The native extension handles configuration above. For another Pi extension that already has a context, this sketch shows the call (not runnable as it stands: `ctx` is the context Pi hands your extension, and `workflow`, `input` and `allowedTools` are yours; see [Integrate with your application](../../docs/host-integration.md)): ```js import { runWorkflow } from '@parcha/agentrun-dsl'; import { createPiHostRunner } from '@parcha/agentrun-pi'; const result = await runWorkflow(workflow, input, { runNode: createPiHostRunner(ctx, { tools: allowedTools }), // Supply runJudge separately for Jev nodes or semantic verification. }); ``` `ctx` supplies the active model and public model registry. Tools are explicitly supplied and receive the original context; outer hooks are not copied. The captured active model remains fixed for the run. A node thinks at its own `thinking` level; a node that names none thinks at medium, not at the host session's level, unless the runner's own `thinkingLevel` option sets another. Thinking is never off: a level of `off` runs at medium. The runner bounds turns, submissions, and time, and refuses an absent model instead of selecting a fallback. These limits do not impose token or spending caps. For a standalone application, `createPiRunner(options)` accepts a configured SDK `ModelRuntime` and its selected `model`. Supply `tools` explicitly. It loads no ambient filesystem tools, workspace instructions, extensions, prompt templates, or skills. `createPiHostSessionFactory(ctx)` exposes the same native child session transport for integrations that own their own runner. Factories are unnecessary for ordinary extension use. LLM `instructions` are literal text. Refer to "the text field in the JSON input" and declare `requires: ["text"]`; `{text}` inside instructions stays literal. An optional node `state` map scopes the JSON user message. Placeholder substitution applies to designated fields such as `state` and `call.args`. The child model delivers `{value: result}` through its `submit` tool. Schema and review rejections return feedback to the same session. Exhausting a turn, submission, timeout, or cancellation limit throws `PiRunError`; a rejected draft is never returned as success. A host can explicitly pass `null` for runner `maxTurns`, `maxSubmissions`, or `timeoutMs` to disable that limit. Omission keeps the default; external cancellation still interrupts unlimited runs. ## SDK: retain candidates and check fixed fixtures `authorWorkflow` lives in `@parcha/agentrun-dsl` beside the language it teaches. It saves candidate files separately from the native extension's current draft and named procedure revisions. Give it a Pi runner with no host tools, and bound its submissions by the candidate limit. This is a sketch, not runnable as it stands: `options` is your configured `PiRunnerOptions` and `checkAgainstYourFixtures` is your own function (see [Integrate with your application](../../docs/host-integration.md)): ```js import { authorWorkflow } from '@parcha/agentrun-dsl'; import { createPiRunner } from '@parcha/agentrun-pi'; const candidate = await authorWorkflow({ request: 'Extract a numeric count from text', outputDir: './candidates', inputKeys: ['text'], runNode: createPiRunner({ ...options, tools: [], maxSubmissions: 4 }), // Your configured PiRunnerOptions. maxCandidates: 4, acceptance: workflow => checkAgainstYourFixtures(workflow), }); console.log(candidate.path, candidate.checks); ``` The host owns `checkAgainstYourFixtures`: return diagnostics, or `[]` to accept. Without that callback, acceptance is labeled `structural` and does not establish correct behavior. The author retains each submitted version and its feedback, never activates it, and cannot edit the host's acceptance checks. See [the author contract](../../docs/authoring.md#one-author-contract) for the host addendum. With `rubricSections`, every supplied section must appear on every generated LLM node, including child workflows. The author conservatively rejects Jev nodes, semantic `ask` predicates, and `verify` clauses under that policy; those need a separately reviewed question contract. Schema properties named `node`, `verify`, or `predicate` remain ordinary data. Supply the authoritative rubric as `deps.sop` when executing the candidate. The package also installs a command line, `agentrun-pi`. Try it from an empty directory after `npm install @parcha/agentrun-pi@beta`: ```sh npx agentrun-pi --help npx agentrun-pi demo cp node_modules/@parcha/agentrun-dsl/skills/author/examples/review-release-notes.json workflow.json npx agentrun-pi validate workflow.json --inputs notes ``` `--inputs` names the keys of the workflow's input object; `review-release-notes.json` takes one, `notes`. `demo` needs no key and no peers. `author` and `run` import a trusted caller-owned `--config` module (its code runs when imported) that exports your runner options, and need the peers listed under Dependencies. `validate` can evaluate code probes. Review executable workflows before using it. ## Durable workflows on pi-durable `@parcha/agentrun-pi/durable` runs a workflow as one pi-durable task, `agentrun.workflow`. `createWorkflowTool(host)` gives a pi-durable session the `agentrun` tool and the task its runs are; `createWorkflowExtension(host)` is the task alone, with `start`, `read` and `stop` for your own code. Install pi-durable and chord beside the package at the tested version: `npm install @earendil-works/pi-durable@1.3.0 @earendil-works/chord@1.3.0`. The `host` object supplies what a run needs (its tools, its node runner, its judge, an optional `reconcile`); see [the agentrun tool in a pi-durable session](../../docs/host-integration.md#the-agentrun-tool-in-a-pi-durable-session) and [Recovery and versions](../../docs/host-integration.md#recovery-and-versions). The code below is a description, not a sample: the host is yours. A run keeps its journal in the session's store. A process that is killed leaves the task pending, and `harness.resume()` in the next process runs the workflow again: committed nodes and effects are answered from the journal and not run again, and the run continues at the first step that did not commit. A typed question is a different matter: a route's answer is kept by the recovery wrapper itself, but any other question (`judge`, `pick`, `sift`, `ask`, `verify`) is answered again from its receipt only when the host wires both hooks of `decisionRecovery({ key, runtime, context })` (`recordDecision` in the dependencies it returns, `decision` in their `recovery`). Without them, a question inside a node that had not committed is asked again after a crash, and a second call can answer differently; see [Decision receipts](../../docs/host-integration.md#decision-receipts). An agent step is run again from its first turn after a crash, unless the host's node runner is the durable one (`nodeRunner`, from the same entry, with `durableNodes: true` in the `adapters` the host returns): then the step is one conversation the task owns, found again by its id, and a turn that committed is not requested again. Neither entry chooses this for you; it is the host's `runNode`. A tool call that was in flight at the crash has an unknown outcome and is never made again: the resume refuses it unless the host's `reconcile` settles it with a recorded result. A typed failure is a recorded outcome (`failed`, with a code), not a throw. A pause stops at a step boundary and the task stays pending until the host closes the Harness; reopening continues it. A cancel (pi's `abortTask`) ends the run for good, and a cancelled run never resumes. ## Sign in, pick a model, check keys `@parcha/agentrun-pi/signin` is what a demo, or your own app, runs before its first model call. The sample below is a sketch, not runnable as written: `spec` is a demo's spec (the `demo.json` of an example in the repository's `examples/` directory), and `provider`, `modelId` and `context` are your app's. It is the only entry of this package that loads `@earendil-works/pi-ai`, an optional peer: install it beside the package (`npm install @earendil-works/pi-ai@1.3.0`) to use this entry. No other entry imports it. ```js import { builtinModels } from '@earendil-works/pi-ai/providers/all'; import { checkKeys, resolveModel, usageLimitError } from '@parcha/agentrun-pi/signin'; const report = checkKeys(spec, { live: true, dir: demoDir }); if (!report.ok) { console.error(report.text); process.exit(report.exitCode); } // 2: nothing was sent const { model, getApiKey, source } = await resolveModel(spec, { provider, model: modelId }); const message = await builtinModels().completeSimple(model, context, { apiKey: await getApiKey() }); if (usageLimitError(message)) throw usageLimitError(message); ``` - `checkKeys(spec, options)` lists what this run needs and what is missing: each key by its env name and where to create it, each program on `PATH` (refused outside its `platforms`, or below its `min` version as the first x.y.z that ` --version` prints), and each package as the demo's directory resolves it, with the exact `npm install --no-save @` line when it is missing. An item with `for: 'live'` or `for: 'kill'` counts only in that mode. A `oneOf` choice is met by any member, and an array member only by all of its items. A Jev judge asks for `TYPESAFE_API_KEY` or `OPENROUTER_API_KEY`. The check sends no request and prompts for nothing; a key's value is never printed. - `checkSetupPrompt(spec, text)` checks a demo's setup prompt against its spec: every key env name, program command and package name in the spec appears in it, and it names no credential env name (`*_API_KEY`, `*_KEY`, `*_TOKEN*`, `*_SECRET*`, `*_PROJECT_ID`) the spec does not list. `node scripts/check-setup-prompts.mjs` runs it for every `examples/*/demo.json` in CI. - `resolveModel(spec, options)` picks the model and its key. An explicit `provider` (and `model`) with that provider's env key wins, passed on as an explicit key; Anthropic takes `ANTHROPIC_API_KEY` only. Otherwise a stored ChatGPT sign-in is used, and the model is chosen from the account's own `GET /v1/models` by what the spec needs (text, tools, images): the spec's default when the account lists it, else the newest one pi-ai knows. With neither, it runs the sign-in. A sign-in's model takes only streaming Responses calls with `store: false` (`responsesOnly`, `unsupported`). The spec's eyes model resolves the same way: an explicit `eyes` model with its key, else the lead model when it meets the eyes' needs, else a `ResolveError` naming the gap; the ChatGPT plan takes no video input. The result serializes without its key. - `signIn(options)` runs pi-ai's own Sign in with ChatGPT once per install, with this app's name and a host id kept for the install. pi-ai's callback port is fixed at 1455; when another login holds it, the sign-in fails at once and says to finish or cancel that login. A pi-ai that ignores the app name (before 1.1) is refused before the page opens. - The sign-in lives in `$AGENTRUN_HOME/auth.json` (default `~/.config/agentrun/`): the directory is 0700 and the file 0600, written whole and renamed into place. An existing directory others can read is refused, not changed. Refreshes are serialized across processes by an operating-system lock, an exclusive SQLite transaction on `auth.json.lock` (an empty database beside it, through `node:sqlite`). The system frees it when its holder exits or is killed, so a crashed run leaves no lock to clear, the rotating refresh token is never sent twice, and a process that waited reads the new token. On Node 22, `node:sqlite` prints one experimental warning when the first write loads it. A token never reaches a log, a URL or a recording. - The plan's usage limit (`subscription_sharing_usage_limit_exceeded`) maps to `UsageLimitError`: "Your ChatGPT plan's usage limit is reached; wait, or set a provider key." It is never retried. ## Develop from source To change the extension itself, clone and build the repository: ```sh git clone --branch main --single-branch https://github.com/Parcha-ai/agentrun.git cd agentrun npm ci --ignore-scripts npm run build agentrun_checkout="$PWD" mkdir -p ../agentrun-pi-project cd ../agentrun-pi-project "$agentrun_checkout/node_modules/.bin/pi" install "$agentrun_checkout/packages/pi" -l "$agentrun_checkout/node_modules/.bin/pi" --offline ``` These commands create a separate `agentrun-pi-project` beside the checkout. You can use your own project directory instead. Pi loads the extension and skill from the checkout, so keep it in place. After source changes, rebuild and run `/reload`. ## Dependencies and retained data Pi is optional for `@parcha/agentrun-dsl`. This package depends on the DSL and the Jev adapter only. The Pi packages it uses (`@earendil-works/pi-coding-agent`, `@earendil-works/pi-agent-core`, `@earendil-works/pi-tui`, and `@earendil-works/pi-ai` for the sign-in entry only) and `typebox` are optional peer dependencies at `*`, as Pi's package guidance requires, so an installed copy never shadows the host's. The release inventory records the locked versions. When loaded as an extension, Pi resolves its core packages and TypeBox through the host loader, and `pi install` adds nothing else. A standalone application or the `agentrun-pi` CLI has no host: install the peers yourself (`npm install @earendil-works/pi-coding-agent@1.3.0 @earendil-works/pi-agent-core@1.3.0 @earendil-works/pi-tui@1.3.0 typebox@1.3.7`). This release supports Pi 1.0.4 to below 1.4, and CI tests 1.0.4, 1.1.0, 1.2.0 and 1.3.0; peer ranges at `*` do not make an older or newer host compatible. The sign-in entry needs pi-ai 1.1.0 or later: pi-ai 1.0.x ignores the app name and would put pi's name on ChatGPT's sign-in page, so `signIn` refuses it with `SignInError` (`APP_NAME_IGNORED`) before the page is shown. The offline loader regression checks competing local dependencies, a native file read, and the scripted workflow. Pi's [package guidance](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/docs/packages.md#dependencies) is the rule this follows. The durable entry (`@parcha/agentrun-pi/durable`) takes pi-durable and chord as optional peers from 1.0.4 to below 1.4; CI runs it on 1.0.4, 1.1.0, 1.2.0 and 1.3.0. A run that pi-durable 1.1 left (killed, paused, or with a tool call in flight) continues on 1.2. The move does not go back. A run that pi 1.2 left with a tool call in flight cannot be continued by pi 1.1: 1.1 waits on the newer task, and the only thing it writes is that task's mark from running back to pending, as pi does for any task a dead process left running. Continue such a run on 1.2, and do not downgrade pi under a live run. A host can call `refuseNewerTasks(harness, context)` from the durable entry after opening its Harness. On pi before 1.2, it then fails such a store with `NewerTaskError` (code `PI_TASK_NEWER`, naming the task's kind and version) instead of waiting. Candidate directories retain request text, rubric text, workflow versions, and feedback. Native Pi messages can contain workflow inputs and results. Choose storage and tool access appropriate for that data. The author skill in `@parcha/agentrun-dsl` describes the native and SDK authoring paths for agents. ## License Apache-2.0. See the included LICENSE and NOTICE files.