Autofix-5 Redesign — Architect's Playbook¶
Goal. Same UI. Same "capture → plan → HITL approve → execute → verify" contract. Under the hood: replace "ask Claude everything" with "ask Claude only when it's actually needed, with only the exact slice of the artifact that matters." Optimise for reliability, minimal LLM context, deterministic where possible, and a KB that actually improves over time.
Table of contents¶
- The core insight — mainframe artifacts have structure. Parse it.
- The intelligent pipeline — 8 stages, LLM only in stage 5 (and only if needed)
- What is the mainframe equivalent of Python AST?
- JCL: parse tree + data-flow graph
- COBOL: FD + WORKING-STORAGE + failing paragraph slice
- Spool: anchored regions, not the whole file
- VSAM / catalog: structured LISTCAT, not raw text
- The context contract — what actually goes into Claude
- Skills as executable routing keys (not prompt hints)
- Real reinforcement learning for a KB that gets smarter over time
- Reliability: schema, grounding, gate tiers, HITL, rollback
- End-to-end trace — same S0C7 in the new architecture
- Migration plan — what to keep, what to add, what to delete
- Success metrics
1. The core insight — parse the artifacts¶
Every mainframe object involved in a fix has a grammar. The current autofix-5 treats them all as text and hands the raw text to Claude. That's why 12-40 KB of context flies on every LLM turn.
| Artifact | Grammar | What a parser gives you |
|---|---|---|
| JCL deck | z/OS JCL grammar (JOB, EXEC, DD, PROC, SET, IF/THEN) | AST → step tree → DD data-flow graph → symbol table |
| COBOL source | ANSI 85 (or IBM Enterprise COBOL 6.x) — 4 divisions, sections, paragraphs, statements | AST → paragraph call-graph → data-flow of WS fields → FD-to-SELECT map |
| JES spool | JCL echo (JESJCL) + system msgs (JESMSGLG/JESYSMSG) + step SYSPRINT/SYSOUT | Message-ID token stream, step boundaries, ABEND anchors, severity ranks |
| VSAM cluster | IDCAMS LISTCAT structured output | Cluster attrs (RECFM/LRECL/KEYS/RECORDSIZE), DATA/INDEX components, record counts |
| Copybook | COBOL data description (01/05/PIC/OCCURS/REDEFINES) | Named-field layout, byte offset & length, packed vs display, hierarchy |
| PROC library | JCL PROC syntax with symbolics | PROC-step tree, symbol table, ENV overrides |
If you parse these, your reducer becomes surgical. Instead of "here's the whole spool + JCL, figure it out", you send "here's the FAILING STATEMENT of the failing STEP with the FIELDS it reads and the CATALOG state of every DSN it touches".
The compression is 10-50×. And it's lossless for the failing decision — the LLM never needed the other 95%.
2. The intelligent pipeline¶
flowchart LR
A[Watcher / Manual /capture] --> B[Multi-artifact reducer<br/>DETERMINISTIC]
B --> C[Fast classifier<br/>Gemini Flash 1 call OR rule-based]
C --> D{Skill Router<br/>DET table lookup}
D -->|Known deterministic pattern<br/>60% of load| E[Template Fixer<br/>ZERO LLM]
D -->|Ambiguous or novel| F[Deep Investigator<br/>Claude Sonnet, reason-act]
E --> G[Plan]
F --> G
G --> H[Orchestrator gate<br/>schema+grounding+tier]
H --> I[HITL approve]
I --> J[Executor DET<br/>IDCAMS + submit + verify]
J --> K{Verify success criteria}
K -->|pass| L[SUCCEEDED]
K -->|fail| M[Replan bounded 1-3x]
M --> D
Which stage costs what:
| Stage | Compute | Cost per incident | Handles what |
|---|---|---|---|
| 1. Reducer (JCL parse, spool anchor, catalog stat) | Deterministic Python | ~10 ms, $0 | Every incident |
| 2. Classifier (opt: Gemini Flash or ML rules) | 1 tiny LLM call OR pure rules | ~$0.001 or $0 | Every incident |
| 3. Skill Router (table lookup) | Deterministic | $0 | Every incident |
| 4a. Template Fixer | Deterministic template rendering | $0 | 60% (deterministic patterns) |
| 4b. Deep Investigator | Claude reason-act, but with SLICED context | ~$0.05 | 40% (novel / ambiguous) |
| 5. Gate + Executor + Verifier | Deterministic | $0 | Every incident |
| Weighted avg | — | ~$0.02 | Full load |
Current autofix-5 average: ~$0.15. Reduction: ~7× at the same or better fix rate, and the deterministic path is strictly more reliable because it doesn't roll dice.
3. What is the mainframe equivalent of Python AST?¶
Python's ast module walks Python source → structured tree. On the mainframe:
| Python | Mainframe equivalent |
|---|---|
ast.parse(source) → Module tree |
JCL: a hand-rolled parser or jcl-parser (Python pkg); COBOL: ProLeap COBOL parser (Java, run as sub-process) or koopa |
ast.walk visit nodes |
Walk JCL steps, COBOL paragraphs |
Symbol table via symtable |
JCL symbols (SET/PROC) + COBOL WS field table |
| Type info | COBOL PIC clause + USAGE (COMP-3 = packed, DISPLAY = zoned, etc.) |
Call graph via mccabe/radon |
COBOL PERFORM graph, JCL step → DSN read/write graph |
| Import resolution | COPY copybook resolution, JCLLIB PROC lookup |
| Bytecode inspection | Object module analysis via IDCAMS/SPZAP (rare) |
The key mental model: the mainframe has fewer languages than Python but they're just as parseable. Every fix decision reduces to one of these:
- "The JCL DD at line X points to a DSN that has state Y — should be Z"
- "The COBOL paragraph at line X reads field F that has invalid data"
- "The IDCAMS control cards for cluster C are malformed"
- "The GDG base B doesn't exist"
You never need to send the whole file. You need to send the failing node + its immediate data-flow neighbours + the concrete catalog state.
4. JCL: parse tree + data-flow graph¶
4a. Parser¶
Use a Python library or roll one. Grammar is simple:
JOB ::= '//' name 'JOB' operand-list
EXEC ::= '//' name 'EXEC' (PGM=id | PROC=id | procname) [,operand-list]
DD ::= '//' name 'DD' operand-list
CONTINUE ::= '// ' operand-list # continuation
COMMENT ::= '//*' anything
INSTREAM ::= '//name DD *' … '/*'
PROC-INCL ::= (resolved from JCLLIB or default PROCLIB)
4b. Data-flow model¶
For each EXEC step:
{
"step": "STEP040",
"program": "CLMPROC1",
"steplib": ["Z76499.BANKDEMO.LOAD"],
"inputs": [{"dd": "CLMIN", "dsn": "SVC.UPLOAD.G0091V00", "disp": "SHR", "recfm": "FB", "lrecl": 200}],
"outputs": [{"dd": "CLMOUT", "dsn": "Z76499.CLMOUT", "disp": "(NEW,CATLG)", "recfm": "FB", "lrecl": 200}],
"sysout": [{"dd": "SYSPRINT"}],
"sysin": [{"dd": "SYSIN", "kind": "instream", "content": "..."}]
}
4c. What this lets the LLM see (10× less bytes)¶
Instead of:
//JOBCARD JOB (DEMO),... [50 lines of JCL boilerplate]
//PROC1 PROC ...
//JCLLIB JCLLIB ...
//STEP010 EXEC PGM=IEFBR14
//DD1 DD DSN=...,DISP=SHR
[... 40 more lines ...]
//STEP040 EXEC PGM=CLMPROC1
//CLMIN DD DSN=SVC.UPLOAD.G0091V00,DISP=SHR ← failing
//CLMOUT DD DSN=Z76499.CLMOUT,DISP=(NEW,CATLG)
...
FAILING STEP:
STEP040 EXEC PGM=CLMPROC1 (STEPLIB=Z76499.BANKDEMO.LOAD, member exists=YES)
CLMIN → SVC.UPLOAD.G0091V00 (exists=YES, recs=1247, recfm=FB/lrecl=200)
CLMOUT → Z76499.CLMOUT (exists=NO)
FAILURE: S0C7 at paragraph 3000-PROCESS-CLAIM line 09244
4d. The library choice¶
- Roll your own — takes ~500 LOC in Python. Deterministic, fast. This is what I'd recommend since JCL is small.
jcl-parseron PyPI — works but not maintained.- IBM's JCL utility (
IEBUPDTE) for structural parse — sent as a batch job (heavy).
5. COBOL: FD + WORKING-STORAGE + failing paragraph slice¶
This is the piece autofix-5 punts on entirely. Let me show what's possible.
5a. When COBOL fails, what's in the spool?¶
A COBOL S0C7 typically emits:
IGZ0037S A DATA REFERENCE OR STATEMENT IN CLMPROC1
IS INVALID OR SPECIFIED FUNCTION
IS NOT IMPLEMENTED.
IGZ0163S ABEND OCCURRED IN PROGRAM CLMPROC1 AT
DISPLACEMENT +0000094C
ON STATEMENT 09244.
5b. What a parser gives you¶
Feed the COBOL source through ProLeap (Java, run as subprocess) → an ANTLR AST → your Python side wraps it:
class CobolField:
name: str # "CLM-SERVICE-DATE"
level: int # 05
pic: str # "9(8)"
usage: str # "DISPLAY" (zoned decimal)
offset: int # byte offset inside parent 01
length: int # 8
parent: str # "CLM-RECORD"
class CobolParagraph:
name: str # "3000-PROCESS-CLAIM"
section: str
start_line: int # 09230
end_line: int # 09280
statements: list[Statement]
performed_by: list[str] # who calls this paragraph
performs: list[str] # who this paragraph calls
reads_fields: set[str]
writes_fields: set[str]
5c. Slicing to a failing paragraph¶
Given "S0C7 at STATEMENT 09244", the reducer:
1. Locates the paragraph containing line 09244 (paragraph 3000-PROCESS-CLAIM, lines 09230-09280)
2. Reads the statements in a ±5-line window around 09244:
09241 IF CLM-RECORD-VALID
09242 PERFORM 3100-CALC-INTEREST
09243 MOVE CLM-AMOUNT TO WK-AMT
09244 MOVE CLM-SERVICE-DATE TO WK-DATE ← failing MOVE
09245 ELSE
09246 PERFORM 9000-REJECT-RECORD
09247 END-IF
CLM-SERVICE-DATE back:
- Defined under FD for CLMIN (input file DD)
- CLMIN DD points to SVC.UPLOAD.G0091V00
5. Sends Claude a ~300 byte slice:
COBOL FAILURE:
program=CLMPROC1 stmt=09244 paragraph=3000-PROCESS-CLAIM
stmt: MOVE CLM-SERVICE-DATE TO WK-DATE
source_field: CLM-SERVICE-DATE PIC 9(8) DISPLAY (numeric, 8 bytes)
inside FD CLMIN reading Z76499.SVC.UPLOAD.G0091V00
target_field: WK-DATE PIC 9(8) DISPLAY
abend: S0C7 (data exception — non-numeric data in a numeric MOVE)
Claude now knows: the input data has non-numeric bytes in CLM-SERVICE-DATE. It can either:
- Recommend EDIT_COBOL_SOURCE to add IF NUMERIC guard before the MOVE
- Or recommend MANUAL_SME_ACTION (fix the feed) — because the executor can't safely edit COBOL source
5d. COBOL fix action kinds you should add¶
Currently EDIT_COBOL_SOURCE exists as an ActionKind but isn't executable. Options:
| Kind | Meaning | Executable? |
|---|---|---|
PATCH_COBOL_LINE |
Replace ONE statement at line N with a new statement | ✅ if you're willing to invoke IEBUPDTE/ISPF Edit |
INSERT_COBOL_BEFORE |
Insert a statement before line N | ✅ same |
REBUILD_COBOL |
Recompile + relink from the SRC library | ✅ if you have a JCL job that does it |
MANUAL_COBOL_FIX |
Human-only | ✅ (hand off) |
For the auto-executable ones, the safety rule: any COBOL patch must trigger a REBUILD_COBOL (compile + link-edit). Executor can queue a rebuild JCL and wait for it. The rebuild is deterministic — same source in, same load module out.
5e. Why AST slicing wins¶
- Full COBOL program: 5000-15000 lines
- Failing paragraph: 50 lines
- Failing statement + field defs: 5 lines + 2 field defs = ~200 bytes
Claude gets 100% of the decision info in 1% of the bytes.
6. Spool: anchored regions¶
The current spool_digest is already good — it does token-budgeted anchor reduction (see autofix/bridge/spool_digest.py). Keep it, but change what gets sent to Claude:
6a. Instead of sending the whole digest…¶
Send only:
- The completion block (retcode, abend, failing_step)
- The top 2 windows by severity rank (not top 8)
- The msg-id token summary (list of unique msg-ids present + their families)
6b. Structured, not raw¶
{
"outcome": {"retcode": "S0C7", "failing_step": "STEP040", "failing_program": "CLMPROC1"},
"top_signals": [
{"msg_id": "IGZ0037S", "severity": "S",
"text": "A DATA REFERENCE OR STATEMENT IN CLMPROC1 IS INVALID..."},
{"msg_id": "IGZ0163S", "severity": "S",
"text": "ABEND OCCURRED IN PROGRAM CLMPROC1 AT DISPLACEMENT +0000094C ON STATEMENT 09244."}
],
"step_ccs": {"STEP010": 0, "STEP020": 0, "STEP030": 0, "STEP040": "ABEND S0C7"}
}
7. VSAM / catalog: structured LISTCAT¶
Right now the agent calls run_idcams with LISTCAT ENTRIES('...') ALL and gets 200 lines of raw IDCAMS output as an "observation". Claude then parses it.
Deterministic fix: parse LISTCAT server-side into a struct:
class VsamCluster:
dsn: str
exists: bool
org: Literal["KSDS", "ESDS", "RRDS", "LDS"]
recfm: str
lrecl_min: int
lrecl_max: int
keys_off: int
keys_len: int
rec_total: int
data: DataComponent
index: IndexComponent
Send Claude a 500-byte struct instead of a 6000-byte raw LISTCAT dump. Same info, ~12× smaller, plus you can validate/gate on the struct (e.g. "cluster empty? recommend LOAD skill" — deterministic).
Same trick for dataset_stat (already partially done), list_members, job_status.
8. The context contract — what actually goes into Claude¶
For the 40% of incidents that go to Claude, the prompt has EXACTLY these blocks:
SYSTEM (cached, ~3 KB):
= taxonomy + action kinds + check kinds
= fix rules (imperative)
= SKILLS matched for THIS class (2-3 skills, ~500 B)
USER (per-incident, ~1-2 KB):
== INCIDENT ==
run_id: autofix_...
outcome: {retcode, abend, failing_step, failing_program}
top_signals: [msg_id, severity, text] × 2
step_ccs: {step: cc}
== JCL PARSE ==
failing_step: { program, steplib, inputs[], outputs[], sysin }
(+ concrete catalog state for each DSN: exists, attrs)
== COBOL SLICE (if applicable) ==
program: CLMPROC1
failing_stmt: <5-line window around abend statement>
fields: [name, PIC, USAGE, source_dd, source_dsn]
== VSAM STATE (if applicable) ==
{ cluster, recfm, lrecl, rec_total, ... }
== KB LESSONS (2-3, ~500 B) ==
{ title, rule }
Total per turn: ~5-8 KB instead of the current ~20-40 KB. And it doesn't grow across tool calls, because the tool observations are ALSO structured (not raw text).
9. Skills as executable routing keys¶
Right now skills are advisory prompt text (autofix/bridge/agent/skills.py). Upgrade them to executable routing keys:
@dataclass
class Skill:
id: str
version: int
title: str
# ── Matching (deterministic) ──
match_msg_ids: set[str] # prefix set, e.g. {"IGD17101"}
match_abends: set[str]
match_keywords: set[str]
match_predicate: Callable[[Bundle], bool]
# ── The auto-fix path (deterministic template) ──
auto_fix_template: Callable[[Bundle], Plan] | None
# If set AND match_predicate returns True with high confidence,
# emit the plan directly. NO LLM CALL.
# ── The LLM fallback path ──
llm_instructions: str
llm_confidence_gate: float
9a. Example — duplicate-catalog skill (100% deterministic)¶
Skill(
id="dup-catalog-fix",
version=1,
title="DISP=NEW on existing dataset",
match_msg_ids={"IGD17101"},
match_predicate=lambda b: (
b.top_signals[0].msg_id.startswith("IGD17101")
and b.jcl_step.output_dds[0].disp.startswith("NEW")
and b.catalog[b.jcl_step.output_dds[0].dsn].exists
),
auto_fix_template=lambda b: Plan(
failure=FailureClass.DATASET_NOT_FOUND,
remediation_actions=[
Action(kind="MODIFY_DD",
parameters={"dd_name": b.jcl_step.output_dds[0].name,
"operand": "DISP", "new_value": "SHR"}),
Action(kind="RESUBMIT_JOB"),
],
success_criteria=[
Criterion(check="JOB_RETURN_CODE", operator="EQ", value="0000"),
Criterion(check="NO_ABEND"),
],
proposed_jcl=b.jcl_source.replace_dd_disp(
b.jcl_step.output_dds[0].name, "SHR"
),
tier="A",
),
llm_instructions="If dataset exists and job reuses it → DISP=SHR/MOD.",
llm_confidence_gate=0.85,
)
This one skill handles ~15% of your traffic with zero LLM cost.
9b. Match confidence¶
Each skill computes confidence ∈ [0, 1]. High-confidence match → template path. Low-confidence → LLM with the skill as prompt hint.
10. Real reinforcement learning¶
Current KB is a greedy multi-armed bandit with Laplace-smoothed win rate. Upgrade path:
10a. UCB1 exploration (5 lines of code, big impact)¶
def score_with_ucb(lesson, total_offers_across_kb):
stats = lesson["stats"]
n_i = stats["offered"] or 1
win_rate = (stats["wins"] + 1) / (stats["wins"] + stats["losses"] + 2)
exploration_bonus = math.sqrt(2 * math.log(total_offers_across_kb + 1) / n_i)
return win_rate + exploration_bonus
Under-tried lessons now get a boost. Over time, either a lesson proves itself (win_rate rises, exploration term shrinks) or it burns off (loses enough to prune).
10b. Contextual bandit — same abend, same fix¶
class ContextKey(NamedTuple):
failure_class: str
msg_id_prefix: str # "IGD17101"
abend_code: str
dsn_hlq: str # "Z76499"
q_table: dict[ContextKey, dict[SkillId, float]] = {}
def choose_skill(context, candidates):
q = q_table.setdefault(context, {})
return argmax(candidates, key=lambda s: q.get(s.id, 0.5) + ucb_bonus(s, context))
def update(context, skill_id, outcome_ok):
q_table.setdefault(context, {})
old = q_table[context].get(skill_id, 0.5)
alpha = 0.2
reward = 1.0 if outcome_ok else 0.0
q_table[context][skill_id] = old + alpha * (reward - old)
Now the SAME abend, on the SAME kind of job, remembers what worked last time. This is what "learns over time" actually means.
10c. Trust decay¶
Data drifts. A fix that worked in June may not work in December (feed format changed, catalog policy shifted). Add exponential decay:
for lesson in kb.all_active():
stats = lesson["stats"]
stats["wins"] *= 0.9
stats["losses"] *= 0.9
lesson["score"] = laplace(stats["wins"], stats["losses"])
10d. What "real RL" would add on top¶
- Delayed reward — a fix that succeeds today but causes a downstream job to fail tomorrow → propagate negative reward back
- Off-policy learning — replay historical incidents to bootstrap Q-values on new signals without paying live LLM cost
- Policy gradient — parameterize skill selection as a neural policy on the ContextKey features and gradient-update on outcomes
For a mainframe fix system, UCB1 + contextual + decay is 95% of the value and 5% of the complexity of full RL. Stop there.
11. Reliability guarantees¶
Layer these in order — each stage is a guardrail:
| Guardrail | What it prevents | How |
|---|---|---|
| Schema gate | Malformed plan JSON | Pydantic schema validation |
| Verbatim grounding | LLM-fabricated msg-ids / DSNs | Every quoted evidence excerpt must exist char-for-char in the digest |
| Executor ceiling | Unauthorized actions | Only 15 ActionKinds; anything else forces MANUAL_SME_ACTION |
| Scope gate | Foreign HLQ / cross-user damage | Target DSNs must be under job owner's HLQ or explicitly whitelisted |
| Tier system | Destructive automation | A=safe auto, B=needs confirm, C=SME hand-off |
| HITL | Any surprising fix | Every plan waits at AWAITING_REVIEW until a human approves |
| Rollback | Half-applied resource state | Every resource action logs its inverse; compensate on failure |
| Budget cap | Runaway loops | Per-incident $ ceiling; halt to needs_human when hit |
| Attempt cap | Infinite replans | MAX_ATTEMPTS=3, then hand off |
| Idempotency | Duplicate captures | Deterministic run_id (jobname+jobid) — dup capture no-ops |
11a. Two-part verify (already in autofix-5 — keep it)¶
- APPLIED: every resource action returned success (no error/refusal)
- EFFECTIVE: every
success_criterionverified against the resubmitted job
SUCCEEDED requires BOTH. This is honest — the current design is right.
11b. Add: "confidence-aware" HITL¶
- Template-path fixes with confidence > 0.95 and tier A: auto-approve (still logged, revert-able)
- Everything else: HITL
This is what makes the system usable at scale without babysitting every fix.
12. End-to-end trace — same S0C7 in the new architecture¶
Compare to the current 3-10 turn Claude loop.
Input: CLMADJ00 JOB10101 ABEND S0C7 in STEP040 CLMPROC1
STAGE 1 — Reducer (deterministic, 15 ms)
Input: raw spool (2400 lines, ~180 KB) + JCL (85 lines) + COBOL
Output: {
outcome: {retcode:"S0C7", failing_step:"STEP040",
failing_program:"CLMPROC1"},
top_signals: [IGZ0037S, IGZ0163S at stmt 09244],
jcl_step: {inputs:[CLMIN→SVC.UPLOAD.G0091V00 (exists,recs=1247)],
outputs:[CLMOUT→Z76499.CLMOUT (exists=NO)]},
cobol_slice: {para:"3000-PROCESS-CLAIM", stmt:09244,
stmt_text:"MOVE CLM-SERVICE-DATE TO WK-DATE",
fields:[CLM-SERVICE-DATE PIC 9(8) DISPLAY]}
}
Size: ~1.2 KB
STAGE 2 — Classifier (Gemini Flash, 1 call, ~$0.001)
Output: {class:"S0C7_DATA_EXCEPTION", confidence:0.94,
hypothesis:"non-numeric data in CLM-SERVICE-DATE from feed",
recommend_skill:"cobol-data-exception"}
STAGE 3 — Skill Router (deterministic, 1 ms)
Lookup: class=S0C7 → skill "cobol-data-exception"
match_predicate: TRUE
auto_fix_template: EMPTY (S0C7 needs judgement — LLM path)
Route: DEEP INVESTIGATOR
STAGE 4b — Deep Investigator (Claude, 1-2 turns, ~$0.02)
SYSTEM (cached ~3 KB): taxonomy + rules + skill
USER (~1.2 KB): the bundle from Stage 1
Claude reasons and calls submit_plan (1 tool call):
{ failure: S0C7_DATA_EXCEPTION,
actions: [MANUAL_SME_ACTION{intent:"Fix feed or add IF NUMERIC
guard in CLMPROC1 para 3000, ln 09244"}],
tier: "C" }
STAGE 5-6 — Gate + HITL
Schema OK. Grounding OK. Tier C.
→ AWAITING_REVIEW. SME reviews.
Total: ~$0.021, ~1.5 seconds compute, 1 Claude turn.
Compared to autofix-5: $0.15, 4-8 Claude turns.
7× cost reduction on the same incident, and cleaner reasoning trace.
13. Migration plan¶
Keep from autofix-5¶
- ✅ UI (
ui/) — no changes - ✅ Orchestrator state machine (
autofix/orchestrator/app.py) — capture/gate/HITL/execute flow - ✅ Executor (
autofix/bridge/executor.py) — proven, deterministic - ✅
spool_digest.py— already token-budgeted, good starting point - ✅ KB storage schema (
autofix_kbcollection) - ✅ Brain toggle infrastructure (add
brain=hybridas new option)
Add¶
| Component | File(s) | Effort |
|---|---|---|
| JCL parser | autofix/bridge/parsers/jcl.py |
2-3 days |
| COBOL slicer (ProLeap wrapper) | autofix/bridge/parsers/cobol.py |
1 week |
| LISTCAT parser | autofix/bridge/parsers/vsam.py |
1 day |
| Skill registry with templates | autofix/bridge/skills/*.py |
3-5 days |
| Fast classifier | autofix/orchestrator/classifier.py (Gemini Flash) |
2 days |
| Contextual bandit | Upgrade autofix/orchestrator/kb.py |
3 days |
| Confidence-aware auto-approve | orchestrator app.py + admin toggle | 2 days |
Delete¶
- 🗑️
autofix/orchestrator/plangen.py— dead code (parallel Gemini planner) - 🗑️ The static
skills.py(replaced by skill registry)
Compatibility¶
- Old runs stay compatible — same schema versions
- Old KB lessons stay compatible — new UCB scoring reads the same
statsfield - Same UI — no frontend changes required in phase 1
14. Success metrics¶
Track these once the new pipeline is live:
| Metric | Current autofix-5 | Target |
|---|---|---|
| Median cost per incident | ~$0.15 | < $0.03 |
| P95 cost per incident | ~$0.40 | < $0.10 |
| % of incidents auto-fixed with zero LLM turns | 0% | > 50% |
| % of incidents fixed in 1 Claude turn | ~20% | > 80% |
| Median wall-time capture → SUCCEEDED | ~90s | < 30s |
| KB pruning rate | tracked | < 20% quarterly |
| SUCCEEDED without HITL (auto-approve) | 0% | > 40% |
| First-try fix rate on repeat abends | tracked but flat | rising to > 90% |
TL;DR for the architect¶
You already own the two ends of the pipeline: the deterministic reducer skeleton and the executor. What autofix-5 is missing is the middle:
- Parse the artifacts (JCL/COBOL/VSAM/spool) into structured form. Slicing beats compression.
- Route by skill match (deterministic table) not by LLM prompt hints.
- Templates for the 60% deterministic cases — zero LLM.
- Sliced context for the 40% that need Claude — 5 KB not 20 KB.
- Contextual bandit KB — remembers which skill won for which (msg, abend, hlq) tuple.
- UCB1 exploration + trust decay — keeps improving without freezing on early lucky wins.
- Confidence-aware auto-approve — the tier-A high-confidence path skips HITL, tier-B/C wait.
Do those seven things and the system is faster, cheaper, more reliable, and genuinely smarter on the same abend showing up twice.