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.
199 lines
8.1 KiB
Markdown
199 lines
8.1 KiB
Markdown
# 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/<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`](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 `<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](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 |
|
|
| `<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`](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/<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
|
|
|
|
```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 <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:
|
|
|
|
```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 <scenario> --apply
|
|
```
|