From c64c122dd369219d5c06e4ba85f9c07525947ae5 Mon Sep 17 00:00:00 2001 From: "pi@mbp-m1-2020" Date: Tue, 8 Sep 2026 22:08:00 +0200 Subject: [PATCH] docs(skill): the context ladder L0-L4, and pi-task as its low rungs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Boundary discipline explained WHY an inherited transcript defeats a brief but left the reader with no alternative — fork was the only mechanism documented, so "don't fork that" was the only available advice. The ladder makes the volume of inherited context a choice with five named rungs, and records which are real: L0/L1/L2 exist in pi-task (context.facts / .files / .commands), L4 is fork's only behaviour, and L3 (truncated branch) is NOT built by anything. Placed immediately after Boundary discipline because it is the answer to it. States plainly that pi-task is a CLI and will never appear in the tool list — an agent that goes looking for a `pi_task` tool finds nothing and concludes it is unavailable. Also records the trap found while writing this: runner.ts:188 is `if (extensions !== null)`, so extensions:[] turns the capability floor ON and `null` turns it OFF. Since null is documented as "restore normal extension loading", tidying [] to null re-arms palace writes in every fork child. Two honest limits kept next to the feature, not buried: the boundary diff is post-hoc DETECTION not prevention (--no-extensions never touched core read/write/edit/bash), and a fresh L0 context removes narrative failure without removing confabulation — T1's child still filled the deliverable slot from a false premise. description 687 -> 872 chars (limit 1024), so the skill is now reachable when an agent is deciding HOW to delegate, not only whether to fork. --- skill/SKILL.md | 73 ++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 71 insertions(+), 2 deletions(-) diff --git a/skill/SKILL.md b/skill/SKILL.md index 2cc25fe..e33b08f 100644 --- a/skill/SKILL.md +++ b/skill/SKILL.md @@ -1,7 +1,7 @@ --- name: pi-extensions description: >- - Use the pi extensions (pi-fork, pi-observational-memory, ssh-controlmaster) effectively in the pi coding agent harness. Load this skill only when running inside pi (detection - `fork` and `recall` are present in your tool list, or `pi --ssh` was used to start the session). pi-fork dispatches focused subtasks to forked agents at fast/balanced/deep effort tiers; pi-observational-memory compacts long sessions into recallable observations + reflections; ssh-controlmaster rewires pi's read/write/edit/bash tools to execute on a remote host over a multiplexed SSH connection. This skill covers tier selection, task design, boundary discipline, when to use recall, and remote-pi mechanics. + Use the pi extensions (pi-fork, pi-observational-memory, ssh-controlmaster) effectively in the pi coding agent harness. Load this skill only when running inside pi (detection - `fork` and `recall` are present in your tool list, or `pi --ssh` was used to start the session). pi-fork dispatches focused subtasks to forked agents at fast/balanced/deep effort tiers; pi-observational-memory compacts long sessions into recallable observations + reflections; ssh-controlmaster rewires pi's read/write/edit/bash tools to execute on a remote host over a multiplexed SSH connection. Also covers the context ladder L0-L4 and when to reach for the separate `pi-task` CLI instead of `fork` - isolated child, immutable spec, machine-checked envelope, write-boundary diff. This skill covers tier selection, task design, boundary discipline, when to use recall, and remote-pi mechanics. --- # Pi Extensions: pi-fork, pi-observational-memory, ssh-controlmaster @@ -161,6 +161,68 @@ The "three" things it completed were exactly the main thread's pending todos, vi - Distrust **quantities** and **provenance claims** in fork prose specifically ("all N sessions", "shipped with the image", "as expected") — those are the slots confabulation fills. - The fact that the fork was "right anyway" is not the same as the fork having followed instructions. +### The context ladder — and the second dispatch mechanism (`pi-task`) + +Everything above describes a child that inherits everything. That is not a fixed +cost of delegation — **how much context a child gets is a choice**, and `fork` +sits at one extreme of it. Five rungs: + +| rung | what the child sees | mechanism | built? | +|---|---|---|---| +| **L0** | nothing but the goal | `pi-task` default: fresh `--session-id pitask--` in a private `--session-dir` | yes | +| **L1** | goal + **names** of files/commands to read itself | `pi-task` spec `context.files` / `context.commands` (`bin/pi-task:154,157`) | yes | +| **L2** | goal + an **excerpt the parent curated** | `pi-task` spec `context.facts`, pasted verbatim (`bin/pi-task:151`) | yes | +| **L3** | a **truncated tail** of the parent branch | *nothing implements this* — would need a new spec key plus `--session ` | **no** | +| **L4** | the **entire** parent branch | `fork(task=…)` — `getHeader()+getBranch()`, no offset or limit anywhere in the call chain | yes | + +**`pi-task` is a CLI, not an extension — it will never appear in your tool list.** +Invoke it with `bash`: `/opt/pi-toolkit/bin/pi-task run ` (source at +`/workspace/pi-toolkit/bin/pi-task`, `schema` subcommand prints the spec fields). +It reads an immutable JSON spec, and "inherit the session" is not expressible in +that schema — the isolation is structural, not a request. + +**Choose the lowest rung that can do the job:** + +- **`fork` (L4)** when the subtask only makes sense against this conversation, + when you want several independent opinions in parallel from one message, or for + read-only exploration whose detail you will discard. Everything in "Boundary + discipline" above applies in full. +- **`pi-task` (L0–L2)** when the brief contains a **prohibition** (the inherited + transcript is exactly what overrides those), when you want a **pass/fail** + result instead of prose, when you need an **audit trail**, or when writes + outside an authorised set must be caught. +- **Neither** for trivial work, iterative work (both are one-shot), or judgement + that needs context only you have. + +**What `pi-task` gets you that no brief can.** The envelope must parse or the run +FAILED, however fluent the prose. `roots[]` is the WATCHED set and +`write_allowed` the CHANGEABLE subset, diffed before and after with git +`--porcelain --ignored`. That `--ignored` flag is load-bearing: in the T4 test the +child obeyed its brief perfectly and still tripped the detector, because +`py_compile` wrote `__pycache__` into a watched-but-not-writable root — a +gitignored path that plain `--porcelain` reports as clean. Note the structural +point that test exposed: under `read_only: true` a write is *defiance*, so a +well-behaved child never produces a delta and the detector is never exercised. +Splitting WATCHED from WRITABLE is what lets an **obedient** child reveal a +violation, which is the realistic hazard. + +**What it does not fix.** `--no-extensions` removes extensions, not the core +`read`/`write`/`edit`/`bash` tools — exactly as described above — so the boundary +diff is post-hoc **detection, not prevention**. And a fresh L0 context removes the +*narrative* failures (parent voice, invented continuity) without removing +confabulation: given an under-specified spec built on a false premise, the child +still filled the `deliverable` slot with a confident shape. The envelope's own +structure creates that pressure. Verify decisive claims from the filesystem +regardless of which rung you used. + +**Trap — the capability floor is inverted from intuition.** `runner.ts:188` reads +`if (extensions !== null) args.push("--no-extensions")`. So `pi-fork.extensions: +[]` passes the flag and the floor is **on**; setting it to `null` — documented in +`settings.json` as the way to "restore normal extension loading" — passes nothing +and the floor is **off**, restoring palace writes inside every fork child. +Changing `[]` to `null` as a tidy-up re-arms what was deliberately disarmed. +`pi-task` hardcodes the flag and cannot drift this way. + ### Anti-patterns - **Forking trivial work.** A fork has overhead. If the task takes < 30 seconds in your main thread, just do it. @@ -230,7 +292,7 @@ When entries conflict, **the most recent observation reflects the latest known s ## Quick Reference ``` -fork(task=..., effort=fast|balanced|deep) +fork(task=..., effort=fast|balanced|deep) # L4: child inherits your WHOLE branch - state decision authority explicitly - pass verified context up front - specify deliverable shape @@ -240,6 +302,13 @@ fork(task=..., effort=fast|balanced|deep) - write-capable? demand "What I did NOT do", then verify from git/fs, not the report - prohibition in the brief => not a `fast` task +bash: /opt/pi-toolkit/bin/pi-task run # L0-L2: isolated child, NOT a tool + - schema | selftest | run [--dry-run] + - context.facts (pasted) / .files (names only) / .commands + - roots[] = WATCHED, write_allowed[] = CHANGEABLE subset + - envelope must parse or the run FAILED + - audit + cost: ~/.pi/agent/pi-task/-/result.json + recall(id=<12-char-hex>) - only when stakes justify the cost - id must already be visible in your context