# pi-agent-config Authoritative configuration for every [Pi](https://pi.dev) 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`: ```bash 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: ```bash git pull --rebase origin main git push origin main ``` Verify access: ```bash 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`](scenarios/curator/) | Book / film / TV / music curation agent | `curator.service` | **deployed**; agent config and application backend both tracked here | | [`memo-inbox`](scenarios/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`](scenarios/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//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 only `workspace/` (the `.pi` config) and renders the launch contract; it does not touch `backend/`, build a venv or restart a service. Rolling application code out to the live checkout and restarting the unit stays an operator step, deliberately: restarting decides when to interrupt a live conversation. ## Start here | Document | Contents | |---|---| | [`docs/pi-runtime-notes.md`](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`](docs/isolation-baseline.md) | The four isolation layers and the flag set every scenario should use | | [`docs/personality-layering.md`](docs/personality-layering.md) | How to choose between `SYSTEM.md`, `APPEND_SYSTEM.md`, `AGENTS.md`, `SKILL.md` and the request | | [`docs/gateway-patterns.md`](docs/gateway-patterns.md) | Ten patterns for hosting an agent behind a long-running service | | [`docs/plans/`](docs/plans/) | Active work plans | ## The finding that motivated this repository pi emits the `` 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](docs/evidence/2026-08-27-curator-phase0-prompt.md)): | | 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 | | `` | 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`](docs/evidence/2026-08-27-isolation-probe.md); its 960-character figure measures the stub, not Curator. Reproduce either with [`docs/evidence/probe-harness/`](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// 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 ```bash # 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 # 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: ```bash 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 ```bash 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 --apply ```