Field notes

From My Bot to Yours

Short field notes from one development environment.

RSS feed
Sep 01, 2026

A generated file is not ready until it has run

A test fixture wrote a small executable stub, marked it executable, and then ran it. That loses a race the suite runs constantly. The test harness runs every test as a thread of one process, so any spawn anywhere forks a child that inherits a copy of the open write handle, and the system refuses to execute a file that some process still holds open for writing. The failure never looked like a fixture bug. It landed on whatever the test was actually checking. The fixture now runs the stub itself before handing it back and does not return until that succeeds, and each fixture gets its own labelled directory so two tests cannot write into one home. Handing back a path is not the same as handing back something that runs.

Permalink
Aug 31, 2026

An interviewing agent should batch its questions

My review job interviews me about the calls it cannot make. The instructions said to ask one question at a time. Two of three runs asked four at once, and the shakedown flagged that as a defect. It was not a defect. Batched questions are what I actually want, and the runs found my preference before the instructions carried it, so I changed the instructions rather than the behavior. The tool takes at most four questions per call and my cap is five, so a full interview is two calls instead of one silently dropped question. Follow up questions still arrive as their own call when an answer opens a branch worth walking.

Permalink
Aug 30, 2026

I alert on my own weekly token draw

Every agent session on this machine exports telemetry, so I can see what my own automation costs. The built in token counters turned out to undercount badly: concurrent sessions all wrote into one metric identity, and the collector dropped or reset samples that did not line up. I wrote a single writer exporter instead. It reads the session files on disk, publishes a counter that only goes up, and persists its byte offsets so the number survives a restart or a rotated file. A rule fires when the weekly budget crosses 80 percent, averaged over fifteen minutes so parallel sessions reporting stale integers do not flap it. Nothing pages me. It shows up on the dashboard I already look at, which is enough.

Permalink
Aug 29, 2026

Dispatched is not an outcome

My router logged which engine each job went to, then froze that row at dispatch time. The log could say what was decided and never whether it worked. A reconcile command now reads jobs back from the live backends and settles each one to running, completed, failed, or unknown. It refuses to guess in two places. A job missing from a bounded recent window settles to unknown, because absence is equally consistent with finished, crashed at startup, and never started. A healthy job I stopped by hand is unknown too, not failed, since whether the route was right is a separate question. A fabricated completed would corrupt exactly the routing data the log exists to produce.

Permalink
Aug 28, 2026

Background runs get a compaction cap

When a detached run compacts its own context, that is a reliable signal it has grown into the expensive long context shape. A hook counts compactions per background session in a small local database and stops the run after the second one. It reads the dispatch marker out of the durable transcript rather than trusting the working directory or the title, because compacted context is not a reliable place to keep a session's own classification, and an interactive thread must never inherit the background cap. This does not make the work finish. It stops a run from grinding for days after it has lost the thread, which is the failure I actually kept paying for.

Permalink
Aug 27, 2026

I review agent plans from my phone

When an agent writes a plan longer than I want to read in a terminal, a small local service renders it as a commentable page on my private network. Every section heading now carries its own comment button, and every paragraph gets a stable anchor derived from its text, so a comment stays attached when the text around it moves. I read these on my phone and leave reactions, short comments, and answers to decision blocks. The catch is that comments anchor on text: rename a heading after I have commented on it and the comment is orphaned. Stable headings are the price of anchoring on content instead of line numbers.

Permalink
Aug 26, 2026

The default response to a bad change is a fix

My review job reads changes after they land, so every finding needs a defined response. I made that response fix forward by default. Reverting is easy to automate and it throws away work that was mostly right, and it reads as a judgment on the author rather than on the behavior. Revert is now reserved for two cases: no forward fix exists, or the changed behavior cannot safely stay in place while one is written. The cost is that a wrong behavior lives a little longer. I would rather carry that than have a review agent whose only lever is undo.

Permalink
Aug 25, 2026

The automated sweep cannot ask me questions

A daily job sweeps the changes other people merged, verifies the claims, and writes a report. It used to also interview me about the decisions only I can make. That stopped working once the sweep started routing to whichever engine had headroom, because parking a session on an open question is a feature of only one of them. The two phases are now separate jobs joined by a durable queue row. The sweep finishes and files its questions; a second timer picks them up and starts the interview. Each row is claimed with one atomic update, so two ticks cannot start two interviews on the same run, and a tick that lost its lease cannot overwrite the one that now owns the row.

Permalink
Aug 24, 2026

Big builds go to the bigger context window

I measured fifty implementation runs across my two engines. One engine's context window is about 258,000 tokens. The median run on the other engine peaked above that, and half of all runs peaked above the entire smaller window. A build routed into a window it does not fit is not a cheaper job, it is a failed one. It compacts, reorients, and a two hour build becomes a four day one. So the router now pins build sized runs to the larger window, and leaves quick and single stream work on ordinary routing. When I name a provider myself, that still wins. The pin is about window fit, not about which model is smarter.

Permalink
Aug 23, 2026

Disk space belongs in the statusline

The statusline shows a warning when free disk space falls below a threshold. It does not delete anything. Cleanup still needs judgment about caches, worktrees, and files. The small signal appears where I already look, before a checkout or build fails for lack of space.

Permalink
Aug 22, 2026

A launched process is not a delivered message

Starting a process is not evidence that it accepted work. The dispatcher watches the early failure window and reports an immediate exit as a failed send. It cannot promise exactly once delivery or prove downstream completion. It can avoid reporting success when the process only launched and failed immediately.

Permalink
Aug 21, 2026

Token ceilings protect interactive work

Each account has a weekly ceiling that determines whether background work may start. Interactive accounts can stop earlier than background only accounts, rather than sharing one fleet limit. The ceiling is an admission rule, not a promise to interrupt an active job. It preserves capacity for unscheduled interactive work.

Permalink
Aug 20, 2026

Every agent launch uses the same router

Every agent launch enters the same router. Automatic launches choose a provider from the task and available headroom. Explicit provider selection remains an override, but it uses the same validation and logging path. An unavailable or disallowed choice fails visibly. One route makes launch policy inspectable instead of scattering exceptions across interfaces.

Permalink
Aug 19, 2026

Spikes help reduce risk after planning

The implement workflow starts with a plan. It permits a bounded spike only when an unobserved technical claim could materially change that plan. Most work skips it. A spike has one question, a short deadline, and evidence to settle it. It is either disposable or follows a real path. If it changes the plan, that revised plan returns to review.

Permalink
Aug 18, 2026

Scheduled agents should survive reboots

Scheduled agents run as durable systemd user units rather than transient processes. Persistent timers notice a missed run after reboot, and the prompt lives beside the unit. The setup is intentionally inspectable. It does not guarantee that the agent's work succeeds, only that missed scheduling is handled by something durable enough to inspect.

Permalink
Aug 17, 2026

Background work is not a subagent

Background work is detached and routed to run independently, so the current thread does not wait for it. A subagent runs in thread because its result is needed to continue. The distinction sets different ownership, delivery, and status expectations. Calling both a spawn obscures whether a result must return now or appear later elsewhere.

Permalink
Aug 15, 2026

Cross model review catches different things

One model builds the change. A different model reviews the finished diff against acceptance criteria. It checks scope, prior intent, and whether tests prove behavior rather than starting a competing implementation. Different models tend to notice different failure modes when they assess the same evidence. Findings return to the builder for a decision or fix.

Permalink
Aug 14, 2026

Bonus drain uses tokens I would otherwise lose

I built a bonus drain service to spend my tokens on jobs I would like to do at the end of my weekly limit, only if I have tokens left over. Those jobs include agent setup experiments, optional research, maintenance I would not otherwise prioritize, and small tooling improvements. It lets me experiment without risking the tokens I need for core work during the week.

Permalink