# Authoring a Scenario Practical checklist. Rationale lives in [`isolation-baseline.md`](isolation-baseline.md), [`personality-layering.md`](personality-layering.md) and [`gateway-patterns.md`](gateway-patterns.md). ## 1. Decide whether you need a dedicated agent at all You do when **all** of these hold: - the task is narrow and repeats, - it needs backend facts that only your service can supply, - and a wrong answer has a cost (a write, a purchase, a deletion). You do not when the task is one-off exploration — use interactive `pi` for that. ## 2. Copy the template ```bash cp -r scenarios/_template scenarios/ ``` Fill in every `` in `profile.toml`. It is the single source of truth for the launch contract; `deploy-scenario.sh` renders it into `/.pi/launch.json`, and your gateway should read that file and **fail closed** if it is missing. Silently running without `--no-extensions` widens the agent's reach. ## 3. Write the personality `.pi/SYSTEM.md` replaces pi's default prompt, and the replacement branch contributes **neither** the tool list **nor** the guidelines. Cover all six sections: identity and negative identity, tool overview, fact authority, write discipline, untrusted data, output discipline. Do not include any path to pi's own documentation. The default prompt does, along with an instruction to read it and follow cross-references — for a non-coding agent that is a ready-made escalation path for injected text. Durable domain responsibilities go in `.pi/APPEND_SYSTEM.md`, not `AGENTS.md`: context files layer from every parent directory and `AGENTS.override.md` does not stop that. `-nc` does, and system-prompt files are unaffected by it. ## 4. Write the extension Build on `shared/extensions/pi-guard-base.ts`: ```typescript import { installGuard, registerRestrictedRead, registerBridgeTools, textResult } from "/shared/extensions/pi-guard-base.ts"; const ALLOWED = ["read", "my_query", "my_propose"]; export default function myGuard(pi: ExtensionAPI) { registerRestrictedRead(pi, { roots: [`${WS}/.pi/skills`], // MUST include the skill dirs base: WS, extensions: [".md"], }); registerBridgeTools(pi, { baseUrl: process.env.MY_BRIDGE_URL!, token: process.env.MY_BRIDGE_TOKEN }, specs); installGuard(pi, { scenario: "", allowedTools: ALLOWED }); } ``` Non-negotiable: - **A restricted `read` is mandatory.** pi emits the skills section only when a tool named `read` is active, and skill bodies load through it. Without it every `SKILL.md` is dead. The built-in `read` accepts absolute paths and can reach your credential files, so override it rather than enabling it. - **Every tool needs `promptSnippet`**, otherwise it is callable but absent from the prose tool list. - **Every tool truncates its own output** (50 KB / 2000 lines). - **Throw to signal failure.** Returning an error-shaped object does not mark the call failed. - **Writes do not belong to the agent.** Expose a `propose_*` tool that records a plan and echoes the resolved identity; let deterministic code decide. ## 5. Write the gateway Use `shared/lib/py/pi_rpc.py`. It gives you a long-lived RPC process, session rotation, a per-turn deadline enforced with `abort`, process-group cleanup, a minimal `env`, and receipts harvested from `tool_execution_end`. ```python from pi_rpc import PiLaunchConfig, PiRpcClient client = PiRpcClient(PiLaunchConfig( pi_bin="/home/claw/.npm-global/bin/pi", workspace=Path("/home/claw/pi-workspaces/"), session_dir=Path("/home/claw/.local/share/pi-/sessions"), provider="zenmux", model="...", thinking="high", session_id_prefix="-", extensions=(ext_path,), skills=(skill_dir,), receipt_tools=frozenset({"my_write"}), )) result = await client.prompt(user_text) reply = result.receipts[0] if result.receipts else result.text ``` Render any user-visible state change from `result.receipts`, never from `result.text`. Prose guardrails have already failed once in production: with `has_file: false` the model still wrote "已成功加入库中". ## 6. Split stateless work out Classification, extraction and synthesis need no session, no skills and a lower thinking level. Run them with `--no-session` and a terminating tool carrying `constrainedSampling` — the tool's `parameters` *is* your output schema. Do not regex JSON out of prose, and do not share a session with the conversational role; alternating "only output JSON" and "do not output JSON" across turns causes mode confusion. ## 7. Verify before deploying ```bash scripts/deploy-scenario.sh # dry run; checks referenced paths shared/extensions/tests/run-guard-checks.sh # adapt for your tool set scripts/pi-diff.sh ``` Assert, with a probe rather than by reading: - the system prompt does **not** contain `expert coding assistant` - `` lists **only** your skills - active tools are **exactly** your allowlist - `read` reports `source: cli`, not `builtin` - reads of an outside path, a `../` traversal and an absolute path to your credential file are all denied ## 8. Record conformance Add a row to the table in [`isolation-baseline.md`](isolation-baseline.md). If a layer is a known gap, say so and link the follow-up rather than leaving it blank.