Skip to content

Repository files navigation

agent-pi

CI

A containerized DevOps agent built on the Pi harness (@earendil-works/pi-coding-agent). It operates and maintains self-describing services — starting with the sibling llm-router: inspecting its REST API and hosted Agent Skill, evaluating metrics and logs, and (with human approval) acting on Docker lifecycle and configuration.

📄 Want the "why it's safe" story, for a non-technical audience? Read What an auditable AI agent looks like — control, traceability, and measured proof, with the eval numbers.

Why Pi

Pi is a minimal agent harness with four core tools (read/write/edit/bash) and no built-in permission system — its own guidance for autonomous use is to "deploy in a container and build a custom approval extension for side-effecting tools." That is exactly this project:

  • The container is the security boundary (non-root, no raw docker.sock).
  • A maker-checker approval gate (.pi/extensions/20-approval.ts) blocks every mutating tool until a human approves, and audits every decision as JSONL.
  • Docker access is scoped through a docker-socket-proxy allowlist.
  • Pi's own brain is independent of the router it manages (local llama.cpp), so it keeps working during a router outage; the router /v1 is only a fallback.

Architecture

llm-router-net (existing)
├─ llm-router  (managed service)   :8000  /system/*, /admin/*, /v1
├─ prometheus                      :9090  time-series metrics (PromQL)
├─ docker-guard                    :2375  deny-by-default boundary filter
│    └─(docker-control, internal)→ docker-socket-proxy → /var/run/docker.sock:ro
└─ agent-pi (node, non-root)
     • Pi harness + project extensions (.pi/extensions)
     • reasons with local llama.cpp (independent of the router)
     DOCKER_HOST=tcp://docker-guard:2375   (never mounts the socket; cannot
     reach the proxy directly — the guard is the only Docker path)

docker-guard (src/docker/guard.ts, property-tested) allows ONLY the five real DockerClient calls on allow-listed names — create/DELETE/exec/kill and non-allow-listed names are refused at the boundary (Claim A; see docs/THREAT_MODEL.md). Verify it live: scripts/verify-boundary.sh (guard mode).

Service registry

agent-pi manages N self-describing services, one descriptor per service in services/<id>.yaml (identity, discovery paths, admin/auth, metrics, per-service policy, allowlist, autonomy, skill). The watcher runs one monitor per enabled service; each learns its thresholds/log-patterns from its own manifest. See fixtures/dummy-service/ for a generic Tier-2 example.

Extensions

File Role
.pi/extensions/00-provider.ts Registers the local llama.cpp brain + router fallback.
.pi/extensions/10-devops-tools.ts Read-only, service-scoped tools: service_health, service_manifest, service_metrics, service_capabilities, service_skill (digest-verified).
.pi/extensions/30-docker-tools.ts docker_ps + docker_start/stop/restart (queued for approval, per-service allowlist).
.pi/extensions/40-router-admin.ts llm-router config (Tier 3): guardrail/model toggles, reload, rollback.
.pi/extensions/20-approval.ts Maker-checker gate for mutating built-ins + JSONL audit.

Supporting libraries live in src/ (registry/, discovery/service.ts, monitor.ts, notify/, audit.ts); Pi-side behavior in policy/*.yaml.

Quick start (local, without container)

Requires Node 22 and a local llama.cpp OpenAI-compatible endpoint on :8888.

pnpm install --ignore-scripts

LLAMACPP_BASE_URL=http://localhost:8888/v1 \
ROUTER_URL=http://localhost:8000 \
OPENAI_API_KEY=sk-local \
PI_OFFLINE=1 \
node_modules/.bin/pi -a --offline --model llamacpp/Qwen3.5-9B-local \
  -p "Call service_health for llm-router and summarize whether it is healthy."

-a trusts the project-local .pi/ files for the run; --model provider/id selects the local brain.

Run in the container

cp .env.example .env          # adjust model / URLs / allowlist
docker compose up -d          # starts agent-pi + docker-socket-proxy
docker exec -it agent-pi pi -a --model llamacpp/Qwen3.5-9B-local \
  -p "check the llm-router health"

agent-pi joins the existing llm-router-net, so it reaches llm-router:8000 and prometheus:9090 by name. It never mounts /var/run/docker.sock.

Safety model

  • Read-only tools run autonomously; every mutating action requires approval.
  • Headless (no attached UI) → mutations are blocked by default.
  • Docker access goes through docker-guard (deny-by-default: only start/stop/ restart + inspect on ALLOWED_CONTAINERS), which fronts the internal-only docker-socket-proxy. The container-name + verb allowlist is a boundary control, not just agent code.
  • All proposed/approved/denied/executed actions are appended to logs/audit.jsonl.

Status

Phase M0 (scaffold + read-only boot) is implemented and validated. See BACKLOG.md for the phased roadmap (observe → lifecycle → config → bounded autonomy).

About

Containerized Pi (pi.dev) DevOps agent that manages self-describing services (llm-router): inspection, metrics/logs, and approval-gated lifecycle & config.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages