Reading progress 0 / 20
AIdeazz AI Lab · Panama · Field Journal

AI OpsWIKI

Rev 12  ·  5 chapters  ·  7 entries  ·  7 cross-references  ·  2026-08-19

Debug it with me.

Real outages on live production systems, set as chapters. Open one and it unfolds a beat at a time — the symptom, then your guess, then what was actually happening.

Commit to an answer before you turn the page. That is the whole difference between reading a postmortem and keeping one.

i Symptom What it looked like from outside
Enquiries arriving through the website chat widget were recorded correctly but received no reply draft for twenty minutes, while the application log stated the handoff had succeeded.

You are on call and this lands. Where do you look first?

ii Root cause What was actually happening
The chat path handed each lead to an external workflow endpoint and drafted locally only *if that handoff failed*. The endpoint returned HTTP 200 even though the workflow behind it was switched off, so the failure branch never executed. The lead was neither lost nor answered: it waited for a timeout-based safety net designed as a last resort, not as the primary path.

You have found it. What do you change — and what do you deliberately leave alone?

iii The fix What changed
The local draft is now produced unconditionally rather than as a fallback, and the external handoff is fire-and-forget. Duplicate suppression absorbs the overlap, so both paths running costs nothing.

It looks fixed. How would you prove it, without trusting the config?

iv Verified by Proof, not hope
Duplicate suppression confirmed in production — a second identical submission returned a duplicate result referencing the first draft rather than producing a second approval card.

Last one. What is the lesson that outlives this system?

v The rule this earned What generalises
Never branch on an acknowledgement. If the fallback logic reads “if the handoff failed, do it myself”, it will never run, because the handoff reports success.

i Symptom What it looked like from outside
Inbound enquiries produced a customer record and an acknowledgement email, but no reply draft ever reached the operator. Test submissions produced nothing at all, which is indistinguishable from a completely dead pipeline.

You are on call and this lands. Where do you look first?

ii Root cause What was actually happening
The component that wrote every reply lived inside a hosted workflow tool and could call only one model vendor. That vendor's prepaid balance reached zero, and the workflow returned “credit balance is too low” on every run for four days. Five other providers were configured and healthy on the application server the whole time, but the call was not made there, so none of them could be reached. Compounding it, the drafting instructions existed in four separate copies; three had drifted out of date, and one still pitched a sales call to job applicants.

You have found it. What do you change — and what do you deliberately leave alone?

iii The fix What changed
Drafting moved into the application itself, behind a five-provider fallback chain ordered by use case. The reply endpoint now writes the draft when no draft text is supplied, which made the workflow tool optional rather than required, and drafting happens inline the moment a lead arrives instead of waiting on an external schedule. All four copies of the instructions were reduced to one.

It looks fixed. How would you prove it, without trusting the config?

iv Verified by Proof, not hope
Read from production logs rather than configuration. A provider probe returned HTTP 400 “credit balance too low” for the primary vendor while the chain routed to the next provider and produced a 605-character reply in 2.4 seconds. End-to-end runs were confirmed for both a first-time enquirer and a returning one, each producing a customer record, an acknowledgement to the sender, a copy to the shared mailbox, and an approval card — under 30 seconds from submission.

Last one. What is the lesson that outlives this system?

v The rule this earned What generalises
Redundancy only counts if it sits in the path where the call is made. A fallback chain configured elsewhere in the estate protects nothing.

i Symptom What it looked like from outside
Approving and sending a reply created a note and an email activity on the customer record, but the deal stayed in the “not triaged yet” column. A lead that had been personally answered was indistinguishable on the board from one nobody had touched.

You are on call and this lands. Where do you look first?

ii Root cause What was actually happening
The deal was created in the first stage and nothing in the send path ever updated it. The board had stopped describing reality, which is the only thing a board is for.

You have found it. What do you change — and what do you deliberately leave alone?

iii The fix What changed
A successful send now advances the associated deals to the “sent, awaiting reply” stage, from both the one-tap and the edited-reply paths. The advance is forward-only, because the stage that follows “sent” is “they replied, act now” — so stamping “sent” on a follow-up to someone who had already replied would have buried the one deal that needed attention that day. Closed and unrecognised stages are left untouched rather than guessed at.

It looks fixed. How would you prove it, without trusting the config?

iv Verified by Proof, not hope
Confirmed in both directions on a live record. The first run advanced the deal and reported one moved; the second reported “already at or past sent, not moved back” and moved zero.

Last one. What is the lesson that outlives this system?

v The rule this earned What generalises
Where stages encode who must act next, transitions must be forward-only, and the ordering belongs in one list — internal stage identifiers rarely resemble their display labels.
Vocabulary earned Monotonic state machine

i Symptom What it looked like from outside
A published engagement rate could not be substantiated. The startup banner appeared 4,357 times in the logs; the line proving a completed cycle appeared zero times. The behaviour had never occurred, whatever the configuration said.

You are on call and this lands. Where do you look first?

ii Root cause What was actually happening
Three layers. The first run was scheduled five minutes after startup; an external scheduled job was restarting the process every five minutes; and that job was a health check whose text match never matched the process manager's table output, so it judged a healthy process dead, permanently.

You have found it. What do you change — and what do you deliberately leave alone?

iii The fix What changed
The health check was rewritten to read structured state rather than to match rendered text. The process stayed up, and the first engagement cycle in the agent's history fired the same day.

It looks fixed. How would you prove it, without trusting the config?

iv Verified by Proof, not hope
Real replies and follows confirmed from logs after the fix.

Last one. What is the lesson that outlives this system?

v The rule this earned What generalises
Never claim agent behaviour from configuration. Grep for the action line, not the setup line.

i Symptom What it looked like from outside
Pending milestones resurfaced on every automation cycle and were published more than once.

You are on call and this lands. Where do you look first?

ii Root cause What was actually happening
A triple mismatch between the flag that marked work as done, the field the filter read, and the key the completion endpoint matched on, so completed work never looked completed to the next cycle.

You have found it. What do you change — and what do you deliberately leave alone?

iii The fix What changed
The read excludes either flag, the completion endpoint falls back through several keys, and the client sends enough context for fallback matching.

It looks fixed. How would you prove it, without trusting the config?

iv Verified by Proof, not hope
A clean API snapshot showing zero pending items, plus two full automation cycles with no duplication.

Last one. What is the lesson that outlives this system?

v The rule this earned What generalises
If an operation can run twice, it must be safe to run twice. Deduplicate on a key that genuinely identifies the work.

a.k.a. 200 OK is not success; 202 does not mean done

When you hand work to something asynchronous — a queue, a webhook, a workflow tool, a background job — the response you get back means "I have received this". It does not mean "I have done this", and very often it does not even mean "I intend to do this".

This is the trap behind a large share of “the data just vanished” incidents. The sending side logs a success, the receiving side never processes anything, and both halves look healthy in isolation. A queue that accepts your message and never reads it looks exactly like one that works.

Defences, in order of strength:

  1. Do not branch on the acknowledgement. If your fallback logic reads "if the handoff failed, do it myself", it will never run, because the handoff reports success. Make the local path unconditional and let idempotency absorb the duplicate.
  2. Confirm from the other side. Check that the work actually completed — a status endpoint, a result record, a callback — rather than trusting the receipt.
  3. Set a deadline. If the expected outcome has not appeared within N minutes, treat it as failed and act, rather than waiting forever.

a.k.a. safe retries; deduplication

An operation is idempotent if repeating it changes nothing beyond the first time. Setting a value to 5 is idempotent. Adding 5 is not.

This is the property that makes reliability affordable. Networks time out, retries fire, and redundant paths overlap — so in any real system some operations will happen more than once. If those operations are idempotent, that is a non-event. If they are not, your safety net becomes the thing that corrupts the data or spams the customer.

The usual implementation is a fingerprint: a hash of the inputs that identify the work. Before acting, check whether that fingerprint was already handled inside some window; if so, do nothing, and say so in the log.

The detail that separates a junior implementation from a senior one is what goes into the fingerprint. Too narrow and genuine repeat work gets swallowed; too wide and duplicates slip through. Hashing only “who” would silently discard a real follow-up message from the same person an hour later. Hashing “who plus what they said” collapses the duplicates while letting a genuine second message through.

a.k.a. forward-only transitions

When a record moves through stages — an order, a ticket, a deal, a deployment — the sequence usually carries meaning: later stages represent more progress. A monotonic state machine enforces that an update may move a record forward, never backwards.

Without that rule, a routine automated update can quietly destroy information. An order that goes from Shipped back to Processing has lost the fact that it shipped. Nobody notices, because no error was raised: the write succeeded perfectly.

The damage is worst when the stages encode who needs to act next. If “we contacted them” sits before “they replied to us”, then an automation that stamps “we contacted them” on every outgoing message will drag replied-to records out of the human's action queue — burying exactly the items that most needed attention.

Implementation is simple and worth doing every time:

  • Keep the ordering in one list, not in scattered comparisons. Internal state identifiers frequently do not resemble their display labels, so comparing them by name is guesswork waiting to break.
  • Compare positions before writing, and skip if the record is already at or past the target.
  • Leave unrecognised states alone rather than guessing where they belong.

a.k.a. quiet failure; failing without a signal

The most expensive bug class there is, because the clock keeps running while everyone assumes things are fine.

A silent failure is not a crash. A crash is loud and gets fixed. A silent failure is a component making a defensible local decision — drop this message, skip this record, return an empty string — that nobody downstream is told about. From the outside, a system that is working perfectly and a system that is completely dead can produce the identical observation: nothing happened.

The defence is not “add more logging”. It is to make the healthy state provable, so that “nothing happened” can be distinguished from “nothing was supposed to happen”. Two things do that:

  • Log the outcome, not the attempt. “sending notification” tells you nothing. “notification DELIVERED (id 4661)” versus “notification REJECTED 400” tells you everything.
  • Run a canary. A synthetic transaction pushed through the real path on a schedule, which shouts when it does not come out the far end. Without one, you are relying on a customer to report your outage.

a.k.a. SPOF

Every system has a critical path — the sequence of steps that must all succeed for the thing to work. A single point of failure is any step in that path with no alternative.

The trap is that these are usually invisible until they fire, because they hide behind something that has never failed before: a vendor account, a prepaid balance, one API key, one machine, one person who knows how the deploy works.

The lesson that generalises, and the one most teams get wrong: redundancy only counts if it is in the path. Having five interchangeable providers configured somewhere in your estate does nothing if the one place that actually makes the call can only reach one of them. Spare tyres in the garage do not help on the motorway.

Practical test: for each external dependency on your critical path, ask "if this returns an error for the next 72 hours, what does the user see?" If the answer is “nothing at all”, that dependency is a single point of failure, and the fallback belongs where the call is made — not elsewhere.

a.k.a. DRY; configuration drift

When the same rule, prompt, threshold or piece of logic exists in more than one place, the copies begin identical and end different. Nothing announces the divergence. Someone updates one copy, the others keep running the old behaviour, and the system's actual conduct is now split across versions that no single file describes.

The failure is especially nasty when a copy lives somewhere code review cannot see it: a hosted workflow builder, a dashboard setting, a scheduled job on one machine, a prompt pasted into a vendor interface. Those copies never appear in a diff, so the drift stays invisible until it produces a visibly wrong result in front of a customer.

Two defences that work:

  • One definition, imported everywhere. Every consumer reads the same file. Where a copy must physically live elsewhere, generate and push it from that file rather than editing it by hand.
  • Detect drift automatically. Re-read the remote copies on a schedule and raise an alert when one no longer matches the source. A copy you cannot diff is a copy you must monitor.

a.k.a. observability; probe, do not assume

A setting, an environment variable or a present API key is a statement of intent. It is evidence that somebody meant for a behaviour to occur. It is not evidence that the behaviour occurs.

The gap between the two is where the longest outages live, because reading the configuration feels like verification. It produces confident, wrong statements: the key is set, so the provider works; the schedule says every fifteen minutes, so it runs every fifteen minutes; the file was deployed, so the new code is running.

Each of those has a cheap, decisive check that costs seconds:

  • Probe the dependency, do not read its credential. A key that exists proves nothing about the balance behind it.
  • Grep for the action line, not the setup line. A startup banner proves the process started, not that it ever did its work.
  • Compare timestamps after a deploy. If the running process is older than the file on disk, it is still executing the previous version from memory.

The rule this earns: never report a system's behaviour from its configuration. Grep the line that proves the behaviour happened, and quote it.

Colophon

The durable copy of this journal is Markdown in the repository — one file per entry, one per chapter. The page you are reading is generated from those files by node scripts/generate-ai-ops-wiki.mjs and never hand-edited, so it cannot drift from its source. That is the single-source-of-truth rule in Part Two, applied to the journal itself.

A chapter declares the entries it earned; the reverse reference is built at render time from that one declaration, so the two can never disagree. --lint checks the whole corpus for orphan entries, unproven claims and dangling references, and a reference to an entry that does not exist fails the build rather than shipping a dead link.

Set in Fraunces, Newsreader and IBM Plex Mono.