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.
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user