2026-09-19 Declarative Pi Packages and MCP Integration
What I set out to do
Add declarative management for Pi (@earendil-works/pi-coding-agent) packages and extensions in Nix/Home Manager, enabling Model Context Protocol (MCP) servers via pi-mcp-adapter to match the other managed coding agents (Claude Code, Codex, and Gemini).
What I actually did
- Investigated Pi configuration model: Pi reads installed packages from
~/.pi/agent/settings.jsonunder"packages": [...]. On startup, missing packages declared in settings are automatically fetched and installed by Pi into~/.pi/agent/npm/. - Evaluated MCP discovery: Checked
pi-mcp-adapter’s discovery hierarchy. The adapter natively searches standard user-global MCP files before Pi-specific overrides:~/.config/mcp/mcp.json(User-global standard MCP)~/.pi/agent/mcp.json(Pi global override) Home Manager’sprograms.mcp.enable = trueincoding-agents/common/mcp.nixalready deploys all shared MCP servers (acl-anthology,arxiv,jupyter,litellm,obsidian,playwright,signoz) directly to~/.config/mcp/mcp.json.
- Implemented module options in
pi.nix:programs.pi.enable(defaults to true)programs.pi.packages(list of package strings, e.g."npm:pi-mcp-adapter")programs.pi.extensions(mapping of local TypeScript extension paths to link into~/.pi/agent/extensions/)programs.pi.enableMcpIntegration(defaults toconfig.programs.mcp.enable, auto-injecting"npm:pi-mcp-adapter")
- Managed
settings.jsonviahome.mutableFile: Used RFC 7396 JSON merge patch to injectpackages = lib.unique cfg.packages;into~/.pi/agent/settings.json. Preserves Pi runtime keys (theme,lastChangelogVersion,defaultProvider,defaultModel). - Documented Managed Components in
AGENTS.md: Added Pi module tracking under Managed Components.
Verification
just check: all pre-commit hooks, linters,nixpkgs-fmt, and formatting passed cleanly.home-manager buildandhm switch: generation built and activated without collision or error.- Verified
~/.pi/agent/settings.jsonmerged properly with"packages": [ "npm:pi-mcp-adapter" ]. - Live test via
pi -p: verifiedpi-mcp-adapterdiscovers and queries tools across both HTTP servers (obsidian, 18 tools) and stdio servers (arxiv, 16 tools).
Commits
7b718c5feat(pi): manage pi packages, extensions, and mcp integration in nixbb99c06refactor(pi): extend upstream programs.pi-coding-agent module5518243feat(pi): manage skills and wire portable skills in nixf3d338dfeat(pi): configure context, prompt templates, and subagents809d1cafeat(pi): enable automode guardrail via @czottmann/pi-automode3eb38d3feat(pi): configure automode to start enabled by default with in-tree allow rules8fd71bffeat(pi): configure OpenTelemetry GenAI tracing via pi-otel
Follow-up: Extending Upstream programs.pi-coding-agent
Rather than maintaining a standalone programs.pi option namespace, we refactored pi.nix to extend Home Manager’s upstream programs.pi-coding-agent module directly:
- Upstream integration:
- Extended
options.programs.pi-coding-agentwithpackages,extensions, andenableMcpIntegration. - Enabled
programs.pi-coding-agentby default in the desktop profile (lib.mkDefault true), which automatically installspkgs.pi-coding-agent(0.85.1) wrapped withpkgs.nodejsinextraPackagesinto Tier 1 Home Manager packages.
- Extended
- Mutable settings coordination:
- Upstream generates a read-only Nix store symlink for
settings.jsonwhenevercfg.settings != { }. Pi requires this file to be mutable for tracking runtime state (such aslastChangelogVersionandtheme). - Force-disabled upstream’s symlink (
home.file."${cfg.configDir}/settings.json".enable = lib.mkForce false;). - Managed
settings.jsonthroughhome.mutableFileusing an RFC 7396 merge patch to unifycfg.settingsandpackages = lib.unique (cfg.packages ++ (cfg.settings.packages or [ ])).
- Upstream generates a read-only Nix store symlink for
- Verification:
just checkpassed cleanly.hm switchbuilt and activated successfully.- Verified
/Users/achhina/.nix-profile/bin/piis on PATH and runs version 0.85.1. - Ran live verification query with
pi -pconfirming all 18 Obsidian tools are discovered viapi-mcp-adapter.
Follow-up: Declarative Agent Skills Integration
Pi natively implements the Agent Skills standard. We added declarative skill management to pi.nix:
- Skill Option: Added
programs.pi-coding-agent.skills(attrsOf path). - Shared Portable Skills: Defaulted
skillstocommonLib.portableSkillsfromcoding-agents/common/lib.nix, validating each directory withcommonLib.validateSkillDir. - Deployment: Symlinked each declared skill into
${cfg.configDir}/skills/<name>(~/.pi/agent/skills/). - Slash Commands: Enabled
"enableSkillCommands": trueinsettings.jsonso interactive/skill:<name>invocations work directly. - Verification:
- Checked
~/.pi/agent/skills/contains all 10 portable skills (commit-message,conflicts,deep-research,mermaid,python,python-configuration,python-observability,python-testing,session-analysis,update-docs). - Queried
pi -plive, verifying that Pi discovers and presents all 10 skills and their descriptions. - Ran
just checkand committed atomically.
- Checked
Follow-up: Global Context, Prompt Templates, and Autonomous Subagents
Added complete agent workflows to pi.nix:
- Global Context (
AGENTS.md):- Configured
programs.pi-coding-agent.context = ../common/context/AGENTS.md;to deploy shared rules (communication style, TDD rules, Obsidian capture workflow) directly to~/.pi/agent/AGENTS.md.
- Configured
- Prompt Templates (
~/.pi/agent/prompts/):- Added
programs.pi-coding-agent.promptsoption, defaulting to the 5 shared agents incoding-agents/common/agents/(comment-remover,commit,github-automation,python-reviewer,template-filler). - Symlinked into
~/.pi/agent/prompts/<name>.md, providing autocomplete/nameslash commands in the Pi interactive editor with argument expansion.
- Added
- Autonomous Subagents (
@tintinweb/pi-subagents):- Added
programs.pi-coding-agent.enableSubagents(defaulting to true), auto-registeringnpm:@tintinweb/pi-subagentsunder packages. - Added
programs.pi-coding-agent.agentsoption, symlinking the 5 shared agent definitions into~/.pi/agent/agents/<name>.md. - Enables the
Agenttool in Pi for autonomous child-process delegation with isolated context and custom agent types (commit,python-reviewer, etc.).
- Added
- Verification:
- Verified
~/.pi/agent/AGENTS.mdsymlink exists and points to store path. - Verified all 5 prompt templates in
~/.pi/agent/prompts/and all 5 agent definitions in~/.pi/agent/agents/. - Tested live via
pi -p, confirming that prompt templates, subagent tools (Agent,SubagentWorkflow,get_subagent_result,steer_subagent), skills, and MCP tools are all loaded and active. - Ran
just checkand committed atomically.
- Verified
Follow-up: Declarative Auto Mode Guardrails
Integrated Claude Code-style auto mode guardrails into Pi via @czottmann/pi-automode:
- Module Option: Added
programs.pi-coding-agent.enableAutoMode(bool, defaulttrue) topi.nix. - Package Registration: Injected
npm:@czottmann/pi-automodeintoprograms.pi-coding-agent.packages, ensuring declarative propagation into~/.pi/agent/settings.jsonvia RFC 7396 merge patching. - Capabilities:
- Deterministic AST command checking via
unbashto block destructive root operations and privilege escalations. - Dual-stage classification (fast 1-token filter + detailed JSON review) for file writes and shell execution.
- Persistent status line in the TUI (
AM●) tracking allowed vs. denied tool invocations. - Slash commands for runtime control:
/automode status,/automode on,/automode off,/automode reload, and/automode defaults.
- Deterministic AST command checking via
- Declarative Default Configuration:
- Added
programs.pi-coding-agent.autoModeSettingsoption inpi.nix. - Deployed
~/.pi/agent/extensions/pi-automode/config.jsonviahome.mutableFilewithenabled = trueandallowInsideWorkingDirectory = true. - Injected local project development permissions into
autoMode.allowso local edits, test runs, and builds run autonomously without unnecessary classification blocks, while preserving hard boundaries against system files, credentials, and sensitive configurations.
- Added
- Verification:
just checkpassed cleanly.hm switchapplied without conflict, creating~/.pi/agent/extensions/pi-automode/config.json.- Verified that Pi starts with auto mode enabled by default (
AM●). - Committed atomically (
809d1ca,3eb38d3).
Follow-up: Declarative OpenTelemetry GenAI Tracing via pi-otel
Integrated OpenTelemetry GenAI semantic conventions tracing into Pi via pi-otel:
- Central Telemetry Option: Added
local.telemetry.pi.enable(bool, defaultcfg.enable) intelemetry.nix. - Module Integration in
pi.nix:- Added
programs.pi-coding-agent.enableTelemetrydefaulting toconfig.local.telemetry.active && config.local.telemetry.pi.enable. - Added
programs.pi-coding-agent.otelSettingsto govern theotelconfiguration block. - Injected
npm:pi-otelintoprograms.pi-coding-agent.packages. - Configured
settings.json.otelvia RFC 7396 merge patch:endpoint = config.local.telemetry.endpoint;(pointing to local SigNoz collector athttp://localhost:4317)protocol = "grpc";serviceName = "pi";spanNaming = "genai";(activates OTel GenAI agent conventions:invoke_agent pi,chat {model},execute_tool {tool})captureContent: followsconfig.local.telemetry.logContent("full"vs"metadata_only")propagateToShell = true;(injectsTRACEPARENTinto bash child processes)signals = { traces = true; metrics = true; logs = false; };
- Added
- Verification:
just checkpassed cleanly.hm switchapplied without collision, updating~/.pi/agent/settings.jsonwith theotelconfiguration and package.- Pi auto-downloaded and installed
pi-otel(85 packages added). - Updated Managed Components in
AGENTS.mdto record OTel tracking. - Committed atomically (
8fd71bf). - Verified live telemetry stream in SigNoz ClickHouse (
signoz_traces.signoz_index_v3): 48 spans recorded underserviceName = 'pi', including top-levelinvoke_agent pi, conversational turnspi.turn, model completionschat openrouter/z-ai/glm-5.3-flashandchat openrouter/moonshotai/kimi-k3, and tool executions (execute_tool bash,execute_tool read,execute_tool mcp,execute_tool automode_inspect).
Follow-up: Structured Model Slug Parser and Catalog Filtering for LiteLLM
Implemented declarative model slug resolution and catalog filtering directly at the LiteLLM gateway layer:
-
Problem:
- Clients (Open WebUI, Pi, Codex, curl) had to specify full provider paths (e.g.
openrouter/z-ai/glm-5.3-flash). - Pi automode’s turn-level bypass sent bare slugs (
z-ai/glm-5.3-flash) directly to LiteLLM, resulting in “no healthy deployments”. - Model presets (
@preset/...) caused cost calculation failures because LiteLLM’s internal cost map lacked keys with preset suffixes. - The
/v1/modelsendpoint exposed hundreds of internal permutation variants (:batch,:nitro,:floor).
- Clients (Open WebUI, Pi, Codex, curl) had to specify full provider paths (e.g.
-
Architecture and Implementation:
- Grammar Decomposition: Built
litellm_model_resolver.pyparsing incoming slugs across[broker/][author/]model[:modifier][@preset/preset]. - Candidate Matching and Deterministic Ranking: Resolves flat slugs (
glm-5.3-flash) and author paths (z-ai/glm-5.3-flash) to canonical wire paths (openrouter/z-ai/glm-5.3-flash). Disambiguates duplicate family names using canonical author heuristics (e.g.meta-llamaforllama-*,z-aiforglm-*) and optional environment pins. - Preset Support: Strips
@preset/...for pricing lookups while re-attaching it for OpenRouter wire calls. Automatically mirrors base cost entries inlitellm.model_costand stampsopenrouter_presetintorequester_metadatafor SigNoz OTel spans. - Catalog Filtering: Wraps
/v1/modelsand/modelsviaLITELLM_WORKER_STARTUP_HOOKS. Projects clean, flat model IDs withowned_byset to author. Preserves:freevariants while pruning:batch,:nitro,:floor, and@preset/*permutations. - Catalog Listener: Added
register_catalog_listenertolitellm_openrouter_enrichment.pyso live 5-minute OpenRouter updates propagate to the resolver index. - Pi Extension Simplification: Removed turn-level rewrite in
litellm-route.ts, allowing Pi to send canonical or bare slugs transparently.
- Grammar Decomposition: Built
-
Verification:
- Unit test suite: 22/22 unit tests passing in
test_litellm_model_resolver.py. - Enrichment suite: 28/28 tests passing in
test_litellm_openrouter_enrichment.py. - Pre-commit and linters:
just checkpassed cleanly across all hooks (ruff, ruff-format, shellcheck, statix, nixpkgs-fmt). - Nix evaluation:
homeConfigurations."aarch64-darwin"evaluated cleanly. - Committed atomically (
110afde).
- Unit test suite: 22/22 unit tests passing in
Catalog Middleware Update for FastAPI and Starlette
In live testing, mutating route.endpoint in app.routes did not intercept /v1/models because Starlette compiles route.app = request_response(route.endpoint) at router construction time, and FastAPI wraps routes inside fastapi.routing._IncludedRouter.
- Resolution:
- Implemented
CatalogFilterMiddlewareas a standard ASGI middleware on LiteLLM’s FastAPIapp. - Appended to
app.user_middlewareand invalidatedapp.middleware_stack = Noneinsidelitellm_model_resolver:init, triggering a clean rebuild of Starlette’s middleware pipeline. - Intercepts GET requests to
/v1/modelsand/models, filters the response JSON viafilter_catalog_models, and updatesContent-Length.
- Implemented
- Verification:
- Added unit tests in
test_litellm_model_resolver.pycovering ASGI message interception, passthrough of non-matching paths/methods, non-JSON responses, and middleware registration idempotency (25/25 tests passing). just checkpassed cleanly across all linters and hooks.- Deployed via
hm switch --override-input media-stack /Users/achhina/projects/github.com/achhina/media-stack. - Verified live
/v1/modelsreturns clean flat IDs (glm-5.3-flashowned byz-ai), retains:freevariants (qwen3.8-27b:free,deepseek-v4-flash-0731:free), and excludes unwanted modifiers. - Verified live completions with bare
glm-5.3-flashand author-qualifiedz-ai/glm-5.3-flashreturn 200 OK. - Verified
@preset/...routes directly to upstream OpenRouter.
- Added unit tests in