Add the Pi harness adapter and E2E harness coverage #195
- LA
Part of GitHub stack #202 (#181 harness).
Part of #181. This is slice 5 of 5 stacked PRs for the pluggable agent harness RFC. It covers RFC phase 8 (the Pi adapter) and phase 10 (E2E harness coverage). Phase 9, the author's own runtime adapter, is deferred.
Stacked on #194 (`feat/harness-4-workers-deploy`). Review #186, #187, #192 and #194 first; this diff contains only the slice 5 commits.
## What changes
- **E2E harness coverage (`415101c`).** A separate `AGENT=on` Playwright project runs alongside the existing `AGENT=off` suite. It uses a test-only `HarnessV1` (`e2e/harness/fake-harness.ts`) and a Streamable HTTP fake GitHub MCP server (`e2e/harness/fake-mcp-server.ts`). A scripted Planner turn calls `read_plan` and the repository-bound `list_pull_requests` through the real host tools, conversation projection, socket stream and durable transcript. The runner starts a third disposable database for it (`compose.yaml`, `scripts/e2e.ts`). - **Pi adapter (`d43063f`).** - **Pin:** `@ai-sdk/harness-pi` is exact-pinned at 1.0.128. - **Why a wrapper:** the published `createPi()` throws `HarnessCapabilityUnsupportedError` for structured output. So `apps/server/src/harness/pi/adapter.ts` wraps it: a synthetic host tool captures the model's final answer, and the outer stream still sees only host tool calls plus one synthesized text and finish step. Everything else passes straight through: turns without structured output, and the built-in, abort and destroy behavior. - **Contract:** the contract suite passes against the real `createPi()` agent loop with a local OpenAI-completions model stub. That run used no host credential and didn't read `~/.pi/agent/auth.json`. - **Harness map:** `pi` is added, with its own auth guard. `HARNESS_AUTH` is required, only Pi's documented modes are accepted, `direct` is refused, and a non-loopback bind allows only `ai-gateway`. - **Docs (`ec12ca1`).** The docs name the variables each Pi auth mode reads. They also say that `HARNESS=pi` needs a model from Pi's catalog, such as `github-copilot/gpt-5.6-luna` (enforced since `d330c0f`). The Copilot entitlement wording in README, `hosted-agent.md` and `self-hosting.md` is now scoped to the default `copilot-sdk` harness, and `AGENTS.md`'s E2E summary is corrected. - **Local E2E workers (`d7b0f0c`).** This one is outside the RFC's scope. Several `AGENT=off` motion tests poll an element that exists only for its 180–250 ms exit animation. At Playwright's default of 8 workers on a developer machine, a varying set of them failed. Local runs are now capped at 4 workers; CI keeps its default and retries.
## Deviations and caveats for review
- **Pi wrapper instead of the bare adapter.** The RFC planned to add `@ai-sdk/harness-pi` to the map directly. That isn't possible while the published adapter rejects structured output. The wrapper can be deleted if a later Pi release supports it. - **Pi reads host `AGENTS.md` files (security, fixed in `d330c0f`).** `@ai-sdk/harness-pi` 1.0.128, still the latest release, builds Pi's resource loader itself and disabled extensions, themes and prompt templates, but not context files, so Pi read `AGENTS.md`/`CLAUDE.md` from `/home/user`, `/home` and `/` on the host. A `bun patch` (`patches/@ai-sdk%2Fharness-pi@1.0.128.patch`) now sets `noContextFiles: true`. A contract test writes an `AGENTS.md` into a temp parent of the session working directory and asserts it never reaches the model's system prompt; the test fails with the patch hunk removed. - **Unknown `MODEL` under Pi (fixed in `d330c0f`).** Pi silently switched to its default model for an ID it did not recognize, while the banner printed the requested ID. The same patch now fails the turn with `Pi does not recognize model <id>`, and `HARNESS=pi` requires an explicit `MODEL`, because the default `gpt-6-luna` is a Copilot ID. - **Known race (P3, not blocking).** The wrapper submits the synthetic result on the next macrotask (`setTimeout(0)`). In the pinned Pi 1.0.128, that lands after Pi registers the pending host tool result. A future Pi version that adds an asynchronous step before registration could drop the result, and the worker turn would hang until aborted. The contract suite pins today's behavior. **Resolved in #196**, which replaces the host result tool with a terminating Pi extension tool, so there is no timing assumption left.
## Interactive Edge verification
With `HARNESS=pi HARNESS_AUTH=auto MODEL=github-copilot/gpt-5.6-luna` on a signed-in local build in Microsoft Edge, `@chopin` answered with an accurate README summary after 5 host tool calls. The PR-listing answer and the foreign-repository check under Pi were not captured; the default harness passes both in #192.

## Workflow note
The workflow's review loop ran out of rounds without approving. The last substantive review had one blocking item, which is fixed: a commit trailer claimed an E2E run passed when it had exited non-zero. The final two review rounds returned no verdict, because a reviewer session hit a process-cleanup error in the workflow runtime. I checked that the blocking finding was fixed and reran the full checks at `d7b0f0c` myself; results are below.
Assistant-model: Claude Opus 5.5, GPT-6 Sol, GPT-6 Luna Assistant-workflow: harness-stack (run 68e4dd7d-1808-4864-9dfa-71d2d4eb5e98) Assistant-verification: Bun Pi contract passed: contract suite against the real createPi() agent loop with a local model stub (built-ins inactive, structured output through the wrapper, abort, destroy) Assistant-verification: Bun Pi red check passed: the same suite against bare createPi() failed structured output with HarnessCapabilityUnsupportedError Assistant-verification: bun run ci passed: dprint, oxlint, tokens at d7b0f0c Assistant-verification: bun run types passed: all workspaces and E2E at d7b0f0c Assistant-verification: bun test passed: 1591 pass, 2 PostgreSQL skips, 0 fail at d7b0f0c Assistant-verification: bun run e2e passed: CI=1 under flock, 228 passed and 3 flaky that passed on retry (responsive-workspace.e2e.ts:110 and :186, sidecar.e2e.ts:400) at d7b0f0c, including the new harness project Assistant-verification: Playwright E2E passed: three consecutive local runs of the exact locked bun run e2e at 4 workers, 231/231 each (from d7b0f0c's commit record) Assistant-verification: agent-browser Edge manual scenario passed: HARNESS=pi @chopin produced an accurate README summary through 5 host tool calls Assistant-verification: agent-browser Edge manual scenario unavailable: Pi PR listing and foreign-repository refusal were not captured Co-authored-by: @lavaman131
chopinmentionedlast month on GitHub
chopinsubscribedlast month on GitHub
lavaman131mentionedlast month on GitHub
lavaman131subscribedlast month on GitHub
lavaman131added-to-stacklast month on GitHub
lavaman131ready-for-reviewlast month on GitHub
lavaman131referenced this in Return Pi structured output from a terminating extension tool #196last month on GitHub
- MA
Two things came up in review: 1. Pi can auto read `AGENTS.md` from the server’s filesystem at `/home/user`, `/home`, and `/` and put it into context, without using a file read tool. Conflicts with the [issue spec](https://github.com/githubnext/chopin/issues/181) saying agent should have no access to the host filesystem. Should disable this? - Pi walks from the working directory up [through its parents](https://github.com/earendil-works/pi/blob/d981de1229ef899957bbe968bc8dcda02a21f477/packages/coding-agent/src/core/resource-loader.ts#L137-L152) and [reads AGENTS.md](https://github.com/earendil-works/pi/blob/d981de1229ef899957bbe968bc8dcda02a21f477/packages/coding-agent/src/core/resource-loader.ts#L71-L83) with readFileSync. It then adds the contents to the [model’s system prompt.](https://github.com/earendil-works/pi/blob/d981de1229ef899957bbe968bc8dcda02a21f477/packages/coding-agent/src/core/system-prompt.ts#L150-L155)
3. Chopin defaults to `MODEL=gpt-6-luna`, but Pi does not recognize that ID. Pi chooses another available model while Chopin’s startup banner still says `gpt-6-luna`. - Chopin requests gpt-6-luna when MODEL is unset and [passes that value](https://github.com/githubnext/chopin/blob/0eadd35ee78a21df70dd5d1de076543e70792a54/apps/server/src/harness/agents.ts#L49-L53) to Planner. - Pi’s resolver [returns undefined](https://github.com/vercel/ai/blob/ee3169b3c4880e2abe4d0d7c781243bb81822ec4/packages/harness-pi/src/pi-model-resolver.test.ts#L130-L136) for an unknown model. #195 states that gpt-6-luna is absent from Pi’s catalog. Pi only adopts a requested model [when resolution succeeds](https://github.com/vercel/ai/blob/ee3169b3c4880e2abe4d0d7c781243bb81822ec4/packages/harness-pi/src/pi-session.ts#L1238-L1240); otherwise Pi [selects an available](https://github.com/earendil-works/pi/blob/d981de1229ef899957bbe968bc8dcda02a21f477/packages/coding-agent/src/core/sdk.ts#L196-L225) initial model, or fails if none is available. - Chopin’s banner still prints its [configured value](https://github.com/githubnext/chopin/blob/0eadd35ee78a21df70dd5d1de076543e70792a54/apps/server/src/config.ts#L114-L119), not Pi’s effective model.
Found by Sol 6 High in thermonuclear code review, appears to check out
- LA
@MaggieAppleton Both findings checked out and are fixed in d330c0f:
1. **Host `AGENTS.md`**: `@ai-sdk/harness-pi` 1.0.128 builds Pi's `DefaultResourceLoader` without `noContextFiles` and has no setting for it, so I added a `bun patch` (`patches/@ai-sdk%2Fharness-pi@1.0.128.patch`) that sets `noContextFiles: true`. A new contract test places `AGENTS.md` in a host parent of the session working directory and checks it never reaches the model's system prompt. 2. **`gpt-6-luna` under Pi**: the same patch makes an unresolvable model fail the turn (`Pi does not recognize model <id>`) instead of silently falling back, and `HARNESS=pi` now requires `MODEL` at startup. The banner can no longer name a model Pi isn't running.
With the patch hunks disabled, both new contract tests fail and reproduce what you described. With them enabled, the full suite passes. #196 is rebased onto this.
Assistant-workflow: inline Assistant-verification: bun test contract passed: host AGENTS.md excluded from the system prompt; gpt-6-luna rejected before any model request Assistant-verification: red check passed: both new tests fail with the patch hunks disabled Assistant-verification: bun test passed: 1594 pass, 2 skip, 0 fail Assistant-verification: bun run types passed; bun run ci passed User-preference: Patch pinned dependencies with bun patch when the upstream adapter exposes no option for a needed safety fix Co-authored-by: @lavaman131
MaggieAppletonmentionedlast month on GitHub
MaggieAppletonsubscribedlast month on GitHub
lavaman131mentionedlast month on GitHub
lavaman131subscribedlast month on GitHub
lavaman131referenced this in Move workers to structured output and add harness deployment modes #194last month on GitHub
lavaman131removed-from-stacklast month on GitHub
lavaman131base-ref-force-pushedlast month on GitHub
lavaman131head-ref-force-pushed 4b48675dcbe6last month on GitHub
lavaman131added-to-stacklast month on GitHub
lavaman131merged c9d8a9fa933dlast month on GitHub
lavaman131head-ref-deletedlast month on GitHub