githubnext/chopinPublic

Let's compose rich plans together

Add the Pi harness adapter and E2E harness coverage #195

Mergedlavaman131merged 6 commits intofeat/harness-4-workers-deployfromrefs/pull/195/head
  1. 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.

    ![Planner under HARNESS=pi: README summary after 5 host tool calls](https://github.com/user-attachments/assets/f8db9846-3b4d-42a2-ba54-1398765c6a54)

    ## 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

  2. chopinmentionedlast month on GitHub

  3. chopinsubscribedlast month on GitHub

  4. lavaman131mentionedlast month on GitHub

  5. lavaman131subscribedlast month on GitHub

  6. lavaman131added-to-stacklast month on GitHub

  7. lavaman131ready-for-reviewlast month on GitHub

  8. lavaman131referenced this in Return Pi structured output from a terminating extension tool #196last month on GitHub

  9. 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

  10. LA
    Author

    @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

  11. MaggieAppletonmentionedlast month on GitHub

  12. MaggieAppletonsubscribedlast month on GitHub

  13. lavaman131mentionedlast month on GitHub

  14. lavaman131subscribedlast month on GitHub

  15. lavaman131referenced this in Move workers to structured output and add harness deployment modes #194last month on GitHub

  16. lavaman131removed-from-stacklast month on GitHub

  17. lavaman131base-ref-force-pushedlast month on GitHub

  18. lavaman131head-ref-force-pushed 4b48675dcbe6last month on GitHub

  19. lavaman131added-to-stacklast month on GitHub

  20. lavaman131merged c9d8a9fa933dlast month on GitHub

  21. lavaman131closedlast month on GitHub

  22. lavaman131head-ref-deletedlast month on GitHub