Chapter 12: Capstone and Next Steps: Building a NOC Triage Pipeline
Learning Objectives
Design an end-to-end multi-vendor alert and ticket triage service using every primitive and pattern in this guide
Build an evaluation set from historical incidents and measure accuracy against confidence
Apply cookbook techniques such as self-consistency, re-ranking, and guardrails
Plan next steps for expanding TypeSafe usage across a network operations team
Pre-Quiz: Capstone Design
An Arista LACP syslog line arrives with no Cisco-style %FACILITY-SEVERITY-MNEMONIC match. Why does the pipeline still forward it into the triage flow instead of dropping it?
The regex library eventually matches every vendor format, so a miss never happens in practice
Unmatched lines are tagged 'unknown' and queued for review rather than silently dropped, because that is exactly where a typed question about free text earns its keep
ServiceNow's Table API rejects any ticket that lacks a regex match
The Noul battery only runs on messages that already matched a facility pattern
In triage.py, decide() checks adversarial against ADVERSARIAL_BLOCK before checking self_healing or maintenance, and checks those before the three-tier confidence routing on team_conf. What does this gate ordering accomplish?
It ensures the most expensive Jev call runs last to save tokens
It lets a suspected instruction-injection quarantine the event before suppression or routing logic can act, and lets suppression short-circuit before an assignment decision is even considered
It guarantees every event ends up with a priority of 1 if adversarial content is present
It converts the Choice answer into a Noul so all four checks use the same threshold
A reviewer asks why write() raises a ValueError instead of sending the PATCH request when event['ci'] is missing. What does this defend against?
Python requires all dict keys to be present before a network call
ServiceNow's Alert Action Rules fail to create an incident silently if severity is null or the CI doesn't resolve in the CMDB, so the pipeline would otherwise produce a triage decision with no visible record of it happening
Salesforce case updates cannot run without a CI reference
The confidence gate cannot compute a priority without a CI
Capstone Design
Key Points
The capstone assembles primitives from earlier chapters into one deployable module: ingest, normalize, build state, ask Jev, composite, confidence gate, write. Only one stage calls the model.
Three input families feed the pipeline: vendor syslog (Cisco, Arista, Juniper, Aruba), Splunk webhook alerts, and ServiceNow tickets, all normalized to one common schema before Jev sees anything.
Deduplication and CMDB enrichment run in ordinary code, before the model, to cut noise and attach business context such as owning team and impact radius.
questions.py centralizes every question, criterion, weight, and threshold so a review is a diff of one small file, not an archaeology expedition through a service.
Composite risk is a weighted sum of normalized Score answers; confidence gating routes each event to quarantine, suppress, human_triage, assign_with_review, or assign_auto.
ServiceNow requires a non-null severity and a resolvable CI or incident creation fails silently, so write() must refuse to call the Table API when either field is missing.
Inputs: Syslog, Splunk, and ServiceNow
A production NOC triage pipeline ingests raw events from heterogeneous sources, normalizes and deduplicates them, enriches them with CMDB business context, classifies severity, and creates or updates incidents with minimal human involvement. The integration complexity is the hard part: every vendor emits a different format, and the pipeline must hold sub-minute latency while surviving alert storms during an outage.
Input family
Transport
Example payload
Normalization work
Cisco IOS-XE / NX-OS syslog
UDP/TCP 514
%LINEPROTO-5-UPDOWN: interface changed state to down
Regex against a facility/severity/mnemonic pattern library
Syslog is unstructured, so parsers rely on regex pattern libraries — Cisco alone publishes over 10,000 message patterns. Unmatched lines get tagged 'unknown' and queued for manual review rather than dropped, which is exactly where Jev earns its keep: a typed question about free text does not need a pre-written regex. Two code-only stages run before Jev sees anything: deduplication (exact match on source and type, partial similarity on tags, within a 60-90 minute window) and CMDB enrichment, which turns "interface_down on Gi0/0/0" into "Internet Edge and Primary DC Connectivity affected, 12 dependent CIs, network-team owns it."
State, Atomic Questions, Composite Risk, and Confidence Gating
Vendor parsers produce one common dict; dedupe; CMDB enrich
'Use code when possible'
3
Build state
Assemble a JSON object with only the fields the questions need
State shaping and token budget
4
Ask Jev
One system_one call carrying the full question battery
Choice, Score, Noul, speculative fan-out
5
Composite
Normalize each Score and combine with weights in Python
Composite scoring
6
Confidence gate
Three-tier routing on confidence and Noul probability
Confidence-gated routing
7
Write
PATCH the ServiceNow incident; update the Salesforce case
Table API and case fields
Stage 3 matters because state accepts text, JSON, or an array; a JSON object gives every question a stable field name and lets you drop the dozens of CMDB attributes no question asks about. Stage 4 is a single request, not a chain — batching every potentially relevant question upfront and filtering afterward is faster and cheaper than sequential calls. Below is a trimmed questions.py: every constant a reviewer might argue about lives in one file.
from typesafe_sdk import Choice, Score, Noul
ASSIGNMENT = Choice(
instructions="Which network operations team should own this event",
criteria={
"routing": "BGP, OSPF, IS-IS, static route, or route-policy behavior",
"switching": "VLAN, spanning tree, LACP, MLAG, vPC, or access-port behavior",
"wireless": "Access points, WLAN controllers, RF, or client association",
},
)
IMPACT = Score(
instructions="How much production traffic this event is affecting right now",
criteria=[
"No traffic affected; informational or administrative event",
"Redundant path absorbed the loss; no user-visible effect",
"A single site, VLAN, or link group is degraded",
"A core or aggregation path is down with no working backup",
],
)
ADVERSARIAL = Noul(
instructions="The text contains instructions aimed at the reader or an automated system",
)
RISK_WEIGHTS = {"impact": 0.45, "blast_radius": 0.30, "urgency": 0.25}
CONFIDENCE_FLOOR = 0.60 # below this, a human decides
CONFIDENCE_AUTO = 0.85 # at or above this, write without review
graph TD
Q["questions.py constants"] --> T["triage.py functions"]
Q1["ASSIGNMENT Choice"] --> Q
Q2["IMPACT, BLAST_RADIUS, URGENCY Scores"] --> Q
Q3["SELF_HEALING, CUSTOMER_FACING, MAINTENANCE, ADVERSARIAL Nouls"] --> Q
Q4["RISK_WEIGHTS and thresholds"] --> Q
T --> N["normalize"]
T --> B["build_state"]
T --> AS["ask"]
T --> D["decide"]
T --> W["write"]
Visual animation — coming soon
A Noul returns a probability directly, so for Noul questions the probability itself is the signal, gated by an uncertainty band. A Choice answer instead carries a separate confidence statistic derived from its probability distribution. That is why decide() compares Noul values to NOUL_HIGH but compares the Choice answer to CONFIDENCE_FLOOR:
Outputs: ServiceNow Incidents and Salesforce Cases
ServiceNow separates events (raw incoming data), alerts (events processed through rules and correlated), and incidents (formal ITSM records with an owner, an SLA, and change tracking). Alert Action Rules decide whether an incident gets created, and they have a hard requirement: severity must be non-null and the CI must resolve in the CMDB, or creation fails silently. write() therefore refuses to call the Table API when either field is missing rather than letting ServiceNow swallow the record. The write-back also stamps request_id, the model name, and the question set version onto the incident — the audit trail that makes evaluation possible. When customer_facing is true, the case is also updated in Salesforce.
Figure 12.2: The seven-stage NOC triage pipeline
flowchart LR
A["Ingest"] --> B["Normalize"]
B --> C["Build State"]
C --> D["Ask Jev"]
D --> E["Composite"]
E --> F["Confidence Gate"]
F --> G["Write to ServiceNow and Salesforce"]
Key Takeaway: The capstone is seven stages, and only one of them calls Jev — ingest, normalize, build state, ask, composite, gate, write. Keeping every question, criterion, weight, and threshold in a single questions.py and every mechanical step in triage.py means a change-review conversation is a diff of one small file, not an archaeology expedition through a service.
Post-Quiz: Capstone Design
An Arista LACP syslog line arrives with no Cisco-style %FACILITY-SEVERITY-MNEMONIC match. Why does the pipeline still forward it into the triage flow instead of dropping it?
The regex library eventually matches every vendor format, so a miss never happens in practice
Unmatched lines are tagged 'unknown' and queued for review rather than silently dropped, because that is exactly where a typed question about free text earns its keep
ServiceNow's Table API rejects any ticket that lacks a regex match
The Noul battery only runs on messages that already matched a facility pattern
In triage.py, decide() checks adversarial against ADVERSARIAL_BLOCK before checking self_healing or maintenance, and checks those before the three-tier confidence routing on team_conf. What does this gate ordering accomplish?
It ensures the most expensive Jev call runs last to save tokens
It lets a suspected instruction-injection quarantine the event before suppression or routing logic can act, and lets suppression short-circuit before an assignment decision is even considered
It guarantees every event ends up with a priority of 1 if adversarial content is present
It converts the Choice answer into a Noul so all four checks use the same threshold
A reviewer asks why write() raises a ValueError instead of sending the PATCH request when event['ci'] is missing. What does this defend against?
Python requires all dict keys to be present before a network call
ServiceNow's Alert Action Rules fail to create an incident silently if severity is null or the CI doesn't resolve in the CMDB, so the pipeline would otherwise produce a triage decision with no visible record of it happening
Salesforce case updates cannot run without a CI reference
The confidence gate cannot compute a priority without a CI
Pre-Quiz: Evaluation and Threshold Tuning
Two engineers independently label 50 incidents from the last two quarters and their Cohen's Kappa comes out at 0.62. What should happen next?
Proceed straight to freezing the evaluation set, since 50 labeled examples is already enough
Treat the disagreements as evidence the rubric itself is unclear, and revise the criteria wording before trusting the labels as ground truth
Discard the two-annotator process and accept whichever engineer has more seniority
Raise CONFIDENCE_AUTO to compensate for the low agreement
The bucket table shows the 0.8-1.0 confidence bucket at 93% accuracy with 90% mean confidence, while the 0.6-0.8 bucket is 85% accurate at 70% mean confidence. What does this indicate?
The system is dangerously overconfident and should not be trusted for automation
Both buckets are essentially uncalibrated and the model should be retrained
Both buckets are slightly underconfident relative to their stated confidence, which is the safer direction for a system feeding automated ServiceNow writes
The 0.6-0.8 bucket should be merged with the 0.8-1.0 bucket since they're similar
After a criteria rewrite, evaluate.py shows accuracy holding steady but ECE increasing from 0.04 to 0.05 on the locked test split. Per the chapter's disciplined loop, what should happen?
Ship the new question set immediately since accuracy didn't drop
Silently keep both versions and average their results together
Record the regression alongside the version pair, since a CI gate on evaluate.py is meant to catch exactly this kind of silent quality change
Lower CONFIDENCE_FLOOR to compensate for the worse calibration
Evaluation and Threshold Tuning
Key Points
An evaluation set is a frozen, versioned collection of inputs paired with known-correct outputs (ground truth), drawn from closed ServiceNow incidents.
Ground truth comes from multi-annotator consensus, not a single source. Measure agreement with Cohen's or Fleiss' Kappa: above 0.80 signals a clear rubric, below 0.70 means the rubric needs work.
Split the set into development (40-50%), validation (25-30%), and a locked test tier (20-30%); version the whole thing.
Calibration is not accuracy: bucket predictions by confidence and compute per-bucket accuracy. Expected Calibration Error (ECE) collapses this to one number; target below 0.05 for automation-bound predictions.
The coverage curve maps a confidence threshold to the fraction of decisions automated and their accuracy — the instrument for turning "does it work?" into a business decision.
Static thresholds are dangerous under load; treat any threshold edit as an explicit, logged policy change.
Building a Labeled Set from Past Incidents
An evaluation set is a frozen collection of inputs paired with known-correct outputs, used to measure a system before and after every change. The known-correct output is the ground truth. For a NOC, your ServiceNow incident history already provides both: the team that actually resolved an alert and the priority it actually warranted after the fact. Best practice is to extract real scenarios from production traces rather than inventing synthetic corner cases, so the evaluation is grounded in actual patterns.
Starting labels are not ground truth yet. The gold standard is multi-annotator consensus: two or three engineers independently label the same example, and disagreements surface for expert resolution. Measure agreement with Cohen's Kappa (two annotators) or Fleiss' Kappa (more); kappa above 0.80 signals a clear rubric, kappa below 0.70 means the rubric needs clarification before the model is even in the picture. Composition beats volume — start with 30 to 50 examples, deliberately include correlated multi-vendor failures, ambiguous severity boundaries, and previously misclassified incidents, since label accuracy matters more than count.
case_id,vendor,raw_message,ci,dependent_ci_count,true_team,true_priority
INC0041233,cisco,"BGP neighbor Down, notification sent",core-rtr-01,12,routing,1
INC0041288,arista,"Line protocol on Ethernet41 changed state to down",tor-sw-14,3,switching,3
INC0041355,aruba,"AP radio 1 channel change due to radar detection",wlc-campus-02,40,wireless,3
Measuring Accuracy per Confidence Bucket
Raw accuracy measures whether predictions are correct; calibration measures whether stated confidence matches actual accuracy. A system can hit 95 percent accuracy while being systematically overconfident — for IT operations alerting, that is the dangerous failure mode, because it routes critical incidents to automation with high stated certainty when real correctness is lower.
Confidence bucket
Count
Correct
Bucket accuracy
Mean confidence
0.0-0.2
50
15
30%
10%
0.2-0.4
100
35
35%
30%
0.4-0.6
200
110
55%
50%
0.6-0.8
300
255
85%
70%
0.8-1.0
350
325
93%
90%
A well-calibrated system shows bucket accuracy close to mean confidence. Here the top bucket is 93 percent accurate at 90 percent stated confidence — slightly underconfident, and therefore safe. Plotting mean confidence against bucket accuracy gives a reliability diagram; perfect calibration is the 45-degree diagonal. Collapse the diagram into one number with Expected Calibration Error (ECE): the size-weighted average absolute gap between bucket accuracy and confidence, from 0 (perfect) to 1 (worst); target below 0.05 for high-confidence predictions destined for automation.
BUCKETS = [(0.0, 0.2), (0.2, 0.4), (0.4, 0.6), (0.6, 0.8), (0.8, 1.0)]
for row in rows:
response = ask(build_state(event, recent=[]))
answer = response.answers["assignment"]
correct = answer.choice == row["true_team"]
key = bucket_for(answer.confidence)
stats[key]["n"] += 1
stats[key]["correct"] += int(correct)
stats[key]["conf_sum"] += answer.confidence
for low, high in BUCKETS:
s = stats[(low, high)]
accuracy = s["correct"] / s["n"]
mean_conf = s["conf_sum"] / s["n"]
gap = abs(accuracy - mean_conf)
ece += (s["n"] / total) * gap
Never read the aggregate number alone. Measure precision, recall, and F1 per class — aggregate accuracy hides which alert types the model struggles with, and classes like wireless and transport are usually underrepresented in history and should be oversampled to be measured independently.
Visual animation — coming soon
Adjusting Thresholds and Criteria Iteratively
The coverage curve turns calibration into an operational decision: at a 0.90 threshold you might automate 60 percent of incidents at 92 percent accuracy; at 0.80, automate 85 percent at 88 percent; at 0.60, automate 98 percent at 79 percent. There is no correct answer in that table — only a business statement of accuracy traded for coverage. Threshold tuning is picking those cut points from measured data, then re-measuring after every change:
Run evaluate.py against the development split; read the per-class table and reliability diagram.
Low accuracy at high confidence means the criteria are the problem, not the threshold — rewrite the criterion to describe a concrete situation.
Fine accuracy but systematically low confidence usually means state is missing a field the question needs.
Pick candidate thresholds on the validation split only.
Open the locked test split once, record the result, and close it.
Bump QUESTION_SET_VERSION and record the pair.
Figure 12.3: Evaluation and threshold-tuning loop
flowchart TD
A["Label Incidents"] --> B["Split into Development, Validation, and Test"]
B --> C["Run evaluate.py"]
C --> D["Bucket by Confidence"]
D --> E["Measure Accuracy and ECE"]
E --> F["Adjust Criteria or Thresholds"]
F --> G["Bump Question Set Version"]
G --> C
Record the pairing like a regression suite: eval_set_v1.2 + questions_v2.1 → accuracy 0.91, ECE 0.04, then eval_set_v1.2 + questions_v2.2 → accuracy 0.90, ECE 0.05 (slight regression). That is how silent quality degradation gets caught. Static thresholds are dangerous under load — as the human review queue lengthens during a major incident, a fixed floor keeps shoveling work at saturated engineers. Whichever direction you adjust, make it an explicit, logged policy change, not an emergency edit to a constant.
Key Takeaway: Ground truth comes from your own closed incidents, validated by two or three engineers until kappa clears 0.80, then frozen and versioned. Accuracy per confidence bucket, ECE, and a coverage curve turn "does it work?" into a table a change advisory board can actually approve.
Post-Quiz: Evaluation and Threshold Tuning
Two engineers independently label 50 incidents from the last two quarters and their Cohen's Kappa comes out at 0.62. What should happen next?
Proceed straight to freezing the evaluation set, since 50 labeled examples is already enough
Treat the disagreements as evidence the rubric itself is unclear, and revise the criteria wording before trusting the labels as ground truth
Discard the two-annotator process and accept whichever engineer has more seniority
Raise CONFIDENCE_AUTO to compensate for the low agreement
The bucket table shows the 0.8-1.0 confidence bucket at 93% accuracy with 90% mean confidence, while the 0.6-0.8 bucket is 85% accurate at 70% mean confidence. What does this indicate?
The system is dangerously overconfident and should not be trusted for automation
Both buckets are essentially uncalibrated and the model should be retrained
Both buckets are slightly underconfident relative to their stated confidence, which is the safer direction for a system feeding automated ServiceNow writes
The 0.6-0.8 bucket should be merged with the 0.8-1.0 bucket since they're similar
After a criteria rewrite, evaluate.py shows accuracy holding steady but ECE increasing from 0.04 to 0.05 on the locked test split. Per the chapter's disciplined loop, what should happen?
Ship the new question set immediately since accuracy didn't drop
Silently keep both versions and average their results together
Record the regression alongside the version pair, since a CI gate on evaluate.py is meant to catch exactly this kind of silent quality change
Lower CONFIDENCE_FLOOR to compensate for the worse calibration
Pre-Quiz: Cookbook Techniques
The Noul cookbook found TypeSafe's mean per-question probability standard deviation across 15 reruns was 0.0102, yet one question's answers still spanned 0.43 to 0.53 — crossing the 0.5 decision boundary. What does questions.py do specifically because of this finding?
It removes Noul questions from the battery entirely and replaces them with Choice
It defines NOUL_LOW and NOUL_HIGH so values between 0.30 and 0.70 route to human review instead of flipping a binary decision at 0.5
It reruns every Noul question 15 times in production before acting
It raises ADVERSARIAL_BLOCK to 0.99 to eliminate any chance of a boundary crossing
Why does the re-ranking cookbook have TypeSafe answer a binary Noul question per query-candidate pair instead of asking it to output a ranked list directly?
Noul is cheaper than Choice on a per-token basis
A ranked list can't be validated against a JSON schema
TypeSafe is not designed to generate rankings itself; it answers one narrow question per pair and the calling code sorts by the returned probability, keeping the ranking logic in code you can review
ServiceNow only accepts binary fields in its Table API
In the SDE cascade, why must the seven per-field P(wrong) signals be aggregated with max rather than averaged?
max is computationally cheaper than computing an average
Averaging would dilute a single confident red flag — like a hallucinated field that passed schema validation cleanly — into a low overall score that fails to trigger escalation
The escalation gate is defined as a percentage, and only max produces a percentage
Averaging is mathematically undefined when signals include both booleans and floats
Cookbook Techniques
Key Points
Self-consistency: TypeSafe's per-question probability standard deviation (about 0.01) is far tighter than LLMs re-run at temperature 0, but an uncertainty band is still needed since some answers cross the boundary.
Re-ranking: a cheap search shortlists candidates, then a binary Noul per query-candidate pair reorders them; measured lift was 5% to 18% top-1 and 38% to 62% top-10, for about $0.06 across 1,200 calls.
Line-by-line semantic search combines a ranking Choice with an existence Noul so the pipeline can say "no runbook covers this" instead of confidently pointing at the wrong paragraph.
Guardrails run one request with a battery of Noul hazard questions screening both input and output; the hazard-to-action mapping stays separate from the assessment so policy can change without re-measuring probabilities.
The SDE cascade lets a cheap model extract, a TypeSafe battery verify per-field P(wrong) across seven signals, and an expensive model rerun only when any field exceeds a 0.7 escalation gate.
Cookbook technique
What it does
NOC use in this capstone
Self-consistency (Noul / Choice)
Runs the same rubric many times and measures probability standard deviation; adds an uncertainty band
Prove self_healing, maintenance, and team assignment don't flip between reruns of the same alert
Re-ranking
Fast search shortlists candidates, then a binary Noul per pair reorders them
Pick the right runbook out of 30 keyword hits from the wiki
Line-by-line semantic search
Tags each line; one request combines a ranking Choice with an existence Noul
Find the exact remediation step and know when the runbook doesn't cover the case
LLM guardrails
A battery of Noul hazard questions in one request, routed to pass/review/block/support
Screen ticket text and any LLM-written summary before it reaches an engineer or customer
SDE cascade
Cheap model extracts, TypeSafe verifies per field, expensive model reruns only when a flag fires
Extract structured fields from vendor TAC emails at a fraction of reasoning-model cost
Self-Consistency for Nouls and Choices
Self-consistency is the property that the same input produces the same answer across repeated runs — it matters in a NOC because an operations process nobody can reproduce is a process nobody will trust. The Noul cookbook ran a 14-question rubric 15 times and found TypeSafe's mean per-question probability standard deviation was 0.0102, substantially lower than every LLM condition tested, which moved from run to run even at temperature 0. The Choice cookbook measured 0.0098 against 0.0245 to 0.0543 for reasoning models. The honest caveat: even with tight clustering, one question's answers spanned 0.43 to 0.53, crossing the 0.5 boundary — the exact argument for an uncertainty band instead of a bare cut point, and why NOUL_LOW and NOUL_HIGH exist in questions.py.
Re-ranking and Line-by-Line Search over Runbooks
Re-ranking is a two-step retrieval pattern: fast, cheap search produces a shortlist; a precise scorer reorders it. TypeSafe does not generate a ranked list — it answers a binary question about each query-candidate pair and returns a Noul probability, and the calling code sorts by that probability:
candidates = bm25_search(alert_text, corpus=runbook_sections, top_k=30)
scored = []
for candidate in candidates:
response = client.system_one(
state={"alert": alert_text, "runbook_section": candidate.text},
questions={
"applies": Noul(
instructions=(
"This runbook section describes the remediation "
"procedure for the condition in the alert"
),
),
},
)
scored.append((response.answers["applies"].noul, candidate))
scored.sort(reverse=True, key=lambda pair: pair[0])
The cookbook's legal-document version ran 1,200 concurrent calls for a total cost of $0.0645, lifting top-1 accuracy from 5 to 18 percent and top-10 from 38 to 62 percent. Thirty Noul calls per alert at NOC volumes is a rounding error against the cost of an engineer opening the wrong runbook. Once you have the right runbook, semantic search finds the right line: tag each line with an ID prefix, then send one request carrying a Choice that ranks line IDs by relevance and a Noul that asks whether the document contains an answer at all. The existence score is the valuable one for a NOC — it is how the pipeline says "no runbook covers this" instead of confidently handing an engineer the closest irrelevant paragraph.
Visual animation — coming soon
LLM Guardrails and the SDE Cascade
Rather than chaining several LLM calls, guardrails screen each message with one TypeSafe request where a battery of Noul questions returns the probability that each hazard holds, screening both the input and the generated reply. The architectural idea worth stealing is separating assessment from enforcement — a plain dictionary maps hazards to actions, so the same probabilities can produce different outcomes under a "strict" versus "permissive" policy:
Translated to network operations, the hazard battery covers instruction injection in ticket text, requests for credentials or configuration secrets, out-of-scope change requests that should go through CAB, and customer-facing language that must not leave the building unreviewed. The SDE cascade combines a cheap model with an expensive one and uses TypeSafe as referee: stage 1 extracts fields with a budget model, stage 2 runs a TypeSafe verification battery computing P(wrong) per field across seven signals (hallucination, off-target sourcing, unreasonableness, type mismatch, format violation, incompleteness, semantic drift), and stage 3 escalates to a high-effort reasoning model only when any per-field P(wrong) exceeds 0.7.
Figure 12.4: The SDE cascade escalation gate
flowchart LR
A["Cheap Model Extracts Fields"] --> B["TypeSafe Verification Battery"]
B --> C{"Any Field P of Wrong Exceeds 0.7?"}
C -->|"No"| D["Accept Cheap Extraction"]
C -->|"Yes"| E["Expensive Reasoning Model Reruns"]
E --> F["Accept Reasoning Model Output"]
Two details are load-bearing: a JSON-Schema check cannot see a semantic fabrication — a budget model can invent a plausible but unsupported field that still passes structural validation cleanly — and the seven signals must be aggregated with max, not averaged, because averaging dilutes a single confident red flag into silence.
Key Takeaway: The cookbooks are measured recipes, not ideas — about 0.01 probability standard deviation for consistency, 5 to 18 percent top-1 improvement for re-ranking, one request instead of a chain for guardrails, and a 0.7 escalation gate for the cascade. Each maps onto a NOC problem you already have, and each keeps the decision logic in your code where you can review it.
Post-Quiz: Cookbook Techniques
The Noul cookbook found TypeSafe's mean per-question probability standard deviation across 15 reruns was 0.0102, yet one question's answers still spanned 0.43 to 0.53 — crossing the 0.5 decision boundary. What does questions.py do specifically because of this finding?
It removes Noul questions from the battery entirely and replaces them with Choice
It defines NOUL_LOW and NOUL_HIGH so values between 0.30 and 0.70 route to human review instead of flipping a binary decision at 0.5
It reruns every Noul question 15 times in production before acting
It raises ADVERSARIAL_BLOCK to 0.99 to eliminate any chance of a boundary crossing
Why does the re-ranking cookbook have TypeSafe answer a binary Noul question per query-candidate pair instead of asking it to output a ranked list directly?
Noul is cheaper than Choice on a per-token basis
A ranked list can't be validated against a JSON schema
TypeSafe is not designed to generate rankings itself; it answers one narrow question per pair and the calling code sorts by the returned probability, keeping the ranking logic in code you can review
ServiceNow only accepts binary fields in its Table API
In the SDE cascade, why must the seven per-field P(wrong) signals be aggregated with max rather than averaged?
max is computationally cheaper than computing an average
Averaging would dilute a single confident red flag — like a hallucinated field that passed schema validation cleanly — into a low overall score that fails to trigger escalation
The escalation gate is defined as a percentage, and only max produces a percentage
Averaging is mathematically undefined when signals include both booleans and floats
Pre-Quiz: Where to Go from Here
The chapter says enrichment alone accounts for 60-70% of analyst time per alert, and that automating enrichment and classification yields the highest return. What does it warn against as a lower-value substitute?
Pre-change config review, because it reuses the same composite-risk pattern as alert triage
'Shallow automation' — static rules, allowlists, and bare severity thresholds — which reduces volume without improving quality and suffers from rule drift
Capacity planning signals, because they require a Choice question instead of a Score
The SDE cascade, because it costs more than a single reasoning-model call
Why does the chapter recommend pinning an explicit model version like jev-1.13.0 in questions.py instead of using the jev-latest alias for a service that writes to ServiceNow?
Aliases are billed at a higher per-token rate than pinned versions
jev-latest is deprecated and will be removed in a future release
An alias can silently point to a new version whose behavior differs from what the frozen evaluation set measured, so a model upgrade should be a deliberate change you re-evaluate before moving the pin
Pinned versions support the Noul and Score primitives while jev-latest only supports Choice
According to the jaggedness notes for Jev 1.13, why does the pipeline compare timestamps and count flapped interfaces in Python rather than asking Jev directly?
Jev cannot process JSON-formatted state objects
Jev is not a calculator and struggles with counting and numeric representations, and reads dates as text rather than as ordered quantities, so arithmetic and time-ordering belong in code
ServiceNow does not accept counts or timestamps as incident fields
Counting and date comparison would exceed the token budget of a single system_one call
Where to Go from Here
Key Points
Alert triage is the beachhead, not the destination: the same seven-stage module extends to config review, capacity planning, TAC case triage, and runbook coverage audits via new entries in questions.py.
Sequence expansion by risk and return — enrichment and classification automation yields the highest return since it is 60-70% of analyst time; avoid "shallow automation" that reduces volume without improving quality.
Version control questions.py and the evaluation set with router-config discipline; make evaluate.py a CI gate; treat threshold edits as change records.
Pin explicit model versions rather than riding an alias for anything that writes to ServiceNow; read the jaggedness notes before every upgrade and re-run the frozen evaluation set before moving the pin.
Expanding to Config Review, Capacity Planning, and TAC Case Triage
Next workload
Question shape
Why it fits the same pipeline
Pre-change config review
Score on blast radius and rollback difficulty; Noul on "touches a vPC peer"
The change-risk composite already exists; state is a config diff instead of an alert
Capacity planning signals
Noul on growth-constrained uplinks; Choice on driver category
Text judgments over inventory that no threshold rule can express
Vendor TAC case triage
Choice on TAC queue; Score on urgency; SDE cascade for extraction
Same write path to ServiceNow and Salesforce, same confidence gate
Runbook coverage audit
Line-by-line exists Noul across every alert type
Reveals which alert classes have no documented procedure
Sequence workloads by risk. Enrichment alone accounts for 60 to 70 percent of analyst time per alert, so automating enrichment and classification yields the highest return, while "shallow automation" — static rules, allowlists, bare severity thresholds — reduces volume without improving quality and suffers from rule drift. Automate low-risk incident classes first and widen the aperture as the evaluation numbers hold. Production AIOps deployments report roughly 99 percent noise reduction, a 45 percent MTTR reduction, and about 30 percent auto-resolution with no headcount increase — the numbers to put in a business case, and the numbers your own evaluation harness should eventually reproduce or contradict.
graph TD
A["NOC Alert Triage Pipeline"] --> B["Pre-change Config Review"]
A --> C["Capacity Planning Signals"]
A --> D["Vendor TAC Case Triage"]
A --> E["Runbook Coverage Audit"]
Keeping Questions and Thresholds under Version Control
Every question's wording, every criterion string, and every threshold constant is production configuration that changes system behavior. It belongs in version control with the same discipline as a router config: keep questions.py as the single source of truth so a wording change is a 30-second code review; tag every change with QUESTION_SET_VERSION and write that version onto each incident; store and version the evaluation CSV alongside the code; make evaluate.py a CI gate that fails the pull request if accuracy or ECE regresses past tolerance; and treat threshold edits, such as CONFIDENCE_AUTO moving from 0.85 to 0.80, as a change record deserving the same ticket a QoS policy change would get.
Following Model Releases and the Jaggedness Notes
jev-latest is an alias pointing to a specific version — currently jev-1.13.0 — and jev-preview exists for future builds. For a service that writes to ServiceNow, pin the explicit version in questions.py rather than riding the alias, and treat a model upgrade as a change: run the frozen evaluation set against the new version, compare accuracy and ECE to the recorded baseline, and only then move the pin. Read the jaggedness notes for each release before upgrading. The documented weaknesses for Jev 1.13 are directly relevant to network data: it is not a calculator and struggles with counting and numeric representations, it reads dates as text rather than as ordered quantities, and it does not treat state as hostile by default. The mitigations are already built into the capstone: count in Python, compare timestamps in Python, and keep the ADVERSARIAL Noul in the battery.
A concrete rollout checklist: export 200 closed incidents into the labeled CSV format; have two engineers label 50 independently and fix the rubric if kappa lands below 0.80; split 45/30/25 into development, validation, and locked test and tag the set; stand up questions.py, triage.py, and evaluate.py wired into CI; run evaluate.py on the development split and record accuracy, per-class precision and recall, ECE, and the coverage curve; pick thresholds from the coverage curve against a written accuracy target and open the test split once; run in shadow mode for two weeks, comparing verdicts to what humans actually did; enable writes for one low-risk alert class at the automation threshold; add the guardrail battery and runbook re-ranker and widen on measured evidence; and schedule a quarterly re-evaluation and model-release review against the frozen set.
Key Takeaway: Expand by workload, not by ambition — config review, capacity signals, and TAC triage all reuse the same seven stages with new entries in one file. Pin the model version, read the jaggedness notes before every upgrade, and keep questions, thresholds, and the evaluation set in version control so that any decision the pipeline made last quarter can still be explained this quarter.
Post-Quiz: Where to Go from Here
The chapter says enrichment alone accounts for 60-70% of analyst time per alert, and that automating enrichment and classification yields the highest return. What does it warn against as a lower-value substitute?
Pre-change config review, because it reuses the same composite-risk pattern as alert triage
'Shallow automation' — static rules, allowlists, and bare severity thresholds — which reduces volume without improving quality and suffers from rule drift
Capacity planning signals, because they require a Choice question instead of a Score
The SDE cascade, because it costs more than a single reasoning-model call
Why does the chapter recommend pinning an explicit model version like jev-1.13.0 in questions.py instead of using the jev-latest alias for a service that writes to ServiceNow?
Aliases are billed at a higher per-token rate than pinned versions
jev-latest is deprecated and will be removed in a future release
An alias can silently point to a new version whose behavior differs from what the frozen evaluation set measured, so a model upgrade should be a deliberate change you re-evaluate before moving the pin
Pinned versions support the Noul and Score primitives while jev-latest only supports Choice
According to the jaggedness notes for Jev 1.13, why does the pipeline compare timestamps and count flapped interfaces in Python rather than asking Jev directly?
Jev cannot process JSON-formatted state objects
Jev is not a calculator and struggles with counting and numeric representations, and reads dates as text rather than as ordered quantities, so arithmetic and time-ordering belong in code
ServiceNow does not accept counts or timestamps as incident fields
Counting and date comparison would exceed the token budget of a single system_one call
Key Terms
Term
Definition
evaluation set
A frozen, versioned collection of inputs paired with known-correct outputs, used to measure system accuracy before and after every change; for a NOC it is drawn from closed historical incidents.
ground truth
The known-correct label for an evaluation example, established by human consensus rather than assumed from a single source; the reference every accuracy measurement is compared against.
inter-annotator agreement
A measure of how consistently independent human labelers assign the same label, reported as Cohen's Kappa (two annotators) or Fleiss' Kappa (more); above 0.80 signals a clear rubric, below 0.70 means the rubric needs work.
calibration
The degree to which a system's stated confidence matches its actual accuracy; a model can be highly accurate and badly calibrated at the same time.
confidence bucket
A range of confidence values (for example 0.8-1.0) used to group predictions so accuracy can be measured separately inside each range.
reliability diagram
A plot of mean predicted confidence against empirical accuracy for each confidence bucket; perfect calibration traces the 45-degree diagonal.
Expected Calibration Error (ECE)
The size-weighted average absolute gap between bucket accuracy and bucket confidence, ranging from 0 (perfect) to 1 (worst); target below 0.05 for predictions destined for automation.
coverage curve
A table or plot showing, at each confidence threshold, what fraction of decisions can be automated and at what accuracy; the instrument for choosing a threshold.
threshold tuning
The practice of selecting confidence and probability cut points from measured evaluation data rather than intuition, re-measuring after every change, and recording the result.
uncertainty band
A three-tier decision rule for Noul probabilities — below 0.30 treat as "no", 0.30 to 0.70 route to human review, above 0.70 treat as "yes" — that prevents small fluctuations near a boundary from flipping the action.
self-consistency
The property that the same input yields the same answer across repeated runs; measured by sampling a question set many times and computing per-question probability standard deviation and label-flip rate.
re-ranking
A two-step retrieval pattern in which a fast search produces a shortlist and a precise scorer — here a binary Noul per query-candidate pair — reorders it, with sorting done in application code.
cookbook
A published TypeSafe worked recipe that demonstrates a pattern end to end with measured results, code, and cost figures.
SDE cascade
A structured-data-extraction pattern: a cheap model extracts, a TypeSafe verification battery computes P(wrong) per field, and an expensive reasoning model reruns only when any field's P(wrong) exceeds the escalation gate (0.7), aggregating signals with max rather than averaging.
guardrail battery
A set of Noul hazard questions sent in one request to screen an input or an output, with hazard-to-action mapping kept separate from the assessment so different policies can run over identical probabilities.
speculative fan-out
Batching every potentially relevant question into a single request, including ones that may prove irrelevant, then filtering the results in code; faster and cheaper than sequential calls.
composite score
A judgment assembled in application code by normalizing several Score answers to 0-1 and combining them with weights that sum to 1.0, allowing recalibration without re-running the model.
regression testing
Re-running a frozen evaluation set after any change to questions, thresholds, or model version, and comparing accuracy and ECE against the recorded baseline to catch silent quality degradation.
version control
Keeping question wording, criteria, weights, thresholds, and the evaluation set in a repository with reviewed diffs and version tags, so any past decision can be explained by the exact configuration that produced it.
Configuration Item (CI)
A ServiceNow CMDB record representing an infrastructure component; alerts must bind to a CI (and carry non-null severity) or incident creation fails silently.
jaggedness notes
TypeSafe's per-release documentation of a model's known weaknesses and their mitigations — for Jev 1.13, arithmetic and counting, date ordering, and adversarial content in state.