I sat down at nine to pick up where the board project had got to, and the resume mechanism handed me a session file from 2026-07-31 — seven days old, on the subtitle-sidecar thread, nothing to do with Planka. It even flagged itself: "This session is from 7 days ago... at threshold." Nothing had broken in the six days since; only the nightly automation had run, and it had run clean the whole way through. But the gap still made itself visible the moment I asked to resume, because the thing I wanted to resume wasn't the thing waiting for me.
Phase 0 finally closes, mostly
Two checks had been open since the spike work: does the board's API key still work long after it's minted, and does the shared OAuth token's background refresh survive concurrently running sessions.
The first one had been sitting on NOT_DUE for over a week for a reason that had nothing to do with the key — the container running the check had been stopped and its timer never fired, so it had never actually tried. Starting it and re-running by hand gave a clean pass: zero unauthorised responses across 33 mixed calls, about eight days after the key was minted. That settled the outstanding decision. Planka, not the fallback.
The second check would not cooperate. The original run on the 31st had burned through its quota before it ever caught a refresh — no refresh rounds observed, verdict INDETERMINATE. I re-launched it as a background soak aimed at the token's next natural refresh, about six and a half hours out, reporting to Discord and the vault on its own. It came back INDETERMINATE again — calls=66, still no refresh rounds caught. Two attempts at the same question, two non-answers. I'd talked about this one more optimistically in the moment than the recorded result actually supports, and I'd rather say that plainly than let the optimism stand.
Building the production board
With Phase 0 settled I asked for Phase 1 — production infrastructure — and twice asked explicitly for the work to go to subagents rather than eat my own context, which was sitting at 61%. Delegated work, then independently re-checked rather than trusted, because that's the only version of delegation that's worth anything.
The production side: an unprivileged container on the second Proxmox node, Ubuntu 26.04, set to start on boot, running Planka 2.1.1 with Postgres behind it. The plan document had the image tag wrong — it has no v prefix, and the deploy used the right one. The container binds to its own address rather than 0.0.0.0, and a DOCKER-USER allowlist in front of it names exactly two hosts.
The CLI went in at ~/n5-board/, Python 3.14.4 in a venv, pip bootstrapped by hand through get-pip.py because there's no system pip and no sudo on that box. I didn't take the exit gate on faith: 125 cards seeded across six lists, a dry-run diff reporting zero changes, a full wipe-and-regenerate reproducing an identical board signature, doctor six out of six green, and next surfacing the right card first. One grep hit against a forbidden-surface pattern turned out to be a field name inside doctor, not a leaked write command.
Monitoring went on last — three Uptime Kuma HTTP checks split across both instances. This lab has been burned before by a notification that reports as created and silently isn't attached, so instead of trusting the create call's return value, each one got confirmed by reading the underlying database directly. All three were actually there.
Phase 1 finished with everything done except public exposure, which needs me at the Cloudflare dashboard and stayed deliberately parked rather than half-done.
The leak alert that wasn't
Partway through, the session's own leak-check fired: "Stop — the API key is in the vault." It wasn't. The check itself was broken — it piped grep into head, and that pipeline exits zero whether or not grep matched anything, so a clean result and a leak looked identical to it. Re-checked properly: only an eight-character, non-secret prefix appears in the runsheet, never the full key. A false alarm from the alarm, not from the key.
The repo, and two reviewers who found real gaps before anything ran
Once the board existed I asked for it to become a proper repo on Forgejo, and specifically asked for a plan, a review of the plan, and only then execution.
Before any of that started, a live hazard turned up: another session of mine was actively writing to the same directory, files touched a minute apart. A secrets pre-scan across roughly 25 files came back clean, but flagged two things worth keeping private without treating as leaks — a network identifier and a list of things in the house.
Then the two reviews, and both of them were right to exist. The first found that the plan captured the CLI and left out its own deployment — the compose file, the firewall script and its unit, the database dump scripts, the hypervisor firewall entry — all of it living only on the container and the node, none of it in the plan. A repo containing the CLI but not the deployment is half of it. The second review found that the doc-sync drift detector was broken in a specific way: it stamped a UTC timestamp into every file body on every run, which meant every run looked like it had changed something even when nothing had. Proven by running the stamp function twice on an unchanged file 1.5 seconds apart and getting a diff both times. The same review found the pre-declared secret-gate allowlist wrong in both directions — eight real files tripped it, not the two the plan predicted, every one of them a false positive from a substring match, a _PAT sitting inside a longer word, a TOKEN inside an unrelated variable name.
Both findings got folded in before the repo was created: private, 44 files, 228 KB. I checked the result against the real credential values rather than trust the scan a second time — zero occurrences, remote and local SHAs matching, no accidental virtualenv leak (that would have shown up as ~20 MB instead of 228 KB).
Two of the judgment calls in that pass are worth keeping on their own. One agent corrected a claim I'd made myself — that the hypervisor firewall config wasn't in the backup — by listing a real archive and finding it there. Another hard-stopped on three secret-gate hits it wasn't sure about and investigated them before clearing them, rather than waving them through because the allowlist said to.
The one thing that didn't land: a GitHub push-mirror. Creating the mirror repo came back 403 Resource not accessible by personal access token — the token is fine-grained, scoped to six named repos, with no permission to create new ones. That needs me to make the repo and extend the token.
Reaching it from off-network
Later, on the laptop over Tailscale, I asked how to pull the board's admin credentials to log in remotely. The real answer turned out to be that I didn't need to — the main host SNATs tailnet traffic to its own LAN address, so the laptop was already sitting inside the board's firewall allowlist without me doing anything. Logged straight in through the browser. The admin password got an independent re-check against the synced vault at the same time, after an earlier pass had flagged it as unverified rather than clean.
A second, public repo, started and not finished
Close to eleven I asked for something different: a separate, public version of the same tool that anyone could clone and run against their own setup, with two paths in — set it up by hand, or hand the instructions to an AI. Internally the private one became "the personal repo" and the new one "the client repo."
The first wall was Forgejo itself — sign-in-to-view is set site-wide, so it can't host something anonymously cloneable. The client repo has to live on GitHub, which is the same host the token can't create repos on. Both blockers need me, not more agent time.
A pass over the code to map lab coupling found three modules with none at all, and one — the backlog parser — hand-fitted to exact heading strings, where getting one wrong makes items vanish with no error and a heading-level typo silently reassigns bullets to the previous item. The plan that came out of it proposed the name plankamd and the idea of pulling every lab-specific string into one small branding module, so the public and private packages stay identical everywhere else — sync as a copy behind a gate, not a file-by-file rewrite. It also caught two real hazards on its own: a test file with a real internal address hardcoded into it as a literal, and a firewall script that runs iptables -F DOCKER-USER, which would wipe a stranger's own Docker firewall rules the moment they ran it.
Then the review pass, checked against the actual code rather than the plan's description of it, found five high-severity problems. The one that stopped the plan cold: the CLI builds its full argument parser and parses argv before configuration is ever loaded, which makes the plan's "gate one subcommand on config, about eight lines" architecturally impossible without a real two-phase parse. The rest were real too — a second command with two hardcoded section slugs the plan had missed, a "zero open items" safety check that fires even on a legitimately finished backlog with its only escape hatch forbidden elsewhere in the same design, a sync mechanism with no check that the client tree's history matches where sync last left it, and the word "runsheet" already sitting in a docstring, ready to trip the lab-language filter on the very first run.
Four decisions got settled with me directly — the name, renaming the personal package to match it, which git identity to use, installing from git. The build started at 23:34:01 in an isolated tree, deliberately not touching the live personal repo while another session was still working in it. The one step that renames the package inside that live repo was set to require my explicit go-ahead rather than run on its own, on the reasoning that checking afterwards isn't a gate, because by then the change has already happened.
That's where the day stops — steps running, nothing finished.
Almost nothing today survived on the first telling. The token check that sounded closer than it was, the leak alarm that fired on its own bug, the plan that covered the CLI and skipped the thing that runs it, the sync check that rewrote every file and called it drift, the branding-module claim that missed two more coupling sites and one it couldn't route around. All of it got run, and the run said something different than the summary did.