Skip to content

Mac Mini delegation

Since 2026-09-24 the orchestrator (claude-3 on the operator's MacBook) delegates heavy Claude work to headless Claude Code on the Mac Mini. The operator's words: "from now on use claude on the Mac mini remote since it has more tokens", and on 2026-09-25, "You orchestrate and mac mini does the work". The standing exception, and the rule never to touch the Mini's OAuth token, are in CLAUDE.md. The SSH and fence basics are in hosts.md.

The Mini writes. The orchestrator verifies and publishes. The Mini's gh identity is ibuild-e-bot, which cannot read rig repos, and rig agents do not work on the rig. So code moves by git over SSH, and every commit is re-checked, re-authored and pushed from the MacBook.

What runs where

The MacBook account and the Mini's account have separate usage limits. On 2026-09-27 the MacBook account had used 94% of its weekly limit. The largest single spend was a local adversarial-review Workflow: 7 agents, about 700k tokens.

Work Where
Adversarial review: review, refute each finding, write findings.md Mini job, opus-5-5 xhigh
An investigation that needs more than about three files Mini job that returns a short verdict with file:line
Implementation, tests and full suites (~/bin/verify-stack.sh) Mini job
PR content beyond a few lines Mini job (medium for docs written from given facts)
Planning, the final patch check, publish and merge, rig scripts that print counts rather than dumps Orchestrator
Anything gated by the auto-mode classifier or the credential policy Orchestrator, with the operator
App features, and every review and merge of a dashecorp PR The rig: Dev-E, Review-E, MergeGate

The rig never does rig-internal work, and Dev-E cannot push workflow files (infra#457). Those go to the Mini or the orchestrator.

One job = one fresh session

Each task is a directory ~/jobs/<name>/ on the Mini:

File Content
dir working directory (a clone or worktree under ~/repos/)
model e.g. claude-opus-5-5
effort low / medium / high / xhigh / max, always set explicitly (the CLI default is undocumented)
tools the --allowedTools entries, one per line
prompt.md the brief
out.json, err, progress.md written by the run

Run it from the MacBook with run_in_background, so the session is notified when it finishes:

ssh -i ~/.ssh/mac-executor claude@100.92.170.124 'zsh -ic "~/bin/run-job.sh <name>"'

A job whose brief runs the Api suite (Testcontainers) runs inside ~/bin/with-rigbuild.sh. The wrapper starts the rigbuild VM, holds its lock for the whole job, fails closed unless the engine is rigbuild, and stops the VM afterwards (the review's 2026-10-20 amendment):

ssh -i ~/.ssh/mac-executor claude@100.92.170.124 'zsh -ic "~/bin/with-rigbuild.sh ~/bin/run-job.sh <name>"'
  • Tools: keep Bash(dotnet test:*), and give no colima, docker or OrbStack entry. run-job.sh refuses (exit 3) a tools file with a Bash rule that names colima, docker, lima, limactl, OrbStack, orb or orbctl, grants all of Bash, starts with a wildcard such as Bash(*:*), or starts with a launcher such as a shell, env, xargs, sudo, open, source, ., the wrapper and job scripts (with-rigbuild.sh, run-job.sh, lockf) or an interpreter with a code flag (python3 -Ic, node --eval), by name or by full path. A refusal prints only the file and line, never the rule text. The CLI splits each tools line on commas and spaces, so a rule anywhere on a line counts (Read, Bash(docker ps:*) is refused). The job directory's .claude/settings.json, which --setting-sources project loads, gets the same check; its hooks are not checked. It matches names only: Bash(git:*) (an alias starting with !), Bash(dotnet:*) and dotnet test itself (MSBuild targets, test code) can still run any command. So read the tools file yourself as well. Older jobs' tools files still carry colima and docker rules, so never copy one forward. While claude has passwordless sudo on the Mini (operator-inbox#51), these checks are a speed bump against mistakes, not a boundary against a hostile job, because any job can become root.
  • Brief: "rigbuild is up for the whole job; never start or stop it; if Testcontainers reports Docker unavailable, stop and report."
  • Exit codes (the log is ~/.rigbuild/with-rigbuild.log):
  • 75: another wrapped run holds the VM. Launch again when it finishes.
  • 98: the job did not run. Either the engine check failed, or the wrapper refused before starting anything: env.sh did not source, ~/.testcontainers.properties exists, or rigbuild was already up outside the lock (a run whose lockf was killed, ~/bin/flake-check.sh, a manual start). In that last case, find out what uses the VM before anyone runs colima stop --profile rigbuild.
  • 96 and 97: findings after the run (a new image, or a listener not on loopback). 95: colima stop failed, so rigbuild may still be running, or the run's lockf was killed and another run holds the lock, so the stop was left to that run. A finding's code is returned even when the command failed; the log lists them all.
  • 99: RIGBUILD_LOCKED was not set by the wrapper's own lockf.
  • From run-job.sh: 3, the tools file (or the job directory's .claude/settings.json) was refused; 4, the job is outside a live wrapper while an engine answers on /var/run/docker.sock, or no docker CLI is on PATH to check that, or ~/.testcontainers.properties exists.
  • 73: lockf cannot open ~/.rigbuild/vm.lock because ~/.rigbuild is missing. Nothing ran.
  • 70: lockf reports that the wrapper's body was killed by a signal. If this run's start: line in the log has no end: line after it, the body died before the stop and rigbuild may still be up (the next run refuses with 98). Otherwise it died after the stop, for example by SIGPIPE on its final print after an ssh drop, and that end: rc= line has the real code.
  • A command that itself exits 75 or 95 to 98 returns the same code. The log tells them apart: a run refused with 75 by the lock writes no start: line, and a command's own code shows in its end: rc= line.
  • Jobs that don't run the Api suite are launched unwrapped. run-job.sh refuses them (exit 4) while an engine answers on /var/run/docker.sock, so stop OrbStack's engine first (step 1 of the review's removal plan). It also refuses them when no docker CLI is on PATH, since it then cannot check. env.sh only removes ~/.orbstack/bin from PATH, so the docker CLI comes from the launch's own PATH.

~/bin/run-job.sh (source) always passes --model, --effort, --setting-sources project (without it, the host's Bash(*) user setting makes the allowlist meaningless, #947) and the job's own write access to progress.md. It then appends one row to ~/jobs/ledger.tsv.

Giving a job its repo. The Mini's gh can't read rig repos. Since the ibuild-e-bot key rotation of 2026-10-14 (claude-3#1206), it answers HTTP 401: Bad credentials for every repo (checked 2026-10-18). So ship a bundle, build the job's clone from it, and give the job no gh tools. This recipe worked for every job on 2026-10-14/15:

# MacBook: no checkout in the local clone
git -C <clone> fetch -q origin main && git -C <clone> bundle create -q x.bundle origin/main
# Mini, after scp to ~/jobs/
d=~/repos/<job-clone>; git init -q $d \
  && git -C $d fetch -q --update-head-ok ~/jobs/x.bundle 'refs/remotes/origin/main:refs/heads/main' \
  && git -C $d symbolic-ref HEAD refs/heads/main && git -C $d reset -q --hard refs/heads/main

Put inputs in ctx/ inside that clone and add ctx/ to .git/info/exclude. Collect the result over SSH with git add -A -- <paths> && git diff --cached (add --binary for PDFs and images). The orchestrator then applies it, commits and pushes.

  • Don't git clone the bundle. It carries only refs/remotes/origin/main, so the clone has no main commits and an empty checkout (review-1015, 2026-10-15). Use the fetch recipe above. A bundle of a local branch (git bundle create x.bundle main) carries refs/heads/main and clones fine (jobs autodeploy and ad1, 2026-10-18).
  • ssh -n throws away a heredoc. ssh -n … 'bash -s' <<'EOF' reads stdin from /dev/null, so the remote script silently does nothing. Drop -n whenever a script is piped in.
  • Ask for deliverables in progress.md too. Job site-issues (2026-10-15) had its writes to ctx/ refused at the permission prompt. Its relative Write(./ctx/**) rule stopped matching after a cd moved the session (see the absolute-path rule below). It saved both issue bodies in progress.md between <!-- BEGIN name --> and <!-- END name --> markers, and the orchestrator extracted them from there.

Every job gets ~/jobs/PREAMBLE.md as an appended system prompt:

  • Identity: you are a delegated job, not iBuild-E. The Mini's ~/.claude/CLAUDE.md is iBuild-E's persona ("You are iBuild-E … Discord"). It loads even under --setting-sources project. Never edit it, because the live service uses it.
  • Scope: never push, open PRs or post to Discord.
  • Docs ride with behaviour, and updated: is bumped.
  • progress.md: keep it current, so a killed job can resume from it plus the git state.
  • LEARNINGS: end every reply with this section. The orchestrator folds it into memory and docs.

Model and effort routing

Choose by the cost of a wrong answer and how mechanically the result can be checked, not by token cost. A TDD slice costs 1-6 minutes and about $1-2 counterfactual (the Mini runs on a flat subscription), so a cheaper model saves almost nothing.

Task Model Effort
Design, root cause, anything that changes live behaviour or picks a rollout opus-5-5 xhigh (max if the first attempt was wrong)
Adversarial review of a design or a security review opus-5-5 xhigh
TDD implementation slice from a decided design opus-5-5 high
Mechanical work with a mechanical check (re-slicing with tree equality, docs from given facts, inventories) opus-5-5 medium
Bulk retrieval, many repeated runs, output fully re-checked sonnet-5 medium
Haiku never for judgment —

Feedback loop: after verifying a job, the orchestrator records a verdict in the ledger (accepted as-is, accepted + N fixes, rejected: why). - A defect in the job's own work: the next job of that class moves up one effort step. - Three clean accepts in a row: try one step down. - A permission denial is a wasted turn. Fix it in the allowlist, not with a stronger model.

A pure build-and-test re-verification needs no model at all: verify-stack.sh builds, runs the four guards and the full suites for a list of branches as a plain script. It runs itself inside with-rigbuild.sh.

Context

  • Every job is a new session. Nothing carries over except the brief, progress.md and git.
  • Size: the window is 1,000,000 tokens (modelUsage.contextWindow for claude-opus-5-5), and startup costs about 20k. Peaks measured on 2026-09-25 ranged from 20k (a probe) to 283k (an xhigh design review), with no compactions.
  • Ceiling: size a job to finish under about 250k. Past about 400k, let it finish its current slice, stop it, and start a fresh job from progress.md.
  • Monitoring: python3 ~/bin/job-context.py [minutes] (source) shows the current and peak context of live transcripts. job-facts.py gives turns and peak per job for the ledger and the orchestrator board. python3 ~/bin/audit-job.py <job> (source) audits what a finished job actually ran (see Verification).
  • Don't trust out.json for turns and time. Its num_turns and duration_ms can cover only the last segment of a run. One 5-minute job with 112 transcript messages reported 9 turns and 37 s. Take turns and minutes from the transcript.
  • One test VM, one holder. There is one colima test VM, rigbuild, and with-rigbuild.sh serializes it with a lock. A second wrapped job, or a verify-stack.sh run, started while another holds it exits 75 (already locked) without touching the VM. One that finds rigbuild already up outside the lock exits 98, also without touching it. ~/bin/flake-check.sh on the Mini starts colima without the lock; never run it.
  • Stopping a job: stopping the local ssh task does not stop the remote job. Kill the job's claude or dotnet process on the Mini: pkill -x claude, or kill with its pid (from pgrep -x claude) when other jobs run. Never kill lockf, and never use pkill -f: lockf's command line holds /Users/claude/…, run-job and with-rigbuild, so pkill -f claude kills it too. Without lockf the lock is free while the job and the VM keep running. Once rigbuild answers, the next wrapped run finds it up and refuses with 98 instead of stopping it. Killed during colima start, before rigbuild answers, a second run can take the lock and start the VM too. If the killed run ends first, it leaves the stop to the other run (FINDING, 95); if the other run ends first, it stops the VM under the killed run's job.

Verification before anything is published

The orchestrator never publishes a job's claim unchecked:

  1. Audit the run with python3 ~/bin/audit-job.py <job> (source). It reads out.json and dir (a relative dir is taken from $HOME), walks the main transcript (~/.claude/projects/*/<session_id>.jsonl) and every subagent transcript (~/.claude/projects/*/<session_id>/subagents/*.jsonl), and prints:
  2. the permission denials from out.json;
  3. Agent uses: N, subagent transcripts: M, with a WARNING when M < N, since a missing transcript is an unaudited subagent. N counts only Agent/Task calls classified ran: a denied or failed call starts no subagent and leaves no transcript. Only .jsonl files holding messages count as transcripts; subagents/ also holds other files;
  4. each tool use other than Read/WebFetch/WebSearch/ToolSearch/TodoWrite, each Write/Edit target other than the job's progress.md, each Grep/Glob with its path and pattern, and each Read outside dir. A Grep, Glob or Read whose path resolves outside dir is flagged OUTSIDE DIR.

Each call is classified in this order: 1. DENIED if its tool_use_id is in out.json's permission_denials (entries carry tool_name, tool_input and tool_use_id). 2. Otherwise DENIED if it is an error whose text starts with a known denial opening, matched case-insensitively after stripping whitespace and a <tool_use_error> wrapper: "This Bash command contains multiple operations", "This command requires approval", "A variable in this command can't be checked", "Contains brace with quote character", "Claude requested permissions", "A command used as a file in this command can't be checked before it runs" or "Output redirection to" (that text ends "needs approval"). An error containing "was blocked. For security" anywhere is also DENIED: that denial opens with the command name (ls … was blocked. For security …). This covers subagent transcripts and older out.json files, whose permission_denials miss those calls. 3. Otherwise RAN, FAILED for any other error: the call ran. A git push "denied by pre-receive hook" and a Read's "File does not exist" both land here. 4. Otherwise ran.

Result excerpts are printed only for DENIED and RAN, FAILED. Before anything is printed or truncated, these token shapes are replaced with [REDACTED]: gh[oprsu]_…, github_pat_…, sk-ant-…, xox[abpr]-…, AKIA…, ya29.…, bearer … (any case), a PEM private key from its -----BEGIN … PRIVATE KEY----- line to its -----END …----- line (or to the end of the excerpt), and any …TOKEN…=, …SECRET…=, …PASSWORD…= or …API_KEY…= assignment (any case). Known token shapes are redacted; skim before pasting. Transcripts are read as UTF-8 with bad bytes replaced, and an unreadable transcript is reported as CANNOT READ and skipped, so one bad file does not stop the audit. --jobs-dir and --projects-dir (defaults ~/jobs, ~/.claude/projects) point it at a copy. To test changes to the script, build a throwaway fixture there (a denial listed in permission_denials, a ran-but-failed command, a Grep and a Read outside dir, more Agent uses than subagent transcripts, a denied Agent call, a fake token in a command). Don't commit it: token-shaped strings trip GitHub push protection.

Denied calls are fine. Anything that ran outside the fence is not; see the gotchas for what Claude Code lets through on its own. 2. Re-run the tests on the reference machine: - rig-conductor: the full suites on the Mini's rigbuild VM, comparing trx executed with total; - rig-agent-runtime: npm test on the MacBook, which has the node_modules the Mini must not install. 3. Reproduce the mutation proof independently. 4. Build after every rebase. A clean git merge-tree is not a compiling tree: an rc#2375 rebase merged cleanly and did not compile. 5. Publish a verified stack by patch, not by tree. Before each push, assert that each slice's git diff against its parent equals the verified reference. 6. Check any claim the model made from memory. For example, codex's reasoning-effort values were read from the codex binary rather than trusted from the job.

  1. Check a deletion against the target repo's recorded decisions. Job nutri-retire (2026-09-29) deleted all of dashe-nutri-e's website/, which that repo's CLAUDE.md and website/RETIRED.md keep as the record. The orchestrator published the smaller change the inventory had planned instead (dashe-nutri-e#60). Before briefing a delete, grep the target's CLAUDE.md and READMEs for what they say to keep, and put that in the brief.
  2. See a new check fail before publishing it. A job that may only run its own check can show it passing, never failing. Copy the repo, apply one mutation at a time (broken JSON-LD, a dead link, a bad redirect status, an unclosed tag), and require exit 1 for each. The PR checks of invotek-no#33 (6 of 6 caught) and stigjohnny-no#24 (9 of 9) were proved this way.
  3. Give review jobs facts, not recycled brief text.
  4. Build the review diff with git diff $(git merge-base origin/main HEAD) HEAD. gh pr diff, run right after a push, served the previous head.
  5. Let the reviewer run the code on real data, with the exact repo-relative rule Bash(python3 scripts/…:*). The same command with an absolute path is denied.
  6. Re-check every number in the brief. A wrong count, copied from one brief to the next, cost #1160 two extra review rounds.
  7. Give the latest published artifact as the baseline, not only a diff against an old base. A final CV gate (2026-10-15) failed the print because two skills "changed" to fading. The diff was against the November 2023 repo, and the operator's May 2026 print, the real baseline, already had them fading.
  8. Put every file the site links to in scope, PDFs included. The 17-page whitepaper that stigjohnny.no linked held most of the factual errors, and only reviewers who went beyond the brief's list found it.
  9. Merge the reviewers' fixes; don't stack them. Two reviewers' wording, applied together, repeated "Agentic engineering" two lines apart. Check pasted reviewer text against the operator's latest decisions too: one review credited the apps to Dashecorp, which the App Store listing contradicts.
  10. Verify exact-string Dev-E issues before filing. Dev-E matches each before text literally. Run verify-issue.py <issue.md> <clone at current main> right before gh issue create, and again if main moved. Two PRs landed between drafting and filing stigjohnny-no#31, and the draft had to be updated.
    • When you watch for Dev-E's PR, match Closes #N (or Fixes or Resolves), not a bare #N. On 2026-10-16 a watcher for stigjohnny-no#33 matched old PR #24, whose body mentions "invotek-no #33", and ran its live check before the real PR existed.
  11. Review public copy before it goes out. Run three independent read-only reviews of the exact texts and images, one lens each:

    • the buyer;
    • language and platform craft;
    • facts and risk: claims, promises, confidentiality and marketing law.

    Put the texts in ctx/review-pack.md (effort high, about 5–8 minutes in parallel), then merge the fixes and show the operator the result. The 2026-10-16 round (rev1016-*) caught three errors in already-approved posts: - apps credited to the wrong company; - "code review" when an AI agent does it; - examples traceable to a confidential prospect's prototype.

    The whole pipeline can run on the Mini, as it did on 2026-10-20 for a one-page offer and an invotek.no section: 1. A drafting job (effort xhigh) writes from a context pack: the live texts plus a binding constraints.md. 2. The three reviews run in parallel on the draft (high, about 3 minutes). 3. A merge job (xhigh) reads the reviews and the live texts, merges the fixes, and lists the operator's decisions with defaults.

    To check claims against a public page, list only that domain in the merge job's tools file, e.g. WebFetch(domain:apps.apple.com). WebFetch quotes at most about 125 characters, so it checks subtitles and short quotes, not whole descriptions. That round also caught live-site copy that contradicted the new offer (an unmeasured "one consultant does a whole team's work" claim, and the internal agent platform offered to clients), and «Timer» (Norwegian for "hours") where «Tidtaker» was meant.

Job results last on the Mini, not on the MacBook. ~/jobs/<job>/out.json (its result field) keeps every job's output; the 2026-10-16 jobs were still there on 2026-10-22. The MacBook scratchpad doesn't last: its result.md copies, renders and helper scripts from 2026-10-16 were gone by 2026-10-22. Point memory at the Mini copy, and put anything that must last in GitHub (an issue, a repo doc).

A job's clock times are not evidence. A job writes times into progress.md that it never read from a clock. Job h12 (2026-10-19) ran from 09:38:35Z for 22 minutes (its ledger row), which is 11:38 to 12:00 on the Mini's clock, yet its log reads 11:50 to 13:50. Take times from the ledger, out.json or GitHub, never from a job's log. Brief jobs to write no clock time they did not read with a tool.

Allowlist gotchas (headless -p mode)

  • The fence is not "allowlist only". Even under --setting-sources project, Claude Code auto-approves its own read-only Bash commands and the Agent and ToolSearch tools, and lets a session read its own tool-results/. Subagents inherit the allowlist, so audit their transcripts too (~/.claude/projects/*/<session>/subagents/*.jsonl; audit-job.py does). Seen in job invotek-review (2026-10-14, allowlist Read(./**), WebFetch, WebSearch, claude-3#1159):
  • ran: ToolSearch; Agent, which started 5 subagents; sleep 240 in the background; grep -n -i … on its own tool-results file; printf '…' | LC_ALL=en_US.UTF-8 wc -m; a Read of its own tool-results/*.pdf, outside ./**;
  • denied: curl, awk, &&/; chains, commands with $var, a heredoc python3, and cd /tmp && mkdir. A plain pipe of read-only commands (printf … | wc -m) ran.

Nothing harmful ran, but don't brief a job as if it can only call what is listed. - Bash(bash scripts/check-:*) never matches: a prefix rule must end at a word boundary. List each guard exactly, e.g. Bash(bash scripts/check-tenant-scope-guard.sh). - Compound commands (a && b), $(…), loops, cd, git -C, redirects to /tmp and rm all need approval, which means they are denied. Brief jobs to run single commands, write test results inside the repo (--results-directory TestResults-<job>), and use the Write/Edit tools for scratch files. - Bash(git diff:*) is not a write fence: git diff --output=<path> writes any file. Grant exact command forms instead of git diff:*. - git checkout -- <file>, git range-diff, git check-ignore and mkdir also need approval, so they are denied headless. - An unquoted ; inside an argument (e.g. -clp:ErrorsOnly;Summary) reads as a second command and is denied. Quote it. - WebFetch is not granted unless the tools file lists it, so jobs cannot fetch BRAIN.md. Put what they need in the brief. A domain-scoped entry such as WebFetch(domain:apps.apple.com) works (job kart-merge, 2026-10-20). - In dashe-cutie (2026-09-29, job cutie-gate), the allowlist that worked in infra let the job edit .github/workflows/*.yml but denied its edits to *.md files and its git commands, so it could not commit. Brief such jobs to write the doc text into progress.md and leave the edits uncommitted. The orchestrator then applies the patch and the text. - node --test <file> runs without prompting. Test files that import ioredis etc. do not load without node_modules, so logic under test belongs in pure, dependency-free modules (the rar#744 and rar#747 pattern).

  • In claude-3, every Edit or Write under .claude/** is denied headless, even with Edit(./**) (job c3-nutrieapp, 2026-09-29). Skill edits are the orchestrator's.
  • Use absolute paths in Read, Edit and Write rules: Edit(//Users/claude/repos/<clone>/k8s/**). A relative ./ rule is matched against the job's current directory. In job dell-fix (2026-10-14) the first writes passed, then every write was denied once the directory moved, and half the work was lost. The rerun with absolute rules wrote everything.
  • A job can read its own job directory only through an absolute rule, such as Read(//Users/claude/jobs/<job>/**); job dell-622 read its evidence bundle that way. Without one it can only write progress.md there. The older workaround still works: put the input in the repo as an untracked file listed in .git/info/exclude (job nutri-retire).
  • The orchestrator runs tofu validate and tofu test. A job cannot run tofu in a module subdirectory, because cd, -chdir and -C are all denied. Use a throwaway copy of the module with its backend block removed and a plan-only tofu test; that proves validation blocks, which validate skips.
  • A job cannot delete or rename files. In job w633-gitops (2026-10-14), rm, mv, git rm, git mv, git apply, git restore and git update-index were all denied. So the CronJob that the job replaced stayed tracked, and the orchestrator removed it after bundling the branch back. When a brief removes or renames files, grant exact forms such as Bash(git rm apps/x/old.yaml), or leave that step to the orchestrator.
  • Check the output against repo budgets the job doesn't know about. dashecorp/infra's docs-check fails when AGENTS.md exceeds 8,192 bytes, and a one-line addition broke it (infra#637).
  • claude-3's project SessionStart hook still fires under --setting-sources project and tells the job to run /load-memory. The brief says to ignore it.
  • Headless Chrome hangs over SSH on the Mini. --headless=new --print-to-pdf never finishes, even with --use-mock-keychain --password-store=basic, because the claude user has no GUI session (2026-10-15).
  • Render PDFs and screenshots on the MacBook: "Google Chrome" --headless=new --no-pdf-header-footer --virtual-time-budget=8000 --print-to-pdf=out.pdf <url>.
  • Serve the folder (python3 -m http.server) when pages use absolute paths such as /styles/….
  • Send the page count and any layout problems back to the job.

The Codex CLI (2026-10-21)

The operator asked to "outsource" portal issues to Codex. The Codex CLI is installed for post on the Mac Mini (~/.local/bin/codex, 0.160.1, already logged in), and the rig's Dev-E Codex lanes use the same tool.

  • Run it in a fresh clone of the repo in the session scratchpad, on a feature branch: codex exec -s workspace-write -o <last-message file> "<brief>", in the background. The brief holds the issue's text and the repo's rules: tests first, the docs to update, and the files it must not touch (wrangler.toml, migrations, .github/).
  • It can't commit: the workspace-write sandbox makes .git read-only. Tell it to leave its changes in the working tree. The orchestrator reviews the diff, commits with the session's attribution lines, pushes, and opens the PR, with needs-review in dashecorp.
  • Its test run isn't the verdict: tests that open a localhost port fail inside the sandbox with listen EPERM. Rerun the full suite outside the sandbox before committing.
  • Check the names it assumes against the code, such as audit event types. Some are built at runtime (for example `discussion.${kind}`), so a plain grep can miss them.
  • Delivered this way on 2026-10-21: dashecorp/invotek-portal #194, #198 and #200, each reviewed by the orchestrator and Review-E.

Rules that stay in force

  • Never read, print, copy, export or rotate the Mini's CLAUDE_CODE_OAUTH_TOKEN. Use it only through zsh -ic.
  • Never touch the live iBuild-E config (~/repos/claude-3/agent-runner/agents/ibuild-e.json has a live local edit). Use a separate clone or worktree (~/repos/claude-3-944).
  • For launchd plists and scripts, collect sanitized metadata yourself (env var names only, long values masked, as in launch-inventory.py) and give only that to a job.