Repository navigation
Conversation
An `extensionFactories` entry received only Pi's `ExtensionAPI`, so an extension could not reach the sandbox the session runs against or the instructions the caller set for the turn. A Task-style tool that runs a child Pi session against the parent's sandbox with the parent's instructions had no way to get either. Each factory now gets a second argument, `PiHarnessExtensionSession`: the restricted sandbox session (the same view the builtin tools use), the session work directory, and an `instructions()` getter. Instructions arrive per turn and the factory runs before the first turn, so a getter over the session's current instructions is the only shape that stays correct. A second argument keeps every existing `(pi) => void` factory assignable; the session wraps each factory in a one-line adapter before handing the list to Pi's resource loader.
`mcpServers` was served by pi-mcp-adapter, a third-party Pi extension loaded through a dynamic import, with `mcpSettings` (vercel#22196) steering its tool prefix and output guard. Pi 1.0 ships MCP as a built-in extension with a `tool_search` tool for deferred exposure, so the adapter is a second MCP client in the process with its own naming and no deferred mode. This removes it and wires the native extension. When servers are configured the session loads `createToolSearchExtension()`, `createMcpExtension()` and one factory that registers every entry with `pi.registerMcpServer`. The extension gets `loadConfig` returning no servers and `logPath: devNull`, so no mcp.json is read and no mcp.log is written under Pi's global agent directory; OAuth never runs for servers with an `Authorization` header, so mcp-auth.json is never touched either. `exposure` defaults to `direct` because Pi's own default, `codemode`, needs an extension the harness does not load. `mcpServers` entries are typed as Pi's `McpServerConfig` with that narrower exposure, exported as `PiMcpServerConfig`, so adapter-only fields stop compiling instead of being passed through. The `session_shutdown` emit on dispose stays: Pi's `dispose()` does not emit it and the native extension closes its connections only from that event. Native names are always `mcp__<server>__<tool>` and results over 20 KB are always truncated with the full text spilled to the host temp dir, so `mcpSettings.toolPrefix` and `outputGuard` are deprecated; a value the native extension cannot honor fails session start rather than silently changing the model-facing tool names. The translator drops the adapter's `mcp` proxy-tool case and keeps the `mcp__` prefix classification. `@earendil-works/pi-ai` leaves the dependencies with the adapter; nothing in the package imported it. Tests: the mocked session tests assert the native wiring, and a new pi-mcp.test.ts runs real Pi against a hand-rolled streamable-HTTP MCP server and a scripted OpenAI-compatible model: direct tools are declared on the first request, a deferred tool is loaded by `tool_search` and streams under its real name, every MCP request carries the bearer header, destroy sends DELETE for each MCP session, and the agent dir holds no MCP files.
…workspace-mirror The mirror removal takes away the host copy of the workspace that inline extensions could read. The extension session is the replacement, so this branch builds on it.
Closed
4 of 5 tasks
4 of 5 tasks
…ls from settings Before, session start probed whether the sandbox workspace was visible on the host and, when it was not, copied the workspace into a host directory and patched Node's fs module so Pi's own resource discovery found AGENTS.md, CLAUDE.md and project skills at the sandbox path. Each turn re-synced the copy. The path mapper resolved relative tool paths through that host directory. This deletes the mirror, the fs patch and the host probe. Pi now runs with noSkills and noContextFiles, its settings manager points at a project directory that never exists, and the new resources setting supplies sandbox skills (listed through skillsOverride, with each skill directory added to the readable roots so the read tool can open SKILL.md). Project context that AGENTS.md or CLAUDE.md carried goes in instructions; the setting has no context-file field because instructions already reaches the system prompt. The path mapper resolves relative paths against the sandbox work directory alone. The mirror existed only to feed Pi's filesystem discovery, at the cost of copying the workspace and patching a Node built-in. Passing skills as data keeps Pi off the host filesystem and makes the session's prompt inputs explicit. Inline extensions that read the host copy must read through session.sandboxSession instead.
…tart makes no sandbox calls Before this commit, createPiSession resolved the sandbox HOME and every fileToolPathPolicy denied root in the sandbox, then built the path mapper and remote ops, before it returned. Every turn also ran writeSkills, which reads the skills manifest from the sandbox even when the turn has no skills. So a session start and a chat-only turn each cost sandbox round trips, and an unresolvable denied root failed session start. Now HOME, the canonical denied roots, the path mapper and the remote ops form one record that resolveSandboxPaths creates the first time something needs it: a native file tool call, a turn with skills to write, or the legacy session-file pull and persist. Concurrent callers share the in-flight resolution, and a failed resolution is dropped so the next use retries it. Native tools receive a getter for the remote ops instead of the ops themselves. A turn writes skills only when it has some or when an earlier turn left some to remove, so a chat-only turn makes no sandbox calls. One lazily resolved record keeps the mapper and ops consistent with the HOME they were built from, and it leaves createPiSession with no sandbox calls before it returns. That is the property the lifecycle-data change in the next commit relies on. An unresolvable denied root now fails the file tool call that needs it, and the docs and the fileToolPathPolicy TSDoc say so.
gdaybrice
force-pushed
the
gdaybrice/pi-drop-workspace-mirror
branch
from
October 12, 2026 10:40
3b5649e to
2e1b428
Compare
Review found the provider docs page still described one-argument extension factories, and the README example claimed to use all three session fields while using two. The provider page now lists `sandboxSession`, `sessionWorkDir` and `instructions()` with an example, and both examples run a command in the session's sandbox so each field appears. The example type-checks against `createPi`. `extensionSession` moves above the comment about MCP servers, which it had been inserted beneath.
…Pi's stored sign-ins out Review found four gaps between what the native MCP change promised and what it enforced. Each has a test that fails on the previous commit. A server without an `Authorization` header was sent the OAuth token of whoever had signed in to it with the Pi CLI on the same machine, because Pi's MCP extension defaults its credential store to `mcp-auth.json` in the host agent directory. The extension now gets an in-memory store, so the file is neither read nor written. An entry Pi rejects no longer starts a session without that server. Pi records an error thrown by an extension factory and carries on, so the registration factory keeps the error and session start rethrows it after the first resource load. `exposure: 'codemode'`, its `codemode-deferred` alias and the same values in `toolExposure` now fail session start. The type already excluded them, but `mcpServers` is typed `Record<string, unknown>` at the public boundary, and the tools were unreachable because the harness does not load Pi's codemode extension. An entry with `exposure: undefined` is served as `direct`. The spread that applied the default let the explicit `undefined` win, and Pi then fell back to codemode. The provider docs page still described the adapter's `mcpSettings`; it now describes native MCP. The README and changeset say that an HTTP server needs credentials only when it requires authentication.
…workspace-mirror # Conflicts: # packages/harness-pi/src/pi-session.ts
felixarntz
added a commit
that referenced
this pull request
Oct 14, 2026
…3100) ## Background An `extensionFactories` entry received only Pi's `ExtensionAPI`, so an extension could not reach the sandbox the session runs against or the instructions the caller set for the turn. A Task-style tool that runs a child Pi session against the parent's sandbox with the parent's instructions had no way to get either. #23103 also builds on this: it removes the host copy of the workspace, which leaves `session.sandboxSession` as the only way an extension can read project files. ## Summary - Each factory now receives a second argument, `PiHarnessExtensionSession`: the restricted `sandboxSession` (the same view the builtin tools use), the `sessionWorkDir`, and an `instructions()` getter. - Instructions arrive per turn and the factory runs before the first turn, so the getter reads the session's current instructions rather than a value captured at creation. - Existing `(pi) => void` factories stay assignable and keep working unchanged. ## End-to-End Verification Run against the real Pi runtime through `HarnessAgent`, with a local just-bash sandbox and Claude Haiku 4.5 over the AI Gateway. The same scenario was run on `main` as the control. - Registered an extension factory that records its second argument and ran a turn with instructions containing a token. On `main` the factory receives nothing. On this branch it receives the session work directory, a sandbox session, and `instructions()` returns the turn's instructions including the token. ## Checklist - [x] All commits are signed (PRs with unsigned commits cannot be merged) - [x] Tests have been added / updated (for bug fixes / features) - [x] Documentation has been added / updated (for bug fixes / features) - [x] A _patch_ changeset for relevant packages has been added (for bug fixes / features - run `pnpm changeset` in the project root) - [ ] I have reviewed this pull request (self-review) ## Related Issues Split out of #22289. --------- Co-authored-by: Felix Arntz <felix.arntz@vercel.com>
…orkspace-mirror # Conflicts: # content/providers/02-ai-sdk-harnesses/03-pi.mdx # packages/harness-pi/README.md # packages/harness-pi/src/index.ts # packages/harness-pi/src/pi-harness.test-d.ts # packages/harness-pi/src/pi-harness.ts # packages/harness-pi/src/pi-mcp.test.ts # packages/harness-pi/src/pi-paths.test.ts # packages/harness-pi/src/pi-paths.ts # packages/harness-pi/src/pi-session.test.ts # packages/harness-pi/src/pi-session.ts
…f allowed roots Writable and readable paths go through one check with the denied roots first, the read tool uses the session work directory directly, and the session tests drop the file-system imports the workspace mirror needed.
Contributor
Open
5 tasks done
Contributor
Author
|
Superseded by #23370, which combines the workspace-mirror removal and the Pi session in lifecycle data into one PR against main. |
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Background
At session start the harness copied the sandbox workspace to the host and patched Node's
fsmodule to serve that copy at the sandbox path. The copy existed only so Pi's filesystem discovery could findAGENTS.md,CLAUDE.mdand project skills, at the cost of copying the workspace and a process-widefspatch. Session start also resolved the sandboxHOMEand everyfileToolPathPolicydenied root, so even a turn that never touches a file made sandbox calls.Summary
fspatch and the host probe are deleted. Native file tools resolve relative paths against the sandbox work directory directly, and Pi reads no project settings, context files or skills from the host.resources.skillssetting covers skills that already exist in the sandbox: they are listed to the model by name and description, must exist atfilePath, and are read with thereadtool. Skills passed toHarnessAgentkeep working as before. There is no setting for context files; project context goes ininstructions, which already reaches the system prompt.HOME, the canonical denied roots, the path mapper and the remote ops are resolved the first time a native file tool runs or a turn has skills to write, and the resolution is retried on the next use if it fails. A turn without skills or native file tool calls makes no sandbox calls.fileToolPathPolicyfrom feat(harness-pi): add configurable writable sandbox roots for Pi's native file tools #18632 keeps working without the mirror: readable, writable and denied roots are checked against sandbox paths only, denied roots first, and writable roots reach Pi through the same first-use resolution. The two feat(harness-pi): add configurable writable sandbox roots for Pi's native file tools #18632 tests that exercised host mirror paths are removed with the mirror.Behavior changes for existing users
AGENTS.md,CLAUDE.mdor project skills from the sandbox workspace on its own. Put project context ininstructions, and list sandbox skills throughresources.skills.session.sandboxSession(feat(harness-pi): pass the harness session to extension factories #23100).End-to-End Verification
Run against the real Pi runtime through
HarnessAgent, with a local just-bash sandbox whose calls were logged and Claude Haiku 4.5 over the AI Gateway, on this branch merged with currentmain.HarnessAgent's own work directorymkdirandonSession, which feat(harness): defer sandbox setup to first use for harnesses without a bootstrap recipe #23101 defers.bash: the command's output comes back (VERIFY-42).fileToolPathPolicy: { writableRoots: ['/tmp/pi-out'] }, the model writes/tmp/pi-out/hello.txtwith thewritetool and the file in the sandbox has the requested content.resources.skillsis covered by a test that runs real Pi against a scripted model; it was not run against a live model.Checklist
pnpm changesetin the project root)Related Issues
Split out of #22289.