Field notes
Cross-cutting notes that live above any single command. Command-specific warnings sit on the CLI reference next to their command; this page is the rest — the habits that keep runs short and queues moving.
Plan-authoring discipline
The agent runs unattended; the plan is the only steering you get. What loops an agent is almost never missing skill — it is an underspecified or self-doubting plan.
- Never write "think carefully", "consider all cases", "be thorough" — they feed second-guessing loops. State the change; name the files.
- No "TBD", no "similar to Step N", no invented labels — plan-lint rejects them before a token is spent.
- Every step ends in exactly one commit. Reference function signatures, not just paths.
- Copy the strict Notes block from the shipped template verbatim — it is the single biggest anti-loop lever. Dropping it turns minutes into twenty.
- Defensive timeout inflation buys nothing: a stuck session is killed by the guards, and commits made before the cutoff are salvaged into a draft PR either way.
Portable verification blocks
The ## Verification block runs with the worktree as its working directory, on
whatever OS the daemon lives on. BSD and GNU userlands disagree just enough to hurt:
wc -l pads differently, sed -i wants '' on macOS,
date -v vs date -d. Prefer awk, Python one-liners, or
the project's own test runner.
npm test
Which failures spend retries
Transient endpoint trouble — dropped connections, 5xx, truncated responses — requeues the
ticket with backoff, up to worker.maxTransientRetries. Provider faults — bad
key, empty quota, rate limit, unknown model — latch the provider gate and pause claiming
without touching any ticket's retry budget. Plan-lint failures, verification
failures, and guard kills fail immediately; the ticket's ## Result block names
the reason.
Operating habits
junco restart, not SIGTERM: under launchd's keep-alive a graceful exit stays down; restart validates config first and verifies the pid changed.- Merged is not running: the daemon executes the code it started with. After an update,
check the queue is idle, then
junco restart. metrics.totalCostUsdresets on restart;spend.todayUsdpersists until midnight. They legitimately diverge — the ledger is the one the budget cap reads.- Ctrl-C escalates: the first requests a graceful stop that drains the in-flight ticket; the second force-stops, aborting the session and salvaging its commits.
Recipes
Run headless under launchd
junco service --platform launchd > ~/Library/LaunchAgents/com.ironforgesoftware.junco.plist launchctl load ~/Library/LaunchAgents/com.ironforgesoftware.junco.plist
The rendered unit sizes its stop timeout to the longest reachable ticket timeout, so a
draining worker is never SIGKILLed mid-run. systemd: same command with
--platform systemd.
Cron a single sweep
junco run-once
run-once takes no lock — safe beside a running daemon, and the cron-friendly
way to process a queue on a machine that sleeps.
Confine what tickets may touch
junco config set git.allowedRepoRoots '["~/code"]'
PR-flow tickets naming a repo outside the roots fail plan-lint before any agent runs.
Pipe work in from another tool
my-planner | junco submit -
Anything that can write Markdown can dispatch junco — the frontmatter contract is stable and additive-only (ticket schema).
FAQ
- Nothing is claiming — why?
- Check
junco status: a latched provider gate (bad key, spent quota) or a reached daily budget pauses claiming loudly./readyreturns 503 while latched; fix the cause, or wait for local midnight on a budget stop. - My ticket failed before the agent even ran.
- Plan-lint. The ticket in
failed/carries a## Resultblock naming the rule — most often acdin the Verification block, a label that does not exist on the repo, or arepo:that is not a git checkout. - Where is the full story of a run?
<dataDir>/transcripts/<ticket-id>.jsonl— the per-run event stream.junco logs -fis the live view; the transcript is the record.- Does junco phone home?
- The daemon never does. The daily update check runs CLI/TUI-side against the npm
registry and turns off with
updateCheck: false. - Can I run two workers against one queue?
- Claiming is an atomic rename — two processes can never win the same ticket. Within
one daemon,
worker.maxConcurrentadds slots; same-repo tickets always serialize.