Canonical, version-controlled setup for my coding agents. The repository keeps user-authored resources together while leaving credentials, sessions, caches, installed packages, and other runtime state in their native harness directories.
~/coding-harness/link.sh --forceWithout --force, the script refuses to replace any existing path. Forced
replacements are moved to ~/.coding-harness-backups/<timestamp> first.
Run the script again after cloning or pulling this repository. It is safe to run repeatedly; already-correct links are left untouched.
In a Git checkout the script also sets core.hooksPath=.githooks, enabling the
tracked pre-commit hook. It refuses to replace a different hooks path, or to
bypass active hooks in .git/hooks, unless --force is given. --force does
not back those hooks up. The pre-commit hook runs bun scripts/pi-load-check.ts
when staged changes touch Pi runtime inputs (shared/AGENTS.md,
shared/skills, pi/agent/{agents,extensions,prompts,mcp.json,packages.txt}).
The check starts Pi in RPC mode without any model call. It fails when the
installed links do not point at this checkout, an extension fails to load, or a
canonical skill or prompt is not registered from here. It tests the working tree
the links point at, not only the staged snapshot.
.agent/verify.sh runs the offline checks listed in AGENTS.md, about two
minutes in total, and is the verifier verify-turn and the Claude Stop hook
discover. It makes no model calls. Paid probes stay manual and are parked as
work in progress (PRB-19): they run only on explicit request and are not
evidence for changes. See probes/README.md.
pi/agent/packages.txt records the Pi packages this setup expects:
| Package | Source |
|---|---|
| pi-worktree-agents | git:github.com/simonamtoft/pi-worktree-agents |
| pi-status-footer | git:github.com/simonamtoft/pi-status-footer |
Install them with:
~/coding-harness/link.sh --packagesThis runs pi install per entry, which writes the packages array in the
local ~/.pi/agent/settings.json. Entries already installed are skipped.
When developing one of the plugins, point its settings entry at the local
checkout instead (for example ../../projects/pi-worktree-agents, relative to
~/.pi/agent). The install step recognises a checkout by directory name and
leaves the override in place.
shared/skills/implement-powerpoint/ and shared/skills/implement-brand-web/
are gitignored. This repository is public, and they contain Implement-internal
slides, colleague photographs and brand guidance. Both still deploy to both
harnesses through the shared/skills link. implement-brand-web exists only on
the machine that created it, so copy it across by hand. Get the
implement-powerpoint.skill file from Implement, then unpack it:
unzip implement-powerpoint.skill -d ~/coding-harness/shared/skills/After unpacking a new release, reapply the uv run -q --with lxml --with pillow
line below "All commands are" in its SKILL.md. implement-brand-web reads
this bundle's brand references when it is present.
experiments/bonsai/: local-model playground with tracked setup notes; downloaded models and runtime state stay ignored. Not deployed bylink.sh.probes/: behavior scenarios for isolatedshared/AGENTS.mdcomparisons and whole-Pi-harness checks, plus local-only records reused by later runs on this machine. Parked as work in progress (PRB-19); paid runs are manual, on request only. See probes/README.md. Not deployed bylink.sh..agent/verify.sh,.githooks/, andscripts/: this repository's offline verifier, the pre-commit hooklink.shenables, and the model-free Pi harness load check. Not deployed into either harness.shared/: the commonAGENTS.md, skills, and command-safety/read-routing regression contracts consumed by both harnesses.pi/agent/: Pi instructions, extensions, agents, prompts, and thepackages.txtmanifest. Provider/model configuration is local-only and intentionally ignored by Git.DECISIONS.mdanddecisions/: the decision ledger — approaches this repository rejected or reversed, standing constraints, and rationale the code does not carry.DECISIONS.mdis the index and entry format; each area keeps its own file underdecisions/so agents read only the area they touch, and append to it when a direction is refused.
The Pi sandbox extension is enabled automatically from pi/agent/extensions/.
It hard-denies the high-risk Bash commands in shared/command-safety.tsv (such
as Git force-push, destructive Git rewrites, sudo, pipe-to-shell execution,
and unsafe deletion), then limits model filesystem tools to the session's current directory, while also
providing a private mode-0700 workspace under the process temp directory for
scratch artifacts. Sessions started in this harness or ~/pi-plugins may also
edit the user-owned plugin checkouts under ~/pi-plugins. Delivered reports and
handoffs in retained private session
workspaces stay readable, while other retained scratch content prompts and
writes remain scoped to the current session. Reads elsewhere prompt, apart from
direct reads in the canonical shared tree (including installed Pi skills that
resolve there) and read-tool access to installed Volta package content and
Playwright's managed browser cache. Any Git repository can list its own recent Pi
sessions with the model-callable recent_sessions tool
and directly read matching transcripts with the read tool. Sessions started at
the canonical checkout root also get read-tool access to
~/.pi/agent/sessions and content search and bounded JSON-field extraction from
oversized records; transcript writes and general Bash access remain blocked.
Recursive tools do not receive the shared-tree exception. The sandbox follows
symlinks before checking and hard-denies common secret paths
everywhere. Agent control files and plugin source are write-protected unless Pi
was started inside this canonical coding-harness checkout. Session transcripts
and local Pi configuration are normalized to owner-only permissions at startup.
Its Bash protections are lexical checks, so they catch explicit paths and known
high-risk commands but do not constitute an OS-enforced sandbox; use a container
or VM when that boundary is required. Quoted bodies passed to inline interpreter
evaluation are treated as code rather than path arguments, so they are not
inspected for paths, though secret literals inside them are still denied.
The stay-awake extension holds caffeinate -i -s on macOS during agent runs
and automatic verification, including repair turns. It releases the assertion
when each run or verifier finishes, so an idle session waiting for input does not
keep the Mac awake. The display may still dim, and nothing prevents sleep when
the lid closes on battery.
claude/: Claude hooks, settings, agents, statusline, and themes.
Only genuinely harness-neutral resources live in shared/, and they are the
single source of truth for both harnesses:
~/coding-harness/shared/AGENTS.md -> ~/.pi/agent/AGENTS.md + ~/.claude/CLAUDE.md
~/coding-harness/shared/skills -> ~/.pi/agent/skills + ~/.claude/skills
~/coding-harness/shared/command-safety.tsv -> shared command-deny regression contract
Everything else is harness-specific, because the two harnesses disagree on format or capability:
| Resource | Why it is not shared |
|---|---|
pi/agent/prompts/ |
Pi reads prompts/; Claude Code reads commands/. review.md also calls review_changes, a Pi extension tool. |
pi/agent/agents/ |
Agent frontmatter differs per harness (model and tools vocabularies). The reviewers are Pi-only because review_changes drives them. |
claude/agents/ |
A Claude-shaped presenter so the shared present skill works in both harnesses. |
The present and swarm skills stay shared: their workflows are
harness-neutral, and only the delegation call differs (Pi's subagent tool vs
Claude Code's Task tool). Both variants are documented in each skill; Pi uses
the standard /skill:swarm command rather than a separate prompt alias.
Per-project verifiers are harness-neutral too. Pi's verify-turn extension and
Claude's verify-turn.sh Stop hook both resolve .agent/verify.sh, then a
verify task in a Taskfile, so a repo wires one verifier for both. Pi discovers
and executes these repository-controlled commands only after Pi project trust
and a separate content-bound verifier approval for the current session.
Pi's optional fast diagnostics are also project-owned: an executable
.agent/diagnostics.sh receives the changed path after each successful Pi edit or
write and reports advisory feedback. It is separate from the full end-of-turn
verifier, requires a session-scoped content-bound approval, and is documented in
pi/agent/extensions/diagnostics/README.md.
Pi's ~/.pi/agent/settings.json, ~/.pi/agent/models.json, and
~/.pi/agent/subagents.json remain local. The settings file contains a device
id, the last-seen changelog version, package paths relative to the native Pi
directory and personal preferences; the model catalog holds provider endpoints
and credentials; and subagents.json optionally maps agent names to
machine-specific provider/model values. Authentication and generated state are
also intentionally not versioned.
pi/agent/settings.example.json lists the shared defaults. For a new machine,
copy it to ~/.pi/agent/settings.json (or merge it into an existing file), then
run link.sh --packages to add the plugin entries. Merge the providers you need
from pi/agent/models.example.json into ~/.pi/agent/models.json: replace the
PrivateGPT placeholder host, set HETZNER_API_KEY and IM_GPT_API_KEY in the
environment, and /login as needed. bonsai-local is only for the local
experiment. Agent pins in pi/agent/agents/ use the
PrivateGPT provider name, so dispatch fails clearly where that provider is not
defined. Prefer editing those pins over keeping a subagents.json that
duplicates them; keep the file only for genuine per-machine overrides.
Because models.json is local, providers are added per machine. To expose
Hetzner Inference models in Pi:
-
Create a token at https://experiments.hetzner.com: log in with your Hetzner account, open APPS → Inference, and click Create API Token (top right). Then export it where Pi runs, e.g. in your shell rc:
export HETZNER_API_KEY=... -
Add a
hetznerentry under the top-levelprovidersobject in~/.pi/agent/models.json(merge with an existingprovidersobject if present):"hetzner": { "baseUrl": "https://inference.hetzner.com/api/v1", "api": "openai-completions", "apiKey": "$HETZNER_API_KEY", "models": [ { "id": "Qwen/Qwen3.6-35B-A3B-FP8", "contextWindow": 262144 }, { "id": "Qwen3.8-27B", "contextWindow": 262144 } ] }
Useful optional per-model fields:
name,maxTokens(default 16384),reasoning,input, andcost. -
Verify with
pi --list-models, or open/modelin a session — the file reloads when the dialog opens, so no restart is needed. The models stay hidden from/modeluntilHETZNER_API_KEYis set in Pi's environment.
If the endpoint rejects requests, add provider-level compat flags; for
example, OpenAI-compatible servers that do not understand the developer
role need "compat": { "supportsDeveloperRole": false }. Pi's models.md
doc has the full provider and model schema.
Prefer one focused task per session. In Pi, use /ctx-monitor to inspect which
sources are consuming the context window. When a session grows large, use
/handoff to preserve its decisions and state, then continue in a fresh
session. Reloading AGENTS.md can restore attention to instructions
temporarily, but it does not remove the accumulated context.
Use the shared bulk-read skill for bounded factual extraction when its answer
can replace substantial reading: settings, log events, document facts, or code
inventories. Multi-file code discovery does not require delegation. The parent
inspects source directly for interconnected explanations, debugging, and edits;
independent extraction within those tasks can still benefit from a worker.
Small lookups and required complete reads stay direct. Selection is
instruction-driven, not automatic dispatch or an enforced cumulative read budget.
Pi's read-routing extension and Claude's
route-bulk-read.py PreToolUse hook redirect broad Read calls for regular files
larger than 16 KiB to the shared bulk-read skill. The parent supplies explicit paths and a
question when extraction fits; otherwise it uses bounded original-source reads.
The read-only worker returns answered facts with source support, unresolved facts,
and coverage limits, targeting at most 600 words. Pi resolves the worker's configured canonical
or local provider/model pin independently of the parent, passing both values
explicitly to the child; an unpinned worker inherits the parent's exact model.
This is predictable routing, not a same-provider data-egress boundary: the child
fails instead of silently switching providers when its selected provider/model is
unavailable. Claude uses Haiku; use bounded direct reads when that provider is not
permitted for the source.
Explicit limits of 1–350 lines remain available for original-source inspection; an offset alone or an oversized limit does not bypass routing. Read complete source in chunks when reasoning requires it. Governing Markdown stays directly readable: AGENTS/CLAUDE/SYSTEM instructions, SKILL files, CONTEXT files, DECISIONS, Markdown below skills/agents/adr/adrs/decisions, and Claude rules. Native image, PDF, and notebook inputs also stay on the direct path. These exemptions never grant filesystem permission or weaken secret checks.
The guards compare file size against one threshold and never open or return file
contents. Size tracks token cost far more closely than line count does: measured
across prose and code, characters per token stayed near 3.6 while tokens per line
varied fourfold. The 16 KiB gate sits inside the measured delegation break-even
band; see the routing README for the
numbers. Bash output limiting remains independent. The guards do not
intercept Bash, search output, or files already injected into context. Worker calls are exempt
from cost routing: Pi children load only the sandbox and secret-result guard extensions;
Claude uses the hook's agent_id to distinguish children from top-level custom agents. No
routing-specific toggle, full-read exemption state, or automatic writer is added.
Pi's secret-result guard replaces finalized tool output containing an exact protected
environment value before the result reaches the model or normal session log. It
automatically selects nontrivial values from secret-like names such as *_API_KEY,
*_TOKEN, *_SECRET, *_PASSWORD, *_CREDENTIAL, and *_PRIVATE_KEY.
Set PI_PROTECTED_ENV_VARS to a comma-separated list of additional variable names;
explicitly selected values may be shorter. The guard covers parent and subagent tool
results, but it does not prevent direct network use or transformed versions of a value.
Existing directory links deploy all resources without changing link.sh.
Reload Pi with /reload and start a new Claude session to load the new resources.
Claude requires python3, already used by other hooks. Run the shared policy
fixtures through both implementations:
bun test pi/agent/extensions
PYTHONDONTWRITEBYTECODE=1 python3 claude/hooks/test/read-routing-test.pyThis is a cost-routing pilot, not a claimed percentage saving. Compare parent context, total parent-plus-worker cost (including cache effects), latency, missed facts, and subsequent rereads on representative tasks before tuning thresholds. See the routing diagrams and evaluation guide for provisioning, current limitations, and session-probe evidence. A worker failure or permission denial falls back to permitted bounded direct reads, not a silent model upgrade or security bypass.