Skip to content

Course · Pi · Daily workflow

Assemble the harness

Every piece in the tree below, you built or sketched somewhere earlier in this course (arriving fresh? Read “you built” as “the course built” - each entry below restates what the file does and whether it’s done). This lesson teaches nothing new - it’s the build log: the same directory you’d walk a new teammate through on their first day, with every file tagged back to the module that taught you to write it.

Here’s the claim, stated so you can check it yourself: of the 18 files below, most are finished and tested somewhere earlier in this course. A few are still just plans. Before you read past the tree, count them yourself - built (you wrote it, ran it, watched it work) versus design (the lesson sketched it and said, plainly, that it wasn’t done). Then read the walk below and see if your count matches.

stash/
├─ PLAN.md # the current unit of work, at the repo root (module 6, built)
├─ .pi/
│ ├─ APPEND_SYSTEM.md # stash's five conventions, written once (module 3, built)
│ ├─ settings.json # project config - model routing, defaults (module 4, built)
│ ├─ damage-control-rules.yaml # the stash.db/.pi guardrail, in plain YAML (module 6, built)
│ ├─ extensions/
│ │ ├─ stash-tools.ts # /stash command + resync_feed tool (module 5, built)
│ │ ├─ damage-control.ts # gates deletes of stash.db and .pi (module 6, built)
│ │ ├─ tilldone.ts # one-task-at-a-time gate (module 6, design - not complete)
│ │ ├─ plan-injector.ts # keeps PLAN.md in front of the model (module 6, built)
│ │ ├─ statusline.ts # live footer: model, context %, cost (module 6, built)
│ │ └─ dispatch.ts # the lead's one tool: spawn a worker (module 9, built)
│ ├─ prompts/
│ │ └─ triage-fetch.md # the recurring triage pass, /triage-fetch (module 7, built)
│ ├─ skills/
│ │ └─ add-feed-source/
│ │ └─ SKILL.md # "wire a new feed source into stash" (module 7, built)
│ ├─ themes/
│ │ └─ stash-dark.json # the one purely cosmetic file in here (module 7, built)
│ └─ agents/
│ ├─ verifier.md # read-only child that checks the builder (module 8, built)
│ ├─ resync-chain.yaml # audit -> first-sync -> verify chain (module 8, design - Pi doesn't run this file)
│ └─ expertise/
│ ├─ extraction-lead.md # what the extraction lead learned (module 9, built)
│ ├─ storage-lead.md # what the storage lead learned (module 9, built)
│ └─ interface-lead.md # what the interface lead learned (module 9, built)
└─ (the actual stash codebase)

Nothing in that tree is exotic. It’s markdown, YAML, JSON, and six small TypeScript files, all readable in one sitting - which is itself the point Pi has been making since module 2: a harness you can read in one sitting is a harness you actually trust. Note where PLAN.md sits: at the repo root, not under .pi/, because the injector reads it from the working directory and it belongs to the project as much as to the agent.

The rules and the routing - what the agent knows before it does anything

Section titled “The rules and the routing - what the agent knows before it does anything”

APPEND_SYSTEM.md is loaded into every session before you type a word, and it’s where the five conventions of stash (the course’s running example, a small FastAPI feed reader) live once and stop needing repetition: storage through the standard library’s sqlite3, httpx for every outbound request and never requests or urllib, tests with pytest, pydantic models in stash/models.py, feed dates normalized on the way in with their timezone offsets kept. Module 3 is where the friend’s root SYSTEM.md - which Pi never read - became this file, and it’s been paying its 63-word rent every session since.

settings.json carries module 4’s routing: the cheap model pointed at the re-sync sweep’s fetch-parse-upsert rows, the strong one kept for judgment work like hardening fetch_feed against malformed feeds. If a local model is in the mix for throwaway jobs, its practical context ceiling is a limit you measured and accepted in that module, not a surprise to discover mid-task.

The extensions - the features Pi didn’t ship, built on purpose

Section titled “The extensions - the features Pi didn’t ship, built on purpose”

stash-tools.ts is the first extension you wrote, and it’s two things in one file: the /stash command you type, with its undated / unlinked / blank filters against the backlog rows, and the resync_feed tool the model calls with a typed integer feed ID. Still the most-used file in the directory, because it turned the week’s most repeated chore into one keystroke plus one tool call. Built, and proven by use. One boundary worth naming while you’re in this section: everything these extension files gate is Pi’s tool calls, the commands the agent runs inside a session. The commands you type in your own shell never pass through them - which is why the Try It at the end of this page can tell you to delete a file whose deletion the damage-control gate would block the agent from making.

The other four extensions all came out of module 6, and they don’t all land the same. damage-control.ts is the one guarding the file git can’t give back: it loads .pi/damage-control-rules.yaml on session_start and blocks deletes aimed at stash.db or anything under .pi/ (both sit in the gate’s noDeletePaths list, and its matcher catches paths inside .pi/ too - deleting the rule file or the harness directory would disarm the gate), asks before an rm -rf, and stops raw DROP TABLE / DELETE FROM SQL against the database. The module’s own Try It had you write it and then watch the hard-stop actually fire. Built and tested.

plan-injector.ts keeps PLAN.md in front of the model on every turn, re-reading the file from disk each time so your hand-edits can’t go stale - the lesson shows the whole thing, no elisions. statusline.ts swaps the stock footer for one that tracks what you actually watch during a long re-sync: model, context percentage, cost.

Context percentage and cost recompute from the session’s current branch on every render, so a /fork (Pi’s command for branching a session) can’t leave them stale. The minimal footer is shown whole in its lesson; the richer tally version is described and handed to Pi to write, then reviewed. Both built.

tilldone.ts is the one from that module that isn’t finished. It’s meant to give the model a task list and block every other tool until exactly one task is in progress, with task state rebuilt from session history rather than a side file. Its own lesson says so, plainly, of the gate it shows: “the real implementation must define tasks and rebuild it from session history.”

The rebuild is taught as a pattern plus a verify-checklist, not as finished code. That’s not the lesson being sloppy - it’s an honest flag left in place on purpose, and it’s sitting there unresolved right now.

The sixth extension came later: dispatch.ts is module 9’s one-tool lead, the spawn-and-read-a-worker code that made leads structurally unable to edit a file. Built, and it carries the same LF-only splitter for the workers’ JSONL logs that the subagents module settled on.

The skill and the verify loop - the no-code layer and the check that doesn’t trust you

Section titled “The skill and the verify loop - the no-code layer and the check that doesn’t trust you”

skills/add-feed-source/SKILL.md is module 7’s packaged procedure for wiring a new feed source into stash - no code, just the steps: confirm the site actually serves a feed, read the raw XML before touching code, check fetch_feed against a sample and extend it only if the shape defeats it, sync, then confirm the saved rows came out right. Its 61 words of frontmatter sit in the prompt; its 225-word body loads only when a request matches.

prompts/triage-fetch.md and themes/stash-dark.json are the same module’s smaller wins: the recurring failed-sync triage pass you invoke as /triage-fetch instead of retyping, and a 53-token theme file that exists purely because you have to look at the thing all day. All three built, all three exercised in their own lesson.

agents/verifier.md is module 8’s reviewer - a persona file, not an extension, and Pi never auto-loads it: the spawn command wires it in explicitly with --append-system-prompt "$(cat .pi/agents/verifier.md)". It’s built: a real system prompt that decomposes “done” into falsifiable claims - rows exist, published is a real timestamp and not the string 'None', links non-empty, nothing saved twice - and checks each against the builder’s actual session log and stash.db itself.

agents/resync-chain.yaml isn’t in the same state. Its own lesson calls it “a design sketch for a driver you write; Pi does not load this file automatically” - three steps (audit which import URLs have discoverable feeds, first-sync the approved list, verify the saved rows) with $INPUT handing each step the last one’s output. It’s a real, useful sketch of a control-flow shape. It is not a file that runs.

The team - for when one context genuinely isn’t enough

Section titled “The team - for when one context genuinely isn’t enough”

The three files under agents/expertise/ are what’s left on disk from module 9’s migration team - the run that wired the readability engine into stash’s extraction, storage, and interface code across three leads. The team itself is gone by design: leads and workers are spun up per job, and personas are cheap.

What’s worth keeping between runs is the expertise - what each lead worked out about its own lane, so the next lead doesn’t pay to rediscover it from zero. The extraction lead’s file is the fullest: what readability-lxml actually returns, and the site shapes that defeat it. The storage lead’s carries the row-shape lesson - the word_count field it needed to know about before re-processing a single stored row - and the interface lead’s holds what reshaping GET /articles taught it. All three built; all three already earning their keep.

What holds it together - and what doesn’t yet

Section titled “What holds it together - and what doesn’t yet”

None of these pieces talk to each other through anything magic - a settings file, some markdown, and six TypeScript files hooking into the same handful of lifecycle events. Reconcile the count now: 16 of the 18 files above are finished and tested. Two are not - tilldone.ts and resync-chain.yaml, both flagged as incomplete in their own lessons, both still sitting exactly where you left them. If your count came out differently, go back and check which tag you missed; the tree above is the answer key.

Seeing all eighteen files in one tree feels like the harness is done. It isn’t, and the two that aren’t finished are the two that won’t fail loudly - a promise doesn’t error, it just sits there until the day you need it and it isn’t there. Next: the maintenance that keeps the other sixteen from joining them.