Skip to content

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

  1. The core insight — mainframe artifacts have structure. Parse it.
  2. The intelligent pipeline — 8 stages, LLM only in stage 5 (and only if needed)
  3. What is the mainframe equivalent of Python AST?
  4. JCL: parse tree + data-flow graph
  5. COBOL: FD + WORKING-STORAGE + failing paragraph slice
  6. Spool: anchored regions, not the whole file
  7. VSAM / catalog: structured LISTCAT, not raw text
  8. The context contract — what actually goes into Claude
  9. Skills as executable routing keys (not prompt hints)
  10. Real reinforcement learning for a KB that gets smarter over time
  11. Reliability: schema, grounding, gate tiers, HITL, rollback
  12. End-to-end trace — same S0C7 in the new architecture
  13. Migration plan — what to keep, what to add, what to delete
  14. 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:

  1. "The JCL DD at line X points to a DSN that has state Y — should be Z"
  2. "The COBOL paragraph at line X reads field F that has invalid data"
  3. "The IDCAMS control cards for cluster C are malformed"
  4. "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)
...
you give Claude:
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
200 bytes vs 2000 bytes. And Claude has exactly what it needs.

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-parser on 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.
Plus a CEEDUMP with register values and stack trace.

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
3. Resolves the field types from WORKING-STORAGE / FD:
CLM-SERVICE-DATE: PIC 9(8) DISPLAY   ← expects 8 numeric bytes
WK-DATE:          PIC 9(8) DISPLAY
4. Locates the source of the field's data — trace 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"}
}
That's ~400 bytes. Beats the current ~12 KB.


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"])
Old wins matter less. Recent evidence matters more.

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)

  1. APPLIED: every resource action returned success (no error/refusal)
  2. EFFECTIVE: every success_criterion verified 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_kb collection)
  • ✅ Brain toggle infrastructure (add brain=hybrid as 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 stats field
  • 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:

  1. Parse the artifacts (JCL/COBOL/VSAM/spool) into structured form. Slicing beats compression.
  2. Route by skill match (deterministic table) not by LLM prompt hints.
  3. Templates for the 60% deterministic cases — zero LLM.
  4. Sliced context for the 40% that need Claude — 5 KB not 20 KB.
  5. Contextual bandit KB — remembers which skill won for which (msg, abend, hlq) tuple.
  6. UCB1 exploration + trust decay — keeps improving without freezing on early lucky wins.
  7. 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.