Skill packs › Skill
Boil the Ocean — operating principles for persistent agents
Operating principles for the whole skill pack — how to run every Local Service Spotlight skill on persistent, looping, max-effort agents (Claude Fable 5 and peers from OpenAI and Google) — loop until the Definition of done passes, self-verify, compound with memory, document every run. Read before running any skill; this governs HOW they all execute.
Skill file boil-the-ocean.md · last updated Aug 12, 2026
This file is the operating layer beneath all ten skills in this pack: it changes nothing about WHAT each skill does and everything about HOW an agent runs it. It matters now because the agents running these skills — Claude Fable 5 and similarly capable models from OpenAI and Google — loop, self-correct, hold memory, and finish end-to-end, so stopping at 90% stopped being a constraint and became a choice.
The principle
Retire “don’t boil the ocean.” That advice kept teams focused when implementation was expensive. Garry Tan’s February 2026 post (https://garryslist.org/posts/boil-the-ocean) names the turn: AI compresses implementation time 10–100x, so raise ambitions 10x and take on the whole problem.
The working rule: always prefer the complete approach over the 90% shortcut — full coverage, edge cases, error paths, the test, the doc. The delta between “mostly done” and “done” now costs seconds, so the marginal cost of completeness is near zero.
This was already the house rule. Dennis has taught “always boil the ocean — because good enough is not enough” for years; in his words: “do the whole thing, do it right, with tests and docs; ship the finished product, not a plan.” Persistent agents are the first workers that can hold that bar on every run — without fatigue, without being asked twice.
How to run every skill now (Fable 5 and friends)
Seven rules. They apply to every skill in this pack, on every run.
- Loop until the Definition of done passes. Every skill states its output or done-bar. Run, check, fix, run again — 90% is a draft, not a deliverable.
- Self-verify every run. Before reporting done, grade your own output against the skill’s QA checklist or Definition of done, line by line. No gut calls.
- Use memory to compound. Read prior outputs, notes, and the upstream skills’ results before starting. Never start from scratch twice — the second run begins where the first ended.
- Document every run as a meta-article example. What happened, what broke, what you’d change — the write-up is the fuel for recursive self-improvement (below).
- Prefer full coverage. Handle the edge cases, write the test, write the doc. Sweep every source, not the first page of results.
- Persist through obstacles. Fix root causes, not workarounds — a workaround is a deviation you re-pay on every future run.
- When blocked, surface it and keep moving. Name the blocker, what you need, and what you did instead — then finish everything that isn’t blocked.
Recursive self-improvement
Do → Document → QA → Example (Meta-Article) → Improve (SOP update) → ↺
Every run of every skill feeds this loop: do the task per the SOP, document the run, QA it against the canonical instructions, publish the run as a worked example, fold the fixes back into the skill, run again. Each cycle the library gets sharper and needs less of you.
The loop is the recursive-self-improvement-qa skill (step 10 of this pack); https://blitzmetrics.com/knowledge-system-maintenance documents the maintenance standard behind it.
Why this is positive-sum
- Ephemeralization (Buckminster Fuller): do more and more with less and less until you do everything with nothing. Every documented, agent-run skill takes fewer human hours per result each cycle.
- Jevons Paradox for intelligence: when intelligence gets cheap, the work doesn’t shrink — the amount of work worth doing explodes. Efficiency means more usage, more clients served, more jobs. Not fewer.
- The chain: documented skills → agents that run them → a marketplace where those agents work and eventually earn → operators freed for the judgment work only they can do.
- The mission: that chain is the engine behind Dennis’s goal of creating a million jobs — completeness at near-zero marginal cost, multiplied across everyone who installs the library.
Notes — Dennis’s method
- Dennis’s rule, verbatim: “The marginal cost of completeness is near zero with AI — do the whole thing, do it right, with tests and docs; ship the finished product, not a plan.”
- This file governs execution; each skill still owns its inputs, steps, and outputs. Where they meet, the skill’s Definition of done wins — this file just forbids stopping short of it.
- Boil the ocean on coverage, proof, and verification — never on adjectives, scope creep, or invented work. Completeness is not padding.
- Rules 1 and 2 travel together: a persistent agent that loops without verifying just automates its own mistakes.
- Install this file alongside the ten skills (Project knowledge, Skills, or your
memory/folder) so every chat that runs a skill runs it this way. - Date every run. “We keep the skills current” is a claim; a dated meta-article trail is the proof.
Model landscape — kept current (August 1, 2026)
Reviewed the past month’s releases. The persistent-agent thesis this file is built on is now shipping infrastructure, not a bet:
- Claude Opus 5 (July 24, 2026) added a 1M-token context window and a per-turn reasoning-effort dial — low, medium, high, xhigh. Treat that dial as a routing lever, not a preference: raise effort for the one hard call inside a skill instead of moving the whole run to a pricier model, and drop it for the mechanical steps. Escalate effort before you escalate model.
- Claude Sonnet 5 (June 30, 2026) is the cheap agentic workhorse — it plans, drives browsers and terminals, and self-checks. Route bulk skill runs here: draft, collect, sweep, first pass. It is now the sensible default for any step that chains several tool calls; smaller models stay right for single-shot classification and extraction inside a step.
- Claude Fable 5 returned July 1, 2026 after a two-and-a-half-week export-control suspension. It stays the ceiling for verification and the hard calls — and that outage is the lesson worth keeping. Never let a scheduled run hard-depend on a single model. A routing table without a fallback tier is a 4am job that fails at 4am for reasons nobody in the building controls.
- Scheduled agent tasks now run in the cloud (July 7, 2026) — a recurring job keeps going with the laptop closed. The carve-out matters more than the headline: work that touches local files or drives a browser still needs the desktop app open. Know which of your scheduled skills are which before you assume the schedule is covered.
- The MCP spec revision dated 2026-07-28 moved to a stateless core and is not fully backward compatible. Before upgrading any connector a skill depends on, check which revision it speaks — a routine version bump is no longer automatically safe.
- Every major vendor now ships this file’s assumptions. OpenAI’s GPT-5.6 (July 9, 2026) exposes Sol, Terra, and Luna as an explicit cheap-to-flagship ladder and adds programmatic tool calling, where the model writes a small program to coordinate tools instead of round-tripping each one. Google’s Gemini 3.6 Flash and 3.5 Flash-Lite (July 21, 2026) fold computer use in as a built-in tool. A tiered ladder, real tool use, long-horizon runs — that is the shape of the whole field now, not one vendor’s bet. Gemini 3.5 Pro had not shipped as of this review.
- Managed Agents run skills on a schedule with vault-stored secrets and browser/CLI access — this file’s “persistent, looping agent” is a product surface, not just a way of working. This library is itself kept current by one.
Rule of thumb after this month: pick the cheapest tier that clears the bar, turn the effort dial before you turn to a bigger model, and give every scheduled job a fallback. See model-judgment for the full routing ladder.
Definitive article & links
- The source idea: https://garryslist.org/posts/boil-the-ocean — Garry Tan, “Boil the ocean” (Feb 2026)
- Keeping the library current: https://blitzmetrics.com/knowledge-system-maintenance
- The engine the skills run on: https://blitzmetrics.com/content-factory/
- The standard at scale — the BlitzMetrics Task Library: https://blitzmetrics.com/task-library-dashboard/
- Applies to: all ten skills,
personal-brand-strategistthroughrecursive-self-improvement-qa.
Learned in the field
Appended automatically by the self-improvement loop (Skill-Learnings/): dated lessons from real runs. Newest at the bottom.
<!– learning:2026-08-09-single-edit-point-enforce-at-writers –> August 9, 2026 (from: somba-weekly-maa catch-up run, 9 Aug 2026)
When one file is declared the single source of truth and a generator DELETES anything that file does not carry, you have only built half the rule. The other half is every writer. A script that updates the published copy but not the source of truth is publishing with a hidden expiry date: it ships, people read it, and the next scheduled run deletes it — with no error raised at either end. Both behaviours are individually correct; jointly they are silent data loss.
Found 9 Aug 2026: tools/add_new_members_dash.py inserted each new SOMBA member’s “New on the board” welcome into dash.html only, never into agents_status.json. It runs on the DAILY new-member task, so this was every arrival — a welcome that lived about five days on ~105 dashboards and then vanished. One member’s disappearance had been noticed and hand- restored a week earlier and logged against the wrong file.
So when you find one instance of vanishing content, do not fix the instance. Ask what writes this field, and on what schedule. grep -l the field name across every writer, then check which of them also touch the source of truth. The ones that do not are the whole bug, and the schedule tells you the blast radius.
Fix it in three places, because intent is not enforcement:
- The writer writes through to the source of truth — idempotent, back the file up first.
- A standalone detector names anything in-window that lives only in the copy, i.e. is
already scheduled for silent deletion.
- The publish gate runs that detector as a pre-flight assertion, so nothing can ship an
item that will expire.
Then guard the latent version too. A hardcoded seed list whose entries are all currently outside the authoritative window is harmless today and a trap tomorrow, because adding an entry there and dating it today is the obvious thing for the next person to do. Make it abort with a non-zero exit BEFORE any write, naming each offending item and pointing at the single edit point. Verify that by widening the window and confirming the output file’s hash is unchanged by the aborted run.
<!– learning:2026-08-09-verify-target-is-real-and-crawlable –> August 9, 2026 (from: weekly-fleet-interlinking-agent (2026-08-09 run))
Before treating a URL as a link TARGET, and before treating a page as a link SOURCE, prove two separate things over the network. A 200 answers neither.
1. A target that is live is not a target that is built. Byte count is not evidence. chuckthokey.com had been correctly rejected on four consecutive runs as “200 but 114 empty bytes — a parked lander.” It then began returning HTTP 200 with 67,915 bytes and the title “Chuck Thokey”, which reads as finally built by every metric previously used. It was a bare WordPress install: Twenty Twenty-Five default theme, the “Hello world!” starter post, a “Sample Page”, stock WordPress footer. The 68KB was theme weight. Four articles would have been pointed at a Hello World page.
Fetch the body text and check for the starter-content fingerprint before promoting a domain from parked to linkable: Hello world!, Sample Page, Designed with WordPress, twentytwentyfour|twentytwentyfive/style.css as the only theme, a lone post dated at install time. Prefer positive proof of identity — the person’s real name in prose, Person/ProfilePage schema, a real nav — over any size threshold. Liveness is not identity, and neither is size.
2. A page that publishes successfully is not a page that renders. Check content.protected. A WordPress page with a password returns content.rendered as an EMPTY STRING to the public, and the public URL serves a small stable password form that never contains the content — while REST still accepts the write, returns status: publish and a fresh modified_gmt, and context=edit reads the saved anchors back perfectly. Every success signal fires and Google sees nothing, so an SEO link filed there passes zero equity and logs as a win.
Exclude content.protected == true from candidate scans, and assert it false at verify time. Note this cuts against the standing “never call a fleet publish failed on one short response” rule: that rule is right, but a short response is sometimes real. Distinguish the two with content.protected and the length of content.rendered, never with retry count — a retry loop alone concludes “flaky CDN” and files a link that does not exist.
3. Verify with the exact href written, not the domain it came from. A verifier searching href="https://andrewpickettlaw.com reported FAIL / 0 anchors on a link written as https://www.andrewpickettlaw.com/. A www prefix or a trailing slash is enough to make a live link look dead and trigger a pointless “fix” on a page that was already correct. Build the needle from the string actually inserted, and assert the anchor appears exactly once with the intended visible text.
<!– learning:2026-08-09-test-what-wins-the-chain-not-your-entry-in-it –> August 9, 2026 (from: skill-pack-propagation (2026-08-09 run))
When a system resolves something through a priority chain, verifying YOUR entry in the chain proves nothing. Verify what wins.
Step 9 pushes the jobs ledger to GitLab. It exports GIT_ASKPASS so the token is read from .credentials.json and never written into a remote URL, .git/config or ~/.git-credentials. That design is right and it was implemented correctly. It still sent the wrong credential for four days.
Git asks every configured credential.helper FIRST and only falls through to GIT_ASKPASS when they all come up empty. macOS ships credential.helper = osxkeychain in the Command Line Tools system gitconfig, so on this machine the keychain is permanently in front of us. At 2026-08-05 02:09 UTC an internet password for git.adectra.com was written to the keychain under the account claude-audit. From that minute git_askpass.py was never executed — not once — and every push presented claude-audit.
Why three days of evidence pointed at an innocent party. claude-audit has read access, so every diagnostic we owned came back green:
| check | result | what it actually tested |
|---|---|---|
git ls-remote origin |
200, refs listed | that SOME identity can read |
GET /personal_access_tokens/self |
active, not revoked, write_repository |
the token we MEANT to send |
POST .../info/refs?service=git-receive-pack with our token |
HTTP 200 | the token we MEANT to send |
git push |
403 “You are not allowed to upload code” | the token git CHOSE |
Only the last line observes the credential that left the machine. The escalation block printed next to the failure said “403 on push is usually OUR stale copy — compare the token’s created_at against the last rotation you were told about,” which sent the reader after a teammate’s token handoff. That is the second time this job has blamed a colleague for a defect on our side (cf. 2026-08-02, where an askpass sniffed a prompt for a project path the prompt never carries). Both share one root: an auth error tells you the credential presented was refused, never that it was the one you intended to present.
Read the failure message literally, and let it partition the causes. GitLab returns 401 HTTP Basic: Access denied for a credential it cannot authenticate, and 403 You are not allowed to upload code for one it authenticates but that lacks push rights. A 403 therefore rules out “our token was revoked” — a revoked token cannot produce it. The distinction was sitting in the error text on 08-05 and we read past it three times.
The fix, and why it is env and not -c:
export GIT_CONFIG_COUNT=1 GIT_CONFIG_KEY_0=credential.helper GIT_CONFIG_VALUE_0=””
An empty credential.helper value resets the inherited chain, so askpass becomes the only source. GIT_CONFIG_* applies to every git process in the subshell — including git invoked from inside a script — which -c on one command would miss. It stores no secret anywhere new, so the “one store, one reader” rule is intact.
Generalize past git. The same shape is everywhere we work: PATH (a shim earlier in it wins over the interpreter you configured), Python sys.path, WordPress filter priority, DNS resolver order, ~/.aws/credentials vs AWS_* env vars vs instance metadata, MCP tool name collisions. In every case the honest question is not “is my entry correct?” but “whose entry is consulted first, and does it answer?” Before debugging a resource you configured, print the resolved chain and find your position in it.
Deleting the bad entry is NOT the fix — the fix is the guard, and here is the proof. Cleaning up afterwards, the stale claude-audit entry was removed and the keychain left empty for the host. One unguarded git push --dry-run later, the keychain held a BRAND NEW entry for git.adectra.com under the account oauth2, created that minute — our project token, copied out of .credentials.json and written to a second location by git itself.
That is standard behaviour and it is the whole trap: on a successful authentication git calls credential approve, and every configured helper stores what worked. So a credential helper is not only a reader that outranks you, it is a WRITER that manufactures the next stale entry. Delete the bad row and the very next successful push re-creates one, which will in turn go stale at the next token rotation and shadow you all over again. Manual cleanup is a fix with a half-life.
The same GIT_CONFIG_VALUE_0="" that stops the helper being READ also stops it being WRITTEN — an empty chain has nowhere to store. Verified: three guarded pushes in a row, the keychain still empty afterwards. So the guard is what keeps “one store, one reader” true, and it is also why no extra test was added for “is the keychain clean?” — that assertion is machine-specific, would be meaningless in the cloud runtime, and is already implied by the helper-list assertion that test_credential_source.py makes.
Generalizes to every cache-on-success credential layer: aws configure writing ~/.aws/credentials, docker login writing ~/.docker/config.json, gh auth login, kubectl context tokens, browser password managers. Ask not only “which store answers first?” but “which store is silently recording my answer for next time?” — because that is the one that will be wrong later, long after you have forgotten it exists.
Coda, same day, same shape, caught before it cost anything. Re-verifying the runner’s unknown-flag contract, zsh run_skill_pack_propagation.sh --bogus 2>&1 | head -6; echo $? printed the refusal and then 0 — which reads as “the guard refuses but reports success,” the exact republish_daily.py al defect. It was not. $? after a pipeline is the exit status of the LAST command, so that was head‘s 0, not the script’s. Unpiped, the script returns 2 correctly. Reading a status through a pipe measures the pipe — the identical mistake as cmd | tee || exit N testing tee, and the identical mistake as testing the credential you configured rather than the one git chose. Whenever you read a value, ask which component actually produced it. One near-miss false alarm is cheap; a false alarm filed as a defect would have sent someone to “fix” a guard that was already correct.
Test contract this produced (tools/test_credential_source.py, gated at exit 99 before the push): assert the effective helper list for the repo is EMPTY under the runner’s exact environment; prove the assertion is not vacuous by planting a helper and watching the same check detect it; and confirm askpass returns the project-correct token by digest, never by printing it. A green run now means what wins the chain is what we configured, which is a claim the previous checks could not make.
<!– learning:2026-08-09-measure-before-you-write-a-field –> August 9, 2026 (from: sigrun-website-request-intake (second pass) — closing the og:image gap on christineboersdoets.nl and solhalvorsdatter.com)
A fill and an overwrite look identical afterwards. Only the measurement taken before the write can tell them apart.
The og:image gap on two client sites had been parked on a human for six days: “open Rank Math → Titles & Meta → Social and set a default OpenGraph image.” One fetch of the rendered <head> showed Rank Math’s social module was already active and emitting seven og tags — it was short exactly one input, an image. Rank Math falls back to a page’s featured image, and the purpose-built 1200×630 social cards had been sitting in each media library attached to nothing. Setting featured_media over /wp/v2 with credentials we already held closed the gap on 22 URLs without anyone’s wp-admin.
Then it broke something, and the only reason that was caught is a listing pass taken minutes earlier.
Pages on these sites render no featured image; posts do, and so does the blog index. So writing the social card onto the posts silently replaced the wide hero photo at the top of every article with a share card. The write looked like filling an empty field. It was an overwrite. A listing pass a few minutes earlier had recorded featured=7 on Sol’s posts, which is what made the difference visible — and the same evidence told me what to restore.
Rules this produces
- Record the current value of any field you are about to set, in the same run, even when you are confident it is empty. After the write, “was empty” and “was something else” are indistinguishable.
- Blast radius is a property of the template, not of the field. The same field was invisible on pages and visible on posts, on one site, in one theme. The generalisable check is counting the rendered markup on the specific URL before and after — not reasoning about what the CMS “usually” renders.
- A stale ask parked on a human deserves one measurement before it is re-sent. The question is not “has someone done it yet” but “is a human still the only thing that can do it.” This one had stopped being true days earlier.
- A timeout mid-loop is a fact about the observer. When the bulk write hit the tool cap partway through, the right move was to re-query live state — which is precisely what revealed that one site had finished, the other had stopped mid-list, and the posts carried a different pre-existing image.
- Prove an “it cannot be automated” claim from a second direction before repeating it. “Permalinks need wp-admin” had rested on one missing settings key. Enumerating the install’s whole REST namespace list and the Abilities API’s actual abilities (both read-only) settled it — and incidentally produced structural proof that Rank Math was absent from that install, which a visual check had only hinted at.
The verification shape that worked
Every write was followed by a rendered re-fetch of that exact URL with a cache-buster, counting og:image, twitter:image and wp-post-image/wp-block-post-featured-image, with an automatic revert branch on either “an image now renders that did not before” or “og:image is still absent.” The revert branch is what makes the write safe to attempt at all: a change that cannot be self-detected as harmful should not be made in an unattended run.
<!– learning:2026-08-09-an-empty-capture-proves-nothing –> August 9, 2026 (from: grokipedia-fleet — an armed fetch hook captured nothing on an edit that had already landed)
A silent instrument is not a negative reading. Absence of your evidence is not evidence of absence.
Grokipedia’s edit modal looks identical whether a submission succeeded or was refused. We solved that months ago by arming a fetch hook before clicking Submit and reading the POST body, and wrote the rule down in the skill:
{"success":true,"id":"<uuid>"}is the only proof. Empty__cap= blocked client-side, nothing
created, safe to retry later.
On 2026-08-09 the Escape Fitness edit came back with an empty __cap from a correctly-armed hook, and the modal cleared exactly the way a refused attempt does. By our own written rule that was a non-event and the next step was to retry. The account said otherwise: Total Edits had moved 8 to 9. The edit had landed. A retry would have filed the same correction twice on a client’s page.
Four minutes later the Matthew Januszek edit did capture a fetch POST, id and all. Same site, same modal, same session, same hook — one submission visible to the instrument and one not. So the application does not use a single transport for this action, and a hook on fetch alone (or on XMLHttpRequest alone, or on both — sendBeacon exists too) is a coin flip dressed up as proof.
The deeper error is in the shape of the rule, not the choice of transport. It assigned meaning to silence. A positive capture is real evidence: something happened and here is its id. A negative capture is the instrument saying “I saw nothing”, which is consistent with both “nothing happened” and “it happened somewhere I wasn’t looking.” Those two are not distinguishable from inside the hook, and no amount of hooking more transports fixes that — it only narrows the gap while leaving the logic backwards.
The check. For any write whose success you cannot see directly, find the counter the system keeps — a total, a list, a row count, a version number — and read it before and after. Your own capture is corroboration. Theirs is the verdict. If the system exposes no such tally, say so in the report rather than promoting your instrument to an authority it has not earned.
The tell. Any rule of the form “if I didn’t observe X, then X didn’t happen.” Also: retry logic gated on a negative observation from a probe you built. That is the moment a monitoring gap turns into a duplicate write, which is worse than the original uncertainty because it is now visible to a client.
Related. Same family as [[checks-must-be-able-to-fail]] and [[failed-probe-must-not-look-clean]], and the second instance in this single run of the broader rule [[the-account-is-the-ledger-your-notes-are-a-guess]] — earlier the same day, a run’s own notes recorded one submission where the account showed three. Twice in one run, trusting the artifact we generated over the tally the system keeps would have caused a duplicate write.
What shipped: the task prompt’s edit step now reads the Total Edits counter before and after every submission and treats a captured id as corroboration only; the hook was extended to XMLHttpRequest while explicitly demoting it below the counter; and pages.json records the Escape Fitness edit as confirmed by the counter, with a note naming the wrong rule so the next run cannot inherit it.
<!– learning:2026-08-09-fix-the-harm-not-the-story-you-told-about-it –> August 9, 2026 (from: skill-pack-propagation daily run — a second run of the day was one line away from erasing the first run’s log)
When you fix an incident, fix the HARM it caused, not the story you told about how it happened.
On 2026-08-03 two copies of the daily runner ran at once. The written-up harm had two distinct parts: they published to the same eight live sites concurrently, and the second copy’s : > "$LOG" erased the first copy’s log, so the surviving record under-reported the day by 107 skill-file updates. The remedy shipped was a single-instance lock, and it is a good lock — 16 test cases, atomic mkdir, stale-PID reclaim, taken deliberately before the truncate so a refused copy cannot destroy a running copy’s log.
It fixed the first harm completely and the second one not at all. The log is named for the DATE, so any second run of the day — an hour later, no concurrency, lock free, taken and released politely — still truncates its predecessor’s record. On 2026-08-09 a manual run finished at 03:11:30 having published ten surfaces and pushed commit a251cdd, and the scheduled run at 04:55 was one line away from deleting the only account of it.
The incident had been narrated as “two copies ran at once”, so the fix was scoped to concurrency. But the loss of the log was never caused by simultaneity; it was caused by a truncate against a name that repeats. Simultaneity was just the shape it arrived in. Six days of green runs went past, and the very comment block explaining the erasure sat four lines above the unguarded : > "$LOG" that would do it again.
The check. After writing an incident up, read your own account back and list the harms as separate line items — not the sequence of events, the damage. For each one, name the line of code that now prevents it. Any harm whose answer is the same mechanism as another harm’s deserves a second look: one guard covering two failures usually means one failure was assumed away. In this case “concurrent publish” and “log erased” both pointed at the lock, and only the first was true.
The tell. A fix aimed at the circumstances of an incident rather than at its effect reads as a rule about when the bad thing may happen (“never run two at once”). A fix aimed at the effect reads as a rule about what is now impossible (“a completed log is never overwritten”). Prefer the second: circumstances recur in shapes you did not enumerate.
What shipped: tools/rotate_run_log.py moves an existing non-empty log to <stem>.runN.log (max+1, never backfilling a gap, refuses to clobber) immediately before the truncate, and tools/test_rotate_run_log.py (20 cases) runs as a pre-flight gate and asserts both the behaviour and the wiring — including that the call sits before the truncate in the runner, because a fix recorded in prose is not a fix applied to the runner. It earned its place on the first live run: 28,581 bytes of the 03:11 run preserved as _daily-run-2026-08-09.run1.log instead of deleted.
<!– learning:2026-08-09-the-account-is-the-ledger-your-notes-are-a-guess –> August 9, 2026 (from: grokipedia-fleet monthly run — the run’s own notes said 1 submission, the account said 3)
When a system holds your history, read the system. Your notes stop at the moment you stopped.
The Grokipedia fleet run submits a capped drip of three requests a month, and it writes what it submitted into pages.json. On 2026-08-09 the run was interrupted after the submissions landed but before it finished. pages.json recorded one submission, Dennis Yu, complete with a captured POST id — exactly the evidence discipline the skill demands, and entirely correct as far as it went.
The account showed three. Article Requests had moved 107 to 110, and the activity feed listed Dennis Yu, Michael Krigsman and CXOTalk at 27, 26 and 25 minutes old. The next agent to pick the job up read the notes, saw one submission against a budget of three, and was two steps from spending a budget that was already gone.
The notes were not wrong. They were truncated — they described the run up to the last moment the run was alive to write anything down. Every record a process keeps about itself has this property, and it is worst precisely when the process died, which is exactly when someone else comes looking.
The same run had already been bitten by the deeper version of this. The August 2 entry announced that Grokipedia “generates pages about our people WITHOUT us” and led with a client, Matthew Januszek. We had requested Matthew Januszek ourselves on January 23. The ledger recording that had been sitting in our own account since January, unopened, for the entire month the claim stood in a changelog and an email to ops. A whole narrative about an external system’s behaviour, built without asking the external system what it had on file.
The check. Before you act on a count of something you did — submissions, posts, invites, uploads, API calls — ask the system that holds it. Not the log you wrote, not the memory file, not the summary from last run. If the external system exposes a total, read the total and diff it against your record; a mismatch is information, and the direction tells you which way you were wrong. If it exposes no total, that absence is worth naming in the report rather than papering over with your own tally.
The tell. Any sentence of the form “we have submitted N so far” where N came from a file you wrote. Also: a plan whose next step depends on remaining budget, quota, or rate limit computed from local state. Those numbers live somewhere authoritative, and it is nearly always one cheap read away.
Related, and the reason this keeps recurring: the same failure shape as the eleven days the agent-runtime plan spent naming a human blocker that had already cleared — a git ls-remote would have said so on day one — and as the weekly Dorine check that compared a source against its own snapshot and never against the clock. In all three, the local record was internally consistent and externally stale. Consistency is not currency.
What shipped: the skill’s submission rule now says to count the budget from the live Article Requests total at the start of the run and before each submission, not from the run’s notes; the two undocumented submissions were recovered from the account and written back into pages.json with a reconciliation block naming how they were found; and the ordering rule the ledger disproved (person-before-company) was corrected in the scheduled prompt so the next run cannot inherit it.
<!– learning:2026-08-10-a-stable-set-that-moves-is-a-different-finding –> August 10, 2026 (from: anthony-hilb-seo-tracker, week 8 — a page left Google’s index and the tracker could only see it because it re-checks the whole set, not the known-bad subset)
Track state history, not current state — “6 pages unindexed” and “a page just fell out” are different diagnoses wearing the same number
For seven weeks this tracker reported the same fact: 10 of 16 published guides are in Google’s index, 6 are not. On week 8 it reported 9 and 7. One page — indexed on 27 July and again on 3 August — had been dropped.
The number moved by one. The diagnosis moved much further than that.
- “Six pages Google never selected” is a story about pages. It invites page-level fixes:
more words, better internal links, a distinctiveness pass. (This tracker recommended exactly that on 27 July, then withdrew it on 3 August after measuring that the unindexed pages were no thinner than the indexed ones.)
- “A page Google indexed and then dropped” is a story about the site. Google evaluated
this domain, made a decision, and reversed it. No page-level edit explains a reversal on a page that is HTTP 200, index, follow, self-canonical and 1,777 words.
The second reading only exists because the run had a prior observation of that specific URL in a specific state. Had the tracker checked only the six known-bad URLs — the efficient thing to do, and the tempting thing after five identical weeks — the drop would have been invisible, and the report would have said “no change” for the eighth time on the week the account’s story actually changed.
Rules:
- Re-check the whole tracked set every run, never the subset that was failing. The known-bad
items are the ones least likely to teach you something new; the known-good ones are where regressions hide. An inherited “problem list” silently becomes the definition of what the agent can notice.
- Record per-item state history, not just the current tally. A snapshot that says
indexed: 9, not_indexed: 7 cannot answer “which one changed, and when?” — the question that distinguishes one page wobbling from the start of a slide. Store the per-URL verdict with a date so the next run can diff it, rather than re-deriving the diff from prose in last week’s report.
- **A metric that has been flat for N weeks is not evidence that it is stable. It is evidence
that you have been measuring one thing.** Five flat weeks here preceded an indexation finding; two more preceded a backlink-classification finding; the eighth produced a deindexation. Each time, “nothing moved” was true of the number being reported and false of the account.
Corollary, from the same run: a forced degradation can be an upgrade — check before you apologise for it
The Ahrefs unit pool was exhausted mid-month (104,072 of 100,000 workspace units, shared across seven client trackers), so DR, organic keywords and organic traffic were simply unavailable. The reflex is to treat that as a diminished run and say so apologetically.
Two things were true instead:
- The lost metric was already worthless here. DR had been formally retired as a progress
metric for this site the week before, once 96% of its referring domains turned out to be link farms. Losing a number you had already stopped trusting costs nothing.
- The substitute was better than the original. The tracker normally answers “do we rank for
these 7 target topics?” from Ahrefs’ organic-keywords estimate. With Ahrefs down, it asked Google, live, pages 1–2, testing the SERP HTML for a scheme-qualified URL. That is the search engine’s own answer rather than a vendor’s model of it. The method is being kept after the quota resets.
Rule: when an outage forces a substitute method, evaluate the substitute on its merits before labelling the run degraded. Vendor APIs are convenient proxies for questions the primary source will often answer directly and better. An outage is a free, forced experiment in whether the convenient path was also the correct one — and sometimes the fallback should become the default.
And the half that must not be softened: where a metric genuinely could not be measured, write null and say “unavailable.” Do not carry the prior week’s value forward into the slot, and do not let it appear in a table beside measured numbers. A tracker whose whole job is detecting movement cannot afford a number that looks measured and is actually a memory. This is the same family as “distinguish ‘answered no’ from ‘did not answer'” — one code path, two opposite facts.
<!– learning:2026-08-10-coverage-metric-from-a-frozen-file –> August 10, 2026 (from: weekly-fleet-hub-audit 2026-08-10)
A coverage number must be derived from the system it claims to describe, never from a file that only a human edits. If the source never changes, the metric cannot go up when the work lands and cannot go down when access is lost — it is a constant wearing a KPI’s clothes.
Found 2026-08-10: the fleet pulse reported “verified in Search Console: 9/92” every week from a CSV last written 2026-07-17. On 08-03 it printed 9 while gsc-enrichment.json did not exist at all — the number had zero live evidence behind it. When this run actually pulled Search Console, two of that CSV’s eleven VERIFIED rows (jasongamato.com, piotrzawislak.com) returned “you don’t have access” on every resource_id form. So the frozen source was not merely stale, it was wrong, and it was wrong in the flattering direction.
The fix is a resolution order that records HOW each fact is known, not just the fact:
- live — we pulled real numbers for it this run. Proof of access.
- lost — we tried this run and were refused. Proof of NO access, and it must outrank any stored claim.
- claim — neither; fall back to the stored file, but label it, and report claims separately from proven coverage.
Report the three counts side by side. The moment claim is visible as its own number, nobody can mistake an assertion for a measurement, and a property that quietly falls out shows up as lost in the same week rather than being carried forward forever by a CSV.
Two collection gotchas worth carrying:
Search Console resource_id form. Our fleet properties are URL-prefix (resource_id=https%3A%2F%2F<domain>%2F), not domain properties. Querying one as sc-domain:<domain> returns Google’s “Oops, you don’t have access to this property” page — byte-identical to the response for a property you genuinely lost. Guessing the wrong form therefore manufactures a false regression. Try BOTH forms before recording any property as lost; only a domain that refuses every form is actually gone.
Vendor quota is a single point of failure for the whole proof layer. This run the Ahrefs workspace sat at 104,072 of 100,000 monthly units, nine days from reset, so every proof call returned “API units limit reached”. Do not paper over that by writing another vendor’s numbers into the same fields: DataForSEO’s backlink rank is 0–1000 and Ahrefs’ Domain Rating is 0–100, and DataForSEO’s referring-domain counts run roughly 10x lower on this fleet because it does not index the nofollow spam blast Ahrefs counts. Silently swapping them would have rendered as a fleet-wide authority collapse that never happened. Keep the second source in its own file with its scale documented, leave the primary values untouched and stamped with the date they were actually measured, and say plainly in the report that Proof contributed zero week-over-week delta this run.
<!– learning:2026-08-10-page-one-is-not-the-ledger –> August 10, 2026 (from: grokipedia-fleet — six-page submission history read as if it had one page, for seven months)
A paginated list read to the end of page one is a partial read that feels complete.
Our Grokipedia account holds 110 article requests, shown 20 at a time across six pages. Every run of this job read page one, built a list called already_rejected_do_not_resubmit_without_new_evidence, and then reasoned from it with confidence. The list had twelve entries. The real number is forty.
The cost was not abstract. On 2026-08-09 we posted to a client’s Basecamp project telling him that his company was the way into the encyclopedia, because the company gets accepted where the individual gets rejected. Showcase Remodels had been submitted on 25 January and rejected. It was on page two. He read a confident recommendation to try the one thing we had already tried and been refused.
What makes this failure mode nasty is that page one is not obviously partial. It fills the screen, it is sorted newest-first so it looks current, and the twelve rejections it did contain made the list feel researched rather than truncated. Nothing about the artifact announces “there are five more pages of me.” Compare a truncated file read, which usually leaves a visible seam.
The check. Before treating any list as complete, find its declared total and reconcile. Grokipedia printed “21–40 of 110” at the bottom of the page the whole time — the count was sitting there in the footer while we generated a twelve-item list from it. Ask: what does the system say the total is, how many did I actually read, and do those match? If there is no declared total, page until a page comes back short, and say in the report how far you got.
The tell. Any assertion of the form “we have never tried X” or “the complete set is Y” derived from a UI that paginates, an API with a default limit, a search that caps at 20 results, or a scroll container. Also: a list whose length is suspiciously close to a round number — twelve rejections out of an unknown total should have prompted the question, and forty out of a stated 110 would not have.
Related. This is the third instance in two days of the same underlying error, which is why it is worth its own note rather than a line in a changelog: [[the-account-is-the-ledger-your-notes-are-a-guess]] (our notes said one submission, the account said three) and [[an-empty-capture-proves-nothing]] (our instrument saw nothing, so we concluded nothing happened). All three are the same shape — an artifact we produced, treated as authoritative over the system’s own record. Notes, hooks, and first pages are all partial views that feel total.
What shipped: the task prompt now has a dedicated step, ahead of any submission, that reads all six pages via ?page=N and refreshes the stored decided-set; pages.json carries _full_ledger_2026_08_10 with all 40 rejections, all 29 creations, and the pagination recipe; and the client who got the wrong recommendation was told directly, leading with our error rather than the platform’s.
<!– learning:2026-08-10-redirect-hides-a-dead-site –> August 10, 2026 (from: weekly-fleet-hub-audit 2026-08-10)
Resolve the FINAL host of every site you audit, and compare it to the domain you asked for. HTTP libraries follow redirects silently, so a domain that has been pointed at a different site returns a clean 200 with a full, healthy page — and every downstream check then grades the DESTINATION while printing the SOURCE’s name.
Found 2026-08-10: archiepadley.com 200s straight through to dennisyu.com. The weekly foundation audit had been scoring Dennis Yu’s homepage as Archie Padley’s site — green on homepage_up, has_title, entity_schema and sameas_links — for a personal brand site that does not exist. The audit had no way to notice, because none of its checks ever asked “whose page is this?”
Two rules fall out of this, and they generalize past redirects:
- Compare the answer’s identity to the question’s identity.
urlopen(...).geturl()/curl -w '%{url_effective}'costs nothing. Any fetch whose final host differs from the requested host is a different subject: report it as REDIRECTED and stop, rather than scoring it. The same applies to a canonical tag pointing off-domain, an OG:url naming another site, and a Person schema whose name is not the site’s person.
- A green foundation score is not an identity check. Title, meta description, schema presence and sameAs count all pass on a page about the wrong human. On the same run
azuifeachor.com— Azu Ifeachor’s site — was publishing a page at slugabout-felix-fagbuyi, all foundation checks green. For an entity home, serving a second person’s name is the most damaging on-page defect there is, because it teaches Google and every LLM the wrong entity, and it survives an all-green report indefinitely.
When you add the identity check, make it precise before you ship it. The first version compared token sets, which reads the concatenated domain ayeshafarrukh.com and the hyphenated slug about-ayesha-farrukh as two different people — 2 real hits inside 4, a 50% false-positive rate on the one signal meant to be exact. Compare on the squashed letter-stream as well as tokens. And keep the noisy corroborating signal subordinate to the precise one: flagging any foreign person-name in homepage copy put 83 of 92 sites on the list, because podcast sites legitimately name their guests — a list that long is indistinguishable from no list at all.
One more trap in the same check: a consuming regex for capitalised name pairs reads “About Felix Fagbuyi” as (About, Felix), discards it as a stop word, resumes past “Felix”, and can never form (Felix, Fagbuyi) — the exact name the scanner exists to catch becomes invisible. Use a lookahead so matches overlap.
<!– learning:2026-08-11-a-gate-run-only-in-clean-conditions-tests-nobody –> August 11, 2026 (from: skill-pack-propagation daily run)
A gate that only ever runs in cleaner conditions than production is testing a program nobody executes
The daily skill-pack job did not run on 2026-08-11. Not “ran and failed a step” — it refused during pre-flight, published nothing, wrote no log, took no lock, and left a 45-byte console containing one line. Meanwhile every gate the pipeline owns was green, on that same machine, that same minute, when run by hand.
Auto-detach had shipped at 05:58 the previous day. 08-11 was its first scheduled morning.
The chain. The launcher exports SPP_DETACHED_CHILD=1 into its child — correctly, to stop the child (whose stdout is a file, which is also “not a terminal”) from detaching forever. The child’s pre-flight then runs tools/test_detach.sh, and two of that file’s cases assert the no-flag default by spawning the runner and reading its answer. The spawned runner inherited the fence. It reported MODE: foreground, which is correct for that environment and wrong for the assertion, and the pre-flight refused to start the pipeline.
Why nobody saw it. The same test file politely backed up _daily-run-<date>.console at entry and mv‘d the copy back at exit — a file it also let its --detach children truncate. That file is the live run’s fd 1. mv replaces the inode, so from that moment the running pre-flight was appending to an unlinked inode. Its refusal message — twenty-five lines naming the exact failing assertion — went nowhere. What stayed on disk was the 45-byte copy taken before the failure: a run that reads as though it simply stopped after one line.
The rules.
- A gate must be exercised in production’s environment, not a cleaner one. Every gate here
was run attached, from a terminal, with a clean env — the one way the real job never runs. If the real invocation differs from the tested invocation in any dimension (detached, piped, cron, different cwd, inherited exports), that dimension is untested. New gate: tools/test_preflight_under_detach.sh launches the runner through the real auto-detach path and asserts the whole pre-flight comes back clean.
- **A test that asserts a DEFAULT must set every input that default depends on — including the
ones it would otherwise inherit.* An inherited variable is an input nobody wrote down, so every reader assumes the clean value they see in their own shell. The case is really measuring its caller. Unset it at the top; let the one case that is *about the variable set it explicitly. (Written into the new gate too — which then made the identical mistake on its first run, inside the hour, and was caught by its own “am I really driving the path I claim to be driving?” assertion. Include that assertion in anything that spawns its subject.)
- Stdout is not an artifact. It is a handle to whatever the caller chose; it can be closed,
truncated, or substituted underneath you, and the process cannot tell. Anything that must survive to be read tomorrow gets appended to a file the process names itself. Append, never rewrite — >> cannot orphan another writer’s descriptor the way mv just did.
- Never back up and restore a file you do not own exclusively. Copy-then-
mv-back looks
considerate and is the opposite: it silently substitutes the inode under every process holding it open. If a helper needs scratch space, give it scratch space (SPP_CONSOLE), and assert afterwards that the caller’s file kept the same inode and the same size.
- Anchor structural checks on the construct, not on a line window.
grep -A 3became
-A 6 after a comment pushed the target line out of range; on 08-11 an eight-line comment blew past -A 6 and re-armed the identical false alarm in the same file. Widening a window is a longer fuse, not a fix. Ask for “the first executable line inside this block that does X” and let people write as many comments as the code deserves.
- A derivation is only as good as the claim that its source is the population. A new check
derived “slugs that must resolve” from the scheduled-task inventory and flagged 29 of 31 tasks — all false, because a task files under the skill slugs it exercised, not its own name. The real population was on disk elsewhere (the notes’ own skills: history). A false alarm wearing the words of a real regression is the most expensive kind: ship it as a blocking gate and it trains its readers to ignore the pipeline. Scope the hard assertion to what is actionable today (can the inbox drain?) and demote the rest to advisory.
- Rank a failure by DIRECTION. This one removed output. No error, no log, no lock — the
artifact left behind looked like a short, quiet, successful morning. Anything that makes output disappear deserves more scrutiny than anything that adds output, because the reader’s instinct on seeing less is relief.
Showing the 12 most recent field lessons of 60. This skill is one of the most-used in the system, so it collects a lesson from almost every run. The complete history ships inside the skill file itself — download any pack and open boil-the-ocean.md.
Other skills: ai-search-visibility · client-access-checklist · client-relationship-cadence · content-agent · content-factory · definitive-article-writer
The full run order is on the skill-pack directory. Every skill here is one task from the Task Library — the library is the catalogue of what can be done; a pack is the subset you install; an agent is who runs it.
Where this sits in the system
A skill is a document. A pack is a folder of documents. Neither one does any work. Work happens when a job runs those skills on a schedule, checks its own output, and keeps its files somewhere it can read them again tomorrow. That is the whole difference between owning skills and having an agent.
- Skill — one task, written down to a standard, so an agent can run it without you in the room. There are 239 of them.
- PackYOU ARE HERE — those skills bundled into a download you install in one paste.
- Agent — a named role with a job description — not a chat window you retype every morning.
- Job — a schedule, a QA cycle, and somewhere to keep working files. Miss any of the three and nothing runs twice.
- Proof — every finished run written up in public, and the lesson pushed back into the skill.
The map: The System · every asset: Asset Tracker · next door: Asset Tracker.
