ACP adapter
Make pi-acp available in the host or selected Sandbox, or change command to your ACP launcher.
Built-in Agent provider
piAgent()An ACP profile for Pi that isolates adapter state per evaluation, translates AML permissions into Pi's tool list, and uses the Pi MCP extension for AML Tools and MCP servers.
Pi’s provider options deliberately separate the ACP adapter from the native Pi process:
| Option | Default | Role |
|---|---|---|
command | pi-acp | The outer ACP executable AML spawns and communicates with. |
piCommand | pi | The underlying Pi executable invoked directly or through AML’s generated wrapper. |
AML launches command. When MCP is enabled or permissions differ from Pi’s default, the ACP adapter is instructed to run a generated pi-for-aml wrapper. The wrapper invokes piCommand with a restricted --tools list and, when needed, -e <adapter path>.
ACP adapter
Make pi-acp available in the host or selected Sandbox, or change command to your ACP launcher.
Native Pi
Make the executable named by piCommand available in the same environment. The default is pi.
MCP adapter
Install pi-mcp-adapter when the Agent uses AML Tools or MCP. Supply mcpAdapterPath for deterministic resolution,
especially in packaged deployments.
Model credentials
Forward the environment expected by your Pi model provider with env. Pi itself owns provider authentication; AML
does not infer or store credentials.
Install the ACP bridge and native Pi executable in the environment where AML will start the Agent:
npm install --global \ pi-acp@0.0.33 \ @earendil-works/pi-coding-agent@0.84.2
pi --versioncommand -v pi-acpFor AML JavaScript Tools, authored MCP servers, or structured output, install the MCP adapter in the application so import.meta.resolve() can produce a stable path:
npm install pi-mcp-adapternpx pi-mcp-adapter --helpThen pass that path explicitly:
import { fileURLToPath } from "node:url"
const mcpAdapterPath = fileURLToPath(import.meta.resolve("pi-mcp-adapter"))const provider = piAgent({ mcpAdapterPath })The AML repository currently exercises pi-acp@0.0.33, @earendil-works/pi-coding-agent@0.84.2, and pi-mcp-adapter@2.26.0. Treat that set as a reproducible verification baseline, not an open-ended compatibility promise.
For container execution, the Pi image variant includes this Pi, pi-acp, and pi-mcp-adapter baseline. The default full image includes the complete chain too. Local Sandbox still requires it on the host.
This uses Pi without MCP and therefore can use the native piCommand directly when AML permissions are the default. It deliberately selects the opencode-go model path, whose credential is OPENCODE_API_KEY.
import { Agent, AmlRuntime, piAgent } from "@aml-jsx/sdk"
const apiKey = process.env.OPENCODE_API_KEYif (!apiKey) throw new Error("OPENCODE_API_KEY is required")
const provider = piAgent({ command: "pi-acp", piCommand: "pi", model: "opencode-go/deepseek-v4-flash", env: { OPENCODE_API_KEY: apiKey, },})
const result = await new AmlRuntime({ agentProvider: provider }).evaluate( <Agent>Summarize the repository in five bullet points.</Agent>)
console.log(result)In production, inject a valid credential or fail the process before starting the evaluation. Do not commit API keys or write them into a Workspace.
Unlike the other built-in ACP profiles, Pi needs its MCP extension and generated wrapper for AML JavaScript Tools, authored MCP servers, and structured output. The explicit adapter path is the most reproducible setup:
import { fileURLToPath } from "node:url"import { Agent, AmlRuntime, Tool, defineTool, piAgent } from "@aml-jsx/sdk"import { z } from "zod"
const releaseName = defineTool({ name: "read_release_name", description: "Return the application release name.", input: z.object({}), output: z.string(), async execute() { return process.env.RELEASE_NAME ?? "development" },})
const provider = piAgent({ mcpAdapterPath: fileURLToPath(import.meta.resolve("pi-mcp-adapter")), model: "opencode-go/deepseek-v4-flash",})
const result = await new AmlRuntime({ agentProvider: provider }).evaluate( <Agent provider={provider}> <Tool use={releaseName} /> Call read_release_name and return only its exact result. </Agent>)The wrapper adds mcp to Pi’s allowed tool list and writes an agent/mcp.json configuration. MCP servers are lazy and use exact server names. The adapter path may also be discovered from the pi-mcp-adapter executable, but an explicit path avoids differences between development and deployment layouts.
| Option | Type | Default | Semantics |
|---|---|---|---|
command | string | "pi-acp" | Outer ACP executable. |
args | readonly string[] | [] | Extra ACP arguments, passed exactly as supplied. |
env | Record<string, string> | {} | Provider credentials and native environment configuration. |
mcpAdapterPath | string | auto-discovered when possible | Path to the installed pi-mcp-adapter entrypoint. |
model | string | — | Provider-level model fallback. <Agent model="..." /> wins. |
piCommand | string | "pi" | Native Pi command used by the direct or generated wrapper launch. |
thinkingLevel | string | — | Adds the provider-native value as Pi’s thought_level configuration. |
workingDirectory | string | — | Fallback directory when no Sandbox supplies the effective cwd. |
Values such as "medium" in the examples are provider-native examples, not an AML allowlist. AML only checks that the value is a normalized non-empty string without null bytes, then forwards it to Pi ACP unchanged; the adapter must advertise the selected value through ACP.
Precedence and launch behavior:
<Agent model="..." /> overrides piAgent({ model }).thinkingLevel is captured at factory creation and applies to the Pi session configuration.workingDirectory.env is copied into the launch environment, while AML-owned HOME, PI_ACP_PI_COMMAND, PI_CODING_AGENT_DIR, PI_CODING_AGENT_SESSION_DIR, and PI_SKIP_VERSION_CHECK are set for session isolation.pi-for-aml; otherwise the native piCommand may be used directly.The generated wrapper translates AML permissions into Pi’s native tool list:
| AML request | Pi wrapper tools |
|---|---|
filesystem: "read-only" | read, grep, find, ls |
filesystem: "read-write" | Adds edit, write |
shell: true | Adds bash |
| MCP or AML Tools present | Adds mcp and starts the adapter |
The wrapper is a policy mapping, not a host security boundary. Use a Sandbox to constrain the process, filesystem root, network, and credentials. A read-only Sandbox can also reject the spawn() required to start Pi, depending on the Sandbox provider.
Pi ACP does not expose a native system-instruction field in this profile. AML therefore prefixes the first turn with:
<SYSTEM><your system text></SYSTEM>When MCP is enabled, AML also explains that Tools and MCP use Pi’s mcp proxy. This is intentionally different from providers with native system fields; treat the first-turn prefix as part of the authored prompt budget.
For evaluate(value, schema), Pi receives an explicit instruction to call the MCP tool aml_submit_result with the final value in args.result. Returning substitute JSON only as message text is not sufficient. AML immediately validates each candidate against the Standard Schema and returns invalid candidates as tool errors so Pi can correct them. The first valid candidate is accepted and later submissions are ignored.
import { Agent, evaluate, piAgent } from "@aml-jsx/sdk"import { z } from "zod"
const Result = z.object({ count: z.number().int(), proof: z.string(),})
const result = await evaluate( <Agent provider={piAgent({ mcpAdapterPath: "/app/node_modules/pi-mcp-adapter/index.ts" })}> Return count 7 and the proof string "aml" as structured output. </Agent>, Result)Each session receives a private state directory and the following environment:
HOME <state>PI_ACP_PI_COMMAND <native pi command or generated wrapper>PI_CODING_AGENT_DIR <state>/agentPI_CODING_AGENT_SESSION_DIR <state>/sessionsPI_SKIP_VERSION_CHECK 1The private HOME prevents Pi from inheriting the operator’s global ~/.pi/agent/AGENTS.md. Pi still walks parent
directories and the effective working directory for project AGENTS.md or CLAUDE.md files. The AML profile does not
pass Pi’s --no-context-files flag, so those project instructions are combined with AML’s first-turn system prefix.
AML materializes wrapper files, Pi settings, and lazy MCP configuration into that state directory. It then acquires the ACP session, starts the provider, handles turns and Tool/MCP calls, strips Pi ACP startup information from returned text, validates the final response, and releases the session. Reusing a factory does not reuse Pi’s per-evaluation HOME or session map.
pi-acp or pi is missing
Check both executables in the actual runtime environment. Installing Pi on the host does not install it into a Docker, Daytona, or Modal image.
MCP or AML Tools are unavailable
Install pi-mcp-adapter, set mcpAdapterPath to its entrypoint, and verify that the generated wrapper can execute piCommand and the adapter. Confirm <Tool /> is nested under <Agent />.
Structured output rejects
Enable tracing and inspect the agent.output events. Pi must submit at least one valid JSON-compatible value under args.result; invalid attempts may be corrected, while submissions after the first valid result are ignored. Message text that resembles JSON is not the structured result.
The system instruction appears in the answer
Pi receives it as first-turn text because ACP has no native system field here. Make the instruction explicit and ask
Pi to follow it without repeating it. If behavior conflicts with the authored system text, also inspect project
AGENTS.md and CLAUDE.md context files.
Permission behavior is surprising
Check whether the generated wrapper was required, then inspect the Sandbox’s access and root. Pi’s --tools list and the Sandbox runtime are separate enforcement layers.
Configuration fails before launch
Commands, paths, model names, and thinkingLevel must be normalized strings, and args cannot contain null bytes. Pi ACP must advertise the selected thought_level value.
The adapter and wrapper above are Pi-specific; capability definition and runtime behavior remain shared AML contracts: