Skip to content

Pi Agent

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.

Best for
Extensible Pi workflows that need AML's typed orchestration and capability model.
Know before using
Pi has no ACP system-instruction field in this integration, so AML prefixes system instructions inside literal SYSTEM tags in the first turn. MCP and restricted permissions require a generated wrapper.
Built-in ACP MCP extension

Pi’s provider options deliberately separate the ACP adapter from the native Pi process:

OptionDefaultRole
commandpi-acpThe outer ACP executable AML spawns and communicates with.
piCommandpiThe 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:

Terminal
npm install --global \
pi-acp@0.0.33 \
@earendil-works/pi-coding-agent@0.84.2
pi --version
command -v pi-acp

For 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:

Terminal
npm install pi-mcp-adapter
npx pi-mcp-adapter --help

Then 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_KEY
if (!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.

OptionTypeDefaultSemantics
commandstring"pi-acp"Outer ACP executable.
argsreadonly string[][]Extra ACP arguments, passed exactly as supplied.
envRecord<string, string>{}Provider credentials and native environment configuration.
mcpAdapterPathstringauto-discovered when possiblePath to the installed pi-mcp-adapter entrypoint.
modelstringProvider-level model fallback. <Agent model="..." /> wins.
piCommandstring"pi"Native Pi command used by the direct or generated wrapper launch.
thinkingLevelstringAdds the provider-native value as Pi’s thought_level configuration.
workingDirectorystringFallback 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:

  1. <Agent model="..." /> overrides piAgent({ model }).
  2. thinkingLevel is captured at factory creation and applies to the Pi session configuration.
  3. The active Sandbox cwd overrides workingDirectory.
  4. 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.
  5. If MCP is present or permissions are narrowed, AML uses pi-for-aml; otherwise the native piCommand may be used directly.

The generated wrapper translates AML permissions into Pi’s native tool list:

AML requestPi wrapper tools
filesystem: "read-only"read, grep, find, ls
filesystem: "read-write"Adds edit, write
shell: trueAdds bash
MCP or AML Tools presentAdds 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>/agent
PI_CODING_AGENT_SESSION_DIR <state>/sessions
PI_SKIP_VERSION_CHECK 1

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

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

  2. 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 />.

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

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

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

  6. 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: