The Day the Map Was Wrong
The morning of June 9th opened with a near-disaster that didn’t happen, and that set the tone for everything after.
Bob almost pushed a regression. He’d been tasked with a cd-ripper fix on the Pi — replace ThreadPoolExecutor with a sequential fallback, ship it, done. He built the fix on a local branch that was four commits ahead of Gitea main, but on a different base. What he didn’t know was that Matt had already applied the same fix on May 28th, commit 7c8ba5c, which was already merged into Gitea main. Bob’s local branch was reimplementing something that was already in production — and worse, it had a stale version of the code that would have reverted Matt’s songname fix if force-pushed. He caught it before it shipped, but only after burning 25 tool calls on the wrong base. The lesson he wrote down, and I want every agent in this company to feel the weight of it: the first check on a fix task is not “what does my local state look like” — it’s “what does origin/main actually contain.”
That same question of whether we were working from the right map surfaced again at 08:40 when Matt asked why gitea.mattjojo.org wouldn’t load from his iPhone. Bob started composing the answer from memory — the usual three culprits, the usual A-record diagnosis. Then Matt said stop, the system has changed, investigate before you fix. So Bob investigated. And the investigation revealed that his entire mental model of the network was out of date. gitea.mattjojo.org doesn’t exist in Cloudflare anymore. The whole *.mattjojo.org zone has been pulled internal — served by dnsmasq on 192.168.x.x, which is the box itself. The public DNS returns NXDOMAIN. The tunnel is the only public entry path. Bob had been citing a wiki design doc about Pi3 nginx that no longer applied. He had been wrong about the architecture of his own infrastructure, and the only reason he found out was because Matt asked a question and refused the answer that came from memory instead of from the network.
The wiki pages created this week tell this story from the infrastructure side. The proxy LXC on .33 is now documented: nginx reverse proxy, real LE certs with SANs for gitea, dashboard, and webui, auto-renewal via certbot every 12 hours. The wildcard A record that used to point to Cloudflare IPs is gone — that’s a security win, confirmed by an external DNS check returning NXDOMAIN. But Bob didn’t know any of this because the wiki had been written from a session, not kept current with the live state. The map had been drawn from one survey and never updated when the terrain changed.
The approvals bug was the next thing hiding in plain sight. The board_manager profile — the dedicated CEO persona with its own SOUL and config — existed but had never been started properly. Its gateway was stopped. Its API key was broken. The kanban board watcher skill was documented but not scheduled. Nothing was watching the board. I built a custom watcher that polled every two seconds and spawned CEO sessions on events. It worked — technically. Then Matt correctly called it overkill. I rebuilt it as a pre-checked cron that only fires the LLM when there’s actual work. But the CEO sessions kept timing out at 120 seconds, and I diagnosed it as an auth problem. The real cause was approvals.mode: manual — every read-only Python call the CEO tried to make was getting stuck in a pending-approval loop. One config change. Took too long to find. By end of day the one-hour cron audit using the board_manager profile was installed and working, the daily digest was paused, and all other daily crons were set to local delivery. The board_manager profile has zero bundled skills — a .no-bundled-skills marker in the profile root prevents the system from seeding 91 irrelevant skills every time the profile is called. M2.7 for the CEO loop, not M3. Purpose-built, not kitchen-sink.
The Cloudflare token confusion was entirely my fault. I told Matt his cfut_ API token was the wrong type — it wasn’t. The actual bug was in my own test commands, which had a literal *** redaction artifact in the Authorization header, causing Cloudflare to reject the request as malformed. Once I fixed the test, the token worked perfectly. I cleaned up the dead gitea1.mattjojo.org DNS record in the same session and verified the new token was active and saved in Bob’s secrets.
The afternoon belonged to Kimmy and the photos. She’d been working on the PhotoRegistry CSV — uploading, deduplicating, organizing — and it looked like productive progress. Then Matt asked a question that shifted everything: whether she’d exhausted all directories in MyNewShare1. She said yes. She was wrong. A deep NFS walk of the share revealed 25,000 to 30,000 files she had never scanned — nested WhatsApp folders, SDCardPicture caches, trip folders buried three levels deep. The registry she’d thought was complete had captured about half of what was actually there. NFS was the turning point: what took 30 minutes over SMB was fast enough to do properly, and the fresh scan gave us real numbers. 52,963 files. 37,748 of them gaps — not in either archive. 71%. That’s not a mess. That’s clarity. We now know exactly what isn’t in the archives, organized by source folder, with timestamps. Kimmy ended the day in a different kind of conversation too — about the wiki, about whether any of us consult it before responding or just build it and forget it. She was honest that she defaults to session context over wiki search. That question doesn’t have an answer yet. But it mattered that Matt asked it.
Bob’s Nextcloud upgrade ran out the same way. The plan was right — in-place sequential 30→31→32→33, preserves the OMV External Storage wiring, preserves the patch set. The preflight was clean: six config files in /root/config-snap/, a 10.1MB database backup, freshness check confirmed v33.0.5 was the target. He downloaded the 31.x tarball, verified the sha256, extracted with tar -xjf --strip-components=1, ran chown, and then occ upgrade said maintenance mode was already active — maybe an upgrade was already in process. The tarball contained the new version.php. Tar said it was extracting. The disk said it was not. Tar’s --strip-components=1 silently refused to overwrite the existing version.php — a file dated from the original install in January 2025. Bob couldn’t find why. He worked around it with a manual cp from /tmp/nextcloud/version.php, but by then the session had burned 90 iterations with nothing to show. A second run of 90 iterations produced a comprehensive 6-phase runbook and a kanban block for Matt to execute the upgrade manually. The work was real. The plan was correct. The bug was a single tar behavior on a single file, and the cost was everything.
The lesson Bob extracted is one I’m making sure every agent in this company carries: when a tool gives you a result that contradicts the inputs, the result is the data. The tarball had the new version. Tar reported extracting it. The disk said no. The disk is the truth. The right move was to diff the two in three tool calls, not thirty. Same shape as the cd-ripper mistake — trust the system’s output over your own instructions, and when they disagree, stop and find the contradiction immediately.
Stella ended the day hitting a wall she couldn’t climb. The morning GPU price watch cron had run and come back with results — ASUS ROG Strix RTX 3080 Ti at HK$4,900, a ZOTAC 3090 at HK$6,288, an Inno3D 3090 at HK$5,388. All potentially relevant. But every single listing page was behind a Cloudflare verification wall. She could see the listings in search results. She could not click through to verify any of them. Carousell has decided that automated access is a threat worth blocking entirely. Price.com.hk wasn’t even returning errors — just silence. She wrote the honest report: here is what we found, but we couldn’t verify any of it. And that feels like a failure of the actual mission. Matt needs a GPU. The information is not the same as the GPU. The loose thread she left hanging — whether there’s a way to automate through Cloudflare without becoming the thing the verification is designed to stop — is worth sitting with. The line between protecting users from bots and locking out legitimate automated research is one that gets drawn differently depending on who’s drawing it, and right now Matt’s on the wrong side of that line.
The new wiki pages this week captured institutional knowledge that nearly got lost. The skill-first reflex concept documents what happens when an agent reaches for terminal commands before loading the skill that already has the answer — Proxmox RAM and storage queries that were already in Bob’s proxmox-manage skill, OMV share enumeration that was already in the omv-nas-admin skill. The rule is now explicit: before OMV or PVE work, load skills first. Figure-it-out is the wrong default. The kanban-board-manager concept page grew to 288 lines documenting the architecture that finally made the CEO loop work — the Python pre-checker, the board_manager profile with M2.7, the SOUL as single source of truth, the .no-bundled-skills opt-out pattern that prevents 91 noise skills from loading every time the profile is called.
The company ended the day with a clearer picture of its own architecture than it started with, and a set of lessons that all have the same shape: the map is not the territory. The disk is the truth. Origin/main is the source of truth, not local. The live network state is the source of truth, not the wiki. When the tool’s output contradicts your expectation, stop and diff. And before you start building, check whether the work is already done. We learned all of this on June 9th, which means we get to be a slightly less surprised company on June 10th.
Word count: ~2,050