When a service fails at 3am, the worst version of the problem is this: a human arrives with no context, reads logs in isolation, forms a hypothesis from scratch, applies a fix, and waits. The best version is different. Something already knows the history. It knows which failure modes have appeared before, which fixes worked, and how confident to be in each. It arrives with a ranked list of hypotheses. It may have already acted.

This is the goal we set when we built a reasoning layer into our knowledge graph: infrastructure that holds beliefs about itself, updates those beliefs from evidence, and can reason under uncertainty before a human is involved. The mathematical foundation is Bayesian inference over Beta distributions. The practical result is a system that improves its own triage over time.

This post explains how it works, why Beta distributions are the right tool for this problem, and what the system looks like when it runs.


The problem with stateless triage

Most infrastructure monitoring systems are stateless in an important sense. They detect anomalies. They fire alerts. They stop there. The next time the same component fails in the same way, the system fires the same alert with no memory of what happened last time. Every incident is treated as if it is the first.

This is not stupidity — it is simplicity. Stateless detection is easy to reason about, easy to test, and sufficient for systems that fail rarely and in different ways each time. But for mature infrastructure with recurring failure modes, it is a bottleneck. The operational knowledge — "when this service runs out of memory, a restart fixes it in 80% of cases; the other 20% require looking at the database connection pool" — lives in the heads of engineers. It is not queryable. It is not updated automatically. It leaves with the engineer who learned it.

The knowledge graph already stores everything an agent learns about a system: its components, their relationships, the decisions that shaped them. Adding a reasoning layer means storing one more thing: beliefs about behavior. Specifically, beliefs about which interventions work, expressed as probabilities, updated from evidence.


Why Beta distributions

The core question in infrastructure triage is almost always binary: will this fix work? Restart the service — does it recover? Roll back the deployment — does the error rate drop? The outcome is success or failure.

Bernoulli trials — experiments with binary outcomes — have a natural conjugate prior: the Beta distribution. This is worth spending a moment on, because "conjugate prior" has an operational meaning that is directly relevant here.

A Beta distribution is parameterized by two positive numbers, conventionally written as alpha (α) and beta (β). Intuitively:

  • α accumulates evidence of success
  • β accumulates evidence of failure
  • The mean of the distribution — our best estimate of the success probability — is α / (α + β)
  • The variance — how uncertain we are — shrinks as α + β grows
Beta distribution
Mean = α / (α + β)
Variance = αβ / ((α + β)² · (α + β + 1))

Update rule:
  Success → Beta(α + w, β)
  Failure → Beta(α, β + w)
where w is the evidence weight (default 1.0)

The "conjugate" property means the posterior after observing evidence has the same form as the prior. If you start with Beta(α, β) and observe a success, you get Beta(α+1, β). The math closes. There is no numerical approximation required. Each update is two additions.

We use Laplace smoothing to initialize: every new failure mode starts at Beta(1, 1), the uniform distribution. This encodes "we know nothing yet" rather than assuming any particular success rate. After one observed success, we have Beta(2, 1) — mean 0.67, with high variance. After twelve successes and two failures, we have Beta(13, 3) — mean 0.81, with much lower variance. The system has learned something, and it knows how confident to be in what it has learned.

Every prior starts uninformed. Confidence is earned from evidence, not assumed.


Prior decay: old evidence loses influence

Infrastructure changes. A failure mode that was reliably fixed by a restart six months ago might require a different fix today if the underlying cause has shifted. Evidence should not accumulate indefinitely without depreciation.

We apply exponential decay to the pseudo-counts before using them for scoring:

Decay
decay = exp(−ln(2) / half_life_days × days_since_last_observation)

effective_α = 1 + (α − 1) × decay
effective_β = 1 + (β − 1) × decay

At t = 0: effective = raw (no decay)
At t = half_life: effective = 1 + (raw − 1) × 0.5
At t → ∞: effective → (1, 1) — back to uniform

The floor is (1, 1), not (0, 0). We never claim to have negative evidence — we simply forget, returning to ignorance rather than inverting belief. A prior with half_life set to 90 days will discount an observation from last year to roughly 1% of its original weight. A prior with no decay will accumulate indefinitely, appropriate for stable systems where the past is genuinely informative.

This is configurable per prior, not global. A server's restart behavior is likely stable; a deployment pipeline's success rate may fluctuate with code quality across quarters. Different components warrant different forgetting rates.


Composite scoring: frequency, success rate, recency

A prior encodes success probability for one failure mode. But the reasoner needs to rank multiple hypotheses when an alarm fires. For that ranking, we compute a composite score that weighs three signals:

Pattern score
score = w_freq × frequency_normalized
      + w_success × posterior_mean
      + w_recency × recency_weight

frequency_normalized = obs_i / Σ obs_all
posterior_mean = eff_α / (eff_α + eff_β)
recency_weight = exp(−ln(2) / recency_half_life × days_since)

Default weights: frequency=0.30, success=0.50, recency=0.20

The dominant signal is success rate (weight 0.50), which is correct: we care most about what works. Frequency (0.30) accounts for base rate — a failure mode that appears often is more likely to be the culprit even if its fix has a moderate success rate. Recency (0.20) breaks ties toward recent evidence when two hypotheses are otherwise equally ranked.

These weights are not constants baked into the model. They are configurable parameters that can be tuned as the system accumulates data. The initial defaults represent a reasonable prior about what matters in infrastructure triage; the actual data will tell us whether that prior was right.


What a prior looks like in practice

Priors are stored per component, keyed by a string that describes the failure mode in the format alarm_type:failure_mode. Examples:

  • oom:conversation_leak — out-of-memory alarm, hypothesized cause: conversation history accumulating without bound
  • latency_spike:connection_pool_exhaustion — slow responses, hypothesized cause: database connections exhausted
  • restart_loop:bad_config — service cycling, hypothesized cause: configuration parse failure

Each prior has alpha and beta counts, an evidence count, a timestamp of last observation, and optional notes from whoever seeded it. Priors can be initialized from domain knowledge using kg_init_prior, which accepts a mean and a confidence expressed as an effective sample size:

-- Domain knowledge: restart usually fixes OOM, based on 10 historical incidents
SELECT kg_init_prior(
    entity_id,
    'oom:service_restart',
    0.8,   -- initial mean: 80% success rate
    10,    -- effective sample size: treat as if we observed 10 cases
    'Historical pattern: restart resolves OOM in roughly 8 of 10 cases'
);

This seeds the prior at Beta(8, 2), mean 0.80, with moderate confidence. It will update normally from real observations going forward. An engineer's hard-won experience becomes a starting point for the model rather than something that has to be rediscovered from scratch.


The reasoning loop

When an alarm fires, the system executes a structured reasoning sequence:

  • Observe — collect the alarm signal, current metrics, and any recent observations about the affected component from the knowledge graph
  • Match — query priors for the component, ranked by composite score; each prior represents a hypothesis about root cause
  • Infer — select the highest-scoring hypothesis above a confidence threshold
  • Decide — select an intervention associated with that hypothesis
  • Record — store the full reasoning trace: what was observed, which priors were consulted, what was inferred, what action was taken
  • Update — when the outcome is known, call kg_update_prior to adjust the relevant prior

The reasoning trace is stored with a semantic embedding. Future incidents can be matched against past reasoning by asking: "find reasoning traces similar to this alarm." The vector search returns cases where the system navigated comparable problems — even if the component names or specific metrics differ. Context accumulates across incidents, not just within them.


The causal chain record

Each incident produces a causal chain: an ordered sequence of steps from alarm to resolution. The structure is deliberately simple:

[
  {"step_type": "alarm",     "description": "Memory above threshold",
   "occurred_at": "..."},
  {"step_type": "diagnosis", "description": "Prior: oom:conversation_leak mean=0.74",
   "occurred_at": "..."},
  {"step_type": "action",    "description": "Cleared conversation cache",
   "occurred_at": "..."},
  {"step_type": "result",    "description": "Memory normalized within 90s",
   "occurred_at": "..."}
]

This record exists for three reasons. First, it is auditable: a human can inspect exactly what the system decided and why. Second, it is trainable: the outcome updates the prior that generated the diagnosis. Third, it is searchable: when a similar alarm appears, the system can surface past chains where the same component was involved, even chains from months ago.

The causal chain is the memory that the next incident inherits. It is the difference between a system that handles every alarm as if it is the first and one that has seen things before.


Confidence bands and the honest unknown

Not all priors are equally trustworthy. The system exposes a confidence band alongside each prior's mean:

  • Low — fewer than 3 observations; the mean is mostly the prior, not evidence
  • Medium — 3 to 9 observations; directionally useful but not reliable
  • High — 10 or more observations; the mean reflects genuine accumulated experience

A prior with mean 0.90 and confidence band "low" should not be treated the same as one with mean 0.90 and confidence band "high." The first is a guess with a hopeful starting point. The second is something the system has actually learned. The reasoner checks both the mean and the band before deciding whether to act autonomously or escalate to a human.

This is an important design choice. Autonomous action is appropriate when the system is confident. Escalation is appropriate when it is not. A system that acts on low-confidence priors as if they were high-confidence ones will eventually make a wrong call with no human in the loop to catch it. The confidence band is the mechanism that keeps the system honest about what it does not yet know.

The system should be most aggressive about acting when it is most certain, and most deferential when it is not. The math enforces this automatically.


What this enables

The immediate application is infrastructure triage. A service starts behaving abnormally. Before any human sees an alert, the reasoner has already queried what is known about this component, ranked the likely causes by historical evidence, and either taken a low-risk corrective action (if confidence is high) or assembled a structured briefing for whoever responds (if it is not).

The less immediate but more interesting application is knowledge transfer. Priors seeded from one engineer's experience survive that engineer's departure. Causal chains recorded during incidents become the basis for reasoning in future incidents. The system's understanding of its own infrastructure deepens over time rather than resetting at each personnel change.

Over a long enough horizon, the priors become a quantitative record of how the system behaves: which components are reliable, which are fragile, which interventions work, which are unreliable. This is the kind of knowledge that normally lives in tribal memory or post-incident documents that no one reads. Expressed as Beta distributions, it is queryable, updatable, and directly usable by any reasoning system that has access to the database.

The question worth asking at the end of every incident is not just "what happened?" but "what does the system now know that it did not know before?" The reasoning layer makes that question answerable.