-
Notifications
You must be signed in to change notification settings - Fork 21
feat(pi): Pi coding agent as an iii worker #314
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
d313acc
feat(pi): Pi coding agent as an iii worker
rohitg00 ac86607
feat(pi): backport worker::list + trust-probe nuggets to the iii context
rohitg00 78173f0
feat(claude-code, codex): sync iii context with harness running-visib…
rohitg00 52962b5
build(pi): minify the bundle and pin the built dependency
rohitg00 31c57fd
docs(pi): add CLI screenshots to the README
rohitg00 13a8d27
fix(pi): report busy on pi::start, warn on malformed model id
rohitg00 d33caf6
chore(pi): wire pi into release + repo registration
rohitg00 File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -25,6 +25,7 @@ on: | |
| - image-resize | ||
| - llm-router | ||
| - mcp | ||
| - pi | ||
| - provider-anthropic | ||
| - provider-openai | ||
| - session-manager | ||
|
|
||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| *.tsbuildinfo |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,183 @@ | ||
| # pi | ||
|
|
||
| Pi coding agent as an iii worker: the Pi API exposed as functions and streams on the iii bus, nothing else. The worker runs the same in-process agent loop Pi runs in the terminal, with the same tools (read, bash, edit, write) against any host directory. `pi::run` executes one headless turn and returns the result; the raw Pi events mirror verbatim onto the `pi::events` stream, and a translated AgentEvent view lands on `agent::events`, so the iii console, the acp worker, and any sibling worker observe a Pi run exactly like a native harness turn. The worker also registers `run::start_and_wait`, the same entrypoint the console and the acp worker drive, so both run Pi with no changes. | ||
|
|
||
| ## Install | ||
|
|
||
| ```bash | ||
| iii worker add pi | ||
| ``` | ||
|
|
||
| Pi runs the loop in-process (no CLI subprocess), so the worker environment needs model credentials — `ANTHROPIC_API_KEY` (or the provider key Pi is configured for) or an existing Pi login. | ||
|
|
||
| ## Skills | ||
|
|
||
| Install the `pi` agent skill for Claude Code, Cursor, and 30+ other agents: | ||
|
|
||
| ```bash | ||
| npx skills add iii-hq/workers --skill pi | ||
| ``` | ||
|
|
||
| ## Quickstart | ||
|
|
||
| From zero to a Pi turn over the bus: | ||
|
|
||
| ```bash | ||
| curl -fsSL https://install.iii.dev/iii/main/install.sh | sh | ||
| iii worker add pi | ||
| iii # starts the engine + worker | ||
| ``` | ||
|
|
||
| Then talk to it like any other function: from the console chat, from `iii trigger pi::run`, or from any SDK: | ||
|
|
||
| ```ts | ||
| import { registerWorker } from 'iii-sdk'; | ||
|
|
||
| const iii = registerWorker('ws://127.0.0.1:49134', { workerName: 'demo' }); | ||
|
|
||
| const res = await iii.trigger({ | ||
| function_id: 'pi::run', | ||
| payload: { | ||
| prompt: 'Add a /health endpoint to server.ts and run the tests', | ||
| cwd: '/path/to/repo', | ||
| }, | ||
| timeout_ms: 600_000, | ||
| }); | ||
| // { session_id, pi_session_id, result, stop_reason, usage, total_cost_usd } | ||
| ``` | ||
|
|
||
| Or straight from the terminal with the `iii trigger` CLI: | ||
|
|
||
| ```bash | ||
| # one full turn (raise the timeout; the default 30s is too short for agent turns) | ||
| iii trigger pi::run --timeout-ms 600000 \ | ||
| --json '{"prompt":"add a /health endpoint and run the tests","cwd":"/path/to/repo"}' | ||
|
|
||
| # quick reads use key=value syntax | ||
| iii trigger pi::sessions::list | ||
| iii trigger pi::status session_id=<session_id> | ||
|
|
||
| # background turn + control | ||
| iii trigger pi::start --json '{"prompt":"...","cwd":"/path/to/repo"}' | ||
| iii trigger pi::stop session_id=<session_id> | ||
|
|
||
| # ask the running engine for a function's description | ||
| iii trigger pi::run --help | ||
| ``` | ||
|
|
||
| A turn from the CLI returns the result with token usage and cost: | ||
|
|
||
|  | ||
|
|
||
| `iii trigger pi::run --help` prints the published request schema as a parameter table: | ||
|
|
||
|  | ||
|
|
||
| Call `pi::run` again with the returned `session_id` to continue the same conversation: the worker maps iii session ids to Pi session files in engine state and resumes automatically. | ||
|
|
||
|  | ||
|
|
||
| Two ids come back from every run. `session_id` is the iii session id: the key for `pi::status`, `pi::stop`, `pi::steer`, resume, and the stream group. `pi_session_id` is Pi's internal session id — returned for reference, not a lookup key. | ||
|
|
||
| Long turns: use `pi::start` to return immediately, then watch `agent::events` (group_id = your session_id) for `message_complete`, `function_execution_start/end`, and `turn_end` frames. `pi::stop` interrupts a live run, `pi::status` reads a point-in-time view, `pi::sessions::list` enumerates past sessions. | ||
|
|
||
| ## Functions | ||
|
|
||
| | Function | Purpose | | ||
| | --- | --- | | ||
| | `pi::run` | Run one turn, wait, return the final result | | ||
| | `pi::start` | Fire-and-forget turn; progress arrives on `agent::events` | | ||
| | `pi::steer` | Inject a steering instruction into a live run | | ||
| | `pi::follow_up` | Queue a follow-up message for a live run | | ||
| | `pi::stop` | Interrupt a live run | | ||
| | `pi::status` | Session state, live flag, usage, cost | | ||
| | `pi::sessions::list` | All sessions this worker has run | | ||
| | `run::start_and_wait` | Alias for `pi::run` under the entrypoint the console and acp worker drive | | ||
|
|
||
| `pi::run` accepts either a bare `prompt` string or a `messages` array (`[{ role: 'user', content: [{ type: 'text', text }] }]`), plus `model`, `cwd`, `thinking_level`, `tools`, and `iii_context` overrides. | ||
|
|
||
| ### Raw events | ||
|
|
||
| Every event Pi emits (`agent_start/end`, `turn_start/end`, `message_start/update/end`, `tool_execution_start/update/end`, and the session events `queue_update`, `compaction_start/end`) is mirrored verbatim onto the `pi::events` stream, group_id = session_id. Consumers that want the exact Pi event format read `pi::events`; consumers that want harness-shaped frames read `agent::events`. Same turn, two views. | ||
|
|
||
| ## Steering a live run | ||
|
|
||
| A turn started with `pi::start` keeps its session reachable while it streams. Two functions push instructions into it: | ||
|
|
||
| ```bash | ||
| # start a long run | ||
| iii trigger pi::start --json '{"prompt":"refactor the auth module","cwd":"/path/to/repo","session_id":"s1"}' | ||
|
|
||
| # redirect it mid-flight — applied after the current tool calls finish | ||
| iii trigger pi::steer --json '{"session_id":"s1","prompt":"stop, keep the public API unchanged"}' | ||
|
|
||
| # queue work for after it would otherwise stop | ||
| iii trigger pi::follow_up --json '{"session_id":"s1","prompt":"then add tests for the new paths"}' | ||
| ``` | ||
|
|
||
| `pi::steer` maps onto Pi's steering queue (interrupt-style), `pi::follow_up` onto its follow-up queue (wait-style). Both no-op with `{steered:false}` / `{queued:false}` when no run is live for the session. | ||
|
|
||
| ## The agent on the bus | ||
|
|
||
| By default every turn's prompt carries the iii runtime context: the same engine-grounded rules as the harness identity prompts, retargeted to the `iii` CLI the agent reaches through its shell. The agent discovers capabilities from the live engine instead of memory — `iii trigger engine::functions::list` to find function ids, `iii trigger <fn> --help` as the contract before every first call, the registry flow (`directory::registry::workers::list/info`, `worker::add`) when nothing registered fits — plus the calling rules and error-handling discipline that go with them. Local file edits stay on Pi's native tools; backend actions go through registered functions. | ||
|
|
||
| ```bash | ||
| # the agent answers this by querying the live engine itself | ||
| iii trigger pi::run --timeout-ms 300000 \ | ||
| --json '{"prompt":"List every worker connected to this engine and what each one does.","cwd":"/tmp"}' | ||
| ``` | ||
|
|
||
| Pi answers by querying the live engine itself, grouping every connected worker by role: | ||
|
|
||
|  | ||
|
|
||
| The context is prepended on a fresh session and skipped on resume (it is already in the conversation history). Turn it off entirely with `"iii_context": false` per call or globally in `config.yaml`. | ||
|
|
||
| ## Thinking and tools | ||
|
|
||
| `thinking_level` maps straight onto Pi's reasoning levels, per turn: | ||
|
|
||
| | Level | Behavior | | ||
| | --- | --- | | ||
| | `off` | No extended reasoning | | ||
| | `minimal` / `low` | Short reasoning budget | | ||
| | `medium` | The worker default | | ||
| | `high` / `xhigh` | Deep reasoning (xhigh on supported model families) | | ||
|
|
||
| `tools` is an allowlist: leave it empty for Pi's defaults (`read`, `bash`, `edit`, `write`), or pass a subset to narrow what the turn can do — e.g. `{"tools":["read","bash"]}` for a read-and-run turn that cannot edit or write files. | ||
|
|
||
| ## Configuration | ||
|
|
||
| ```yaml | ||
| engine_url: ws://127.0.0.1:49134 | ||
|
|
||
| defaults: | ||
| model: "" # empty = Pi settings default; else "provider/modelId" | ||
| thinking_level: medium # off | minimal | low | medium | high | xhigh | ||
| cwd: "" # default working directory for runs | ||
| tools: [] # empty = Pi defaults (read, bash, edit, write) | ||
| agent_dir: "" # Pi global config dir; empty = ~/.pi/agent | ||
|
|
||
| events_stream: agent::events # translated AgentEvent frames | ||
| raw_events_stream: pi::events # verbatim Pi events | ||
| iii_context: true # prepend the iii runtime context on fresh sessions | ||
| ``` | ||
|
|
||
| `config.yaml` is the seed: on first boot the worker registers it with the built-in `configuration` worker as the initial value, then reads the live value back and hot-reloads on every `configuration:updated`. `engine_url` is excluded from the managed schema — it is bootstrap, so it stays on the local seed / `--url`. | ||
|
|
||
| ## Observability | ||
|
|
||
| Every `pi::run` is an ordinary traced invocation on the engine: the trace carries the full input payload (prompt, cwd, caller worker id) and the output (result, stop reason, token usage, cost) as span events, with per-function p50/p95/p99 in the console's trace explorer — no extra instrumentation in the worker. | ||
|
|
||
| ## How it maps | ||
|
|
||
| | Pi | iii | | ||
| | --- | --- | | ||
| | `AgentSession.prompt()` turn | `pi::run` invocation | | ||
| | every AgentSession event, verbatim | `pi::events` stream frame | | ||
| | assistant `message_end` | `message_complete` frame on `agent::events` | | ||
| | `tool_execution_start` / `tool_execution_end` | `function_execution_start` / `function_execution_end` frames | | ||
| | final result | `turn_end` + `agent_end` frames, function return value | | ||
| | `steer()` / `followUp()` | `pi::steer` / `pi::follow_up` | | ||
| | session resume | engine state scope `pi_sessions`, keyed by iii session_id | | ||
| | extra capability | another iii worker on the bus (`shell`, `database`, `storage`, ...) | |
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| { | ||
| "$schema": "https://biomejs.dev/schemas/2.4.10/schema.json", | ||
| "root": false, | ||
| "vcs": { "enabled": false, "clientKind": "git" }, | ||
| "files": { | ||
| "ignoreUnknown": false, | ||
| "includes": ["**", "!!**/dist"] | ||
| }, | ||
| "formatter": { | ||
| "enabled": true, | ||
| "indentStyle": "space", | ||
| "indentWidth": 2, | ||
| "lineWidth": 100 | ||
| }, | ||
| "assist": { | ||
| "enabled": true, | ||
| "actions": { | ||
| "source": { | ||
| "organizeImports": "off" | ||
| } | ||
| } | ||
| }, | ||
| "linter": { | ||
| "enabled": true, | ||
| "rules": { | ||
| "recommended": true, | ||
| "suspicious": { | ||
| "noExplicitAny": "warn" | ||
| }, | ||
| "style": { | ||
| "useImportType": "off", | ||
| "useNodejsImportProtocol": "error" | ||
| }, | ||
| "complexity": { | ||
| "noForEach": "off" | ||
| } | ||
| } | ||
| }, | ||
| "javascript": { | ||
| "formatter": { | ||
| "quoteStyle": "single", | ||
| "trailingCommas": "all", | ||
| "semicolons": "always" | ||
| } | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,23 @@ | ||
| engine_url: ws://127.0.0.1:49134 | ||
|
|
||
| defaults: | ||
| model: "" # empty = Pi settings default; else "provider/modelId", e.g. "anthropic/claude-sonnet-4-20250514" | ||
| thinking_level: medium # off | minimal | low | medium | high | xhigh | ||
| cwd: "" # default working directory for runs | ||
| tools: [] # empty = Pi defaults (read, bash, edit, write); else an allowlist of tool names | ||
| agent_dir: "" # Pi global config dir; empty = ~/.pi/agent | ||
|
|
||
| # Stream AgentEvent frames here, grouped by session_id. The console and | ||
| # acp worker both read this stream. | ||
| events_stream: agent::events | ||
|
|
||
| # Raw Pi events (exact AgentSession event shapes: agent_start/end, turn_*, | ||
| # message_*, tool_execution_*) mirrored verbatim here, grouped by session_id. | ||
| raw_events_stream: pi::events | ||
|
|
||
| # Append the iii runtime context to the system prompt: teaches the agent | ||
| # live discovery against the engine catalog through the iii CLI | ||
| # (engine::functions::list, `iii trigger <fn> --help`). Per-turn override | ||
| # via the iii_context payload field; a caller-supplied system_prompt | ||
| # always wins and gets nothing appended. | ||
| iii_context: true |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| # Agent permissions for the pi worker. | ||
| # Spec: docs/sops/new-worker.md § 7. First-match-wins. | ||
| # | ||
| # pi::run / pi::start (and the run::start_and_wait alias) spawn a full Pi | ||
| # coding agent with the host's filesystem and shell — an agent invoking those | ||
| # without human approval is a privilege escalation, so they are NOT | ||
| # allow-listed and stay at the needs_approval default. pi::steer / pi::follow_up | ||
| # inject instructions into a live run, so they stay gated too. Read-only | ||
| # introspection is safe to allow. | ||
| version: 1 | ||
|
|
||
| rules: | ||
| # internal config-reload callback — bus-internal, never agent-callable | ||
| - '!pi::on-config-change' | ||
| - pi::status | ||
| - pi::sessions::list |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| iii: v1 | ||
| name: pi | ||
| language: javascript | ||
| deploy: bundle | ||
| manifest: package.json | ||
| description: Pi coding agent as an iii worker — pi::* functions run headless Pi turns, mirror raw events onto pi::events, and stream AgentEvent frames onto agent::events. | ||
|
|
||
| runtime: | ||
| kind: javascript | ||
|
|
||
| scripts: | ||
| start: node ./index.mjs | ||
|
|
||
| dependencies: | ||
| iii-state: "^0.17.0" | ||
| iii-stream: "^0.17.0" | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,44 @@ | ||
| { | ||
| "name": "pi", | ||
| "version": "0.1.0", | ||
| "private": true, | ||
| "description": "Pi coding agent as an iii worker: headless turns over the iii bus, raw events on pi::events, AgentEvent frames on agent::events.", | ||
| "license": "Apache-2.0", | ||
| "type": "module", | ||
| "engines": { | ||
| "node": ">=22" | ||
| }, | ||
| "packageManager": "pnpm@10.18.2", | ||
| "scripts": { | ||
| "build": "tsc -b", | ||
| "build:bundle": "node scripts/build-bundle.mjs", | ||
| "typecheck": "tsc -b --noEmit", | ||
| "lint": "biome check .", | ||
| "lint:fix": "biome check --write .", | ||
| "test": "vitest run", | ||
| "start": "node dist/index.js", | ||
| "dev": "tsx src/index.ts" | ||
| }, | ||
| "bin": { | ||
| "pi": "./dist/index.js" | ||
| }, | ||
| "dependencies": { | ||
| "@earendil-works/pi-coding-agent": "^0.79.0", | ||
| "iii-sdk": "^0.19.2", | ||
| "yaml": "^2.6.0", | ||
| "zod": "^4.0.0" | ||
| }, | ||
| "devDependencies": { | ||
| "@biomejs/biome": "2.4.10", | ||
| "@types/node": "^22.10.0", | ||
| "esbuild": "^0.25.0", | ||
| "tsx": "^4.19.0", | ||
| "typescript": "^5.7.0", | ||
| "vitest": "^3.0.0" | ||
| }, | ||
| "pnpm": { | ||
| "onlyBuiltDependencies": [ | ||
| "esbuild" | ||
| ] | ||
| } | ||
| } |
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.