research phase 9 w16 pi agent - sinrimin/harness-nexus GitHub Wiki
Status: verified against the official docs (2026-09-17) β pi.dev/docs/latest (installation, settings, custom-provider, models, sessions, session-format, skills, security, rpc) plus the npm registry manifests. NOT rig-verified β this is a feasibility study for a candidate T-wave target, requested by the user; nothing is scheduled. pi is Mario Zechner's (badlogic) minimal terminal coding harness, now maintained by Earendil Works; it also powers OpenClaw.
| Platform surface | Feasibility | Note |
|---|---|---|
| W1 probe + W2 install/upgrade/pin | cheap | npm arm verbatim; pi --version; Node β₯22.19 engine (highest yet) |
| W3 provider push | cheap |
models.json + settings.json, both documented JSON; pi's api enum covers ALL THREE of our provider api kinds β first perfect 1:1 target |
| W4 redacted viewer | cheap | all-JSON config files; auth.json wholesale-redacted, key file wholesale |
| W7 native-sessions rail | cheap | plain JSONL file scan (no zstd, unlike dsh); header carries id/timestamp/cwd |
| C5 ACP chat | the cost center | pi has NO native ACP; needs a daemon-side ACPβpi-RPC bridge (new dialect class) or a young third-party adapter |
| Profile deploy (install adapter) | partial | skills + prompt-templates fit; MCP has NO declarative surface (extension-based), sub-agents/hooks are TS code |
| C3 inventory scan | partial | skills/templates scannable; no MCP arm |
| Hooks | n/a |
HOOK_SUPPORT['pi'] = null (extensions are TS modules, like hermes/codex class) |
Biggest structural risks: pre-1.0 churn (0.55.3 β 0.85.1 in ~4 months, one org/package rename already happened) and the chat bridge being ours to own.
- Canonical npm:
@earendil-works/pi-coding-agentβ 0.85.1 (published 12 days before this note), ~1.88M weekly downloads, MIT, repoearendil-works/pi(exbadlogic/pi-mono). - The original
@mariozechner/pi-coding-agentis DEPRECATED (last 0.73.1, ~4 months stale) with the author's pointer: "please use@earendil-works/pi-coding-agentinstead going forward". We must pin the earendil name and never the old one. - Monorepo packages in lockstep (
pi-ai,pi-agent-core,pi-tui, β¦); docs at pi.dev; enginenode >=22.19.0(registry manifest), binpi. - Movement: very active, pre-1.0, breaking changes land in minors. An onboarding wave must pin exact versions and expect follow-up repairs (same posture we already hold for dsh release candidates).
-
npm install -g @earendil-works/pi-coding-agentβ plain npm channel, no platform-binary postinstall, no native self-update command (upgrade =npm update -g), soHARNESS_PACKAGES['pi'] = '@earendil-works/pi-coding-agent'covers install/upgrade/pin uniformly, exactly like codex/opencode. -
pi --versionprints the version β feeds the standard W1 probe (bin: 'pi', PATH walk; no known-path fallback needed beyond PATH). -
Node β₯22.19 β above dsh's 22.15 floor and our Node 20 CI floor.
The rig daemon runs 22.23 (fine); older daemons get the dsh-style result
warningon install jobs ("pi needs Node β₯22.19").
Home = ~/.pi/agent/ (override: PI_CODING_AGENT_DIR). Everything the
platform would touch is JSON:
-
settings.json(global; project.pi/settings.jsonmerges recursively, arrays replace). Fields we care about:defaultProvider,defaultModel,defaultThinkingLevel,enabledModels(string[] β the model picker set, our W10/W13 switchable-list hook), plus resource arraysskills/prompts/extensions/packageswith glob +!exclusion++path/-pathsyntax (paths resolve against the settings file's dir). -
models.jsonβ custom/override providers. Schema: top-levelprovidersmap; each provider{ baseUrl, api, apiKey?, headers?, authHeader?, models?, modelOverrides? }; each model{ id, name?, api?, reasoning?, input?, contextWindow?, maxTokens?, cost?, compat? }.- Overriding a BUILT-IN provider (e.g.
anthropic) withbaseUrlonly keeps its built-in models and existing auth; addingmodelsMERGES byid(same id replaces, new ids append). This is a cleaner write target than opencode's provider map. -
apivalues:anthropic-messages,openai-completions,openai-responses,google-generative-ai, and more. Our three provider api kinds map 1:1 (anthropic,openai-chat,openai-responses) β pi is the first target supporting all three, soPROVIDER_API_SUPPORT['pi']can be the full set with no flavor filtering. -
apiKeyvalue resolution: literal,$VAR/${VAR}env interpolation, and!command(stdout becomes the value, resolved per request). That gives the opencode-style secret pattern without extension: raw key in a 0600 file +apiKey: "!cat ~/.pi/agent/harness-nexus.key"in models.json. (Escapes:$$,$!.) - Reloads whenever
/modelopens β no restart needed after a push.
- Overriding a BUILT-IN provider (e.g.
-
auth.jsonβ the user's own/login(OAuth/API-key) store. We NEVER write it (same decision as opencode'sauth.json); W4 redacts it wholesale. -
trust.jsonβ per-directory project-trust decisions (Β§6). No secrets; viewer can show it masked as ordinary JSON.
W3 writer sketch: merge-preserving writes of (a) models.json
providers["harness-nexus"] (or a baseUrl-only built-in override when the
user picks a built-in provider id) with our model rows, and (b)
settings.json defaultProvider/defaultModel (+ enabledModels =
unique([model, ...extras]), the dsh-style multi-model set). A
baseUrl-less re-apply removes ours, mirroring claude/opencode semantics.
JSON only β no TOML/YAML writers needed.
The docs tree (quickstart, usage, providers, settings, sessions, extensions, skills, prompt-templates, themes, packages, models, custom-provider, sdk, rpc, json, tui, session-format, β¦) contains no ACP page. Integration is via pi's own RPC mode:
-
pi --mode rpcβ LF-delimited JSONL over stdio (docs explicitly warn against Nodereadline: it splits on U+2028/U+2029, which are legal inside JSON strings β our hand-rolled line reader must split on\nonly). - Commands (stdin):
prompt(message, images,streamingBehavior: steer|followUp),steer,follow_up,abort,new_session,switch_session,set_model,cycle_model,get_available_models,set_thinking_level,get_state,get_messages,get_entries(history,sincecursor),get_commands(slash commands β would light up the W15 palette),get_session_stats,fork/clone,set_session_name,compact,bash/abort_bash. - Events (stdout):
agent_start/end/settled,turn_start/end,message_start/update/end(deltas:text_*,thinking_*,toolcall_*, assemble viacontentIndex;message_end.messageis authoritative),tool_execution_start/update/end(bytoolCallId),bash_execution_update,compaction_*,extension_error, and theextension_ui_requestβextension_ui_responsedialog sub-protocol (select/confirm/input/editor). - Launch flags:
--provider,--model provider/id[:thinking],--name,--session <id|path>,--no-session,--session-dir.
Options for the C5 row:
-
Write our own ACPβRPC bridge (recommended if we onboard). The
daemon already owns two dialect classes (standard ACP + dsh's divergent
dialect); this is a third β a
PiRpcConnectionspeaking pi's JSONL instead of ACP JSON-RPC, mapped onto the same semantic stream (message_updatedeltas β text/thinking chunks,tool_execution_*β tool cards,turn_endβ turn tail,get_available_models/set_modelβ W9 model selector,get_commandsβ W15 palette,get_entriesβ history). Comparable in size to the T1 dsh mapping work. -
Third-party adapter β
pi-acpon npm (user deepstereo, 0.0.33, ~1 month old; the svkozak/pi-acp lineage spawnspi --mode rpcand bridges ACP JSON-RPC over stdio; the victor-software-house fork embeds the pi SDK in-process). Also@frmhd/pi-sdk-acp-adapter(0.2.0). All pre-1.0, community, chasing a pre-1.0 core β fine as REFERENCE implementations for dialect shapes, risky as a hard dependency our chat path rides on. If we want a quick demand test before committing to option 1,pi-acpbehindHN_ACP_COMMAND_PIis the cheapest probe (the env override already exists for exactly this).
Permissions: none to map. pi's security model is full-user-permissions
by design β no per-tool allowlist, no approval request in RPC mode (the
only dialog-ish channel is extension_ui_request, extension-driven). Our
portal permission cards would simply never fire for pi; the honest UI
state is "no permission gating" (note it in the design doc; the
--tools/--exclude-tools CLI flags can constrain the tool set at spawn
if we ever want a locked-down mode).
- Storage:
~/.pi/agent/sessions/--<path>--/<timestamp>_<session-id>.jsonlwhere<path>is the cwd with/,\,:replaced by-(leading separator stripped). Session id = UUID by default (overridable). Plain JSONL β no compression, no multi-frame decode. - First line is a header:
{type:"session", version:3, id, timestamp, cwd, parentSession?}β id + creation time + cwd all right there (dsh needed per-entry digging; pi is cleaner).version1/2/3 with auto-migrate. - Entries:
id(8-char hex typical),parentId, ISOtimestamp, and typesmessage(roles system/user/assistant/toolResult/bashExecution/ custom/branchSummary/compactionSummary),model_change,thinking_level_change,session_info(nameβ the display title),label,compaction,branch_summary,custom. Tree-shaped (id/ parentId); the active leaf is the current position. - Listing = scan the sessions dir (per-cwd grouping falls out of the
folder name; alternatively parse header
cwd), title = lastsession_info.name(else untitled), mtime for recency. History for the resync replay = walk the active leaf chain ofmessageentries β ourHistoryItemuser/event shapes. - Resume:
pi --session <id|path>(partials accepted) at spawn, orswitch_sessionover RPC mid-connection. Both fit the W7 method-from-capability pattern; no adapter spawn needed for LISTING (pure file scan β the dsh arm, minus zstd).
- Tools run with the pi process's full user permissions; no built-in sandbox and no tool allowlist β upstream explicitly declines a partial in-process sandbox ("easy to misunderstand as a security boundary").
- The one gate is project trust: project-level resources
(
.pi/settings.json, extensions, skills, prompts, themes) load only after a trust decision (~/.pi/agent/trust.json, closest parent wins, globaldefaultProjectTrust: "ask" | "always" | "never").AGENTS.mdand bare.pidirs load regardless. -
RPC/-p/json modes never prompt: with no saved decision,
"ask"and"never"silently IGNORE protected project resources. Our chat daemon spawns--mode rpcin the session cwd β user-level resources always load, project.piresources may silently not. Honest rail/config note at implementation time (or pre-seedtrust.jsonfor the base workspace β decision for the design doc).
-
Skills = Agent Skills standard (agentskills.io):
SKILL.mdwith frontmattername(β€64, kebab-case) +description(β€1024, required), optionallicense/compatibility/metadata/allowed-tools/disable-model-invocation. pi tolerates name β directory name (unlike the strict standard). Progressive disclosure via system-prompt index + on-demand read; explicit/skill:nameinvocation. - Load locations: user
~/.pi/agent/skills/and~/.agents/skills/(the cross-harness standard dir β nice interop story), project.pi/skills/+.agents/skills/(trust-gated), plus settingsskillsarray and packages'skills/dirs. Our profile adapter writes~/.pi/agent/skills/β same shape as the opencode adapter. C3 scanner reads it back (SKILL.md present β skill; the dir-nameβ name wrinkle means the scanner should read frontmattername, not the folder). -
Prompt templates = pi's commands analog (user-invocable
/namebodies). Distributed via packages and a settingspromptsarray; exact user-level template directory not captured in this pass β verify at implementation (minor; adapter can also emit a settingspromptsentry). -
Extensions = TypeScript modules (tools, sub-agents, permission
gates, MCP clients, UI). NOT declarative β the platform cannot install
or scan them beyond listing; sub-agents and hooks ride here β both
SKIPPED by a deploy adapter (warning rows),
HOOK_SUPPORT['pi'] = null. -
MCP: no built-in declarative surface. No
mcpServerssettings key; MCP is added by community extensions reading anmcp.jsonconvention (e.g.pi-mcp-extension; upstream issue #563 tracks an official example). A profile's MCP entries therefore CANNOT be installed for pi the way they can for every other target β skip-with-warning is the only honest behavior today. Revisit if/when pi ships first-party MCP config. - pi packages (
pi install, npm/git sources into~/.pi/agent/npm|git) bundle skills/templates/extensions/themes β out of scope for us (we already have our own profile bundling), but the dirs are scanner-relevant provenance markers if we ever distinguish them.
Zero new platform concepts β the W12 opencode shape applies almost
verbatim: pi joins AgentTarget + RUNTIME_TARGETS + SCANNABLE_ TARGETS; HARNESS_PACKAGES['pi']; probe bin pi; PROVIDER_API_SUPPORT ['pi'] = ['anthropic', 'openai-chat', 'openai-responses'];
HOOK_SUPPORT['pi'] = null; TARGET_CONFIG_FILES['pi'] = settings.json,
models.json, auth.json (wholesale), key file (wholesale), trust.json.
Suggested slicing:
- Slice A (cheap, no chat): probe + jobs + W3 writer + W4 viewer + W7 sessions rail + C3 scanner + DEPLOYABLE adapter (skills/templates).
-
Slice B (the cost center): the
PiRpcConnectionchat dialect β delta/tool/turn mapping, history viaget_entries, model selector viaget_available_models/set_model, palette viaget_commands,--session/switch_sessionresume. Optionally preceded by a cheapHN_ACP_COMMAND_PI=pi-acpdemand probe on the rig.
Open items to verify at implementation time (NOT blockers): exact
user-level prompt-template directory; whether a home-level AGENTS.md
(applied globally) is read β pi's rules loading is cwd-ancestor based, so
rules likely deploy as project-level or get skipped like dsh's;
extension_ui_request surfacing (skip initially).
- pi.dev docs β installation, settings, custom-provider, models
(models.json), providers, sessions, session-format, skills, security,
rpc, packages; npm registry manifests for
@earendil-works/pi-coding-agent(0.85.1, engines β₯22.19.0) and the deprecated@mariozechner/pi-coding-agent(0.73.1). - Community ACP bridges:
pi-acp(npm, deepstereo, 0.0.33), svkozak/pi-acp, victor-software-house/pi-acp (SDK-embedding fork),@frmhd/pi-sdk-acp-adapterβ dialect references only. - Upstream issues: earendil-works/pi #563 (official MCP extension
example), #1900 (
PI_CODING_AGENT_DIRin examples).