Files
rune/scripts/process_experience.py
T
Nick deba21fe54
Auto-label / priority-gate (pull_request) Skipped
Auto-label / auto-label (pull_request) Successful in 3s
CI Pipeline / best-practices (push) Successful in 6s
CI Pipeline / documentation (push) Successful in 4s
CI Pipeline / canary (push) Successful in 35s
CI Pipeline / lint (push) Successful in 42s
CI Pipeline / type-check (push) Successful in 31s
CI Pipeline / gitea-offline (push) Successful in 15s
CI Pipeline / unit-tests (push) Successful in 1m0s
CI Pipeline / best-practices (pull_request) Successful in 4s
CI Pipeline / documentation (pull_request) Successful in 4s
CI Pipeline / lint (pull_request) Successful in 37s
CI Pipeline / canary (pull_request) Successful in 31s
Spec gate (headless) / spec-gate (pull_request) Successful in 7s
CI Pipeline / type-check (pull_request) Successful in 31s
CI Pipeline / unit-tests (pull_request) Successful in 53s
CI Pipeline / gitea-offline (pull_request) Successful in 16s
CI Pipeline / required-checks (push) Successful in 1s
CI Pipeline / required-checks (pull_request) Successful in 1s
Auto-version and Release / release (pull_request) Successful in 38s
feat(TASK-I): PL0 passive Experience Ledger — 10 seed Process Experiences from real runs
scripts/process_experience.py: ExperienceLedger with 12 root-cause layers,
3 result types, confidence tracking, evidence references, correction
tracking, recurrence classification, cost measurement, reusable flag.

10 seed Process Experiences from real runs (#161/#163/#164/#310/#311):
- 5 failures (schema drift, field naming, missing disposition, validator
  mismatch, duplicate contract authority)
- 3 successes (gate blocked missing M1, INNOVATION correctly skipped,
  real external evidence produced decision-changing results)
- 2 inefficiencies (routing waste, detection gap)

All 10 marked reusable_lesson=True. No autonomous self-editing —
observation and evidence capture only.
2026-09-10 02:22:49 +01:00

103 lines
4.3 KiB
Python

#!/usr/bin/env python3
"""process_experience.py — PL0 passive Experience Ledger (TASK I, epic #284).
Observation and evidence capture only. No autonomous self-editing.
Records Process Experience entries from pipeline runs: failures, successes,
inefficiencies — with root-cause attribution, evidence references, and
confidence. These are EVIDENCE RECORDS, not permanent rules.
Root-cause layers:
MODEL_BEHAVIOR — the model did something unexpected
PROMPT_CONTRACT — the prompt didn't communicate requirements clearly
SCHEMA — field names/structure mismatch
NORMALIZER — normalization was missing or incorrect
TOOL_INTERFACE — tool behavior differed from expectation
ARTIFACT_ASSEMBLY — artifact file composition issue
VALIDATOR — gate checked the wrong thing or missed something
ROUTING — the stage shouldn't have run for this task class
STAGE_ARCHITECTURE — the stage design itself is the problem
DETERMINISTIC_RUNTIME — deterministic code issue (not model)
EXTERNAL_DEPENDENCY — external service behaved unexpectedly
UNKNOWN — attribution uncertain
"""
from __future__ import annotations
import json
import os
import time
from datetime import datetime, timezone
ROOT_CAUSE_LAYERS = {
"MODEL_BEHAVIOR", "PROMPT_CONTRACT", "SCHEMA", "NORMALIZER",
"TOOL_INTERFACE", "ARTIFACT_ASSEMBLY", "VALIDATOR", "ROUTING",
"STAGE_ARCHITECTURE", "DETERMINISTIC_RUNTIME", "EXTERNAL_DEPENDENCY",
"UNKNOWN",
}
RESULT_TYPES = {"SUCCESS", "FAILURE", "INEFFICIENCY"}
CANDIDATE_STATUSES = {"SEED", "EVALUATED", "PROMOTED", "REJECTED"}
class ExperienceLedger:
"""Append-only ledger of Process Experience records."""
def __init__(self, path: str):
self.path = path
os.makedirs(os.path.dirname(path), exist_ok=True)
def record(self, task_run_stage: str, result_type: str, symptom: str,
evidence: list[str], root_cause: str, confidence: float,
human_intervention: bool = False, pipeline_sha: str = "UNKNOWN",
correction_applied: str = "", recurrence_class: str = "",
wall_cost_s: float | str = 0, model_cost_s: float | str = 0,
tool_cost_s: float | str = 0, quality_outcome: str = "",
reusable: bool = False, candidate_status: str = "SEED") -> dict:
assert result_type in RESULT_TYPES, f"invalid result_type: {result_type}"
assert root_cause in ROOT_CAUSE_LAYERS, f"invalid root_cause: {root_cause}"
assert candidate_status in CANDIDATE_STATUSES, f"invalid status: {candidate_status}"
entry = {
"experience_id": f"PE-{int(time.time())}-{len(self.load_all()) + 1:04d}",
"task_run_stage": task_run_stage,
"result_type": result_type,
"symptom": symptom,
"evidence_references": evidence,
"root_cause_layer": root_cause,
"confidence": confidence,
"human_intervention": human_intervention,
"pipeline_sha": pipeline_sha,
"correction_applied": correction_applied,
"recurrence_class": recurrence_class,
"wall_cost_s": wall_cost_s,
"model_cost_s": model_cost_s,
"tool_cost_s": tool_cost_s,
"quality_outcome": quality_outcome,
"reusable_lesson": reusable,
"candidate_status": candidate_status,
"recorded_at": datetime.now(timezone.utc).isoformat(),
}
with open(self.path, "a") as f:
f.write(json.dumps(entry) + "\n")
return entry
def load_all(self) -> list[dict]:
if not os.path.exists(self.path):
return []
out = []
with open(self.path) as f:
for line in f:
try: out.append(json.loads(line))
except json.JSONDecodeError: continue
return out
def by_root_cause(self, layer: str) -> list[dict]:
return [e for e in self.load_all() if e["root_cause_layer"] == layer]
def successes(self) -> list[dict]:
return [e for e in self.load_all() if e["result_type"] == "SUCCESS"]
def failures(self) -> list[dict]:
return [e for e in self.load_all() if e["result_type"] == "FAILURE"]
def reusable_lessons(self) -> list[dict]:
return [e for e in self.load_all() if e["reusable_lesson"]]