Files
pi-agent-config/docs/isolation-baseline.md
T
Kai 07dd648b5f feat: scenarios for curator/memo-inbox/pi-grok, deploy and backup tooling
Scenarios
- memo-inbox: mirrored by copying; the live directory was not moved or modified
  and the service was not restarted. All four tracked files match byte for byte
  (pi-diff.sh reports SAME). Marked deploy = "mirror" so deploy-scenario.sh
  refuses --apply: applying a mirror would invert the direction of truth and
  could change a service in daily use.
- curator: target configuration, not yet deployed. .pi/SYSTEM.md replaces pi's
  coding-assistant prompt; durable role text is in .pi/APPEND_SYSTEM.md;
  profile.toml is the single source of truth for the launch contract.
- pi-grok: registered only. It is genuinely a coding agent, so the isolation
  baseline does not apply in full.

Corrections to the documentation, found by testing rather than by reading
- AGENTS.override.md does NOT block parent-directory context files; it only
  shadows its own directory. Verified: with an override file in the workspace, a
  marker in /tmp/AGENTS.md still reached the system prompt. The only effective
  switch is --no-context-files, so durable role text must live in
  .pi/APPEND_SYSTEM.md, which is a system-prompt file and unaffected by -nc.
  Verified end state: no coding-assistant framing, no pi-docs block, own
  identity and role text present, no parent pollution, only own skills/tools.
- PI_CODING_AGENT_DIR isolates settings/models/auth/trust/extensions/skills/
  prompts/themes under the agent directory -- stronger than the --no-* flags
  because it also repoints credentials -- but does NOT cover ~/.agents/skills.
  Measured: find-skills, modsearch and summarize still leak. So it complements
  --no-skills rather than replacing it.
- --append-system-prompt accepts a file path, which pi-grok relies on.
- cwd is what anchors .pi discovery: a probe that forgot cwd silently lost
  .pi/SYSTEM.md and kept the coding-assistant persona.

Tooling (all dry-run by default; none of them restarts a service)
- pi-diff.sh: compares tracked config against the live install in both
  directions, with a key-redacted comparison for models.json
- deploy-scenario.sh: installs a workspace and renders profile.toml into
  .pi/launch.json, then checks that every referenced path exists
- deploy-runtime.sh: renders models.json from its template, refusing placeholder
  or missing keys. Verified byte-identical to the live file
- pi-backup.sh / pi-restore.sh: archives outside the repo, sha256 manifest
  verified before any restore, live paths preserved rather than overwritten

Fixed while testing: pi-backup.sh compared the destination against the repo root
literally, so a relative --dest ./backups wrote credential archives into the work
tree. Now canonicalised with realpath; ./backups, an absolute in-repo path and
./docs/../backups are all refused.
2026-08-26 23:17:12 -07:00

8.4 KiB

Isolation Baseline for a Dedicated Pi Agent

Applies to every scenario in scenarios/. Mechanisms verified against pi 0.84.3 — see pi-runtime-notes.md for the source quotes and evidence/ for reproduction.

A "dedicated agent" is not achieved by narrowing what the model is told. It is achieved by narrowing what the process loads and what it can call. Pi offers four independent layers. Use all four; each one covers a different failure mode.


The four layers

┌──────────────────────────────────────────────────────────────┐
│ L1 Loading      what enters the process at all               │
│   --no-extensions -e / --no-skills --skill /                 │
│   --no-prompt-templates / --no-themes / -nc / project trust  │
├──────────────────────────────────────────────────────────────┤
│ L2 Personality  who the agent is                             │
│   .pi/SYSTEM.md (replace) · .pi/APPEND_SYSTEM.md · -nc        │
│   AGENTS.md only if parent-dir layering is acceptable          │
├──────────────────────────────────────────────────────────────┤
│ L3 Capability   which tools exist and are active             │
│   --no-builtin-tools · --tools · --exclude-tools              │
│   pi.setActiveTools() · same-name registerTool override       │
├──────────────────────────────────────────────────────────────┤
│ L4 Invocation   per-call authorization                       │
│   pi.on("tool_call") -> { block: true, reason }               │
└──────────────────────────────────────────────────────────────┘

Layers 3 and 4 must be enforced inside the extension, not only on the command line, so that a flag mistake in a systemd unit cannot silently widen the agent's reach.


Baseline flag set

pi --mode rpc \
   --session-id   "<scenario>-<subject>" \
   --session-dir  "<dedicated session dir>" \
   --no-builtin-tools \
   --no-extensions        -e     "<abs>/scenario-tools.ts" \
   --no-skills            --skill "<abs>/.pi/skills/<name>" \
   --no-prompt-templates \
   --no-themes \
   --approve \
   --provider "<provider>" --model "<model>" --thinking "<level>" \
   --name "<display name>"

Launched with:

Property Value Reason
cwd the scenario workspace bounds AGENTS.md and .pi discovery
env explicit minimal allowlist keeps backend API keys out of the node process
start_new_session=True yes lets the parent kill the whole process group
timeout handling killpg, not kill pi is a node CLI and may leave children

Why each flag

Flag Failure mode it prevents
--no-extensions + -e <abs> ~/.pi/agent/extensions/*.ts (currently herdr-agent-state.ts, pi-memo-trust.ts) being injected into an unrelated agent. The absolute -e path also loads before project trust resolves, so the extension works without trusting the directory.
--no-skills + --skill <abs> ~/.agents/skills/* leaking in. Probed leak: ["find-skills","modsearch","summarize"]find-skills actively instructs the agent to install more skills.
--no-prompt-templates ~/.pi/agent/prompts/*.md becoming callable slash commands.
--no-themes pointless discovery I/O and a source of run-to-run variation.
--no-builtin-tools bash/edit/write/grep/find/ls being active. Chosen over --tools because it imposes no registry-level allowlist, so an extension may register tools dynamically. Built-ins stay registered-but-inactive; the extension governs the active set.
--approve required to load .pi/SYSTEM.md and .pi/settings.json. Combined with --no-skills it does not re-open skill auto-discovery.
--session-id + --session-dir sessions colliding with the user's interactive history.

Do not use --no-tools

It disables extension tools as well, which removes read, which removes the skills block from the system prompt (see pi-runtime-notes.md §1). Any SKILL.md then becomes dead weight. This was a real, long-lived defect in the Curator scenario.


Required extension responsibilities

A scenario extension must do all five:

const ALLOWED_TOOLS = ["read", /* … scenario tools … */];

export default function scenarioGuard(pi: ExtensionAPI) {
  // 1. path-restricted `read` override — shadows the built-in by name
  pi.registerTool({ name: "read", /* allowlist-checked implementation */ });

  // 2. scenario tools, each with promptSnippet so they appear in the prose list
  pi.registerTool({ name: "…", promptSnippet: "…", promptGuidelines: [ "…" ] });

  // 3. declarative active set, reasserted after resource discovery
  const restrict = () => pi.setActiveTools(ALLOWED_TOOLS);
  pi.on("session_start", async () => restrict());
  pi.on("resources_discover", async () => restrict());

  // 4. per-call gate — defence in depth against layer-3 mistakes
  pi.on("tool_call", async (event) => {
    if (!ALLOWED_TOOLS.includes(event.toolName)) {
      return { block: true, reason: `<scenario> blocks tool: ${event.toolName}` };
    }
  });

  // 5. every tool truncates its own output (50 KB / 2000 line caps)
}

The read override must still permit the skill directory, otherwise skill bodies cannot be loaded (progressive disclosure, pi-runtime-notes.md §2).


Measured effect

Same workspace, same extension, only flags differ:

Configuration system prompt active tools skills visible coding-assistant persona pi-docs paths
--no-tools (Curator, as found) 1859 (none) no yes yes
--no-builtin-tools, no isolation 3413 own + read 3 foreign yes yes
+ --no-skills --skill 2619 own + read 1, correct yes yes
+ .pi/SYSTEM.md --approve 960 own + read 1, correct no no

Full baseline is 72 % smaller than the unisolated default and strictly more capable, because the skill is finally reachable.


Residual risk

  1. No kernel boundary. Pi ships no sandbox; extensions run with the process's full permissions. The read override is application-level. A future step is bubblewrap (examples/extensions/sandbox/) or a container.
  2. env discipline is the only secret boundary. Without an explicit env= allowlist the node process inherits every *_API_KEY in the unit file.
  3. Parent-directory context files. Nothing prevents a future ~/AGENTS.md from layering into every scenario, and AGENTS.override.md does not prevent it -- it only shadows its own directory (verified: a marker in /tmp/AGENTS.md still reached the prompt). The only effective switch is -nc, which means durable role text must live in .pi/APPEND_SYSTEM.md rather than AGENTS.md.
  4. Workspace should be read-only to the service. --approve trusts everything project-local, so a writable .pi/ is a code-execution path. Enforce with systemd ReadOnlyPaths=.
  5. Prompt injection is not solved by any of this. Untrusted text must be delimited and labelled as data, and any write must be gated by deterministic code outside the model.

Per-scenario conformance

Scenario L1 loading L2 personality L3 capability L4 invocation env minimal
curator target target target target target
memo-inbox gap gap ok (setActiveTools) ok (tool_call) gap
pi-grok not assessed not assessed not assessed not assessed not assessed

memo-inbox already implements L3 and L4 correctly and is the reference for those layers; its L1/L2/env gaps are tracked as a follow-up and must not be changed in the same pass that migrates its configuration into this repository.

Check conformance with:

scripts/pi-diff.sh <scenario>