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.
136 lines
5.2 KiB
Markdown
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.
|