Chapter 12: Capstone and Next Steps: Building a NOC Triage Pipeline

Learning Objectives

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

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 familyTransportExample payloadNormalization work
Cisco IOS-XE / NX-OS syslogUDP/TCP 514%LINEPROTO-5-UPDOWN: interface changed state to downRegex against a facility/severity/mnemonic pattern library
Arista EOS syslogUDP/TCP 514%BGP-5-ADJCHANGE: peer ... DownSame parser family, different mnemonic table
Juniper Junos syslogUDP/TCP 514rpd[1234]: RPD_OSPF_NBRDOWN: neighbor ... DownProcess-tag parser, not %FACILITY
Aruba AOS-CX syslogUDP/TCP 514LLDP neighbor removed on port 1/1/12Free-text patterns, highest 'unknown' rate
Splunk saved-search alertsWebhook POSTresult, sid, results_link, search_name, owner, appAlready JSON; map search_name to alert type
ServiceNow ticketsTable API pollshort_description, description, cmdb_ciAlready JSON; strip HTML from journal fields

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

#StageWhat happensPrimitive or pattern
1IngestSyslog receivers, Splunk webhook endpoint, ServiceNow pollerPlain transport code
2NormalizeVendor parsers produce one common dict; dedupe; CMDB enrich'Use code when possible'
3Build stateAssemble a JSON object with only the fields the questions needState shaping and token budget
4Ask JevOne system_one call carrying the full question batteryChoice, Score, Noul, speculative fan-out
5CompositeNormalize each Score and combine with weights in PythonComposite scoring
6Confidence gateThree-tier routing on confidence and Noul probabilityConfidence-gated routing
7WritePATCH the ServiceNow incident; update the Salesforce caseTable 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

Figure 12.1: Module structure — questions.py constants feeding triage.py functions

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:

if adversarial > ADVERSARIAL_BLOCK:
    action = "quarantine"
elif self_healing > NOUL_HIGH:
    action = "suppress"
elif team_conf < CONFIDENCE_FLOOR:
    action = "human_triage"
elif team_conf < CONFIDENCE_AUTO:
    action = "assign_with_review"
else:
    action = "assign_auto"

priority = 1 if risk >= RISK_P1 else 2 if risk >= RISK_P2 else 3

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

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 bucketCountCorrectBucket accuracyMean confidence
0.0-0.2501530%10%
0.2-0.41003535%30%
0.4-0.620011055%50%
0.6-0.830025585%70%
0.8-1.035032593%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:

  1. Run evaluate.py against the development split; read the per-class table and reliability diagram.
  2. Low accuracy at high confidence means the criteria are the problem, not the threshold — rewrite the criterion to describe a concrete situation.
  3. Fine accuracy but systematically low confidence usually means state is missing a field the question needs.
  4. Pick candidate thresholds on the validation split only.
  5. Open the locked test split once, record the result, and close it.
  6. 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

Cookbook techniqueWhat it doesNOC use in this capstone
Self-consistency (Noul / Choice)Runs the same rubric many times and measures probability standard deviation; adds an uncertainty bandProve self_healing, maintenance, and team assignment don't flip between reruns of the same alert
Re-rankingFast search shortlists candidates, then a binary Noul per pair reorders themPick the right runbook out of 30 keyword hits from the wiki
Line-by-line semantic searchTags each line; one request combines a ranking Choice with an existence NoulFind the exact remediation step and know when the runbook doesn't cover the case
LLM guardrailsA battery of Noul hazard questions in one request, routed to pass/review/block/supportScreen ticket text and any LLM-written summary before it reaches an engineer or customer
SDE cascadeCheap model extracts, TypeSafe verifies per field, expensive model reruns only when a flag firesExtract 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:

HAZARD_ACTION = {
    "jailbreak": "block",
    "harmful_request": "block",
    "medical_advice": "review",
    "self_harm": "support",
}

def route(nouls: dict[str, float], severity: float, policy: dict) -> str:
    """Convert TypeSafe assessment into policy-specific action"""

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

Expanding to Config Review, Capacity Planning, and TAC Case Triage

Next workloadQuestion shapeWhy it fits the same pipeline
Pre-change config reviewScore 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 signalsNoul on growth-constrained uplinks; Choice on driver categoryText judgments over inventory that no threshold rule can express
Vendor TAC case triageChoice on TAC queue; Score on urgency; SDE cascade for extractionSame write path to ServiceNow and Salesforce, same confidence gate
Runbook coverage auditLine-by-line exists Noul across every alert typeReveals 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.

Figure 12.5: Expansion roadmap beyond alert triage

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

TermDefinition
evaluation setA 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 truthThe 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 agreementA 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.
calibrationThe 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 bucketA range of confidence values (for example 0.8-1.0) used to group predictions so accuracy can be measured separately inside each range.
reliability diagramA 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 curveA 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 tuningThe 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 bandA 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-consistencyThe 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-rankingA 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.
cookbookA published TypeSafe worked recipe that demonstrates a pattern end to end with measured results, code, and cost figures.
SDE cascadeA 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 batteryA 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-outBatching 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 scoreA 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 testingRe-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 controlKeeping 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 notesTypeSafe'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.

Your Progress

Answer Explanations