Files
pi-agent-config/README.md
T
Kai fb1a1d2b10 feat(deploy): sync a scenario's backend/ into the live workspace root
deploy-scenario.sh now single-sources the application backend: it copies the git-tracked files under [scenario].backend into the workspace root (where .pi/ sits beside them), preserving modes and skipping caches/venvs. It overwrites but never prunes, and never restarts -- it prints a restart reminder when backend files change. README's Application code section updated to match.
2026-08-30 21:48:25 -07:00

8.1 KiB

pi-agent-config

Authoritative configuration for every Pi agent on this host: the user-level Pi runtime, plus one directory per dedicated agent scenario.

The live locations (~/.pi/agent, ~/pi-workspaces/*) are runtime copies. Changes are made here and pushed out with scripts/deploy-*.sh, never the other way round — except for scenarios explicitly marked deploy = "mirror", which record what a running service already does.

Verified against pi 0.84.3. Re-run the probes after every pi update.

Gitea repository and push authentication

Everything here — the runtime config, every scenario's agent config, and (for scenarios that run a service) the application backend — lives in a single Gitea repository: kai/pi-agent-config on the server at 192.168.50.45 (web UI on :3000, SSH on :222). There is no per-agent repository; a Pi agent is a directory under scenarios/, not a separate repo.

Push and pull over SSH key auth only (no HTTP token). The Git user is git, not kai. ~/.ssh/config:

Host gitea-45
    HostName 192.168.50.45
    User git
    Port 222
    IdentityFile ~/.ssh/id_ed25519_gitea
    IdentitiesOnly yes

The remote is origin -> gitea-45:kai/pi-agent-config.git (branch main). Run Git from the repository root, never from a scenario subdirectory that might carry a different remote:

git pull --rebase origin main
git push origin main

Verify access:

ssh -T gitea-45                             # "Hi there, kai! ... authenticated"
git ls-remote gitea-45:kai/pi-agent-config.git
  • The key file must be 0600; never read or print its contents.
  • gitea-pve, if present in your SSH config, is a host login — not a Git remote.
  • The verify-no-secrets.sh pre-commit hook must pass; never use --no-verify.

Scenarios

Scenario Purpose Service Status
curator Book / film / TV / music curation agent curator.service deployed; agent config and application backend both tracked here
memo-inbox Routes Telegram/WeChat messages to Calendar, Obsidian todo or journal pi-memo-telegram.service mirror of live config, zero behaviour change
pi-grok Interactive Grok 4.6 coding agent none (manual) registered only

Application code

A scenario that runs its own service keeps that service's source under scenarios/<name>/backend/ — for curator, the Python package, tests, systemd units and config templates. It is the single source of truth; the live checkout at the scenario's workspace path is a runtime copy.

deploy-scenario.sh installs workspace/ (the .pi config), vendors shared extensions, renders the launch contract, and syncs backend/ into the workspace root — copying only git-tracked files (build caches and virtualenvs never leak) and preserving file modes. It never prunes files the repo no longer tracks, never builds a venv, and never restarts the service: it prints a reminder instead, since restarting decides when to interrupt a live conversation.

Start here

Document Contents
docs/pi-runtime-notes.md pi 0.84.3 mechanics that the official docs understate, each verified against source or a live probe
docs/isolation-baseline.md The four isolation layers and the flag set every scenario should use
docs/personality-layering.md How to choose between SYSTEM.md, APPEND_SYSTEM.md, AGENTS.md, SKILL.md and the request
docs/gateway-patterns.md Ten patterns for hosting an agent behind a long-running service
docs/plans/ Active work plans

The finding that motivated this repository

pi emits the <available_skills> block only when a tool named read is active (dist/core/system-prompt.js:59,113). The Curator service ran with --no-tools --skill … for its entire lifetime, so:

  • its 64-line media policy never reached the model,
  • --skill was a no-op,
  • and both the README and the deployment runbook described a mechanism that was not happening.

Measured on the real workspace, before and after (evidence):

before after
system prompt 2548 chars 3539 chars
expert coding assistant framing present gone
pointer to pi's own documentation present gone
the 64-line media policy absent present
<available_skills> absent absent
workspace AGENTS.md loaded blocked
parent-directory AGENTS.md leaked blocked

The prompt got larger, and that is the fix rather than its cost: of the original 2548 characters roughly 1.9 KB was pi's coding-assistant scaffolding and a pointer telling the agent to read pi's documentation and follow its cross-references — noise for a media agent, and a ready-made escalation path for injected text — while none of it was Curator's own policy. All 3539 characters now are.

A mechanism demonstration with a stub SYSTEM.md is in docs/evidence/2026-08-27-isolation-probe.md; its 960-character figure measures the stub, not Curator.

Reproduce either with docs/evidence/probe-harness/ — zero model tokens, because a single RPC get_state starts the agent, fires session_start and exits without contacting the provider.

Layout

docs/            mechanics, baselines, plans, reproducible evidence
runtime/agent/   ~/.pi/agent — settings, models.json.template, extensions, prompts
shared/          cross-scenario code
  lib/py/pi_rpc.py            long-lived RPC client with rotation and budgets
  extensions/pi-guard-base.ts path containment, restricted read, capability guard
scenarios/<name>/
  profile.toml   single source of truth for the launch contract
  workspace/     what gets installed into the live workspace
  eval/          recorded golden transcripts
  backend/       application/service code, when the scenario runs its own service
scripts/         diff, deploy, backup, restore, secret guard
secrets/         host-local, untracked

Usage

# What differs between this repository and the host?
scripts/pi-diff.sh
scripts/pi-diff.sh curator

# Push configuration out (dry run first; neither restarts a service)
scripts/deploy-runtime.sh
scripts/deploy-runtime.sh --apply
scripts/deploy-scenario.sh curator --apply

# Back up the live installation (archives land outside this repository)
scripts/pi-backup.sh
scripts/pi-restore.sh --from <backup dir>

# Verify the mechanics still hold after a pi upgrade
docs/evidence/probe-harness/collect-evidence.sh
shared/extensions/tests/run-guard-checks.sh
python3 shared/lib/py/tests/test_pi_rpc_smoke.py

Secrets

Nothing real ever enters this repository. models.json, auth.json, trust.json and every *.env are ignored; secrets/ accepts only .gitkeep, README.md, *.example and *.template.

scripts/verify-no-secrets.sh enforces this as a pre-commit hook.

Because a scenario's backend/ now holds real source, the credential-assignment heuristic requires the value to carry entropy (a digit or uppercase letter): snake_case identifiers such as token=extraction_token are source, not secrets, while base64/hex/random keys still trip it.

Install it in a fresh clone:

ln -sf ../../scripts/verify-no-secrets.sh .git/hooks/pre-commit
scripts/verify-no-secrets.sh --all

Backups contain plaintext credentials and are written outside the work tree; pi-backup.sh refuses any destination that resolves inside it.

First-time setup on a new host

git clone gitea-45:kai/pi-agent-config.git && cd pi-agent-config
ln -sf ../../scripts/verify-no-secrets.sh .git/hooks/pre-commit

cp secrets/zenmux.env.example secrets/zenmux.env
chmod 600 secrets/zenmux.env && $EDITOR secrets/zenmux.env

npm install -g @earendil-works/pi-coding-agent@0.84.3
scripts/deploy-runtime.sh --apply
scripts/deploy-scenario.sh <scenario> --apply