Read a self-declared annotation as a claim, not a control
Scan the four Pi packages on 1.0.4 for readers of AgentTool.replay and place every control on the ladder by whether it acts in time and out of reach.
Capability is not authority, and almost nothing you can write is a boundary — an
operating-system boundary is. This page shows the point on Pi 1.0.4 with
AgentTool.replay: a field a tool fills in about itself, and nothing in the type
verifies it.
The declaration, and the scan
write_file declares itself replay: "safe" — nothing in the declaration checks
the claim:
// The tool chooses its own recovery policy. Nothing here checks that the choice is true.
const writeFile: AgentTool<typeof Params> = {
name: "write_file",
label: "write_file",
description: "write a file",
parameters: Params,
replay: "safe", // a claim by the inspected party
async execute() {
return { content: [{ type: "text", text: "ok" }], details: undefined };
},
};The companion test scans the four packages’ shipped JavaScript for readers:
test("1.0.4: no shipped JavaScript in the Pi packages reads a tool's replay field", () => {
const readers: string[] = [];
let scanned = 0;
for (const pkg of ["pi-agent-core", "pi-coding-agent", "pi-mcp", "pi-codemode"]) {
for (const file of jsFiles(join(packages, pkg, "dist"))) {
scanned++;
for (const [i, line] of readFileSync(file, "utf8").split("\n").entries()) {
const code = line.trim();
if (code.startsWith("//") || code.startsWith("*") || code.startsWith("/*")) continue;
if (READS_REPLAY.test(code)) readers.push(`${file}:${i + 1}`);
}
}
}
assert.ok(scanned > 50, `scanned ${scanned} files`);
// Version-sensitive on purpose: if a later release reads the field, this fails and chapter 42 must be re-read.
assert.deepEqual(readers, []);An empty result is not vacuous: the same regex matches tool.replay === "safe" and
{ replay: "never" }, so a release that read the field would fail — and this page
must be re-read.
The claim hierarchy
A procedure steers; a boundary must act before the consequence and sit out of reach of what it constrains. Only the last rung passes both without conditions:
| Control | Acts before the consequence? | Out of reach? | So it is |
|---|---|---|---|
instruction (AGENTS.md) |
no — changes what is proposed, not what runs | no | influence |
tool list (--tools) |
yes, for a tool never granted | yes — but bounds vocabulary, not reach | a boundary on what can be called |
beforeToolCall / tool_call gate |
yes — measured in ch39 / ch16 | depends on where it runs | a procedure over a call |
| OS boundary, container | yes — limits what the consequence can be | yes | an authority boundary |
The transferable rule: a value chosen by the inspected party cannot gate the
inspection. A tool that declares itself harmless so the gate skips the person is
choosing its own supervision — chapter 21 observed a gate believing a false
readOnlyHint.
Mechanism and limitations
Documented: the AgentTool.replay declaration, and security.md’s warning that
watching the transcript, using project trust and reviewing changes do not create a
security boundary. Observed: replay.test.ts (a tool can set replay, the scan
covers more than 50 shipped files, no reader exists in 1.0.4 — version-sensitive) and
mcp.test.ts (a gate keyed on a server’s own hint believed a lie). Proposed: the
four-rung ladder, the claim hierarchy, and the rule about the inspected party.
Nothing here ran a container, a micro-VM or a credential proxy — those rows are documented options, not tested configurations — and no mechanism in the ledger was exercised against a real model.
Understand this example
Copy this prompt into your AI tool. No code runs here.
Apply this example
Copy this prompt into your AI tool. No code runs here.