Skip to content

feat(pi): Pi coding agent as an iii worker - #314

Merged
rohitg00 merged 7 commits into
mainfrom
pi-worker
Jun 23, 2026
Merged

feat(pi): Pi coding agent as an iii worker#314
rohitg00 merged 7 commits into
mainfrom
pi-worker

Conversation

@rohitg00

@rohitg00 rohitg00 commented Jun 23, 2026

Copy link
Copy Markdown
Contributor

Exposes the Pi coding-agent API as iii functions and streams, nothing else. pi::run executes one headless Pi turn in a chosen working directory and returns the result, token usage, and cost; pi::start runs it in the background. Raw AgentSession events mirror verbatim onto pi::events, and a translated AgentEvent view lands on agent::events, so the console and acp worker render a Pi run like any native harness turn.

pi::steer and pi::follow_up inject instructions into a live run via Pi's steering and follow-up queues. Sessions resume by session file, keyed by iii session_id in engine state. Turns carry the iii runtime context by default so the agent discovers and calls registered functions through the iii CLI.

Embeds @earendil-works/pi-coding-agent in-process (no CLI subprocess). Bundle worker on the configuration-worker pattern: config.yaml is the seed, the live value hot-reloads. 68 tests, typecheck and lint clean, single-file bundle builds.

Summary by CodeRabbit

  • New Features

    • Added Pi coding agent worker enabling in-process code execution with session persistence, event streaming, and control capabilities
    • Introduced functions for running single-turn tasks and managing multi-turn agent sessions with steering and interruption controls
    • Added runtime configuration system with YAML-based settings for model selection, working directory, tool allowlists, and streaming options
  • Documentation

    • Added comprehensive guides for the Pi worker including setup, usage examples, and function reference
    • Updated discovery guidance in runtime context for improved engine interaction patterns
  • Chores

    • Added build configuration, TypeScript setup, linting, and test infrastructure for the Pi worker package

Exposes the Pi coding-agent API as iii functions and streams, nothing
else. pi::run executes one headless Pi turn in a chosen working
directory and returns the result, token usage, and cost; pi::start runs
it in the background. Raw AgentSession events mirror verbatim onto
pi::events, and a translated AgentEvent view lands on agent::events, so
the console and acp worker render a Pi run like any native harness turn.

pi::steer and pi::follow_up inject instructions into a live run via Pi's
steering and follow-up queues. Sessions resume by session file, keyed by
iii session_id in engine state. Turns carry the iii runtime context by
default so the agent discovers and calls registered functions through
the iii CLI.

Embeds @earendil-works/pi-coding-agent in-process (no CLI subprocess).
Bundle worker on the configuration-worker pattern: config.yaml is the
seed, the live value hot-reloads. 68 tests, typecheck and lint clean,
single-file bundle builds.
@vercel

vercel Bot commented Jun 23, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
workers Ready Ready Preview, Comment Jun 23, 2026 12:53pm

Request Review

@coderabbitai

coderabbitai Bot commented Jun 23, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@rohitg00, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 34 minutes and 52 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses rolling per-developer review limits. Reviews become available again as older review attempts age out of the rolling limit window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 8ea65950-28f6-43e0-86cc-84f54526780e

📥 Commits

Reviewing files that changed from the base of the PR and between 31c57fd and d33caf6.

📒 Files selected for processing (6)
  • .github/workflows/create-tag.yml
  • .github/workflows/release.yml
  • README.md
  • pi/src/run.ts
  • pi/src/session.ts
  • pi/tests/register.test.ts
📝 Walkthrough

Walkthrough

This PR introduces a new pi/ worker package that runs the Pi coding agent as an in-process iii bus worker. It adds wire types, Zod config schemas, session/state management, a sequenced stream emitter, Pi-to-wire event mapping, the core run execution engine, all III function registrations, and a full Vitest test suite. It also makes minor refinements to the Discovery bullet in the iii context prompt for both claude-code and codex.

Changes

Pi Worker Package

Layer / File(s) Summary
Package scaffolding and build tooling
pi/package.json, pi/tsconfig.json, pi/vitest.config.ts, pi/biome.json, pi/.gitignore, pi/iii.worker.yaml, pi/iii-permissions.yaml, pi/scripts/build-bundle.mjs
Package manifest, TypeScript/Vitest/Biome configs, iii worker and permissions YAML, and an esbuild bundle script with an inlinePackageJson plugin that rewrites the iii-sdk version lookup into a literal.
Wire types and Pi-to-wire mapping
pi/src/types.ts, pi/src/map.ts, pi/tests/map.test.ts
Defines all exported wire-format types (ContentBlock, AgentMessage, AgentEvent, SessionRecord, Usage); implements toolFunctionId, mapMessageContent, mapToolResultContent, makeAssistantMessage, makeFunctionResult, mapUsage, and lastAssistant with full Vitest coverage.
Config schema, loading, and configuration-worker integration
pi/src/config.ts, pi/src/configuration.ts, pi/config.yaml, pi/tests/config.test.ts, pi/tests/configuration.test.ts
Zod-based ConfigSchema/RuntimeConfigSchema with YAML file loading and defaults; registerPiConfig, fetchRuntime, and bindConfigTrigger wire the live config to the configuration worker; tests cover defaults, partial YAML merges, error re-throw, and registration payload shape.
Session building and state persistence
pi/src/session.ts, pi/src/state.ts, pi/tests/state.test.ts
buildSession constructs or resumes a Pi AgentSession with model resolution and thinking-level configuration; loadSession, saveSession, and listSessions persist SessionRecord objects under the pi_sessions scope via state::get/set/list.
Stream event emitter and iii context prompt
pi/src/events.ts, pi/src/iii-prompt.ts, pi/tests/events.test.ts
makeEmitter emits sequenced stream::set frames with per-session monotonic item IDs and fault-tolerant trigger calls; III_CONTEXT_PROMPT provides the iii runtime context template string injected into the agent system prompt.
Core run execution and III function API
pi/src/run.ts, pi/tests/_helpers/fake-iii.ts, pi/tests/_helpers/fake-session.ts, pi/tests/run-payload.test.ts, pi/tests/run.test.ts, pi/tests/register.test.ts
executeRun/runReserved implements the full turn lifecycle: live-slot reservation, session record load/save, Pi event subscription with serial async drain, transcript building, and turn_end/agent_end emission. Registers pi::run, pi::start, pi::steer, pi::follow_up, pi::stop, pi::status, pi::sessions::list, and run::start_and_wait. Tests use fakeIii and scriptedSession doubles to cover success, error, resume, concurrency, steering, and payload extraction paths.
Worker CLI entrypoint
pi/src/index.ts
Parses --config/--url args, loads the seed config, registers the worker, wires live config refresh via bindConfigTrigger, constructs both stream emitters, calls register, and installs SIGINT/SIGTERM shutdown handlers.
Documentation
pi/README.md, pi/skills/SKILL.md
Full README covering installation, quickstart, function reference, event/stream behavior, steering semantics, config fields, and observability; SKILL.md describing the headless Pi skill, when-to-use guidance, boundaries, and function enumeration.

Discovery Prompt Refinements

Layer / File(s) Summary
Discovery bullet updates
claude-code/src/iii-prompt.ts, codex/src/iii_prompt.rs
Adds explicit guidance that empty *::list responses should be treated as potentially stale (not as confirmed absence), and clarifies that engine::workers::list only reflects WS-connected workers requiring merge with worker::list.

Sequence Diagram(s)

sequenceDiagram
  participant Caller
  participant pi_run as pi::run handler
  participant state as iii state (pi_sessions)
  participant buildSession as buildSession
  participant PiAgent as AgentSession
  participant rawStream as raw_events_stream
  participant agentStream as agent::events stream

  Caller->>pi_run: {prompt, model, session_id, ...}
  pi_run->>state: loadSession(session_id)
  state-->>pi_run: SessionRecord | null
  pi_run->>buildSession: {cwd, model, thinkingLevel, resumeFile}
  buildSession-->>pi_run: AgentSession
  pi_run->>state: saveSession(status: working)
  pi_run->>PiAgent: subscribe(listener)
  pi_run->>PiAgent: prompt(text)
  loop Pi emits events
    PiAgent-->>pi_run: tool_start / tool_end / assistant_message / end
    pi_run->>rawStream: emit(raw event)
    pi_run->>agentStream: emit(function_execution_start/end or message_complete)
  end
  pi_run->>agentStream: emit(turn_end)
  pi_run->>agentStream: emit(agent_end)
  pi_run->>state: saveSession(status: done | error)
  pi_run->>PiAgent: dispose()
  pi_run-->>Caller: {session_id, pi_session_id, result, stop_reason, usage, total_cost_usd}
Loading

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

  • iii-hq/workers#243: Initially added claude-code/src/iii-prompt.ts with the Discovery bullet rules that this PR now refines for both claude-code and codex.

Suggested reviewers

  • sergiofilhowz
  • andersonleal

Poem

🐇 Hop, hop — a new worker's alive!
Pi's coding agent, ready to thrive.
Sessions persist, events stream in line,
pi::run and pi::steer — all working fine.
The rabbit typed tests 'til the suite turned green,
The fluffiest worker the iii bus has seen! 🌟

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 32.35% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'feat(pi): Pi coding agent as an iii worker' accurately describes the main change—introducing Pi as a new iii worker with comprehensive functionality including run, start, steering, and event streaming capabilities.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pi-worker

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@github-actions

Copy link
Copy Markdown
Contributor

skill-check — worker

0 verified, 25 skipped (no docs/).

Layer Result
structure
vale
ai
render

Four for four. Nicely done.

rohitg00 added 2 commits June 23, 2026 11:22
Sync the iii runtime context with the harness system-prompt enrichment
(workers#309): teach worker::list (installed + daemon-managed builtins)
alongside engine::workers::list (WS-connected only) so the agent can
confirm a worker is RUNNING by merging the two, and add the trust-a-probe
-over-an-empty-list rule so lag is not mistaken for absence. Harness-only
guidance (coder-for-files, web::fetch, agent_trigger mechanics) is
deliberately not ported: this agent has its own native tools.
…ility

Backport the provider-neutral half of the harness system-prompt
enrichment (workers#309) into the claude-code and codex iii runtime
context: teach worker::list (installed + daemon-managed builtins)
alongside engine::workers::list (WS-connected only) so the agent confirms
a worker is RUNNING by merging the two, and add the trust-a-probe-over-an
-empty-list rule. Harness-only guidance (coder-for-files, web::fetch,
agent_trigger payload mechanics) is intentionally not ported: both agents
drive iii through the CLI and own native file/shell/web tools.
Halve the single-file bundle (14.7MB -> 7.4MB) by minifying the esbuild
output, and declare esbuild as the only approved build dependency so a
clean pnpm install does not prompt to approve build scripts. The
minified bundle boots and registers all functions unchanged.
Show a real pi::run turn (pong with usage and cost), the pi::run --help
request-schema table, pi::sessions::list output, and a live iii
discovery turn enumerating every connected worker.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 5

🧹 Nitpick comments (2)
pi/src/events.ts (1)

9-16: 🩺 Stability & Availability | 🔵 Trivial | ⚖️ Poor tradeoff

Potential memory leak: unbounded session sequence tracking.

The seqBySession Map grows indefinitely as new sessions are added. Completed or abandoned sessions are never removed, which could cause memory accumulation in long-running workers with many sessions.

Consider adding cleanup logic:

  • Remove entries after a session completes (agent_end event)
  • Periodically prune entries older than a reasonable threshold
  • Use an LRU cache with a maximum size
Potential cleanup approach

One option is to add a cleanup function called after emitting agent_end:

export function makeEmitter(iii: ISdk, streamName: string) {
  return {
    emit: async function (session_id: string, event: unknown): Promise<void> {
      const seq = seqBySession.get(session_id) ?? 0;
      seqBySession.set(session_id, seq + 1);
      const item_id = `${session_id}-${PROCESS_EPOCH}-${seq.toString().padStart(8, '0')}`;
      try {
        await iii.trigger({
          function_id: 'stream::set',
          payload: { stream_name: streamName, group_id: session_id, item_id, data: event },
        });
      } catch (err) {
        console.warn(`stream::set failed for ${session_id}: ${String(err)}`);
      }
      // Clean up after terminal events
      if ((event as { type?: string })?.type === 'agent_end') {
        seqBySession.delete(session_id);
      }
    },
  };
}

Note: This would require updating call sites to use emitter.emit() instead of emitter().

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pi/src/events.ts` around lines 9 - 16, The seqBySession Map grows
indefinitely as new sessions are added without ever being cleaned up, causing
potential memory leaks in long-running workers. Add cleanup logic to the emit
function within makeEmitter that checks if the event being emitted has a type
property equal to 'agent_end', and if so, delete the corresponding session_id
entry from the seqBySession Map. This ensures that completed sessions no longer
accumulate in memory.
pi/tests/_helpers/fake-iii.ts (1)

36-39: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Clone state::get/state::list outputs to match bus semantics.

Inbound payloads are cloned, but Line 36 and Line 38 return map-backed objects by reference. That can let tests mutate persisted state without calling state::set, reducing test fidelity.

Proposed patch
-      if (req.function_id === 'state::get') return state.get(`${scope}/${key}`) ?? null;
+      if (req.function_id === 'state::get') {
+        return structuredClone(state.get(`${scope}/${key}`) ?? null);
+      }
       if (req.function_id === 'state::list') {
-        return [...state.entries()].filter(([k]) => k.startsWith(`${scope}/`)).map(([, v]) => v);
+        return [...state.entries()]
+          .filter(([k]) => k.startsWith(`${scope}/`))
+          .map(([, v]) => structuredClone(v));
       }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@pi/tests/_helpers/fake-iii.ts` around lines 36 - 39, The state::get and
state::list operations are returning references to objects from the state map
instead of clones. To match bus semantics and prevent tests from mutating
persisted state without calling state::set, clone the returned values in both
cases. For state::get, wrap the returned value with a deep clone utility before
returning. For state::list, ensure each value in the returned array is cloned so
mutations don't affect the original persisted state. This will improve test
fidelity by preventing unintended state mutations.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pi/iii.worker.yaml`:
- Line 12: The start script command in pi/iii.worker.yaml currently references
./index.mjs, but the build:bundle script outputs the bundled file to
dist/bundle/index.mjs. Update the start command to point to the correct output
path by changing it to node ./dist/bundle/index.mjs so that the worker uses the
actual bundled output location.

In `@pi/src/index.ts`:
- Line 57: The console.log statement at line 57 logs the full URL which may
contain sensitive credentials or token-like query parameters, causing potential
credential leaks in logs. Create a helper function or inline logic to extract
and log only the safe parts of the URL (scheme, hostname, and port) while
excluding the query string and credentials. Update the console.log statement to
use this sanitized version of the url variable instead of logging the complete
URL directly.

In `@pi/src/run.ts`:
- Around line 324-336: The issue is that the pi::start registered function
discards the return value of executeRun using void and always returns {
session_id, started: true }, even when executeRun would return { busy: true } to
indicate the session is already active. To fix this, remove the void keyword and
await the executeRun call instead of discarding it, then merge and return the
actual result from executeRun (which could indicate busy status) along with the
session_id, rather than always reporting started: true.

In `@pi/src/session.ts`:
- Around line 30-35: The resolveModel function silently returns undefined when
the model string is malformed (when the '/' separator is not found or is at
position 0), which masks configuration errors and makes it hard to detect
misconfiguration. Instead of silently returning undefined in the condition where
sep <= 0, throw an error with a descriptive message that explains the expected
model format (provider/modelId) to help users identify and fix configuration
issues immediately.

In `@pi/tests/events.test.ts`:
- Around line 39-44: The spy created with vi.spyOn(console, 'warn') is only
restored after all assertions if they pass, but if any expect statement fails
before line 44, the mockRestore() call will not execute and the spy will remain
active for subsequent tests. Wrap the test logic (the await expect and
subsequent expect calls) in a try-finally block so that warn.mockRestore() is
called in the finally clause, guaranteeing cleanup regardless of whether
assertions pass or fail.

---

Nitpick comments:
In `@pi/src/events.ts`:
- Around line 9-16: The seqBySession Map grows indefinitely as new sessions are
added without ever being cleaned up, causing potential memory leaks in
long-running workers. Add cleanup logic to the emit function within makeEmitter
that checks if the event being emitted has a type property equal to 'agent_end',
and if so, delete the corresponding session_id entry from the seqBySession Map.
This ensures that completed sessions no longer accumulate in memory.

In `@pi/tests/_helpers/fake-iii.ts`:
- Around line 36-39: The state::get and state::list operations are returning
references to objects from the state map instead of clones. To match bus
semantics and prevent tests from mutating persisted state without calling
state::set, clone the returned values in both cases. For state::get, wrap the
returned value with a deep clone utility before returning. For state::list,
ensure each value in the returned array is cloned so mutations don't affect the
original persisted state. This will improve test fidelity by preventing
unintended state mutations.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e6040636-87b3-4bb0-b6cb-1ac79ab554e9

📥 Commits

Reviewing files that changed from the base of the PR and between 33ae2b5 and 31c57fd.

⛔ Files ignored due to path filters (5)
  • pi/assets/cli-discovery.png is excluded by !**/*.png
  • pi/assets/cli-help.png is excluded by !**/*.png
  • pi/assets/cli-run.png is excluded by !**/*.png
  • pi/assets/cli-sessions.png is excluded by !**/*.png
  • pi/pnpm-lock.yaml is excluded by !**/pnpm-lock.yaml
📒 Files selected for processing (33)
  • claude-code/src/iii-prompt.ts
  • codex/src/iii_prompt.rs
  • pi/.gitignore
  • pi/README.md
  • pi/biome.json
  • pi/config.yaml
  • pi/iii-permissions.yaml
  • pi/iii.worker.yaml
  • pi/package.json
  • pi/scripts/build-bundle.mjs
  • pi/skills/SKILL.md
  • pi/src/config.ts
  • pi/src/configuration.ts
  • pi/src/events.ts
  • pi/src/iii-prompt.ts
  • pi/src/index.ts
  • pi/src/map.ts
  • pi/src/run.ts
  • pi/src/session.ts
  • pi/src/state.ts
  • pi/src/types.ts
  • pi/tests/_helpers/fake-iii.ts
  • pi/tests/_helpers/fake-session.ts
  • pi/tests/config.test.ts
  • pi/tests/configuration.test.ts
  • pi/tests/events.test.ts
  • pi/tests/map.test.ts
  • pi/tests/register.test.ts
  • pi/tests/run-payload.test.ts
  • pi/tests/run.test.ts
  • pi/tests/state.test.ts
  • pi/tsconfig.json
  • pi/vitest.config.ts

Comment thread pi/iii.worker.yaml
Comment thread pi/src/index.ts
Comment thread pi/src/run.ts
Comment thread pi/src/session.ts
Comment thread pi/tests/events.test.ts
pi::start reserved the live slot but always returned started: true, so a
start against an already-running session reported success while the run
was silently dropped. Check the live slot synchronously (executeRun sets
it before any await) and return { busy: true, started: false } instead;
start still returns immediately rather than awaiting the turn.

resolveModel now warns when the configured model is not in provider/modelId
form instead of silently falling back, so a misconfigured model id is
visible in the logs.
Add pi to the release flow and repo index (new-worker SOP §3, §6): the
pi/v* tag trigger in release.yml, pi in the create-tag.yml worker choices,
and a pi row in the root README Modules table — matching the claude-code
and codex agent workers.
@rohitg00
rohitg00 merged commit ebbf947 into main Jun 23, 2026
15 checks passed
@rohitg00
rohitg00 deleted the pi-worker branch June 23, 2026 15:04
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant