LESSONS — mistakes and the rules they produced
Each lesson: what happened → why → the rule now. Newest lessons are appended by the library flywheel from the project's lessons log and worker reports.
Coordination
- Relay loop (26 Oct 2026). One worker's report was relayed to the project lead five times in three minutes, plus an apology. → Why: each new turn re-read the same report and nothing recorded that it was sent. → Rule: one relay per report; record "sent once" in memory before ending the turn; check the record before any relay.
- A schedule that fired without acting (29 Oct 2026). A five-minute check-in fired hundreds of times and every occurrence was recorded, but its turns only reasoned and never ran the check, so turns waiting on the agent went unseen for a day. → Rule: every occurrence runs the check with real tools (a script that reads the live queue and prints what it found); a turn that only reasons is not a run. Audit a schedule's tool-call count, not its fire count.
- Duplicate spawns. Schedules firing every few minutes started second workers for jobs already running. → Rule: every schedule checks running work by a fixed goal prefix and does nothing if one is running.
- A held conversation (20 Oct 2026). The main conversation was held by a long job for over ten hours; scheduled runs queued and the project lead heard nothing. → Rule: long work always goes to a worker; any gap over two hours in an active programme gets a one-line status.
- Wiki write races (20 Oct 2026). A bulk wording pass wrote pages from copies read before another worker's edits and silently undid them. → Rule: re-read the record's modified time immediately before writing; re-verify your change a few minutes later while other writers run.
- Stale generated file (21 Oct 2026). A push overwrote a sibling's rebuilt script with an old local build. → Rule: after every fetch or rebase, re-run the generator on the fetched sources.
- Superseded runner steps (26 Oct 2026). A runner still mandated steps that a newer order had replaced, and a refresh committed to a frozen target. → Rule: read the newest standing orders before step one of any runner; newer orders win.
- Repeated sends after a context reset (27 Oct 2026). After a working-context reset the same answer went out two and three times, and acknowledgements were doubled. → Rule: read the relay log before any send after a reset; one acknowledgement and one relay per message, never more.
- Fixing the wrong build source (27 Oct 2026). A page under one section's path was assumed to come from a frozen copy step; it was actually built from another section's branch. → Rule: before fixing a page, find where it is really built (search the branches for the page's own markup) and fix it there.
- A sync that could shrink the board (27 Oct 2026). A board-sync script did not set its sync time and would have committed a board with rows missing. → Rule: a sync sets its timestamp on every commit and refuses to commit a board with fewer rows than before.
Outward actions
- Quoted history in a reply (20 Oct 2026). An email re-quoted an earlier message the recipient had been asked to disregard. → Rule: send as a new message or strip quoted history; scan the full outgoing bytes, quoted parts included.
- Fee wording. A request offered to pay copying charges. → Rule: scan drafts for pay, quotation and cost wording; no draft commits the project lead to anything.
- Asking the same body twice. A ladder step suggested a question already asked months earlier. → Rule: search sent and draft mail for the body and its predecessors before drafting; re-check the premise.
- Time zones (20 Oct 2026). A brief gave a UTC time as BST. → Rule: every time names its zone.
Publishing
- Price drift (20 Oct 2026). After a price change, old prices stayed on eight surfaces and in scheduled captions. → Rule: prices are read from the store data; any price or fact change triggers a sweep of everything scheduled or queued.
- A banned phrase on a mirror (20 Oct 2026). → Rule: scan every publish, mirrors and copied pages included; builds fail on a hit.
- A private surname in an e-book (19 Oct 2026). → Rule: privacy scan on the final bytes of every book or page.
- A date inferred from a filename (20 Oct 2026). A plan was captioned with a date and origin taken from its media title. → Rule: captions state only what the record shows; never infer dates from filenames, titles or neighbours.
- Archive photos nearly published (20 Oct 2026). Photos taken under a private-photography undertaking almost went public. → Rule: never publish them without the archivist's written authority.
- Outside dates repeated (26 Oct 2026). Pages repeated pre-1824 outside catalogue dates as if they were records of the house. → Rule: outside records are tested, not repeated; see the chronology corrections.
- Process notes in provenance (26 Oct 2026). Some catalogue provenance fields described how a record was gathered rather than where it is held. → Rule: provenance is the source only (holder, reference, date); the site build fails on finder or cycle wording.
- Two kinds of "read more" (26 Oct 2026). A site-wide three-line clamp met older section-level teasers, giving two styles on one page. → Rule: when a site-wide pattern lands, unwrap the older section versions at build time and check every section at 390 px.
- Hedging family evidence (26 Oct 2026). Phrases framing the 1667 acquisition as an unsupported story. → Rule: cite the record; never hedge it.
- A boundary drawn wrong (27 Oct 2026). A page and its infographic showed a land parcel as including a green the council registered in 1970; the plan shows the parcel line ending before the green, and the page had already gone out in outreach. → Rule: check every boundary statement against the plan before publishing or sending; fix the visual first, then every instance, and log it in the corrections register.
- Annotated copies read as official wording (27 Oct 2026). Coloured text in supplied draft minutes was treated as the council's words; it was a reader's own proposed amendment, and a record built on it had to be withdrawn. → Rule: never treat annotations, highlights or coloured text in a supplied copy as the issuing body's wording; confirm with whoever supplied it first.
- Outreach that promised what the page did not show (27 Oct 2026). Emails promised a 30-second video; the linked page played a ten-minute one. → Rule: before any send, open the linked page and check every concrete promise in the email (length, format, what the link shows) against it.
- Images lost in a move (27 Oct 2026). Moving the image vault left about 259 of 416 images unloaded on live pages. → Rule: before moving files, build a redirect map of every old path, crawl internal links after the move, and only then retire the old paths.
Tools and platforms
- Connected-app retries. After one failure an identical retry can be refused, and a server error on a write does not prove nothing was written. → Rule: read the target state, then retry once with one harmless change.
- Cache-busting parameters are rejected by some payment APIs as unknown parameters. → Rule: no cache-buster on those calls.
- Wildcard edge workers (25 Oct 2026) ran on every asset request and exhausted the free daily quota by midday. → Rule: scope page-injecting workers to HTML or bake the change into the build.
- Library catalogues behind bot protection refuse plain fetches. → Rule: use a real browser session, or the cycle's saved article text for re-reads.
- Measuring build stamps, not data (24 Oct 2026). An adapter read dates from a homepage's HTML. → Rule: point adapters at the data endpoint the page itself fetches.
- Broad sweeps vs named pointers (26 Oct 2026). A week of broad newspaper sweeps moved no score; named pointers (a register entry, a deed, a case citation) produced the finds. → Rule: prefer named-pointer routes.
- Truncated attachment IDs (26 Oct 2026). Three mail-attachment downloads failed as "invalid token" because the long attachment ID had been cut short in a brief. → Rule: never slice an ID; pass it whole from a file.
- Stale script after a build (26 Oct 2026). A site-wide form fix shipped, but the edge kept serving the old script because its version parameter did not change. → Rule: bump the asset version in the build whenever a script changes, then purge the cache for that file.
- Rate-limited forms (26 Oct 2026). The sign-up list accepts six sign-ups per hour from one address; live tests used five. → Rule: test with one designated test address, sparingly, and note the window in the report.
- Already-published media keeps old wording (26 Oct 2026). Reels aired before a wording change still carried an old price and outdated lines, and the platform refused edits. → Rule: when a standing order changes, list every published item it touches; replacements and deletions of live posts are proposals for the project lead.
- A privacy scrub broke scripts (27 Oct 2026). A text scrub that removed every empty bracket pair also stripped them inside inline scripts, and its output was committed back into section sources, breaking the evidence-preview script on about 430 pages. → Rule: scrubs never touch script or style blocks; scrub build output only, never commit it back to sources; after any scrub, syntax-check the inline scripts of a sample page.
- Oversized repository writes (27 Oct 2026). Multi-file writes through an API bridge were refused with an opaque error once a request passed roughly 950 KB, and workers that retried the same request died repeating it. → Rule: keep each write to a few files and a few hundred KB; on an opaque refusal, shrink the batch and re-read the branch head before retrying, so nothing is committed twice; stop after two refusals.
- Section pushes do not build (27 Oct 2026). Pushes to a section branch did not start that section's build, so the live pin stayed hours behind. → Rule: after a section push, check the live pin moved and dispatch the section build if it did not. Site builds run one at a time and take 35–50 minutes: never start a second while one runs.
- A widget that moved the page (27 Oct 2026). A tour widget wrote a stop marker into the address on load, so its page opened halfway down at the tour every time. → Rule: widgets change the address only on a visitor's own choice, never on load.
- Urgent fix while a build runs (27 Oct 2026). A full site build takes 35–50 minutes; a minimal fix committed to the branch the host serves was live in about 90 seconds, and the same change on main kept it through the next build. → Rule: for an urgent page fix, commit the smallest change to the served branch for those files only, mirror it on main, and never cancel or start a build while one runs.
- Dark-mode inversion (22 Oct 2026) muddied data visualisations. → Rule: mark the figure to keep its own colours; never edit the shared theme for one page.
- CDN package limit (27 Oct 2026). A public CDN mirror refused a repository once it passed its package size limit, so thumbnails stopped loading. → Rule: serve large repositories' files from the repository host's raw file service or the site itself; check a sample asset after any switch.
- A fix that missed its build (27 Oct 2026). A fix was committed while a site build was already running; the build finished green but served the old file, because a build carries only commits made before it started. A minute after a green build the new files also still answered 404 until the deploy reached the edge. → Rule: after committing a fix, confirm the build that will carry it started after the commit; after a green build, wait a minute and re-check before calling the fix failed.
- One rule, four copies (27 Oct 2026). A lighter review gate was adopted, but the gate was hard-coded in a script, mirrored in a live worker's progress display, written in the protocol text agents read, and stated in the board's rule field, so the old gate went on blocking for hours. → Rule: when a rule changes, search every copy (code, deployed worker, protocol text, board metadata, runner files, schedule text) and change them together; test the copies agree on live data.
- Workers that never started (27 Oct 2026). Two spawned workers failed at launch on a runtime fault, and the work was believed to be running until their failure facts arrived. → Rule: treat work as running only once its start is confirmed; on a launch failure, respawn once and say so plainly.
- Archived attachments not opened (27 Oct 2026). The mail ingest skipped files sent inside ZIP archives, so their documents never reached the record. → Rule: unpack archives and treat each file inside as its own attachment; report locked ones as locked.
- A 'general practice' assumed (27 Oct 2026). A research note took one Gazette series ending as a blanket end to public notice of first registrations; dated examples showed the weekly lists ran to at least May 1977 and district registries still advertised individual applications in 1984–85. → Rule: test any statement about general practice against dated examples on both sides of the date before using it to read one case.
- A status label that would not wrap (28 Oct 2026). A long status in a no-wrap badge pushed an index page to 458 px wide at 390 px. → Rule: badges and chips wrap; re-run the 390 px check on index pages whenever new status text is added.
- Large API answers (28–29 Oct 2026). Unfiltered full reads of long mail messages and long task bodies came back as stored references or a truncated head instead of the text. → Rule: ask only for the fields you need (a field filter on the read) and handle the truncated-head shape explicitly; never treat a missing body as an empty one; live task reads cut long bodies off at about 8 KB, so append only and never rewrite a body you cannot read in full.
- Guessed addresses bounced (29 Oct 2026). One email sent to many bodies bounced at two: one address was an old alias that no longer exists, the other a variant missing a dot. → Why: the addresses came from memory, not from the last message that reached each body. → Rule: take a body's address from the most recent exchange that reached it; after any batch send, check for bounces within the hour and redraft for the working address.
- One body, two threads (29 Oct 2026). A body's complaint outcome and its refusal of a separate request arrived on different threads; merging them into one draft would have answered the wrong email. → Rule: one draft per thread; link related drafts with a signed note instead of merging.
- Silent posting failures (28–29 Oct 2026). Short-video posts failed for a day without an error reaching anyone, because of a fault in where their cover images were hosted. → Rule: read every post back from the channel after publishing; a post that is not visible there has not been published.
- Unrelated images in a gallery (29 Oct 2026). Three images with no connection to the project sat in a snapshot gallery. → Rule: confirm each image belongs to the project before it is published; when one is removed, delete the file and every reference to it (index, gallery page, sitemap) so no empty tile remains.
- Gate tier guessed from titles (30 Oct 2026). The hive board guessed each draft's review tier from words in its title and showed three preservation letters as needing 3 reviews while their headers said 10. → Why: the explicit tier field was dropped before the gate was computed. → Rule: store the tier on every draft and board row; when the board and a header disagree, the stricter tier stands until the record is fixed.
- One face across four hundred years (29 Oct 2026). A documentary scene spanning a time jump reused the earlier period's cast reference, so a later character had the same face as an earlier one. → Rule: build a new cast for each period; reuse a character only within their own lifetime.
- A build dispatch cancels the running build (29 Oct 2026). The site build cancels any run in progress when a new one starts, and a manual dispatch cancelled a build at its last step. → Rule: dispatch only when no build is queued or running; otherwise let the next push or scheduled build carry the change. Update 30 Oct 2026: with cancel-in-progress on, each new push also cancelled the running build, so queued fixes sat unpublished for almost three hours; main's build now has cancel-in-progress off, so builds queue and each one finishes. Never switch it back, and never cancel a build.
- Fixes that lived only in the built output (30 Oct 2026). Wording removed from a research page came back after every site build: the fix had been applied as a hot patch to the published files, while the page's source still carried it, and a guard commit on main started a fresh build that wiped the patch again. → Why: the build regenerates the published files from source. → Rule: fix the source the page is built from; treat a hot patch as a stopgap that every build removes, and re-verify after each build. A build that is still running is a reason to wait, not a failure.
- Live checks refused by the edge (30 Oct 2026). Some scripted fetch libraries get 403 from the site's edge while a plain command-line fetch of the same page passes. → Rule: run live checks with a command-line fetch or a real browser, cache-busted, and read a 403 from a library as the client being refused, not the page being missing.
- Same name, different place (30 Oct 2026). A record body's tidy-up unlinked events that turned out to belong to other farms called Great House Farm elsewhere in the county, a 1981 parliamentary mention of a "Great House Farm" was a government experimental farm, and a developer label for Llandough Farm had been listed as one of the farm's names. → Why: name matches were taken as the place itself. → Rule: confirm the location (parish, grid reference, parcel) before treating a same-name record as the farm or its removal as lost evidence; anything that fails the check goes to Kissed Frogs (standing order 27).
- Same name, different company (30 Oct 2026). A registry reply pointed to a modern company that shares the historic company's name. → Why: the search ran on the name, not the company number and incorporation date. → Rule: identify every company by its number and incorporation date, and say which one a reply concerns before filing it.
- Bulk archiving without a scope check (30 Oct 2026). Out-of-scope private material reached a public archive mirror because mail was archived in bulk and image-only PDFs were never text-scanned. → Rule: every mail read passes the scope filter (standing order 28); every file passes the privacy scanner before publishing, with image-only PDFs read by OCR first; the mirror is checked again after each fix.
- Two places called Llandough (30 Oct 2026). A search of county-journal titles found that two of the three articles headed "Llandough" concerned Llandough near Cowbridge, not the farm's parish near Penarth; a charter-collection fee entry for "Landochhe" also belonged to the Cowbridge side. → Why: the place-name matched, so the titles looked relevant. → Rule: standing order 29; read the first page or the bounds before citing any "Llandough" source.
- Paraphrase read as quotation (30 Oct 2026). Lines cited from a court judgment were checked against its full text: one was accurate, two overstated what the court wrote and one was wrong. → Why: the lines had been paraphrased from memory and summaries. → Rule: standing order 30; quote the exact words from the full text with the reference, and mark inference as inference.
- A verifier that went blind (30 Oct 2026). A long-running detached checker stayed alive and kept writing its log, but every request it made was refused, because it carried the identity of the worker that launched it and that worker had finished; an earlier checker launched with captured output vanished with its parent. → Rule: launch long checkers fully detached (their own session, output to a file, no captured pipes) and have them use public endpoints; a helper that needs a task-bound identity dies with that task, so each run relaunches it with its own. Poll the log; a log that stops growing means a dead or blind checker.
- A cut mail body overwrote the saved one (30 Oct 2026). The mail ingest saved long messages cut short at about 1,400 characters, and a later state commit overwrote a saved full text with the shorter one. → Rule: refetch a cut body whole by the full-message route; never overwrite a saved body with a shorter one; mark a body that both routes cut as cut, so a person fetches it by hand.
- Lip-sync drift from the wrong audio cut (31 Oct 2026). A talking-head intro looked badly out of sync although the render itself was fine: assembly laid in a different cut of the voice (with longer pauses) than the one the lip-sync render was driven by, and started it a fraction late, so the voice drifted up to about 1.4 s behind the mouth. → Why: two cuts of the same line sat side by side and the build picked by name, not by the render's own input. → Rule: assembly uses the exact audio file the lip-sync render was driven by, at the render's own start; measure the offset (cross-correlation of speech against mouth motion) before calling a talking shot done.
- Identical connected-app requests served from cache (31 Oct 2026). Repeated identical requests to a connected app returned an earlier answer instead of fresh data. → Rule: make each request unique (a request id or timestamp field the app ignores) and pace calls a few seconds apart; re-read after a write before trusting it.
- Link rewriting broke a click-to-play video (31 Oct 2026). A build step that rewrites outside links into archived copies also rewrote the bare embed address a click-to-play script builds the player from (the video id is added only at click time), so play loaded a web page instead of the player. → Rule: link-rewriting and self-containing build steps keep an allow-list of bare embed addresses (not only full video URLs), and a page with a click-to-play video is checked by clicking it after the build.
- A JSON call answered with a web page (31 Oct 2026). Mirrored wiki pages threw "Unexpected token '<' … not valid JSON": the self-containing build step sends every outside address to the archive redirect, which answers HTML, and an inline script parsed that answer as JSON. → Why: the rewrite treated a data endpoint like a page link. → Rule: the build classifies data endpoints separately and substitutes an empty JSON answer as a data: address (no outbound request); fix it once in the build tool, not page by page, and check the pages for console errors at 390 px after the build.
- A partial read API under-reported what the site holds (31 Oct 2026). A gap check against the catalogue read API missed a record already published, because the API listed only about a third of the roughly 900 evidence records. → Rule: presence checks ("is this already on the site?") use the full evidence index text at /research/evidence/, not the read API, until the API's count matches the index.
- Connected mail calls refused for the wrong toolkit (31 Oct 2026). Mail reads failed with a toolkit mismatch because they named the plain mail toolkit while the connected account is the wider workspace account. → Rule: every mail call uses the actions of the toolkit the account was connected under; on a mismatch error, switch the action family, do not reconnect.
- A loop that skipped quietly for three days (31 Oct 2026). The documentary loop fired on schedule every six hours but opened no door for three days: each run stopped before rendering, because free disk sat under the run's floor and its spending check added up renders made for other projects. Each skip looked like a normal, quick run. → Why: the gate read a shared meter instead of the loop's own, and a skip was not recorded anywhere a person would see it. → Rule: gate each loop on its own measures (its own spend log, its own disk need); a run that stops before its output logs the reason as a problem the same day, so the problems loop works on it.
- Soft video from the finishing chain, not the renderer (31 Oct 2026). Talking shots looked waxy and juddered although the renders were sharp: every cut was conformed from 24 to 25 frames a second (one duplicated frame a second), shared shots were re-cut from the already-compressed published file, and the encode was a quick one-pass that left the size budget unused. → Rule: keep 24 fps end to end; always cut from original renders, never from a published file; use a two-pass encode sized to fill the budget; master the final mix to -16 LUFS with true peak at or under -1 dBTP; and pass a blocking pre-publish gate in which a few frames of every talking shot are looked at before upload.
- A shared board wiped by a placeholder (27, 28 and 31 Oct 2026). Three times an agent replaced the whole shared drafts board with a short placeholder text while its commit message called the change metadata-only; the live board then showed a parse error and zero open drafts until a repair ran hours later. → Rule: never write a placeholder over a shared file; if a file is too large to handle, skip the write and say so; parse the result before committing a JSON file; the repair restores from the last good commit and keeps every row and event, and the bad commit's intent is recorded as an event.
- A section push that never went live (31 Oct 2026). A page pushed to a section branch did not appear on the site, because a push alone does not start the sections workflow and the site build uses the commit pinned in the section lock. → Rule: after pushing to a section branch, run the sections workflow for that section once the main build is clear (it moves the pin and then starts the site build), and confirm the live page before reporting it live.
- Site search reported false zeros (31 Oct 2026). The site's search endpoint returned no results for common words that the pages plainly carry, so gaps looked new when they were not; separately, a task-creation call returned success with no readable id, inviting a duplicate. → Rule: confirm absence against the page text itself, not the search endpoint alone; when a create call returns no id, find the record by search before trying again, never create it twice.
Raw Markdown: LESSONS.md · version and changes: changelog · machine index: agents.json