Files
pi-agent-config/docs/scenario-authoring.md
T
Kai f25082223e docs: repository README, contributor rules, scenario template and authoring guide
README leads with the finding that motivated the repository -- pi emits the
skills section only when a tool named 'read' is active, so Curator's
--no-tools --skill combination made its policy unreachable -- with the measured
before/after table and instructions to reproduce it at zero token cost.

AGENTS.md sets seven rules for anyone changing this repository. The third is the
one that matters most: verify pi's behaviour with a probe rather than inferring it
from the docs. Three claims in the first draft of these documents were wrong and
were only corrected by running one.

The _template scenario carries the isolation defaults and inline warnings at the
places where mistakes have already cost time: --no-tools disabling the skills
mechanism, cwd anchoring .pi discovery, the read override being mandatory rather
than optional, and allowed-tools frontmatter not being enforced in 0.84.3.
2026-08-26 23:19:19 -07:00

136 lines
5.2 KiB
Markdown

# 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/<name>
```
Fill in every `<PLACEHOLDER>` in `profile.toml`. It is the single source of truth
for the launch contract; `deploy-scenario.sh` renders it into
`<workspace>/.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 "<repo>/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: "<name>", 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/<name>"),
session_dir=Path("/home/claw/.local/share/pi-<name>/sessions"),
provider="zenmux", model="...", thinking="high",
session_id_prefix="<name>-<subject>",
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 <name> # dry run; checks referenced paths
shared/extensions/tests/run-guard-checks.sh # adapt for your tool set
scripts/pi-diff.sh <name>
```
Assert, with a probe rather than by reading:
- the system prompt does **not** contain `expert coding assistant`
- `<available_skills>` 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.