Field Notes
Real lessons from building and running AI systems — every one a mistake I actually made, turned into a rule so I don’t repeat it. These are the receipts behind the one-line rules on the Tips page; the interactive deep-dive (the 7-rebuild story) lives in the Prompt Lab. Tap any card for the story.
Verbalized sampling beats mode collapseFor open-ended generation, ask for several candidates each tagged with a probability, then pick from the low-probability tail.
Asking directly for “the best option” returns the most typical, generic answer — aligned models keep only ~24% of the base model’s diversity, vs ~67% with this technique. Only for creative work, never facts, code, or math.
One through-line, many lensesWhen several AI personas discuss one topic, give them ONE shared thesis to argue from different angles — not five separate takes.
Each persona made a true but different point, so the piece had five lenses and no spine — and the drift was felt even though every point held up alone. Write the thesis in one sentence first; every contribution must deepen it, not argue a new point about the same topic.
Synthesize, don’t transcribeWhen a subagent returns research, don’t hand back its bullets — that’s transcription, not thinking.
Returned a 3-bullet summary and got “I don’t think you’ve done enough thinking about this.” Work out which findings contradict what you’ve built, which expose a wrong number, which don’t actually apply, and the one thing they prove wrong.
Orchestration is the spectacleIn a live AI demo, the audience experiences the visible orchestration, not the file edits.
Sequential single-agent builds look slow on screen even when they’re fast, and silence during runtime reads as “broken.” Pre-stage research into files, engineer the prompt to force visible parallel subagent dispatch, and script 60–90s of narration for the beat where agents work silently.
Find your own secrets firstBefore asking for any key, token, or config value, search for it yourself — then only ask after exhausting the obvious sources.
Repeatedly asking for values already sitting in a local config file or CI secret store. Check dotfiles, secret stores, config files, and docs before asking — and when you do ask, name exactly what you already checked.
Watchers can match themselvesNever poll with until ! pgrep -f "script-name" — the watcher’s own command line contains the keyword, so it matches itself and never exits.
until ! pgrep -f "script-name" — the watcher’s own command line contains the keyword, so it matches itself and never exits.Stacked watcher loops each kept their own successor alive forever. Use the runtime’s built-in completion notification, or poll a sentinel file the target writes on finish — never a process-name grep that can match the watcher itself.
Integrations die silentlyA webhook can go dead — deleted, revoked, restructured — and nothing tells you. Health-check it yourself.
A webhook returned “not found” for an unknown span while several jobs and a smoke test pointed at it — none alerted, because each caller only checks its own success. Treat “job succeeded but no one heard” as its own failure class, and periodically ping every endpoint directly.
Never git push || trueSuppressing a push failure shows a green pipeline while the output never lands anywhere.
git push || trueA self-push used a token that silently lacked write access; the || true hid the 403 for weeks. Let consequential steps fail loudly; if a push is genuinely best-effort, log the failure visibly — and verify the token scope on any self-push.
Model IDs deprecate without warningA model string can be silently retired — the call shape stays valid but returns “not found,” and generic error handling masks it.
A daily pipeline lost its output when the provider deprecated a pinned model; the exception logged as a generic LLM failure. When expected output goes missing, check the model constants first; prefer “-latest” aliases over dated strings.
SystemExit escapes your safety netsys.exit() raises SystemExit, a BaseException — so a plain except Exception guard does NOT catch it.
SystemExit escapes your safety netsys.exit() raises SystemExit, a BaseException — so a plain except Exception guard does NOT catch it.A “never crashes” wrapper let a helper’s sys.exit(1) on a missing dependency kill an entire multi-worker run, while a separate error-swallowing pattern hid it behind a green status for days. Catch (Exception, SystemExit) together; don’t let library code call sys.exit at all.
sys.exit took down a multi-agent daily run for three days while status reported success.Every LLM call needs its own fallbackA shared provider fallback chain isn’t enough — every call site needs its own try/except, or one bare call is a single point of failure.
A summarization step called the LLM helper bare; when every provider was exhausted at once, that one unguarded call crashed the whole downstream pipeline. Wrap every call site and degrade gracefully — proceed with fewer inputs rather than let the exception propagate.
Fix the generator, not the outputWhen a generated page or report has a bug, fix the generator — patching the output file means the fix vanishes on the next run.
Hand-patched a live generated page; the fix disappeared on the next scheduled regeneration because the template was never touched. Cap and validate at the point of generation; treat bulk “fixer” scripts as a safety net, not the primary defense.
Fallback order is a budget decisionThe order of providers in a fallback chain is capacity planning — which quota you’re willing to spend first.
A morning automation burned one provider’s entire daily quota partway through its run because it was listed first, starving the capacity needed for interactive daytime use. Order by which quota to burn versus protect, not by preference.
Renaming an agent touches everythingRenaming an AI persona is a cross-system refactor, not a find-and-replace.
Every place the old name lives — data schema, pipeline code, bot config, public pages, docs — has to move together or the live product goes inconsistent. Treat it as a feature branch with a dependency map, and migrate additively (add the new name, migrate, then drop the old).
Ask “where’s the verifier?”Before wrapping any task in an autonomous loop, ask for the crisp pass/fail check — not “should we automate this?”
“Agentic loop” framing gets pitched for taste-driven work (design, copy, angles) where “done” is subjective. Only loop mechanical work with real exit codes; for judgment work, write an actual verifier or admit it needs a human.
The optimism cascadeAn edge that only survives the most optimistic fill assumption is a spreadsheet, not an edge — model execution reality first.
A market-maker built state machines and kill switches before the one honest test: a queue-aware fill simulation. Build the cheapest test of the load-bearing assumption first, before the machinery around it.
A gate that blocks profitA filter that stops 100% of losses but also 90% of profit isn’t a good gate — track what it rejects, not just what it passes.
A confidence floor built to prevent losses was shadow-tracked against the bets it filtered out — it was killing the most profitable bet type. Position-aware thresholds beat one universal floor when win rates are asymmetric.
Data velocity beats domain expertiseFor a prediction system, pick the domain with the most repeating, fast-resolving, genuinely-uncertain events — not the one that feels on-brand.
The instinct was the on-brand domain; the data showed another had far more markets near real uncertainty resolving fast. Optimize for volume of training examples and speed of feedback — one-off events teach a model nothing repeatable.
Shelve by choice, not defeatWalking away from a sunk cost on pre-registered criteria is a discipline win — don’t confuse “we could grind a few more dollars” with “worth our attention.”
After four months and two strategies, the honest framing was “we can make a few dollars, but not at the cost of focus.” Pre-register shelve criteria and dates before you start; when “this is the real shot” fires three times in a row, the problem is structural, not parametric.
Label what’s canned vs liveWhen a demo mixes live inference with scripted responses, mark which is which — don’t let a hardcoded reply pass as a live agent.
A multi-agent chat had several personas answering from canned scripts (no model call at all) alongside one genuinely live backend. Architect and disclose the boundary between “live” and “performed” explicitly, rather than letting the UI imply everything is equally live.
Own the pre-work, don’t fake itPre-staged research in a live demo is fine — but never claim work happened live when it didn’t.
A live-build demo pre-loaded reference files the agent read in seconds and acted on as if researching from scratch. If asked “did this happen live?”, point honestly to the pre-work stage — legitimate staging is the trick, not deception. Being upfront is more credible than false spontaneity.