dev: reh3376_dev01 -> main#469
Conversation
…per-prompt channel (Epic 0) Roadmap Q3 Phase 1 rank #1. Audit of all 6 hooks vs the actual Claude Code stdin schemas: prompt-context.sh reads .user_prompt (CC sends .prompt) → channel exits silently on every prompt; post-tool-observe.py reads tool_output (CC sends tool_response) → false "Build/test succeeded" observations with empty output; guidance wrongly coupled to RESULT_COUNT>0; minor pre-compact transcript jq. session-start / pre-bash-check / pre-write-check verified correct. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…rompt channel (Epic 1) Claude Code's UserPromptSubmit stdin field is `prompt`; the hook read `.user_prompt`, which is always empty → exit 0 → per-prompt CMS recall, Jiminy guidance, /strict reformulation, the warm trigger, and the retrieve-time Hebbian reinforcement have NEVER fired in any session. Now reads `.prompt // .user_prompt` (legacy fallback kept). Also decouples guidance from recall: the RESULT_COUNT=0 branch no longer exits — it printed its notice then skipped guidance + warm + retrieval reinforcement, coupling independent deliveries. Both copies (live + installer template). Tier 1 simulated stdin: real .prompt payload → first-ever guidance delivery (J17 T1 bootstrap + DICT, 5363 guidance bytes vs 0 forever); legacy fallback works; short/empty/ malformed payloads exit silently (fail-open preserved). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…"succeeded" observations (Epic 2)
Claude Code's PostToolUse stdin field is `tool_response` (string or
object); the hook read `tool_output`, which is always absent → output_str
empty → error indicators never matched → every go build/go test/pytest
Bash call was recorded as "Build/test succeeded" sight-unseen, and real
errors were never observed.
Now reads tool_response (fallback tool_output) via _response_text(),
normalizing string|dict|list (stdout/stderr join). Success classification
requires NON-EMPTY clean output — a silent success records nothing rather
than fabricating; failures land as error observations with real stderr.
Both copies (template regenerated from fixed live, {{SPACE_ID}}
placeholder preserved, verified identical modulo placeholder). Tier 1
against real CMS: failing build → error obs with stderr; passing →
progress; empty → no record.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ine shape (Epic 3)
Transcript lines are {type, message:{content:[{type, text|name, ...}]}};
the old top-level `.content` read always yielded empty, so pre-compaction
snapshots never carried recent-activity context. New jq walks
.message.content[] extracting .text/.name. Verified against this
session's real transcript (old: nothing; new: real activity). Both
copies, placeholders preserved.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…act pin + close (Epics 4-5) Live in the real session: first-ever guidance delivery (J17 T1 bootstrap + DICT, 5363 bytes vs 0 forever); real failing build → error observation with actual compiler output in CMS. PostToolUse success-only firing documented as a limitation. Hook stdin contract pinned in CLAUDE.md. Drift + clique-semantics findings logged for HOOKSYNC-001 / Phase 2. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…channel (Epic 0) Roadmap Q3 Phase 1 rank #2. Investigation grounded all five findings: template→live drift severed alert delivery (50-entry file actively rotating today, never shown); no Cleared lifecycle (nothing sets the field; no /v1/alert* endpoints); no absence detection for the channel that just had a months-long silent outage; compose publishes 9999 on 0.0.0.0; neural sidecar binds 0.0.0.0:8101 via a 39-day-old process serving pre-J17-fix code. 8 epics: reconcile, CI parity gate, clear lifecycle, hook_events absence rule (reuses V0024 via jobhealth), hooks doctor, PORT-TRUTH rider, Tier 3, docs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…y restored to live (Epic 1)
Live hooks adopted from templates (SPACE_ID substituted): restores the
alert-display blocks (all-pending per prompt; critical/high + degraded
healthz at session start) that the live copies lacked — the NOSILENT
last mile. Reverse drift caught during reconcile: the live hook's T1/T2
bootstrap-detection block (MAX_TIER → /v1/jiminy/bootstrap → ACTIVE
CONSTRAINTS header) never existed in the template and was nearly lost —
now single-sourced in the template and regenerated into live.
Live-verified: one prompt now renders alerts (50 pending incl. live
CRITICALs) + recall + J17:INIT bootstrap + guidance + synergy footer,
coexisting. All 6 hooks byte-identical to templates modulo {{SPACE_ID}}.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…emplates (Epic 2)
Mirrors the compose/launchd parity pattern: every *.sh/*.py template
must byte-match .claude/hooks/ modulo the {{SPACE_ID}} placeholder.
Proven locally: passes clean, fails (with a bounded diff dump) on
deliberate drift. Ends the bidirectional-drift class that severed
alert delivery and nearly lost the T1 bootstrap block.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…vered (Epic 3) Alert.Cleared existed but nothing ever set it: once hooks rendered the file, the same entries would re-render every prompt forever. New: FileBackend.Clear (ids and/or all_before cutoff, idempotent, under the existing lock) → Dispatcher.ClearAlerts → POST /v1/alerts/clear. Hooks now clear exactly what they displayed (fire-and-forget, fail-open); cleared = delivered-to-operator, not resolved — persisting conditions re-fire via the evaluator. Alert IDs now CUIDv2 per the identifier standard (was UnixNano; old ids remain valid opaque strings). Live-verified lifecycle: prompt 1 → "50 pending, showing 10" + 10 cleared; prompt 2 → "40 pending, showing 10" (next batch, no re-render) → 20 cleared. Tier 1: Clear by-id/by-time/idempotent/no-backend. UATS alerts_clear 3/3 live (runner falsy-body inheritance discovered: variant bodies must be non-empty objects). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…self-reports outages (Epic 4) POST /v1/hooks/event records heartbeats into V0024 scheduled_job_events via the jobhealth policy point (job_name hook:<name>; no new sink). Two independent heartbeats: prompt-context fires per delivery (the monitored channel); post-tool-observe fires throttled (HOOK_HEARTBEAT_ COOLDOWN_SEC, default 300 — proves sessions ACTIVE). Evaluator rule hook_channel_silent (distinct service per the NOSILENT cooldown rule): sessions active + zero prompt-context fires in HOOK_SILENT_LOOKBACK_ HOURS (24) → high alert. This is the "job never ran" guarantee applied to the channel whose months-long outage HOOKWIRE-001 found only by manual audit — the next contract drift self-reports. Config: HOOK_HEALTH_ALERT_ENABLED (true), HOOK_SILENT_LOOKBACK_HOURS (24), HOOK_ACTIVITY_MIN_EVENTS (5). Live-verified: real hook fires land rows (session metadata, latency); throttle holds; rule SQL positive + negative branches proven against the real table; UATS hooks_event 3/3. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… (Epic 5)
11 checks: per-hook template parity (the CI gate's local twin),
settings registration, server healthz, a stdin-contract self-test
piping a real-shape UserPromptSubmit payload through the installed
hook (asserts the always-present synergy footer), alert-file state
(pending/total), and the last hook:prompt-context heartbeat age from
scheduled_job_events (SKIP when TSDB unreachable). Table or --json;
non-zero exit on any FAIL.
Live: 11/11 PASS on this machine ("last fire 5s ago" — fed by the
doctor's own self-test); correctly fails (exit 1) on deliberate drift.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ie replaced (Epic 6)
Compose published the API on 0.0.0.0 (unauthenticated admin/destructive
routes exposed off-host): now "${MDEMG_BIND_ADDR:-127.0.0.1}:${MDEMG_PORT}
:9999" — wide bind is an explicit opt-in (both compose copies, CI-synced).
Neural sidecar bound 0.0.0.0:8101 via config.py default AND the plist
arg: both now 127.0.0.1 (both plist copies, CI-synced; SIDECAR_HOST env
overrides). Operational: the 39-day-old sidecar process (started
2026-05-02, serving pre-J17-fix code) replaced — fresh process verified
on 127.0.0.1:8101, both models loaded, health 200.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lose (Epics 7-8) Live-verified across the sprint: alert backlog drained 50→2 on real prompts (display-then-clear); evaluator rules 15→16 (hook_channel_silent loaded); doctor 11/11 + correct failure mode; sidecar fresh on 127.0.0.1:8101 (NLI 234ms). Feature doc docs/features/hook-channel- health.md (config table incl. MDEMG_BIND_ADDR + SIDECAR_HOST). Findings: packaging plists are templates (raw copy → launchd exit 78; service install is canonical); UATS falsy-variant-body inheritance pinned. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…sis latency Caught in the HOOKSYNC-001 full-suite regression: the synchronous /v1/jiminy/guide includes local-model synthesis (~43s observed quiet, ~50s typical per GUIDANCE-SYNTH-001) — the spec's 30s timeout has been silently erroring since synthesis latency grew. Aligned with the JIMINY_WARM_COMPUTE_TIMEOUT_MS budget (90s); hash refreshed; passes live. Pre-existing — not a HOOKSYNC regression (Guide path untouched). The other 3 suite errors were load-induced flakes (pass individually): suite-vs-llama-server slot contention, noted for UXTS-CI-001. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…passes
Root cause: the new 'Verify live hooks match hook templates' CI step
(HOOKSYNC-001) diffs every internal/cli/hook_templates/*.{sh,py} against
.claude/hooks/<name>, but the .gitignore allowlist only un-ignored the
5 original hooks. pre-write-check.py gained a template in this sprint
while its live counterpart stayed gitignored, so CI checked out a tree
without it and failed with 'MISSING live hook:
.claude/hooks/pre-write-check.py'.
Fix: add '!.claude/hooks/pre-write-check.py' to the allowlist and commit
the live hook (already byte-identical to its template modulo SPACE_ID),
preserving the full parity guarantee instead of weakening the CI step.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…n hierarchy (Epic 0) Roadmap Q3 Phase 1 rank #3. Live investigation: point.distance() returns NULL on embedding lists (proven: NULL where vector.similarity.cosine returns 0.627 on the same pair); 3 creation sites affected incl. an ABSTRACTS_TO site the audit missed. Scale worse than audited and growing: 28,332/28,332 GENERALIZES + 36,110/37,996 ABSTRACTS_TO = 64,442 NULL-weight abstraction edges. Neo4j cosine returns [0,1] directly — drop-in. Plan: fix sites (+ CUIDv2 edge ids), LIMIT-5-then-batched backfill, null-weight gauge + alert rule via the existing graph-stats → metric_samples path, UVTS-quick regression guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…cosine replaces point.distance (Epic 1) point.distance() is a spatial-Point function: on embedding lists it returns NULL, so every weight at the 3 abstraction-edge creation sites was never set (100% of GENERALIZES + 95% of ABSTRACTS_TO weightless; the CASE guards passed on good embeddings, then the THEN expr evaluated NULL — edges with good embeddings got nothing while embedding-less ones got the 0.5 fallback). vector.similarity.cosine returns [0,1] directly (live-verified: identical=1.0, orthogonal=0.5, opposite=0.0). Site 1 (theme GENERALIZES) gains the null-guard it never had. Also: edge_id randomUUID() → CUIDv2 per the identifier standard, minted Go-side via memberEdgePairs (Cypher can't generate CUIDv2) and zipped with member ids for UNWIND. All 3 statements EXPLAIN-validated live. Tier 1: pair-builder tests (uniqueness, CUID format, empty input). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… abstraction weights (Epic 2) Standalone subcommand (deliberately NOT folded into `graph repair`, whose orphan sweep would delete the pre-fix orphan observations the operator chose to keep). Weight = vector.similarity.cosine(endpoint embeddings) when both exist, else 0.5 (the creation sites' fallback); similarity_score set alongside; idempotent (pure function of embeddings); batched (default 1000/txn) with --limit for trials. Executed per the small-batch-first rule: dry-run count → LIMIT-5 live trial → hand-verified (stored ≡ independently recomputed to 6dp) → distribution preview over 2000 (min 0.704, mean 0.96; the ~50% near-1.0 mass is single-member-cluster degeneracy — centroid ≡ member embedding, HIDDEN-CHURN-001 territory, faithfully encoded) → full runs. Mid-run the count GREW: the running server predated Epic 1 and kept minting NULL edges — restarted on the fixed binary, swept stragglers, then whk-wms (8,755) + linear (199). Final: 0 NULL / 57,395 edges globally. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pic 3) Query 4 in the graph-stats collector counts NULL-weight GENERALIZES/ ABSTRACTS_TO edges per space → new gauge mdemg_neo4j_graph_null_weight_edges → metric_samples → evaluator rule null_weight_abstraction_edges (service graph-weight-integrity, distinct per the cooldown rule; NULL_WEIGHT_EDGE_ALERT_THRESHOLD default 100, ForDuration 10m). Steady state post-backfill is 0; sustained reappearance = the point.distance bug class regressed at a creation site — it self-reports instead of waiting for the next audit. Live: evaluator rules 16→17; gauge rows persisting at value 0 across all spaces. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…0s batch budget Caught live during the HIDDEN-WEIGHT-001 corpus reingest: the post-ingest /v1/memory/consolidate call used the shared batch-ingest client (--timeout, 300s); consolidating a ~10k-node space exceeds that, so the client reported failure while the server completed the work — the GUIDANCE-SYNTH-001 bug class (long graph/LLM work needs its own budget). New --consolidate-timeout flag / INGEST_CONSOLIDATE_TIMEOUT_SEC env (default 1800s) with a dedicated client. Live-verified: "running consolidation timeout_sec=1800" → complete. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…VTS harness audit + close (Epics 4-5) Tier 3: real consolidation minted edges with varied cosine weights (0.83-0.94) + CUIDv2 ids; at-scale via the corpus reingest (9,500 edges, 0 NULL, mean 0.923); gauge holds 0; evaluator rules 16→17. UVTS harness: corpus space lnl-demo-whk had been deleted with zero trace (no UVTS run since 2026-05-04 measured anything real); restored by operator-directed full reingest. A fresh baseline NUMBER remains blocked by further live-found harness rot — grader/persist breakage, expected- path format drift, vector post-filter dilution (service.go:1137 global top-K then space filter) amplified by the duplicate whk-wms space — complete defect inventory handed to UXTS-CI-001. Retrieval ranking on the restored corpus verified correct (expected files at ranks 1-4). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ns (Epic 0) Roadmap Q3 Phase 1 rank #4. Weekly decay+prune has never executed (--dry-run defaults true; plist passes no override) while reporting success — NOSILENT's blind spot. Tonight's Memory Bloat alerts (79k+ nodes) are the accumulated backlog. Safety verified in code before planning: nodes are tombstoned (never deleted) with abstraction-chain/ degree/recency protections; edge deletion is the designed near-zero- weight lifecycle, meaningful now that HIDDEN-WEIGHT made weights real. Plan: live-by-default plist (+installed refresh), dry_run in job-event metadata (no schema change — disclosed), maintenance_no_live_run evaluator rule, darwin upgrade refreshes plists/hooks, first-ever live run with preview-first protocol. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…-dry-run=false (Epic 1) The weekly LaunchAgent ran `mdemg maintenance` with no dry-run override; the CLI defaults --dry-run=true, so every scheduled cycle previewed and reported success — decay+prune NEVER executed (the 79k-node Memory Bloat backlog). Both plist copies now pass --dry-run=false (the CLI default stays true for safe manual previews — the SCHEDULE is what must not silently no-op); installed plist refreshed + agent reloaded. reportScheduledJobMeta threads job metadata into V0024; maintenance records dry_run so the only-ever-dry-runs pattern is queryable (metadata JSONB — no schema change, disclosed in the plan). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fires when maintenance rows exist in MAINT_LIVE_LOOKBACK_DAYS (default 8) but none ran live (success + metadata dry_run=false) — the only- ever-dry-runs pattern self-reports instead of hiding inside "the job ran". Distinct service maintenance-liveness per the cooldown rule. Config: MAINT_LIVE_ALERT_ENABLED (true), MAINT_LIVE_LOOKBACK_DAYS (8). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…+ hooks (darwin) (Epic 3) Plist/hook fixes shipped in releases but never reached installed machines — the maintenance dry-run override would have sat unreachable next to upgraded binaries forever. Upgrade now re-renders ALREADY- INSTALLED mdemg LaunchAgents from the new binary's embedded templates (refresh-only — never installs new services) + re-syncs mdemg-managed Claude hooks in the current project (marker-checked). Substitution logic single-sourced into renderLaunchdTemplate (Install + Refresh — the drift class that exit-78'd the sidecar during HOOKSYNC live smoke). Mirrors the existing Linux systemd-unit refresh. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…xes (Epic 0) The REWARD-CORRECTNESS-001 follow-ups: (1) hidden.summarize schema object->string (production emits prose; 72 rows mis-flagged invalid-JSON); (2) explanation_quality schema-aware for nested violations[].reasoning (fixes jiminy.evaluate + evaluate_llm scoring correct responses 0.0); (3) keyword-bag specificity/ actionability substantive-floored (jiminy.synthesize valid guidance dropped for lacking magic words). Makes the 4 tasks' grading correct before the baseline recompute. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
… + live validation Three reward/schema mismatches that scored CORRECT responses wrong: 1. hidden.summarize ULTS schema object->string. Production emits bare prose (cluster_summarizer.go), so the object schema mis-flagged 72 valid summaries as invalid-JSON. (Reward already fixed in RC-001; this corrects the spec.) 2. explanation_quality made schema-aware: jiminy.evaluate / evaluate_llm nest reasoning in violations[].reasoning, not a top-level field, so the flat lookup scored every correct response 0.0. Now credits nested reasoning and treats a valid no-violation verdict as a correct "no issues" answer (nothing to explain). Falls back to the flat path. 3. specificity_score / actionability_score substantive-floored (0.7 floor, keyword presence a bounded bonus, hedging/empty/repetition low) — the keyword-bag dropped valid concise guidance below the gate for lacking ~6 magic words. follow_rate inherits it. Live Tier 3 (real production rows, old->new kept@0.8): jiminy.evaluate 0/60->60/60 (mean 0.667->0.967), jiminy.synthesize 3/60->59/60 (0.725->0.879), ape.reflect 47/60->60/60 (0.848->0.956); evaluate_llm unchanged 60/60. New means 0.88-0.97 = correct production output scoring correctly, no over-inflation. 87 unit tests + 609 neural tests + ruff green. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ort 8101->8102 (Epic 1) The capstone of the training-integrity arc: recompute the frozen 0.8338 baseline through the fixed harness (valid_clean + RC-001/002 rewards + GGUF :8102). Epic 1 fixes the stale rl_phase11.yaml mlx_port (8101 mlx_lm.server decommissioned → 8102 llama-server GGUF), flagged by EVAL-INTEGRITY-001. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d harness (Epic 2-4) Recomputed the adapter-promotion baseline through the fixed harness (valid_clean leak-free eval + RC-001/002 corrected rewards + GGUF llama-server :8102) = 0.8655, replacing the stale frozen 0.8338 (valid_golden-leaked, old length-biased rewards, decommissioned MLX serving — not comparable). evaluate_gate_5a now derives the target from the loaded baseline REPORT (single source of truth); the constant is retained only as a >5pp drift tripwire. status ok, 12 tasks, 50 samples/task, 0 zero-call. ape.reflect 0.696 is an eval-harness artifact (stored ~7.5k-token prompts bypass the runtime prompt budget and get cut off mid-JSON), not a model regression. Closes the training-integrity arc: trustworthy gate (EVAL-INTEGRITY-001), correct rewards (REWARD-CORRECTNESS-001/002), sound corpus (APE-PROMPT-BUDGET-001 + DATAPRUNE), honest baseline (this sprint). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…Epic 0) P0, first of two sprints from the guidance-investigation. Fix the two lying signals: (1) the false-positive "guidance not reaching agent" CRITICAL (fires on a healthy Jiminy via an unassessed zero-value JiminyHealthy bool) — real probe + SynergyAssessed guard + rename; (2) the inflated mdemg_jiminy_follow_rate gauge (0.725 vs honest ~0.18 panels — Neo4j dedup-by-guidance_id double-credits multi-outcome ids) — de-inflate so gauge/panels/RSIC agree. Behavioral guidance-relevance/trust-decay fix deferred to JIMINY-EFFECTIVENESS-001 (needs these honest signals to validate against). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Fix the two lying Jiminy health signals (root-caused by the guidance investigation): Fix 1 — false-positive "guidance not reaching agent" CRITICAL. It is an RSIC reflection insight (self_reflect.go) gated on !report.JiminyHealthy, but JiminyHealthy is set only inside a synergy block (self_assess.go) that is conditionally skipped — when skipped it stays at the Go zero-value false, so a healthy, delivering Jiminy fired this CRITICAL ~8x/day. Added report.SynergyAssessed (set true only when the block runs); guard the insight on it; renamed the alert off the misleading "guidance not reaching agent" -> "Jiminy Service Unavailable — catastrophic-forgetting risk". 3 unit tests pin the guard. Fix 2 — inflated mdemg_jiminy_follow_rate gauge (0.725 vs the dashboard panels' ~0.27). The Neo4j Cypher count(DISTINCT CASE WHEN followed THEN guidance_id) / count(DISTINCT guidance_id) double-credits multi-outcome guidance_ids. New DatasetProvider.GuidanceEffectiveness reads constraint_outcomes TSDB with the panels' exact math (followed=1, partial=0.5) over a config window (RSIC_GUIDANCE_EFFECTIVENESS_WINDOW_HOURS, default 168=7d); the assessor overrides js.FollowRate with it, so the gauge, RSIC GuidanceHealth, and the panels all agree. Neo4j path retained as fallback with a disclosing comment. Making the gauge honest correctly LOWERS RSIC's guidance health (it was masking the real problem). Build + lint + config-scanner (688/688) clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…auge's 2nd publisher (Epic 3) Live Tier 3 caught two bugs the unit tests couldn't: 1. The false CRITICAL fired AGAIN after the first restart. Root cause one layer below Fix 1: ReadSynergyMetrics (synergy_reader.go) set JiminyHealthy only `if jiminyCheck != nil`, so a nil check left it at the zero-value false (= "Jiminy down") and the now-guarded insight still fired. Default JiminyHealthy=true; only a real check returning false marks it down (real outages are covered by /healthz + the watchdog). +slog.Warn on nil check. Verified live: 0 jiminy CRITICAL fires since the fixed restart. 2. The gauge stayed inflated (0.732) despite the assessor override running correctly (diagnostic showed tsdb_rate=0.09, n=22). Root cause: the gauge has TWO publishers — self_assess.go AND live_collectors.go (the 15s Prometheus collector) — and the live collector republished the un-overridden Neo4j rate, overwriting it. Extracted the override into Assessor.applyHonestFollowRate and called it from BOTH paths. Verified live: gauge now 0.235, matching the TSDB 7d panel value (0.2353). Also added a diagnostic log at the insight-fire point (surfaces the runtime JiminyHealthy/SynergyAssessed values). go build + ape tests + lint clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…pic 4) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
JIMINY-SIGNAL-001 — honest Jiminy health signals (P0)First of two sprints from the "guidance not reaching agent" investigation. Two health signals were lying in opposite directions — between them they raised a false alarm AND hid the real problem. Fix 1 — false-positive CRITICAL"Jiminy Pipeline Critical — guidance not reaching agent" fired ~8×/day on a healthy, delivering Jiminy. It's not a delivery check — it's an RSIC reflection insight gated on Live Tier 3 caught a second instance one layer below — Fix 2 — inflated follow-rate gauge
Live Tier 3 caught the gauge has two publishers (the assessment path + the 15s Making the gauge honest correctly lowers RSIC's guidance-health dimension — it was masking the real ~24% effectiveness in the very score meant to catch it. Notes3 guard unit tests; build + lint + config-scanner (688/688) clean. Next: JIMINY-EFFECTIVENESS-001 — the real guidance-relevance + trust-decay fix (the J17 T1-promotion unblocker), now validated against these honest signals. Files
|
…t-healthy semantics TestFileSynergyReader_NilJiminyCheck asserted the OLD JiminyHealthy=false-on-nil behavior, which the Epic-3 fix intentionally changed to default-healthy (a nil check = "cannot determine" must not read as "down"). Updated the assertion to pin the new correct behavior. (CI Test gate fix.) Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…8/9) Folded into #469 per operator request. golang.org/x/crypto v0.49.0 carries HIGH CVE-2026-39827/39828/39829 (Go SSH channel-exhaustion DoS), fixed in v0.52.0 — flagged by the Trivy Security Scan (already failing on main). go mod tidy pulled the standard transitive bumps (x/net v0.54.0, x/sys v0.45.0, x/text v0.37.0). go build ./... + go vet clean; no API changes. Unrelated to the JIMINY-SIGNAL-001 logic; included here to green the scan. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…VEs) The crypto bump's transitive x/net (v0.54.0) still had HIGH CVEs (CVE-2026-25680 et al., HTML-parse CPU exhaustion) fixed in v0.55.0 — Trivy showed go.mod 15->6 HIGH after the crypto bump, the remaining 6 all x/net. This completes the Go-side remediation (Python uv.locks already scan clean). go build + vet clean. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(tsdb-consume-001): V0025 retention+compression for unbounded hypertables (Epic 1)
9 retention policies (90d telemetry / 180d forensics / 365d idea-09
observation tables) + 4 compression policies. First-run deletion
forecast: 0 rows on every table (oldest data 2026-03-31). Applied live
twice — idempotent (guarded ALTERs, if_not_exists policies).
TSDB_REQUIRED_SCHEMA_VERSION 24→25. Sprint plan included.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(tsdb-consume-001): honest metrics plane — windowed percentiles, real pool gauges, latency rules over real wall-time (Epic 2)
Four lying mechanisms fixed:
- Synthetic histogram p95/p99 now computed over the bucket delta between
flushes (was lifetime-cumulative: one slow call pegged the gauge forever
— live value was a constant 9.95 top-bucket clamp firing a perpetual
latency CRITICAL). Idle windows emit nothing.
- mdemg_neo4j_pool_* gauges deleted (neo4j driver has no pool API; the
values were a VerifyConnectivity probe counter + perpetual zeros) and
replaced with real pgxpool.Stat() gauges (mdemg_tsdb_pool_*). The
unfireable neo4j_pool_exhausted rule (server-native + Grafana) deleted.
- latency-slo rules rewritten over retrieval_audit.total_latency_ms (real
per-call wall time): windowed percentile_cont + COALESCE always returns
a row — kills the recurring rule-health-*_latency 'no rows' noise.
Config: ALERT_RETRIEVE_{P95,P99}_MS (120s/300s, calibrated to live 7d
p95 61.6s / p99 90s), ALERT_RETRIEVE_LATENCY_LOOKBACK_MIN (30).
- CollectRateLimitMetrics had zero callers AND added the cumulative total
per flush; now delta-tracked and wired into both collection paths.
/v1/system/pool-metrics serves real pgx stats (contract key kept).
mdemg-neo4j dashboard pool panels rewired; 3 UOTS specs updated to the
new truth (file-based specs PASS).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(tsdb-consume-001): writer flush stats → metrics + flush-failure rule (Epic 3)
All 8 buffered TSDB writers self-register their existing Stats() at
construction (internal/tsdb/writer_stats.go registry); the metrics
pre-flush hook surfaces them as mdemg_tsdb_writer_{flush_success,
flush_failures,rows_flushed,rows_dropped}_total{writer=<hypertable>}.
New evaluator rule tsdb_writer_flush_failures (service tsdb-writer,
high) fires on per-writer failure growth in TSDB_WRITER_ALERT_LOOKBACK_MIN
(60) — a wedged writer previously dropped rows in silence (only the
reinforcement writer had visibility). MAX-MIN per label: restart-safe,
COALESCE'd (always returns a row).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(tsdb-consume-001): scorer-drift tripwires over retrieval_audit (Epic 4)
The RRF-SCALE-001 regression class (scorer changes silently breaking
every RetrieveResult.Score consumer: 24-day Hebbian no-op, 9-week
guidance dormancy) becomes self-detecting. Two new evaluator rules:
- scorer_version_change (service scorer-drift): >1 distinct
scorer_version in SCORER_CHANGE_LOOKBACK_HOURS (24) — announces every
scorer change as the prompt to re-audit Score consumers.
- consensus_shift (service consensus-shift): |recent − baseline| mean
consensus_strength > CONSENSUS_SHIFT_THRESHOLD (0.10 ≈ 1σ live),
sample-gated (CONSENSUS_SHIFT_{RECENT_HOURS,BASELINE_DAYS,MIN_SAMPLES}).
Both hand-verified against live data: steady state quiet (1 version,
shift 0.033); a 900h window correctly detects the 3 historical versions.
RETRIEVAL_AUDIT_ENABLED default flipped false→true (the tripwires need
the feed; live .env already ran true) + forwarded in both compose
templates (in sync).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(tsdb-consume-001): guidance_conflicts counter + emergence-cycle duration tripwire (Epic 5)
- mdemg_guidance_conflicts_total{space_id} increments in
consulting.Suggest when detectConflicts finds anything — idea 09's
go/no-go criterion is now measurable from metric_samples (the TSDB
guidance_conflicts rows existed but had no counter/reader).
- mdemg_emergence_cycle_duration_seconds{space_id,cycle} records every
completed consolidation cycle's wall time (cycle = consolidation /
conversation / conversation_full; TotalDuration was computed and
discarded). New rule emergence_cycle_slow (service emergence-cycle):
window MAX > EMERGENCE_CYCLE_ALERT_THRESHOLD_SEC (60) over
EMERGENCE_CYCLE_ALERT_LOOKBACK_MIN (120) — the DBSCAN O(n²) deferral
condition (roadmap §4) is observable for the first time.
- Disclosed deviation: gauge, not the roadmap's 'histogram' — the
registry's fixed ≤10s latency buckets would clamp multi-minute cycles
exactly like the HTTP percentiles this sprint un-broke.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(tsdb-consume-001): V0020 writer-or-drop — decided: writer (Epic 6)
context_catalog_versions existed since Phase 14.2 with ZERO writes ever
(the migration header promised Builder.BuildForSpace would write it;
persistCatalog only wrote Neo4j). Each successful catalog build now
records one row: per-kind bit counts, top-ref arrays, full allocation
JSON. Sync single-row INSERT (model_install precedent — one row per
space per week), not the buffered CopyFrom the V0020 header described.
Hook is BuilderOpts.VersionRecorder (nil = no-op; the CLI fingerprint
backfill intentionally builds without one); server wires it reading
s.tsdbClient lazily at build time. Best-effort: failures log, never
fail the build — Neo4j stays the source of truth.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(tsdb-consume-001): remove the 4 reader-without-writer ft_* dashboard panels (Epic 7)
Model Version Timeline / Benchmark Scores / Training Cycle History /
Exogenous Ratio read ft_model_versions, ft_benchmarks,
ft_training_cycles — 0 rows ever, no writer exists in the codebase.
Tables stay: they are the designated sinks for the FT
recursive-retraining loop (Phases 6/7/9, trigger FT-CLASSIFY-002);
panels restorable from git history when writers ship. Status Notice
records the disposition. Live panels (llm_interactions, Phase 10
benchmark, endpoint health) untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ci: reclaim runner disk before docker-publish builds
Docker Publish on main failed intermittently with 'No space left on
device' killing the runner worker mid-neural-sidecar build (runs
27366931196, 27367756926, 27377325965 — incl. post-PR-440; passes in
between tracked buildx cache warmth). Two multi-arch images + mode=max
gha cache + the sidecar's torch wheels exceed the hosted runner's
~14 GB free disk when caches are cold. Pre-existing infra flake, not
PR-440-caused (that merge touched no docker inputs). Dropping unused
preinstalled toolchains reclaims ~25-30 GB.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(tsdb-consume-001): feature doc + CHANGELOG + CLAUDE.md + post (Epic 8)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(tsdb-consume-001): emergence-cycle gauge writers actually land in hidden/service.go
Live-smoke catch (own fix-commit per the Phase 11.6.2 precedent): Epic
5's combined edit script died on an assert BEFORE writing
internal/hidden/service.go, so commit 86f39ee shipped the gauge, rule,
and config with NO writer — exactly the readers-with-no-writers class
this sprint exists to kill. The Tier 3 wait for the first
mdemg_emergence_cycle_duration_seconds sample (none after a completed
18:06 consolidation cycle) exposed it. All 4 RunConsolidation /
RunConversationConsolidation / RunFullConversationConsolidation
TotalDuration sites now set the gauge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(tsdb-consume-001): record the Epic-5 live-smoke catch in post.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-recursive-000): recursive-retraining loop as-built audit + buildable spec
Doc-only, operator-prompted (PROMPT_ft_recursive_loop_deep_dive_v2.md,
executed with all 7 review amendments). Deliverable:
docs/development/ft-recursive-001/SPEC_recursive_retraining_loop.md —
five-stage as-built map with silent-failure (7) + dead-seam (5)
inventories on HEAD 17c283a; live-demonstrated exhibit A (3 tasks
Ready=YES → ungated insight #29 → executeAlertLog no-op, ≥9 alerts in
one session, shared-Service cooldown suppression); target state machine
(ft_training_cycles ledger single-flight, compute lease + RSIC quiesce
at 9h7m/36GB/85GB actuals, held-call canary, 5-class remediation
taxonomy, gaps-routed idempotent issue filer); 6a/6b/7/9 phased plan
honoring the FT-CLASSIFY-002 trigger; 7 operator decision forks
recommended-not-resolved. 00_README_v2.md STATUS gains a pointer only.
Governance handshake disclosed (jiminy-governance skill unregistered).
No production code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jiminy-outcome-002): Tier-2 not_applicable + verdict provenance
The post-SUPERVISOR-002 effectiveness drop was a measurement correction
biased LOW: the Tier-2 classifier enum had no not_applicable, so
guidance that couldn't apply to an action (hooks deliver up to 10 items
per action) was scored ignored. Both prompts + the Ollama grammar
schema now offer it with the ignored-vs-not_applicable distinction
spelled out; the parser and all four persistence sinks already handled
the value, so denominators self-correct with zero stats-query changes.
ULTS jiminy_evaluate_llm prompt hashes re-pinned (11/11 PASS).
Provenance: ClassificationResult.Source (tier1|llm|heuristic|explicit)
stamped at every decision path, persisted via V0026
constraint_outcomes.classifier_source (schema 25→26) — the
heuristic-fallback artifact class (94.9% dead calls half-crediting as
partial_compliance) is forever distinguishable. Pre-19:00Z history
annotated as heuristic-dominated (Grafana + feature doc).
Live Tier 3: unrelated pairs → not_applicable with coherent reasoning,
zero rows/edges; applicable-but-unaddressed → ignored rows with
classifier_source='llm' (5/5).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(mcp-revive-001): space resolution chain across all MCP memory tools (Epic 1)
Every memory-touching tool now resolves its space as: explicit space_id
param > MDEMG_SPACE_ID env > ide-agent (back-compat). Previously
defaultSpaceID='ide-agent' was hardcoded at 8 handler sites (and was the
fallback at the 3 param-bearing tools), stranding MCP-stored
observations outside the spaces hooks recall from — fragmenting the
connection layer per client. 6 tools gain the space_id parameter
(store, recall, associate, reflect, symbols, ingest_trigger); the
repo's .mcp.json sets MDEMG_SPACE_ID=mdemg-dev so this repo's agent
channel and hook channel share one memory space.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(mcp-revive-001): contract suite for the MCP tool surface (Epic 2)
UATS-style Go contracts over the previously zero-test 1,635-line file:
each handler invoked against an httptest MDEMG backend, asserting the
HTTP mapping (method/path/body), the space-resolution precedence
(explicit > env default > ide-agent), single-space association
invariant, validation-before-HTTP, and backend-500 → tool-error (never
a Go error). 7 contracts green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(mcp-revive-001): eventgraph + strict MCP tools, plugin-orphan reaper (Epics 3+4)
Three new tools: eventgraph_reinforcement_neighborhood +
eventgraph_guidance_outcome_neighborhood (seed_node_id OR query-resolved
seed, per the EVENTGRAPH-CLI-001 precedent; hops/since/limit OMITTED
when unset so server config stays the single source of truth) and
jiminy_strict (the /strict toggle). 23 tools total, all
contract-covered (12 contracts incl. the omit-when-unset pin and the
seed-by-query two-call chain).
Plugin-orphan reaper: launchctl kickstart -k kills the server without
Manager.Stop, orphaning plugin children (3 stale generations observed
live: Apr 30 / May 1 / May 7). startModuleInstance now pgrep-reaps any
prior-generation process holding the module's socket path before
spawning — surgical (full socket path match), loud per-kill warn.
Test decoy lesson: sh -c exec-optimizes argv away; tail -f <path>
carries it like a real plugin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(mcp-revive-001): feature doc + CHANGELOG + CLAUDE.md + post (Epic 5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(tsdb): time-scope V0014's backfill + integrity assertion to its historical window
Live-caught regression (RSIC schema-drift alerts, correctly firing):
V0014's post-migration check asserted ALL-TIME prompt-hash membership
against a frozen hash set, and migrations re-run on every auto-migrate
startup — so the first legitimate prompt evolution (JIMINY-OUTCOME-002's
not_applicable classifier prompt, 8 rows with the new hash) made V0014
RAISE forever, aborting every migration run at 013 and pinning
schema_version=13 while all 26-era objects continued to exist.
V0014 is a historical repair (the Phase 11.6.x task_name swap); its
UPDATEs and the Step-4 assertion now scope to time < 2026-05-02 — the
migration asserts exactly the work it did, immune to future prompt
evolution. Verified live: full chain runs clean, schema_version
restored to 26, drift alert cause removed.
Lesson for migration authors: in a run-every-startup migration model,
integrity checks over LIVE tables must be scoped to the migration's own
target window — an open-ended assertion is a time bomb armed by normal
system evolution.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): sprint plan — distribution-matched consulting.classify distillation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(ft-classify-002): class-stratified distill capture + the real 11.5d root cause (Epic 1)
The 11.5d class skew was NOT (only) sampling: summary_quality penalized
empty summaries, but a correct 'none' verdict REQUIRES an empty summary
by spec — so the reward>=0.8 distill filter silently rejected every
correct-none teacher answer (proof: run 1 with perfect 82%-none input
stratification kept 37/200 pairs with ZERO none). Fixed: spec-compliant
{type:none, summary:''} scores 1.0 (98 reward tests green). Capture
gains --stratify-classify (production-distribution bucketed sampling,
measured live over 4,028 rows). Run 2: 200/200 kept, reward mean 0.981,
train dist none 82/must 12/must_not 3/should 3/should_not 1 — within
±2pp of production on every class, 0-leak vs all 10 sources.
NOTE for the gate epic: stored classify baselines (0.668) were computed
under the biased reward — gates recompute baselines fresh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(ft-classify-002): training config — fresh task-delta LoRA on the production fused model (Epic 2)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ft-classify-002): benchmark config still pointed at decommissioned mlx port 8101
Phase 13.5 cutover updated serving to llama-server :8102 but
benchmark_phase10.yaml kept mlx_port: 8101 — any benchmark run without
an explicit --mlx-base-url override made ZERO model calls and reported
aggregate 0.0000 (caught live launching the FT-CLASSIFY-002 fresh
baseline). UBENCH config sha re-pinned; lint green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): stage-by-stage run record (6a instrumentation input)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(doc-audit-001): amended charter per 3-lens review team (operator-approved)
Adopts the operator's doc-audit orchestrator prompt with the team's
binding amendments: push-based survival loop (commit-without-push lost
state across resets), git ls-files enumerator (find swept ~700 non-repo
files incl. .claude agent memory into a FIX_IN_PLACE authorization),
dev02 branch strategy with CLAUDE.md/CHANGELOG operator carve-outs,
pinned-snapshot convergence, claim-budget batching, JSONL ledger, CI
discipline, and the age-based drift model (96% of docs/architecture
untouched since pre-April is the real hotspot; the MoE-marker class is
already closed). Phased: 001a after FT-CLASSIFY-002, 001b after
DORMANT-CENSUS-001, 001c as a standing mechanism.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): gate results — 2/3 PASS, no-promote recommended
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): CHANGELOG + post — no-promote accepted, sprint closed (Epic 6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(doc-audit-001a): execute the approved fix batch
1. compose LLM_ENDPOINT default 8101→8102 + stale Phase-11.6 comment
(triple-confirmed audit finding; un-overridden Docker deployments
pointed at the port decommissioned 2026-05-03; both copies, parity ok)
2. 00_README_v2.md version ledger unfrozen: v5.13 catch-up entry
(13.5 cutover, MODEL-DIST-001/002, FT-RECURSIVE-000,
FT-CLASSIFY-002) — append-only, R-LT-4-clean
3. README dashboard tab count 8→10
4. pre-campaign-checklist schema v8+→26 (cites config.go as authority)
5. beta-testing version-under-test marker → v0.10.1
6. live-validation F11 framing verified already-correct (no change)
+ CHANGELOG entry for 001a (rides dev01 per charter carve-out)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(surprise-topk-001): sprint plan — honest novelty + a multiplier that can fire
Live baselines escalate the roadmap's diagnosis: the surprise chain is
flat DEAD, not noisy — all 221,504 reinforcement events ever carry
surprise_factor=1.0; node surprise_score avg 0.023 (max 0.503, n=5,808)
vs hardcoded 0.4/0.7 thresholds. Scope: vector-index top-K novelty +
config-driven thresholds recalibrated to the new scale in the same
sprint (RRF-SCALE lesson) + CoactivateSession surprise-CASE audit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(surprise-topk-001): exact top-K nearest-neighbor embedding novelty (Epic 1)
Replaces the unordered LIMIT 50 sample (no ORDER BY) whose comparison
set was dominated by EMPTY-ARRAY embeddings — 4,564 of 5,810
conversation observations (78%) carry size(embedding)=0, passing the
old IS NOT NULL guard while cosine() yields NULL; this is why node
surprise_score averaged 0.023 and every reinforcement event ever
carried surprise_factor=1.0.
New: exact ORDER BY cosine scan over real-embedding (size=dims),
non-archived, space-scoped conversation observations; config
SURPRISE_EMBEDDING_NOVELTY_TOPK (50) + _SIM_FLOOR (0=off). The
db.index.vector.queryNodes route was live-REJECTED: the label-wide
index is crowded by ~100k non-conversation nodes (top-200 near a
conversation seed were ALL emergent_concept centroids — the
HIDDEN-CHURN degeneracy), pruning role-filtered hits to zero; the exact
scan over ~1.2k real rows is deterministic and ~ms (revisit at ~50k).
Live-verified: avg sim 0.680 / count 50 over the true nearest set.
Follow-up recorded: the 78% empty-embedding backlog (embeddings
backfill) is its own item.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(surprise-topk-001): config-driven surprise multiplier thresholds at both Cypher sites (Epic 2)
CoactivateSession DOES compute the surprise CASE (audit result) — the
chain was dead purely from broken scores. The hardcoded 0.7/0.4
thresholds (unreachable: live max score 0.503, avg 0.023 under the old
noise) become SURPRISE_FACTOR_HIGH_THRESHOLD (0.5) /
SURPRISE_FACTOR_MEDIUM_THRESHOLD (0.3) — defaults calibrated to the
exact-top-K novelty scale, parameterized into BOTH ApplyCoactivation
and CoactivateSession Cypher (never recalibrate against the old scale —
the RRF-SCALE lesson). Non-positive config falls back to defaults.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(surprise-topk-001): post + CHANGELOG; threshold recalibration + loud error path (Epics 3-4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-budget-001): sprint plan
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-budget-001): budget derivation, session attribution, surface/outcome split, token floors (Epics 1-3)
Budgets: JIMINY_TIMEOUT_MS default 15s→0 (=derive from the 90s
warm-compute budget — both paths run the same synthesis work; the
independent 15s starved every fresh install, the GUIDANCE-SYNTH-001
class); /reformulate's hardcoded 10s → JIMINY_REFORMULATE_TIMEOUT_MS
(0=derive); config.Validate() warns on explicit budget incoherence.
Attribution: PersistGuidanceOutcome now receives feedbackSessionID
(was literal "" — every GUIDANCE_OUTCOME edge ever has null
session_id; forward-only fix). Surface-vs-outcome split:
mdemg_jiminy_guidance_surfaced_total{space_id} (the honest denominator
— TotalGuidanceIssued only counts guidance that RECEIVED feedback) +
mdemg_jiminy_feedback_dropped_total{space_id} on tracker-expiry drops.
Floors: JIMINY_OUTCOME_LLM_MAX_TOKENS 100→3000 (truncation risk on the
classifier reasoning field → parse fail → heuristic fallback, the
JIMINY-OUTCOME-002 artifact class), SYNTHESIS 2000→3000, EVALUATE
2000→3000 (standing ≥3000 rule; completion stops at JSON end — floors
are free insurance). Stale TTL comment fixed (1800→86400 actual).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-budget-001): CHANGELOG + post (Epic 4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dormant-census-001): sprint plan — the standing dormancy guarantee
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): route↔consumer gate + 187-route inventory skeleton (Epic 1a)
scripts/verify_route_consumers.py extracts the live route table and
fails on bidirectional drift (unlisted route / stale entry) and on any
UNREVIEWED disposition — the bootstrap marker. Gate verified failing on
the 187 fresh UNREVIEWED entries; adjudication next.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): adjudicated 187-route inventory + merge-blocking CI gate (Epic 1b)
All 187 routes carry evidence-based dispositions: 109 ACTIVE,
60 OPERATOR_SURFACE, 13 INTERNAL, 4 PRUNE_CANDIDATE, 1 DEFERRED.
171/187 matched to UATS specs. Orchestrator re-verified every
PRUNE_CANDIDATE and known false positive independently.
Census reversals vs recon (the false-positive class this gate exists
to catch): /viz/topology + /api/graph/* are LIVE Grafana consumers
(topology iframe + nodegraph datasource) — removed from the prune
list; /v1/conversation/snapshot* is NOT called by pre-compact.sh
(saves via /v1/conversation/observe) — OPERATOR_SURFACE.
Hidden consumer surfaced: embedded /ui/ dashboard consumes ~35
routes that look dormant from hooks/CLI/scripts alone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): wire SignalLearner.GetStrength into Guide() ordering (Epic 2)
The Hebbian signal learner (V0024 SignalState, supervised flush,
startup hydration, live emission/response stream since HOOKWIRE-001)
had a read side with ZERO production callers. Guide() now orders
within equal priority by (1-w)·confidence + w·GetStrength(code),
w = JIMINY_SIGNAL_STRENGTH_WEIGHT (default 0.2; 0 = off, pre-census
behavior; clamped to 1). Ordering only — selection/filtering
untouched. Unknown codes blend the learner's 0.5 neutral default.
6 Tier 1 tests pin the contract: weight-0/nil-learner pure
confidence, blend formula, neutral default, clamp, priority
dominance + within-priority strength overtake.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): prune 4 verified-dead routes + handlers (Epic 3)
Pruned (each independently re-verified zero producers + named successor):
- /v1/feedback — gap-feedback intake; live channel is /v1/jiminy/feedback.
Handler + isolated gaps.ProcessFeedback/Feedback removed.
- /v1/memory/ingest-codebase[/] — deprecated since Phase 94 (Deprecation
header); successor /v1/memory/ingest/trigger. Whole handler file removed.
- POST /v1/alerts/grafana — superseded by the native alert evaluator;
only ref was a commented-out contactpoint. Compose env
MDEMG_GRAFANA_ALERT_WEBHOOK_URL removed from both compose files.
NOT pruned (census reversals): /viz/topology + /api/graph/* are live
Grafana consumers; PREDICTS/FORESHADOWS exist nowhere in code (recon
claim was wrong — no-op).
6 UATS specs removed + capability_gaps_full /v1/feedback variants
dropped; UXTS matrix 220→214; inventory entries retained as PRUNED
with removed_in. Gate: 183 live / 187 inventoried, OK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jiminy): codegen collision-path self-deadlock wedged constraint observes
Live-smoke catch (DORMANT-CENSUS-001 Tier 3, own fix commit per
precedent): ConstraintCodeGenerator.GenerateCode's collision branch
called fallbackCode while holding g.mu; fallbackCode locks g.mu again.
sync.Mutex is not reentrant — the first LLM-returned code that
collided with a registered code deadlocked the generator permanently,
and every later constraint-typed /v1/conversation/observe queued
behind it forever (UATS conversation_observe_pinned hung 45-90s+
deterministically; goroutine dump showed the holder 18 min wedged at
codegen.go:121 with N waiters at :47).
Fix: fallbackCodeLocked (caller holds g.mu) used by the collision
branch; fallbackCode wraps it. Regression test drives the real
collision path through a fake OpenAI-compat endpoint with a 10s
deadlock tripwire + post-call usability check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dormant-census-001): feature doc + CHANGELOG + sprint post (Epic 5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dormant-census-001): re-pin capability_gaps_full spec hash after variant removal
The Epic 3 prune removed the spec's two /v1/feedback variants but did
not re-pin its integrity sha256 — the merge-blocking UNTS hash-verify
step correctly caught the drift on PR #452. 214/214 valid locally.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(context-live-001): sprint plan + recon findings (Epic 0)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): version guard + honest consensus denominator (Epic 1)
(1) Cross-version fingerprint guard: deriveQueryFingerprint returns the
active catalog version; it rides RetrieveRequest/ColumnQuery into
ContextColumn and StrictContextMode, where candidates fingerprinted
against a different catalog version score 0 — bit positions reallocate
per build, so cross-version Jaccard is noise (mdemg-dev: 76,906 v1
nodes were silently compared against v3 query bits). Version 0 =
unknown (explicit-fp callers) keeps legacy behavior.
(2) Consensus semantics: columns STRUCTURALLY unable to vote (disabled
by config, or context with no query fingerprint) are no longer
appended, so they stop deflating the denominator — the always-empty
live context column had every live query's consensus hard-capped at
0.8. Errored/timed-out columns still count (documented intent kept).
The column_context.go comment that claimed exclusion now matches code.
(3) Cache integrity: QueryContextFingerprintVersion joins CacheKey
(the reflection forcing-function caught it unprompted); scorerVersion
bumps to v2 (semantics change) and gains a deterministic hash of the
per-category context-weight + sparse-override maps — operator edits to
either JSON now flip the namespace (pointer fields dereferenced; %+v
would have hashed addresses).
Pins: TestContextColumn_VersionGuard,
TestConsensus_DenominatorExcludesAbsentColumns,
TestScorerVersion_FlipsOnCategoryMapChanges.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): stage-6 fingerprint heal + Phase-B refine wired (Epic 2)
Disclosed deviation from the plan sketch: RefineWithCoactivations is
NOT the skew healer — it merges old-catalog bits and bumps the version,
which would relabel v1-semantics bits as current. The healer is
recomputation: new conversation.RecomputeStaleFingerprints (the
backfill CLI's core as a budget-bounded, resumable library call).
Stage 6 now runs on EVERY invocation (not just rebuilds): catalog
freshness/build as before, then (1) heal pass — recompute up to
CONTEXT_FINGERPRINT_HEAL_MAX_PER_CYCLE (2000) stale-version nodes under
the existing 60s budget, flushing partial batches on ctx expiry;
(2) Phase-B refine — RefineWithCoactivations (previously ZERO callers)
over up to CONTEXT_FINGERPRINT_REFINE_MAX_PER_CYCLE (200)
current-version observations with co-activations, marked via
context_fingerprint_refined_version so cycles never re-walk. Driver
injected via SetFingerprintDriver (server.go, next to SetContextCatalog);
nil driver = old rebuild-only behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): classifier→category dispatch on live traffic (Epic 3)
Live traffic never passes ?category=, so SPARSE_GATE_CATEGORY_OVERRIDES
and RETRIEVAL_CONTEXT_COLUMN_CATEGORY_WEIGHTS only ever fired on
benchmark calls. Retrieve now derives req.Category from the
QueryClassifier's types via QUERY_CLASSIFY_CATEGORY_MAP (JSON; default
data_flow→data_flow_integration, architecture→architecture_structure,
relationship→relationship). Explicit body/param category always wins;
first mapped type wins for multi-label; empty map disables. Runs
pre-CacheKey (Category already in the key — cache-safe; classifier
already ran before the key was computed). Vocabulary gap disclosed:
service_relationships/business_logic_constraints have no classifier
equivalent and stay benchmark-only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): server-side query-fingerprint derivation default-on (Epic 4)
CONTEXT_QUERY_AUTO_DEFAULT (default true) makes derivation fire for
every retrieve with query text; per-call opt-out ?context=off|false|0;
explicit ?context=auto still forces it when the config gate is off.
Sequenced after the skew heal + version guard, so the newly-active
column scores healed current-version fingerprints, never cross-version
noise. Cost: reuses the per-(space,version) catalog-vector cache —
one cosine top-K over ≤256 refs per call.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(context-live-001): UVTS A/B artifacts + feature doc + CHANGELOG + post (Epics 5-6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(readme): coverage-gap additions per 3-lane team review (operator-approved)
Executes the corrected recommendations from the README_COVERAGE_GAPS.md
review: new Self-Improving Local LLM section (~20 lines, fact-scrubbed:
fused GGUF serving, no OpenAI auto-fallback, no hosted-SFT claim, no
volatile eval figures; Step 2b kept in place and cross-linked); 3 new
Key Features bullets (never-silent operations, closed guidance loop
with fail-closed-bash/graduated-strict wording, eventgraph federation);
2 folds (RRF/sparse/fingerprints into the Hybrid search bullet;
de-numbered the stale 'all 11 frameworks' guide pointer); 1 sentence
(TSDB telemetry governance in Observability). Rejected per review:
the report's §5.2 Step-2b 'correction' (would have written the
rolled-back Stage-1 distill as canonical) and its stale 0.85xx
figures. Drive-by count fixes: UPTS 27→28 languages, Jiminy 6s→
config-derived budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(negfeed-cooler-001): sprint plan + live-verified recon (Epic 0)
NEGFEED-001 + COOLER-001 — complete the memory lifecycle. Recon
orchestrator-verified live: CoactivateSession 244k rows/14d (73% of
reinforcement_events), apply_negative_feedback 2 rows ever, 1
CONTRADICTS edge in the whole graph — producer-wiring only (downstream
EVENTGRAPH-003/004 built). Three workstreams: CoactivateSession
off-request + delta emission + bounded window; Jiminy-contradicted →
ApplyNegativeFeedback bridge + MCP memory_reject; graduation
unification (RSIC → cooler) + decay-protection retrieval semantics.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(negfeed-001): CoactivateSession off-request + delta emission (Epic 1)
The dominant Hebbian producer (live 244k rows/14d, 73% of
reinforcement_events) ran SYNCHRONOUSLY on the Observe request path and
regenerated the full unbounded C(n,2) session clique every call —
O(n^2) per observe, and evidence_count tracked session length not
co-activation count.
(1) Off the request path: the Observe call site now fires CoactivateSession
in a panic-recovered background goroutine on a detached context
(context.WithoutCancel + 30s timeout) — observe latency decoupled from
session size.
(2) Delta emission: CoactivateSession takes the new observation's
node_id and co-activates it ONLY against the most-recent prior session
members (bounded by LEARNING_SESSION_CLIQUE_WINDOW, default 50; 0 = all
priors). Per-call work O(window) not O(n^2); a pair's edge is
created/strengthened only on genuine co-activation, so evidence_count
counts co-activation events, not session length. Cumulative graph
unchanged (sum of deltas = C(n,2)). DH-004 reinforceSessionObservations
stability raise preserved (full-session by design).
Forward-only: existing inflated evidence_count values left as-is.
Pins: TestCoactivateSession_DeltaPairCount (delta arithmetic + window
cap + cumulative invariant).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(negfeed-001): anti-Hebbian producer — Jiminy contradicted bridge + MCP memory_reject (Epic 2)
The substrate had NO automated weaken producer: ApplyNegativeFeedback
(weaken + CONTRADICTS, EVENTGRAPH-003/004 federation built) had only
the HTTP handler as caller; live, 2 apply_negative_feedback rows ever
and 1 CONTRADICTS edge in the entire graph.
Bridge A (jiminy.NegativeFeedbackApplier interface + adapter over
learning.Service, wired in server.go): on OutcomeContradicted with >1
source node, weaken co-activations AMONG the guidance's source nodes
(query==rejected; ApplyNegativeFeedback's q<>r guard skips self-pairs,
so single-source guidance is a safe no-op and unrelated memory is never
touched; weight floors at 0). Gated JIMINY_CONTRADICTED_WEAKEN_ENABLED
(default true).
Bridge B (MCP memory_reject): the explicit producer — agent rejects a
memory that surfaced wrongly for a context; resolves both sides via
retrieve (single space, mirrors memory_associate) and POSTs
/v1/learning/negative-feedback. Contract test added (MCP-REVIVE-001
rule). 24 MCP tools now.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cooler-001): unify graduation onto the Context Cooler (Epic 3)
Two divergent graduation paths existed: the cooler (threshold 0.8 +
pinned-guard + graduated_at) and RSIC executeGraduateVolatile
(hardcoded 0.7, no guard, no graduated_at). RSIC now delegates to the
cooler via a GraduationProcessor interface + coolerGraduationAdapter —
one config-driven implementation. The rollback snapshot predicate is
aligned to the cooler's (pinned-guard + COOLER_GRADUATION_THRESHOLD via
SnapshotStore.SetGraduationThreshold) so executor and snapshot can't
drift (RSIC-STORM-001 discipline; captureNodeStateP adds param support).
The cooler also runs per-space from (new Step 2 —
Graduation, between decay and prune; skipped in dry-run since it
mutates) — graduation no longer requires the default-off background
loop on its hardcoded mdemg-dev space.
Tests: NilProcessor fail-safe + DelegatesToProcessor (graduate_volatile
removed from the nil-DRIVER loop — it's no longer driver-coupled).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cooler-001): graduated-incident edges resist decay (Epic 4)
Defines what 'graduated' means to retrieval (recon: retrieval read no
graduation signal — greenfield). The lowest-risk, learning-side
interpretation: CO_ACTIVATED_WITH edges incident to a graduated
(volatile=false) node decay at a reduced rate, so stable memory's
associations persist. NOT a retrieval-scorer change — no RRF
score-scale risk, no UVTS gate.
GRADUATED_DECAY_PROTECTION_FACTOR (default 0.5; 1.0=off, 0=no decay)
multiplies the effective decay rate when either endpoint is graduated.
Wired into the live decay job (maintenance Step 1 + the decay command
flag); query returns graduatedIncident per edge. Pin test:
graduated-incident edge retains more weight; factor 1.0 disables.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(negfeed-cooler-001): feature doc + CHANGELOG + post (Epic 6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(sidecar-loop-001): sprint plan + recon (Epic 0) — fix-but-defer
Operator decision: fix-but-defer (keep only valuable data; build clean
dataset to train-size, defer training — rerank is default-off). Recon
live-verified: data IS teacher-labeled (rerank_scores[]) but 100%
mislabeled (collector logs unsorted input candidates vs rerank-sorted
scores; 84% len-mismatch + the 16% positionally wrong), and the trainer
reads flat vs the collector's nested schema (0 records ever parsed).
Scope: collector alignment fix + guard, trainer schema reconcile,
archive the mislabeled pre-fix data, flag honesty, readiness count;
training/A/B deferred.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sidecar-loop-001): collector logs aligned reranked candidates + guard (Epic 1)
The rerank training collector logged req.Candidates[:topN] (unsorted
input) against rerankScores (rerank-sorted output) — candidate<->score
correspondence broken in 100% of records (84% length-mismatch, the
remaining 16% positionally wrong). Now logs result.Results +
result.RerankScores, which are built together from the sorted slice and
align 1:1. Added a Collect guard that drops misaligned (len-mismatch),
empty, or all-zero-score records so the corpus is clean by construction.
Tests: aligned record written; misaligned/empty/all-zero dropped.
This is the same data-mislabeling class behind discarded retrains.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(training-audit): root-cause the 3 discarded retrains — corpus filter + eval + DPO all mislabeled
Operator-triggered audit (3 lanes + code verification). The retrain loop
is corrupted by one class of defect (label/score decoupled from its data)
in three places: (1) the mean>=0.8 distill filter over length/keyword-
biased rewards skews the corpus toward verbose output (summary_quality
was one instance of a systemic pattern; ape.reflect/summarize/synthesize
unfixed); (2) the promotion gate evals on the 99%-leaked valid_golden vs
a frozen stale-reward MLX-form baseline 0.8338 with no zero-call hard-fail
— a worse-than-baseline verdict is uninterpretable; (3) dpo_builder
assigns chosen/rejected by list position, ignoring the outcome it
computes. All file:line-verified. Recommendation: no retrain until fixed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(sidecar-loop-001): feature doc + CHANGELOG; archive mislabeled pre-fix data
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(eval-integrity-001): sprint plan + recon (Epic 0)
Lead sprint of the training-integrity remediation (operator-sequenced
eval-integrity-first). Makes the adapter promotion gate trustworthy:
clean leak-free 17-task holdout (rebuild — valid_clean is stale/wrong
shape), leak-audit preflight, GGUF serving (not the OOM-prone MLX
side-server), zero-call hard-fail, and baseline-as-recomputed-report
(not a frozen 0.8338 constant). Discloses the sequencing constraint:
the honest baseline number is re-run at the close of the reward sprint
(it must use the fixed reward), so this sprint stamps a provisional one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(eval-integrity-001): respect dynamic_prompt — recover enum-templated tasks' production rows (Epic 1)
The clean-eval builder + benchmark matcher strict-hash-matched ANY spec
with a pinned system_prompt_hash, ignoring dynamic_prompt. But
ape.reflect's prompt is built at init() by interpolating
AllowedLLMActions into the text — its hash drifts every time the action
enum changes (RSIC has added actions repeatedly). Result: ape.reflect's
71,033 production rows (under hash ef068ab) were invisible to a filter
pinned to 39b2bc (0 matches) — the single largest training target had
its entire corpus silently excluded from eval AND curation.
Fix: Spec now carries dynamic_prompt; the matcher (run_benchmark) and
build_clean_eval match dynamic/enum-templated prompts by task_name, not
by the moving hash. ape.reflect's spec corrected to dynamic_prompt=true
(was false despite being enum-templated). Proven end-to-end: clean-eval
coverage 10/17 → 12/17 (ape.reflect + hidden.reclassify recovered, 240
rows). 90 neural tests pass.
Residual: 5 tasks (consulting.synthesis, guardrail.evaluate,
metalearn.generalize, retrieval.rerank_nli, summarize.generate) have
~0 production rows — genuinely uncaptured, a separate capture-gap issue.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(eval-integrity-001): wire gate to leak-free 12-task valid_clean (Epic: gate-set)
The promotion gate evaled on the 99%-leaked valid_golden. Re-pointed
benchmark_phase10.yaml + the UBENCH spec to the rebuilt leak-free
valid_clean (240 rows / 12 tasks with real current-prompt production
data; 5 capture-gap tasks excluded as uncoverable — operator-approved
honest-12 over fake-17). Re-pinned config.sha256 + golden_holdout
sha256/expected_rows(240)/expected_tasks(12). ubench lint 100%.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(eval-integrity-001): hard-fail on zero successful calls (Epic 3)
A benchmark run that made zero successful calls — or where no task
contributed a score (weight_used==0) — reported aggregate 0.0,
indistinguishable from a genuine low score. This is the false-0.0 class
behind FT-CLASSIFY-002's four silent failures (port rot, model 404s,
llama-server answering /v1/models before load). run() now computes
specs_with_zero_successful_calls + a status field; a degenerate run is
status=error and main() exits nonzero (the gate/CI/operator rejects it
instead of mistaking a broken run for a model judgment). Pin tests on
_count_zero_call_specs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(eval-integrity-001): wire LLM recorder in ingest process — capture summarize.generate (Epic: capture-gap)
The capture-gap investigation found summarize.generate (default-ON,
LLM_SUMMARY_ENABLED=true) runs in the `mdemg ingest` subprocess where
SetDefaultRecorder was NEVER called (only `mdemg serve` wired it) — so
every summary LLM call was made and silently dropped, losing the task's
entire production corpus (the EMBED-WIRE recorder-gap class, via a
process boundary). Now wires a short-lived buffered LLM recorder in
runIngest before summarize.New (which captures defaultRecorder at
construction), guarded by LLM_INTERACTION_LOGGING, with flush+close on
exit. summarize.generate production rows will now accumulate → coverable
by the clean eval over time (13th task).
The other 4 zero-row tasks (consulting.synthesis, guardrail.evaluate,
metalearn.generalize, retrieval.rerank_nli) are correctly GATED-OFF
(SYNTHESIS/GUARDRAIL/METALEARN/RERANK_ENABLED all default-false) —
expected zeros, no fix needed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(eval-integrity-001): leak-audit gate target (Epic 2)
make test-eval-leak runs audit_eval_leakage.py over valid_clean vs the
SFT training sources, exits nonzero on ANY overlap — the gate must
re-verify the eval is disjoint from training (the valid_golden 99%-leak
class; build-time filtering isn't a substitute as the corpus grows).
The script existed but had zero callers. Verified: valid_clean CLEAN,
0/240 overlap.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(eval-integrity-001): feature doc + CHANGELOG + post (Epic 7)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(eval-integrity-001): restore ape_reflect system_prompt_source to clean file:line
The dynamic_prompt=true change is correct, but I'd also appended an
annotation to system_prompt_source — the ULTS --verify-hashes runner
parses that field AS the file:line to hash, so the annotation broke it
('source file not found'), failing the merge-blocking ULTS gate.
Restored the clean 'internal/ape/llm_reflector.go:74 (rendered with
AllowedLLMActions enum)'; dynamic_prompt=true retained. ape.reflect now
11/11 PASS locally. (UBTS --output + UOBS metric-not-found in the same
CI run are pre-existing continue-on-error soft-fails, not from this PR.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-001): sprint plan (Epic 0)
2nd training-integrity remediation sprint. The distill gate
(x9_distill_capture_v2.py:360: kept = mean(reward_vector) >= 0.8,
global) drops spec-correct-but-terse answers because coverage_score/
explanation_quality/coherence_score reward length over correctness —
gutting ape.reflect (largest target) + summarize + synthesize, then
balanced_sampler amplifies the verbose-skew. Principle: inclusion
selects for CORRECTNESS not length. Fix: length-neutral correctness
rewards + per-task inclusion thresholds + a forcing-function test (each
of the 12 covered tasks' known-correct golden rows clear their gate) +
distribution check. Closes with the eval-integrity-deferred GGUF
serving + honest baseline recompute.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(reward-correctness-001): length-neutral correctness rewards (Epic 1)
The distill inclusion gate (mean(reward_vector) >= 0.8) selected for LENGTH,
not correctness — the corpus-skew mechanism behind 3 discarded retrains. Four
reward functions used length/count ladders that dropped spec-correct-but-terse
answers below the gate and rewarded verbosity upward:
- coverage_score: <20 words→0.4 / <50→0.7 / then rising → now substantive
content scores 0.9 flat (length-neutral); empty→0.0, pure-repetition→0.3.
- explanation_quality: <20 words→0.6 cliff → now substantive→0.9.
- coherence_score: required >=2 sentences + 10 words → now any coherent
non-repetitive response→0.9; pure repetition→0.4.
- insight_count: rewarded bullet COUNT (>=5→1.0) → now >=1 genuine insight→0.9
(no upward count reward; stops bullet-spam, stops dropping single-insight
reflections to 0.5 — ape.reflect, the largest target).
Verified: terse-correct now clears the 0.8 gate; verbosity/bullet-count no
longer rewarded above concise; varied detailed content unaffected (0.9);
empty/repetition still rejected. Tests rewritten to pin the new semantics
(78 pass). Subtler keyword-bag functions (specificity/actionability) left for
the continuation — they reward content signals, not raw length.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(reward-correctness-001): per-task inclusion thresholds + live findings (Epic 2-3)
Epic 2: --reward-threshold-map JSON ({"task": float}) overrides the global
--reward-threshold per task in x9_distill_capture_v2.py, so tasks whose reward
arrays have a different natural ceiling can gate at the right bar. Records the
per-task gate in each row + the manifest. Live-verified end-to-end: real
OpenAI + TSDB run with {"consulting.classify": 0.6} applied the override
(3/3 captured, manifest per_task reward_threshold=0.6).
Epic 3 (live Tier 3, docs/development/reward-correctness-001/live_findings.md):
scored REAL production llm_interactions at the 0.8 gate, old vs new rewards.
Validated Epic 1: hidden.summarize recovered 69/72 real concise summaries the
old length ladder dropped. Surfaced three larger correctness issues the
length fix does NOT close (the real dominant suppressors for the big tasks):
1. ape.reflect (54k, largest target): json_valid mean 0.133 — ~87% of recent
responses TRUNCATED mid-JSON (prompt ~5800 + ~3000 output > 8192 per-slot
KV bound). Production serving/capture defect; gate correctly rejects.
Recommended own-sprint follow-up (raise output budget, re-capture).
2. jiminy.evaluate: explanation_quality=0.0 on correct {violations,warnings}
responses — wrong reward for the schema (no top-level explanation key).
Reward-array fix, operator-gated (changes a ULTS array + re-grades).
3. jiminy.synthesize: keyword-bag follow_rate/specificity just below gate —
the deferred Epic 1 continuation.
Also fixed 2 pre-existing lint nits in the touched file (F541, E741).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-001): CHANGELOG + sprint post (Epic 6 close)
Closes REWARD-CORRECTNESS-001 at Epic 1+2+3. Epic 4 (baseline recompute)
explicitly deferred behind the ape.reflect truncation fix per operator
sequencing — recomputing over a known-truncated corpus would bake in the
corruption. Next sprint: ape.reflect truncation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ape-prompt-budget-001): sprint plan + recon (Epic 0)
Root-caused the ape.reflect ~87% truncation (largest training target) to a
structurally-unbounded prompt: live-measured 7489 tokens (Current Assessment
~3895 + 5-cycle history ~2693), leaving only ~700 of the 8192 per-slot KV
budget for output — 191/200 invalid responses cluster at 490-520 tokens_out,
truncating mid-JSON at the ceiling. Compression already on; not a max_tokens
cap. Plan: bound the prompt to a configurable token budget (gate verbose TSDB
dataset fields, cap history cycles, final drop-oldest guard) so output always
has ~4000-token headroom, with an optional serving-slot increase as the safety
margin. Lever A (structural prompt budget) + Lever B (KV slot) proposed, picked
at execution. Tier 3 proof: fresh ape.reflect json_valid recovers 0.13 → ~1.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(ape-prompt-budget-001): bound ape.reflect prompt to protect output (Epic 1-2)
Implements the structural fix for the ape.reflect ~87% mid-JSON cutoff. The
prompt was unbounded (live 7489 tok), leaving only ~700 of the 8192 per-slot
KV budget for output, so the largest training target's responses were cut off
mid-array.
buildUserPrompt now enforces a token budget:
- gate the verbose TSDB dataset fields (LLMPerformance x17 / Retrieval /
Embedding / TrainingReadiness, ~3895 of 7489 prompt tokens) behind
RSIC_LLM_REFLECT_INCLUDE_DATASETS (default false); scalar health metrics
the detectors use are always kept;
- cap history cycles via RSIC_LLM_REFLECT_HISTORY_CYCLES (default 3, was
hardcoded 5);
- final budget guard (RSIC_LLM_REFLECT_PROMPT_BUDGET_TOKENS default 3500, 0
disables): drops history oldest-first, then trims the assessment tail,
logging loudly what was dropped (never silent). estimateTokens calibrated
to the measured 2.3 chars/tok ratio, slightly conservative.
3 config fields (range-validated, no hardcoding) wired config ->
LLMReflectorConfig -> server.go. 6 Tier-1 tests: dataset gating, history cap,
drop-history-under-budget, trim-assessment-under-budget, under-budget-unchanged,
estimator. Full ape suite + lint + config scanner (687/687) clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ape-prompt-budget-001): feature doc + CHANGELOG + post + CLAUDE.md (Epic 5)
Live Tier 3 result documented: 3/3 fresh post-restart ape.reflect rows valid
JSON (100%, up from ~13%), tokens_in ~2575 (from ~7489). Corrected the stale
CLAUDE.md ape.reflect prompt-size figure (~5800 -> ~7489 live-measured) and
added the per-slot KV "prompt+output share the budget" guidance. Closes
APE-PROMPT-BUDGET-001.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ape-prompt-budget-001): re-pin ape_reflect ULTS source line after import shift
The Epic 1-2 import (log/slog) + struct fields shifted llmReflectSystemPrompt
from line 74 to 80. The ULTS hash verifier reads from line-2 and grabs the
first backtick string; at the stale :74 the search region now included the
`"` backtick in `quoted[i] = `"` + a + `"`` → wrong hash. The system prompt
TEXT is unchanged (hash 39b2bc… still matches at :80). Updated
system_prompt_source :74 → :80. Local glob verify: ape.reflect 11/11 PASS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune-audit-001): non-destructive audit of non-conforming data (Epic 0-1)
Operator-chosen audit-first prune phase. Read-only enumeration of every
non-conforming TSDB/file target with exact counts + a backup/small-batch/verify
prune plan (each category operator-gated).
Findings — PRUNE TARGETS: (A) 2,111 invalid-JSON rows in object/array tasks
(ape.reflect 1890 in the 06-11..06-13 truncation window, rerank_cross 184,
evaluate_llm 18, query_classify 18, classify 1); (B) 21,135 error/empty rows
(mdemg data clean target); (C) rerank mislabeled archive 6,894 events/21M +
valid_golden 108 leaked + ~14 stale April baselines. ~23,246 TSDB rows total
(~22.7%).
NOT prune targets (schema/reward mismatch, data is fine, fix the definition):
hidden.summarize 72 (prose vs object schema), string-schema tasks the jsonb
check false-flags (intent_translate/codegen/synthesize emit valid bare
strings), jiminy.evaluate. Audit pitfall recorded: never run a jsonb-validity
prune predicate against string-schema tasks.
Corrects the "87% of 54k" assumption: ape.reflect corruption is 1,890 rows in
the recent truncation window (forward-fixed by APE-PROMPT-BUDGET-001), not
corpus-wide.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune-audit-001): record Category A prune execution + CHANGELOG (Epic 2 close)
Pruned 1,898 genuinely-corrupt invalid-JSON rows from llm_interactions
(ape.reflect 1,879 / jiminy.evaluate_llm 18 / consulting.classify 1),
backup-first to .mdemg-backup-20260613_195431/dataprune/ (reversible).
102,415 -> 100,517, remaining_corrupt=0, live healthy, recent ape.reflect
14/14 valid.
Small-batch verify caught that the raw pg_input_is_valid predicate over-counted
by 213 (valid JSON behind markdown fences / think-tags that production
SanitizeResponse strips); validated all candidates through a faithful replica
of llmclient.SanitizeResponse and spared the recoverable 213. Categories B
(error rows) + C (file artifacts) deferred. The backup dir is untracked
(not committed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune-audit-001): Category B+C prune execution record
B: 21,254 error/silent-failure rows removed via mdemg data clean (4 spaces),
backed up first. C: rerank prefix-archive (6,894 events/21M, no refs) moved to
backup; valid_golden + ~14 baselines RETAINED (load-bearing — leak source +
regression harness; retire during baseline recompute). Final: llm_interactions
79,461 rows, 0 non-conforming. Verification catch documented: data clean
dry-run per-task table is surviving-rows, not the delete set.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(space): delete pre-check + list panic on non-MemoryNode / null-space data
Two bugs surfaced during the space-hygiene cleanup (removing 24 junk/test/demo
spaces for live testing):
1. `mdemg space delete` gated its pre-check on `count(MemoryNode {space_id})`
but the delete itself is label-agnostic (`MATCH (n {space_id})`). A space
holding only SymbolNodes/Observations (e.g. e2e-test = 10,918 SymbolNodes)
reported "no nodes. Nothing to delete." and silently survived. Pre-check now
counts all labels, matching the delete.
2. `ListSpaces` (`mdemg space list`) panicked — `interface conversion:
interface {} is nil, not string` at the `sid.(string)` assertion — when any
MemoryNode had a null space_id (orphaned/infra artifacts). The query now
excludes null/empty space_id (such nodes are not a "space"), and the
assertion is nil-guarded defensively.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune): Neo4j space hygiene record + CHANGELOG
24 junk/test/demo spaces removed (~143k nodes, backed up); blank-space resolved
(global infra kept null, 155 test MemoryNodes staged for delete); 2 space-tool
bugs fixed (delete pre-check, list panic). Record in space_cleanup.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-002): sprint plan — schema/reward-mismatch fixes (Epic 0)
The REWARD-CORRECTNESS-001 follow-ups: (1) hidden.summarize schema object->string
(production emits prose; 72 rows mis-flagged invalid-JSON); (2) explanation_quality
schema-aware for nested violations[].reasoning (fixes jiminy.evaluate +
evaluate_llm scoring correct responses 0.0); (3) keyword-bag specificity/
actionability substantive-floored (jiminy.synthesize valid guidance dropped for
lacking magic words). Makes the 4 tasks' grading correct before the baseline
recompute.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(reward-correctness-002): schema/reward-mismatch fixes (Epics 1-3) + live validation
Three reward/schema mismatches that scored CORRECT responses wrong:
1. hidden.summarize ULTS schema object->string. Production emits bare prose
(cluster_summarizer.go), so the object schema mis-flagged 72 valid summaries
as invalid-JSON. (Reward already fixed in RC-001; this corrects the spec.)
2. explanation_quality made schema-aware: jiminy.evaluate / evaluate_llm nest
reasoning in violations[].reasoning, not a top-level field, so the flat
lookup scored every correct response 0.0. Now credits nested reasoning and
treats a valid no-violation verdict as a correct "no issues" answer (nothing
to explain). Falls back to the flat path.
3. specificity_score / actionability_score substantive-floored (0.7 floor,
keyword presence a bounded bonus, hedging/empty/repetition low) — the
keyword-bag dropped valid concise guidance below the gate for lacking ~6
magic words. follow_rate inherits it.
Live Tier 3 (real production rows, old->new kept@0.8): jiminy.evaluate 0/60->60/60
(mean 0.667->0.967), jiminy.synthesize 3/60->59/60 (0.725->0.879), ape.reflect
47/60->60/60 (0.848->0.956); evaluate_llm unchanged 60/60. New means 0.88-0.97 =
correct production output scoring correctly, no over-inflation. 87 unit tests +
609 neural tests + ruff green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-002): CHANGELOG + post (Epic 5 close)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs+fix(baseline-recompute-001): sprint plan (Epic 0) + rl_phase11 port 8101->8102 (Epic 1)
The capstone of the training-integrity arc: recompute the frozen 0.8338 baseline
through the fixed harness (valid_clean + RC-001/002 rewards + GGUF :8102). Epic 1
fixes the stale rl_phase11.yaml mlx_port (8101 mlx_lm.server decommissioned →
8102 llama-server GGUF), flagged by EVAL-INTEGRITY-001.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(baseline-recompute-001): honest baseline 0.8338->0.8655 via fixed harness (Epic 2-4)
Recomputed the adapter-promotion baseline through the fixed harness (valid_clean
leak-free eval + RC-001/002 corrected rewards + GGUF llama-server :8102) = 0.8655,
replacing the stale frozen 0.8338 (valid_golden-leaked, old length-biased rewards,
decommissioned MLX serving — not comparable). evaluate_gate_5a now derives the
target from the loaded baseline REPORT (single source of truth); the constant is
retained only as a >5pp drift tripwire. status ok, 12 tasks, 50 samples/task,
0 zero-call. ape.reflect 0.696 is an eval-harness artifact (stored ~7.5k-token
prompts bypass the runtime prompt budget and get cut off mid-JSON), not a model
regression.
Closes the training-integrity arc: trustworthy gate (EVAL-INTEGRITY-001),
correct rewards (REWARD-CORRECTNESS-001/002), sound corpus (APE-PROMPT-BUDGET-001
+ DATAPRUNE), honest baseline (this sprint).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-signal-001): sprint plan — honest Jiminy health signals (Epic 0)
P0, first of two sprints from the guidance-investigation. Fix the two lying
signals: (1) the false-positive "guidance not reaching agent" CRITICAL (fires on
a healthy Jiminy via an unassessed zero-value JiminyHealthy bool) — real probe +
SynergyAssessed guard + rename; (2) the inflated mdemg_jiminy_follow_rate gauge
(0.725 vs honest ~0.18 panels — Neo4j dedup-by-guidance_id double-credits
multi-outcome ids) — de-inflate so gauge/panels/RSIC agree. Behavioral
guidance-relevance/trust-decay fix deferred to JIMINY-EFFECTIVENESS-001 (needs
these honest signals to validate against).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-signal-001): honest Jiminy health signals (Epic 1-2)
Fix the two lying Jiminy health signals (root-caused by the guidance investigation):
Fix 1 — false-positive "guidance not reaching agent" CRITICAL. It is an RSIC
reflection insight (self_reflect.go) gated on !report.JiminyHealthy, but
JiminyHealthy is set only inside a synergy block (self_assess.go) that is
conditionally skipped — when skipped it stays at the Go zero-value false, so a
healthy, delivering Jiminy fired this CRITICAL ~8x/day. Added report.SynergyAssessed
(set true only when the block runs); guard the insight on it; renamed the alert
off the misleading "guidance not reaching agent" -> "Jiminy Service Unavailable —
catastrophic-forgetting risk". 3 unit tests pin the guard.
Fix 2 — inflated mdemg_jiminy_follow_rate gauge (0.725 vs the dashboard panels'
~0.27). The Neo4j Cypher count(DISTINCT CASE WHEN followed THEN guidance_id) /
count(DISTINCT guidance_id) double-credits multi-outcome guidance_ids. New
DatasetProvider.GuidanceEffectiveness reads constraint_outcomes TSDB with the
panels' exact math (followed=1, partial=0.5) over a config window
(RSIC_GUIDANCE_EFFECTIVENESS_WINDOW_HOURS, default 168=7d); the assessor overrides
js.FollowRate with it, so the gauge, RSIC GuidanceHealth, and the panels all
agree. Neo4j path retained as fallback with a disclosing comment. Making the gauge
honest correctly LOWERS RSIC's guidance health (it was masking the real problem).
Build + lint + config-scanner (688/688) clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jiminy-signal-001): two live-smoke bugs — synergy default + the gauge's 2nd publisher (Epic 3)
Live Tier 3 caught two bugs the unit tests couldn't:
1. The false CRITICAL fired AGAIN after the first restart. Root cause one layer
below Fix 1: ReadSynergyMetrics (synergy_reader.go) set JiminyHealthy only
`if jiminyCheck != nil`, so a nil check left it at the zero-value false
(= "Jiminy down") and the now-guarded insight still fired. Default
JiminyHealthy=true; only a real check returning false marks it down (real
outages are covered by /healthz + the watchdog). +slog.Warn on nil check.
Verified live: 0 jiminy CRITICAL fires since the fixed restart.
2. The gauge stayed inflated (0.732) despite the assessor override running
correctly (diagnostic showed tsdb_rate=0.09, n=22). Root cause: the gauge has
TWO publishers — self_assess.go AND live_collectors.go (the 15s Prometheus
collector) — and the live collector republished the un-overridden Neo4j rate,
overwriting it. Extracted the override into Assessor.applyHonestFollowRate and
called it from BOTH paths. Verified live: gauge now 0.235, matching the TSDB
7d panel value (0.2353).
Also added a diagnostic log at the insight-fire point (surfaces the runtime
JiminyHealthy/SynergyAssessed values). go build + ape tests + lint clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-signal-001): CHANGELOG + post with live Tier 3 results (Epic 4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(jiminy-signal-001): update NilJiminyCheck test to the new default-healthy semantics
TestFileSynergyReader_NilJiminyCheck asserted the OLD JiminyHealthy=false-on-nil
behavior, which the Epic-3 fix intentionally changed to default-healthy (a nil
check = "cannot determine" must not read as "down"). Updated the assertion to
pin the new correct behavior. (CI Test gate fix.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(deps): bump golang.org/x/crypto v0.49.0 -> v0.52.0 (CVE-2026-39827/8/9)
Folded into #469 per operator request. golang.org/x/crypto v0.49.0 carries HIGH
CVE-2026-39827/39828/39829 (Go SSH channel-exhaustion DoS), fixed in v0.52.0 —
flagged by the Trivy Security Scan (already failing on main). go mod tidy pulled
the standard transitive bumps (x/net v0.54.0, x/sys v0.45.0, x/text v0.37.0).
go build ./... + go vet clean; no API changes. Unrelated to the JIMINY-SIGNAL-001
logic; included here to green the scan.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(deps): bump golang.org/x/net v0.54.0 -> v0.55.0 (remaining HIGH CVEs)
The crypto bump's transitive x/net (v0.54.0) still had HIGH CVEs (CVE-2026-25680
et al., HTML-parse CPU exhaustion) fixed in v0.55.0 — Trivy showed go.mod 15->6
HIGH after the crypto bump, the remaining 6 all x/net. This completes the Go-side
remediation (Python uv.locks already scan clean). go build + vet clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-effectiveness-001): sprint plan — trust as recoverable EMA (Epic 0)
Re-scoped after live recon corrected the premise: surface…
* feat(tsdb-consume-001): guidance_conflicts counter + emergence-cycle duration tripwire (Epic 5)
- mdemg_guidance_conflicts_total{space_id} increments in
consulting.Suggest when detectConflicts finds anything — idea 09's
go/no-go criterion is now measurable from metric_samples (the TSDB
guidance_conflicts rows existed but had no counter/reader).
- mdemg_emergence_cycle_duration_seconds{space_id,cycle} records every
completed consolidation cycle's wall time (cycle = consolidation /
conversation / conversation_full; TotalDuration was computed and
discarded). New rule emergence_cycle_slow (service emergence-cycle):
window MAX > EMERGENCE_CYCLE_ALERT_THRESHOLD_SEC (60) over
EMERGENCE_CYCLE_ALERT_LOOKBACK_MIN (120) — the DBSCAN O(n²) deferral
condition (roadmap §4) is observable for the first time.
- Disclosed deviation: gauge, not the roadmap's 'histogram' — the
registry's fixed ≤10s latency buckets would clamp multi-minute cycles
exactly like the HTTP percentiles this sprint un-broke.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(tsdb-consume-001): V0020 writer-or-drop — decided: writer (Epic 6)
context_catalog_versions existed since Phase 14.2 with ZERO writes ever
(the migration header promised Builder.BuildForSpace would write it;
persistCatalog only wrote Neo4j). Each successful catalog build now
records one row: per-kind bit counts, top-ref arrays, full allocation
JSON. Sync single-row INSERT (model_install precedent — one row per
space per week), not the buffered CopyFrom the V0020 header described.
Hook is BuilderOpts.VersionRecorder (nil = no-op; the CLI fingerprint
backfill intentionally builds without one); server wires it reading
s.tsdbClient lazily at build time. Best-effort: failures log, never
fail the build — Neo4j stays the source of truth.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(tsdb-consume-001): remove the 4 reader-without-writer ft_* dashboard panels (Epic 7)
Model Version Timeline / Benchmark Scores / Training Cycle History /
Exogenous Ratio read ft_model_versions, ft_benchmarks,
ft_training_cycles — 0 rows ever, no writer exists in the codebase.
Tables stay: they are the designated sinks for the FT
recursive-retraining loop (Phases 6/7/9, trigger FT-CLASSIFY-002);
panels restorable from git history when writers ship. Status Notice
records the disposition. Live panels (llm_interactions, Phase 10
benchmark, endpoint health) untouched.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* ci: reclaim runner disk before docker-publish builds
Docker Publish on main failed intermittently with 'No space left on
device' killing the runner worker mid-neural-sidecar build (runs
27366931196, 27367756926, 27377325965 — incl. post-PR-440; passes in
between tracked buildx cache warmth). Two multi-arch images + mode=max
gha cache + the sidecar's torch wheels exceed the hosted runner's
~14 GB free disk when caches are cold. Pre-existing infra flake, not
PR-440-caused (that merge touched no docker inputs). Dropping unused
preinstalled toolchains reclaims ~25-30 GB.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(tsdb-consume-001): feature doc + CHANGELOG + CLAUDE.md + post (Epic 8)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(tsdb-consume-001): emergence-cycle gauge writers actually land in hidden/service.go
Live-smoke catch (own fix-commit per the Phase 11.6.2 precedent): Epic
5's combined edit script died on an assert BEFORE writing
internal/hidden/service.go, so commit 86f39ee shipped the gauge, rule,
and config with NO writer — exactly the readers-with-no-writers class
this sprint exists to kill. The Tier 3 wait for the first
mdemg_emergence_cycle_duration_seconds sample (none after a completed
18:06 consolidation cycle) exposed it. All 4 RunConsolidation /
RunConversationConsolidation / RunFullConversationConsolidation
TotalDuration sites now set the gauge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(tsdb-consume-001): record the Epic-5 live-smoke catch in post.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-recursive-000): recursive-retraining loop as-built audit + buildable spec
Doc-only, operator-prompted (PROMPT_ft_recursive_loop_deep_dive_v2.md,
executed with all 7 review amendments). Deliverable:
docs/development/ft-recursive-001/SPEC_recursive_retraining_loop.md —
five-stage as-built map with silent-failure (7) + dead-seam (5)
inventories on HEAD 17c283a; live-demonstrated exhibit A (3 tasks
Ready=YES → ungated insight #29 → executeAlertLog no-op, ≥9 alerts in
one session, shared-Service cooldown suppression); target state machine
(ft_training_cycles ledger single-flight, compute lease + RSIC quiesce
at 9h7m/36GB/85GB actuals, held-call canary, 5-class remediation
taxonomy, gaps-routed idempotent issue filer); 6a/6b/7/9 phased plan
honoring the FT-CLASSIFY-002 trigger; 7 operator decision forks
recommended-not-resolved. 00_README_v2.md STATUS gains a pointer only.
Governance handshake disclosed (jiminy-governance skill unregistered).
No production code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jiminy-outcome-002): Tier-2 not_applicable + verdict provenance
The post-SUPERVISOR-002 effectiveness drop was a measurement correction
biased LOW: the Tier-2 classifier enum had no not_applicable, so
guidance that couldn't apply to an action (hooks deliver up to 10 items
per action) was scored ignored. Both prompts + the Ollama grammar
schema now offer it with the ignored-vs-not_applicable distinction
spelled out; the parser and all four persistence sinks already handled
the value, so denominators self-correct with zero stats-query changes.
ULTS jiminy_evaluate_llm prompt hashes re-pinned (11/11 PASS).
Provenance: ClassificationResult.Source (tier1|llm|heuristic|explicit)
stamped at every decision path, persisted via V0026
constraint_outcomes.classifier_source (schema 25→26) — the
heuristic-fallback artifact class (94.9% dead calls half-crediting as
partial_compliance) is forever distinguishable. Pre-19:00Z history
annotated as heuristic-dominated (Grafana + feature doc).
Live Tier 3: unrelated pairs → not_applicable with coherent reasoning,
zero rows/edges; applicable-but-unaddressed → ignored rows with
classifier_source='llm' (5/5).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(mcp-revive-001): space resolution chain across all MCP memory tools (Epic 1)
Every memory-touching tool now resolves its space as: explicit space_id
param > MDEMG_SPACE_ID env > ide-agent (back-compat). Previously
defaultSpaceID='ide-agent' was hardcoded at 8 handler sites (and was the
fallback at the 3 param-bearing tools), stranding MCP-stored
observations outside the spaces hooks recall from — fragmenting the
connection layer per client. 6 tools gain the space_id parameter
(store, recall, associate, reflect, symbols, ingest_trigger); the
repo's .mcp.json sets MDEMG_SPACE_ID=mdemg-dev so this repo's agent
channel and hook channel share one memory space.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(mcp-revive-001): contract suite for the MCP tool surface (Epic 2)
UATS-style Go contracts over the previously zero-test 1,635-line file:
each handler invoked against an httptest MDEMG backend, asserting the
HTTP mapping (method/path/body), the space-resolution precedence
(explicit > env default > ide-agent), single-space association
invariant, validation-before-HTTP, and backend-500 → tool-error (never
a Go error). 7 contracts green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(mcp-revive-001): eventgraph + strict MCP tools, plugin-orphan reaper (Epics 3+4)
Three new tools: eventgraph_reinforcement_neighborhood +
eventgraph_guidance_outcome_neighborhood (seed_node_id OR query-resolved
seed, per the EVENTGRAPH-CLI-001 precedent; hops/since/limit OMITTED
when unset so server config stays the single source of truth) and
jiminy_strict (the /strict toggle). 23 tools total, all
contract-covered (12 contracts incl. the omit-when-unset pin and the
seed-by-query two-call chain).
Plugin-orphan reaper: launchctl kickstart -k kills the server without
Manager.Stop, orphaning plugin children (3 stale generations observed
live: Apr 30 / May 1 / May 7). startModuleInstance now pgrep-reaps any
prior-generation process holding the module's socket path before
spawning — surgical (full socket path match), loud per-kill warn.
Test decoy lesson: sh -c exec-optimizes argv away; tail -f <path>
carries it like a real plugin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(mcp-revive-001): feature doc + CHANGELOG + CLAUDE.md + post (Epic 5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(tsdb): time-scope V0014's backfill + integrity assertion to its historical window
Live-caught regression (RSIC schema-drift alerts, correctly firing):
V0014's post-migration check asserted ALL-TIME prompt-hash membership
against a frozen hash set, and migrations re-run on every auto-migrate
startup — so the first legitimate prompt evolution (JIMINY-OUTCOME-002's
not_applicable classifier prompt, 8 rows with the new hash) made V0014
RAISE forever, aborting every migration run at 013 and pinning
schema_version=13 while all 26-era objects continued to exist.
V0014 is a historical repair (the Phase 11.6.x task_name swap); its
UPDATEs and the Step-4 assertion now scope to time < 2026-05-02 — the
migration asserts exactly the work it did, immune to future prompt
evolution. Verified live: full chain runs clean, schema_version
restored to 26, drift alert cause removed.
Lesson for migration authors: in a run-every-startup migration model,
integrity checks over LIVE tables must be scoped to the migration's own
target window — an open-ended assertion is a time bomb armed by normal
system evolution.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): sprint plan — distribution-matched consulting.classify distillation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(ft-classify-002): class-stratified distill capture + the real 11.5d root cause (Epic 1)
The 11.5d class skew was NOT (only) sampling: summary_quality penalized
empty summaries, but a correct 'none' verdict REQUIRES an empty summary
by spec — so the reward>=0.8 distill filter silently rejected every
correct-none teacher answer (proof: run 1 with perfect 82%-none input
stratification kept 37/200 pairs with ZERO none). Fixed: spec-compliant
{type:none, summary:''} scores 1.0 (98 reward tests green). Capture
gains --stratify-classify (production-distribution bucketed sampling,
measured live over 4,028 rows). Run 2: 200/200 kept, reward mean 0.981,
train dist none 82/must 12/must_not 3/should 3/should_not 1 — within
±2pp of production on every class, 0-leak vs all 10 sources.
NOTE for the gate epic: stored classify baselines (0.668) were computed
under the biased reward — gates recompute baselines fresh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(ft-classify-002): training config — fresh task-delta LoRA on the production fused model (Epic 2)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ft-classify-002): benchmark config still pointed at decommissioned mlx port 8101
Phase 13.5 cutover updated serving to llama-server :8102 but
benchmark_phase10.yaml kept mlx_port: 8101 — any benchmark run without
an explicit --mlx-base-url override made ZERO model calls and reported
aggregate 0.0000 (caught live launching the FT-CLASSIFY-002 fresh
baseline). UBENCH config sha re-pinned; lint green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): stage-by-stage run record (6a instrumentation input)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(doc-audit-001): amended charter per 3-lens review team (operator-approved)
Adopts the operator's doc-audit orchestrator prompt with the team's
binding amendments: push-based survival loop (commit-without-push lost
state across resets), git ls-files enumerator (find swept ~700 non-repo
files incl. .claude agent memory into a FIX_IN_PLACE authorization),
dev02 branch strategy with CLAUDE.md/CHANGELOG operator carve-outs,
pinned-snapshot convergence, claim-budget batching, JSONL ledger, CI
discipline, and the age-based drift model (96% of docs/architecture
untouched since pre-April is the real hotspot; the MoE-marker class is
already closed). Phased: 001a after FT-CLASSIFY-002, 001b after
DORMANT-CENSUS-001, 001c as a standing mechanism.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): gate results — 2/3 PASS, no-promote recommended
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): CHANGELOG + post — no-promote accepted, sprint closed (Epic 6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(doc-audit-001a): execute the approved fix batch
1. compose LLM_ENDPOINT default 8101→8102 + stale Phase-11.6 comment
(triple-confirmed audit finding; un-overridden Docker deployments
pointed at the port decommissioned 2026-05-03; both copies, parity ok)
2. 00_README_v2.md version ledger unfrozen: v5.13 catch-up entry
(13.5 cutover, MODEL-DIST-001/002, FT-RECURSIVE-000,
FT-CLASSIFY-002) — append-only, R-LT-4-clean
3. README dashboard tab count 8→10
4. pre-campaign-checklist schema v8+→26 (cites config.go as authority)
5. beta-testing version-under-test marker → v0.10.1
6. live-validation F11 framing verified already-correct (no change)
+ CHANGELOG entry for 001a (rides dev01 per charter carve-out)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(surprise-topk-001): sprint plan — honest novelty + a multiplier that can fire
Live baselines escalate the roadmap's diagnosis: the surprise chain is
flat DEAD, not noisy — all 221,504 reinforcement events ever carry
surprise_factor=1.0; node surprise_score avg 0.023 (max 0.503, n=5,808)
vs hardcoded 0.4/0.7 thresholds. Scope: vector-index top-K novelty +
config-driven thresholds recalibrated to the new scale in the same
sprint (RRF-SCALE lesson) + CoactivateSession surprise-CASE audit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(surprise-topk-001): exact top-K nearest-neighbor embedding novelty (Epic 1)
Replaces the unordered LIMIT 50 sample (no ORDER BY) whose comparison
set was dominated by EMPTY-ARRAY embeddings — 4,564 of 5,810
conversation observations (78%) carry size(embedding)=0, passing the
old IS NOT NULL guard while cosine() yields NULL; this is why node
surprise_score averaged 0.023 and every reinforcement event ever
carried surprise_factor=1.0.
New: exact ORDER BY cosine scan over real-embedding (size=dims),
non-archived, space-scoped conversation observations; config
SURPRISE_EMBEDDING_NOVELTY_TOPK (50) + _SIM_FLOOR (0=off). The
db.index.vector.queryNodes route was live-REJECTED: the label-wide
index is crowded by ~100k non-conversation nodes (top-200 near a
conversation seed were ALL emergent_concept centroids — the
HIDDEN-CHURN degeneracy), pruning role-filtered hits to zero; the exact
scan over ~1.2k real rows is deterministic and ~ms (revisit at ~50k).
Live-verified: avg sim 0.680 / count 50 over the true nearest set.
Follow-up recorded: the 78% empty-embedding backlog (embeddings
backfill) is its own item.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(surprise-topk-001): config-driven surprise multiplier thresholds at both Cypher sites (Epic 2)
CoactivateSession DOES compute the surprise CASE (audit result) — the
chain was dead purely from broken scores. The hardcoded 0.7/0.4
thresholds (unreachable: live max score 0.503, avg 0.023 under the old
noise) become SURPRISE_FACTOR_HIGH_THRESHOLD (0.5) /
SURPRISE_FACTOR_MEDIUM_THRESHOLD (0.3) — defaults calibrated to the
exact-top-K novelty scale, parameterized into BOTH ApplyCoactivation
and CoactivateSession Cypher (never recalibrate against the old scale —
the RRF-SCALE lesson). Non-positive config falls back to defaults.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(surprise-topk-001): post + CHANGELOG; threshold recalibration + loud error path (Epics 3-4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-budget-001): sprint plan
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-budget-001): budget derivation, session attribution, surface/outcome split, token floors (Epics 1-3)
Budgets: JIMINY_TIMEOUT_MS default 15s→0 (=derive from the 90s
warm-compute budget — both paths run the same synthesis work; the
independent 15s starved every fresh install, the GUIDANCE-SYNTH-001
class); /reformulate's hardcoded 10s → JIMINY_REFORMULATE_TIMEOUT_MS
(0=derive); config.Validate() warns on explicit budget incoherence.
Attribution: PersistGuidanceOutcome now receives feedbackSessionID
(was literal "" — every GUIDANCE_OUTCOME edge ever has null
session_id; forward-only fix). Surface-vs-outcome split:
mdemg_jiminy_guidance_surfaced_total{space_id} (the honest denominator
— TotalGuidanceIssued only counts guidance that RECEIVED feedback) +
mdemg_jiminy_feedback_dropped_total{space_id} on tracker-expiry drops.
Floors: JIMINY_OUTCOME_LLM_MAX_TOKENS 100→3000 (truncation risk on the
classifier reasoning field → parse fail → heuristic fallback, the
JIMINY-OUTCOME-002 artifact class), SYNTHESIS 2000→3000, EVALUATE
2000→3000 (standing ≥3000 rule; completion stops at JSON end — floors
are free insurance). Stale TTL comment fixed (1800→86400 actual).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-budget-001): CHANGELOG + post (Epic 4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dormant-census-001): sprint plan — the standing dormancy guarantee
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): route↔consumer gate + 187-route inventory skeleton (Epic 1a)
scripts/verify_route_consumers.py extracts the live route table and
fails on bidirectional drift (unlisted route / stale entry) and on any
UNREVIEWED disposition — the bootstrap marker. Gate verified failing on
the 187 fresh UNREVIEWED entries; adjudication next.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): adjudicated 187-route inventory + merge-blocking CI gate (Epic 1b)
All 187 routes carry evidence-based dispositions: 109 ACTIVE,
60 OPERATOR_SURFACE, 13 INTERNAL, 4 PRUNE_CANDIDATE, 1 DEFERRED.
171/187 matched to UATS specs. Orchestrator re-verified every
PRUNE_CANDIDATE and known false positive independently.
Census reversals vs recon (the false-positive class this gate exists
to catch): /viz/topology + /api/graph/* are LIVE Grafana consumers
(topology iframe + nodegraph datasource) — removed from the prune
list; /v1/conversation/snapshot* is NOT called by pre-compact.sh
(saves via /v1/conversation/observe) — OPERATOR_SURFACE.
Hidden consumer surfaced: embedded /ui/ dashboard consumes ~35
routes that look dormant from hooks/CLI/scripts alone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): wire SignalLearner.GetStrength into Guide() ordering (Epic 2)
The Hebbian signal learner (V0024 SignalState, supervised flush,
startup hydration, live emission/response stream since HOOKWIRE-001)
had a read side with ZERO production callers. Guide() now orders
within equal priority by (1-w)·confidence + w·GetStrength(code),
w = JIMINY_SIGNAL_STRENGTH_WEIGHT (default 0.2; 0 = off, pre-census
behavior; clamped to 1). Ordering only — selection/filtering
untouched. Unknown codes blend the learner's 0.5 neutral default.
6 Tier 1 tests pin the contract: weight-0/nil-learner pure
confidence, blend formula, neutral default, clamp, priority
dominance + within-priority strength overtake.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): prune 4 verified-dead routes + handlers (Epic 3)
Pruned (each independently re-verified zero producers + named successor):
- /v1/feedback — gap-feedback intake; live channel is /v1/jiminy/feedback.
Handler + isolated gaps.ProcessFeedback/Feedback removed.
- /v1/memory/ingest-codebase[/] — deprecated since Phase 94 (Deprecation
header); successor /v1/memory/ingest/trigger. Whole handler file removed.
- POST /v1/alerts/grafana — superseded by the native alert evaluator;
only ref was a commented-out contactpoint. Compose env
MDEMG_GRAFANA_ALERT_WEBHOOK_URL removed from both compose files.
NOT pruned (census reversals): /viz/topology + /api/graph/* are live
Grafana consumers; PREDICTS/FORESHADOWS exist nowhere in code (recon
claim was wrong — no-op).
6 UATS specs removed + capability_gaps_full /v1/feedback variants
dropped; UXTS matrix 220→214; inventory entries retained as PRUNED
with removed_in. Gate: 183 live / 187 inventoried, OK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jiminy): codegen collision-path self-deadlock wedged constraint observes
Live-smoke catch (DORMANT-CENSUS-001 Tier 3, own fix commit per
precedent): ConstraintCodeGenerator.GenerateCode's collision branch
called fallbackCode while holding g.mu; fallbackCode locks g.mu again.
sync.Mutex is not reentrant — the first LLM-returned code that
collided with a registered code deadlocked the generator permanently,
and every later constraint-typed /v1/conversation/observe queued
behind it forever (UATS conversation_observe_pinned hung 45-90s+
deterministically; goroutine dump showed the holder 18 min wedged at
codegen.go:121 with N waiters at :47).
Fix: fallbackCodeLocked (caller holds g.mu) used by the collision
branch; fallbackCode wraps it. Regression test drives the real
collision path through a fake OpenAI-compat endpoint with a 10s
deadlock tripwire + post-call usability check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dormant-census-001): feature doc + CHANGELOG + sprint post (Epic 5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dormant-census-001): re-pin capability_gaps_full spec hash after variant removal
The Epic 3 prune removed the spec's two /v1/feedback variants but did
not re-pin its integrity sha256 — the merge-blocking UNTS hash-verify
step correctly caught the drift on PR #452. 214/214 valid locally.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(context-live-001): sprint plan + recon findings (Epic 0)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): version guard + honest consensus denominator (Epic 1)
(1) Cross-version fingerprint guard: deriveQueryFingerprint returns the
active catalog version; it rides RetrieveRequest/ColumnQuery into
ContextColumn and StrictContextMode, where candidates fingerprinted
against a different catalog version score 0 — bit positions reallocate
per build, so cross-version Jaccard is noise (mdemg-dev: 76,906 v1
nodes were silently compared against v3 query bits). Version 0 =
unknown (explicit-fp callers) keeps legacy behavior.
(2) Consensus semantics: columns STRUCTURALLY unable to vote (disabled
by config, or context with no query fingerprint) are no longer
appended, so they stop deflating the denominator — the always-empty
live context column had every live query's consensus hard-capped at
0.8. Errored/timed-out columns still count (documented intent kept).
The column_context.go comment that claimed exclusion now matches code.
(3) Cache integrity: QueryContextFingerprintVersion joins CacheKey
(the reflection forcing-function caught it unprompted); scorerVersion
bumps to v2 (semantics change) and gains a deterministic hash of the
per-category context-weight + sparse-override maps — operator edits to
either JSON now flip the namespace (pointer fields dereferenced; %+v
would have hashed addresses).
Pins: TestContextColumn_VersionGuard,
TestConsensus_DenominatorExcludesAbsentColumns,
TestScorerVersion_FlipsOnCategoryMapChanges.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): stage-6 fingerprint heal + Phase-B refine wired (Epic 2)
Disclosed deviation from the plan sketch: RefineWithCoactivations is
NOT the skew healer — it merges old-catalog bits and bumps the version,
which would relabel v1-semantics bits as current. The healer is
recomputation: new conversation.RecomputeStaleFingerprints (the
backfill CLI's core as a budget-bounded, resumable library call).
Stage 6 now runs on EVERY invocation (not just rebuilds): catalog
freshness/build as before, then (1) heal pass — recompute up to
CONTEXT_FINGERPRINT_HEAL_MAX_PER_CYCLE (2000) stale-version nodes under
the existing 60s budget, flushing partial batches on ctx expiry;
(2) Phase-B refine — RefineWithCoactivations (previously ZERO callers)
over up to CONTEXT_FINGERPRINT_REFINE_MAX_PER_CYCLE (200)
current-version observations with co-activations, marked via
context_fingerprint_refined_version so cycles never re-walk. Driver
injected via SetFingerprintDriver (server.go, next to SetContextCatalog);
nil driver = old rebuild-only behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): classifier→category dispatch on live traffic (Epic 3)
Live traffic never passes ?category=, so SPARSE_GATE_CATEGORY_OVERRIDES
and RETRIEVAL_CONTEXT_COLUMN_CATEGORY_WEIGHTS only ever fired on
benchmark calls. Retrieve now derives req.Category from the
QueryClassifier's types via QUERY_CLASSIFY_CATEGORY_MAP (JSON; default
data_flow→data_flow_integration, architecture→architecture_structure,
relationship→relationship). Explicit body/param category always wins;
first mapped type wins for multi-label; empty map disables. Runs
pre-CacheKey (Category already in the key — cache-safe; classifier
already ran before the key was computed). Vocabulary gap disclosed:
service_relationships/business_logic_constraints have no classifier
equivalent and stay benchmark-only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): server-side query-fingerprint derivation default-on (Epic 4)
CONTEXT_QUERY_AUTO_DEFAULT (default true) makes derivation fire for
every retrieve with query text; per-call opt-out ?context=off|false|0;
explicit ?context=auto still forces it when the config gate is off.
Sequenced after the skew heal + version guard, so the newly-active
column scores healed current-version fingerprints, never cross-version
noise. Cost: reuses the per-(space,version) catalog-vector cache —
one cosine top-K over ≤256 refs per call.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(context-live-001): UVTS A/B artifacts + feature doc + CHANGELOG + post (Epics 5-6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(readme): coverage-gap additions per 3-lane team review (operator-approved)
Executes the corrected recommendations from the README_COVERAGE_GAPS.md
review: new Self-Improving Local LLM section (~20 lines, fact-scrubbed:
fused GGUF serving, no OpenAI auto-fallback, no hosted-SFT claim, no
volatile eval figures; Step 2b kept in place and cross-linked); 3 new
Key Features bullets (never-silent operations, closed guidance loop
with fail-closed-bash/graduated-strict wording, eventgraph federation);
2 folds (RRF/sparse/fingerprints into the Hybrid search bullet;
de-numbered the stale 'all 11 frameworks' guide pointer); 1 sentence
(TSDB telemetry governance in Observability). Rejected per review:
the report's §5.2 Step-2b 'correction' (would have written the
rolled-back Stage-1 distill as canonical) and its stale 0.85xx
figures. Drive-by count fixes: UPTS 27→28 languages, Jiminy 6s→
config-derived budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(negfeed-cooler-001): sprint plan + live-verified recon (Epic 0)
NEGFEED-001 + COOLER-001 — complete the memory lifecycle. Recon
orchestrator-verified live: CoactivateSession 244k rows/14d (73% of
reinforcement_events), apply_negative_feedback 2 rows ever, 1
CONTRADICTS edge in the whole graph — producer-wiring only (downstream
EVENTGRAPH-003/004 built). Three workstreams: CoactivateSession
off-request + delta emission + bounded window; Jiminy-contradicted →
ApplyNegativeFeedback bridge + MCP memory_reject; graduation
unification (RSIC → cooler) + decay-protection retrieval semantics.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(negfeed-001): CoactivateSession off-request + delta emission (Epic 1)
The dominant Hebbian producer (live 244k rows/14d, 73% of
reinforcement_events) ran SYNCHRONOUSLY on the Observe request path and
regenerated the full unbounded C(n,2) session clique every call —
O(n^2) per observe, and evidence_count tracked session length not
co-activation count.
(1) Off the request path: the Observe call site now fires CoactivateSession
in a panic-recovered background goroutine on a detached context
(context.WithoutCancel + 30s timeout) — observe latency decoupled from
session size.
(2) Delta emission: CoactivateSession takes the new observation's
node_id and co-activates it ONLY against the most-recent prior session
members (bounded by LEARNING_SESSION_CLIQUE_WINDOW, default 50; 0 = all
priors). Per-call work O(window) not O(n^2); a pair's edge is
created/strengthened only on genuine co-activation, so evidence_count
counts co-activation events, not session length. Cumulative graph
unchanged (sum of deltas = C(n,2)). DH-004 reinforceSessionObservations
stability raise preserved (full-session by design).
Forward-only: existing inflated evidence_count values left as-is.
Pins: TestCoactivateSession_DeltaPairCount (delta arithmetic + window
cap + cumulative invariant).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(negfeed-001): anti-Hebbian producer — Jiminy contradicted bridge + MCP memory_reject (Epic 2)
The substrate had NO automated weaken producer: ApplyNegativeFeedback
(weaken + CONTRADICTS, EVENTGRAPH-003/004 federation built) had only
the HTTP handler as caller; live, 2 apply_negative_feedback rows ever
and 1 CONTRADICTS edge in the entire graph.
Bridge A (jiminy.NegativeFeedbackApplier interface + adapter over
learning.Service, wired in server.go): on OutcomeContradicted with >1
source node, weaken co-activations AMONG the guidance's source nodes
(query==rejected; ApplyNegativeFeedback's q<>r guard skips self-pairs,
so single-source guidance is a safe no-op and unrelated memory is never
touched; weight floors at 0). Gated JIMINY_CONTRADICTED_WEAKEN_ENABLED
(default true).
Bridge B (MCP memory_reject): the explicit producer — agent rejects a
memory that surfaced wrongly for a context; resolves both sides via
retrieve (single space, mirrors memory_associate) and POSTs
/v1/learning/negative-feedback. Contract test added (MCP-REVIVE-001
rule). 24 MCP tools now.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cooler-001): unify graduation onto the Context Cooler (Epic 3)
Two divergent graduation paths existed: the cooler (threshold 0.8 +
pinned-guard + graduated_at) and RSIC executeGraduateVolatile
(hardcoded 0.7, no guard, no graduated_at). RSIC now delegates to the
cooler via a GraduationProcessor interface + coolerGraduationAdapter —
one config-driven implementation. The rollback snapshot predicate is
aligned to the cooler's (pinned-guard + COOLER_GRADUATION_THRESHOLD via
SnapshotStore.SetGraduationThreshold) so executor and snapshot can't
drift (RSIC-STORM-001 discipline; captureNodeStateP adds param support).
The cooler also runs per-space from (new Step 2 —
Graduation, between decay and prune; skipped in dry-run since it
mutates) — graduation no longer requires the default-off background
loop on its hardcoded mdemg-dev space.
Tests: NilProcessor fail-safe + DelegatesToProcessor (graduate_volatile
removed from the nil-DRIVER loop — it's no longer driver-coupled).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cooler-001): graduated-incident edges resist decay (Epic 4)
Defines what 'graduated' means to retrieval (recon: retrieval read no
graduation signal — greenfield). The lowest-risk, learning-side
interpretation: CO_ACTIVATED_WITH edges incident to a graduated
(volatile=false) node decay at a reduced rate, so stable memory's
associations persist. NOT a retrieval-scorer change — no RRF
score-scale risk, no UVTS gate.
GRADUATED_DECAY_PROTECTION_FACTOR (default 0.5; 1.0=off, 0=no decay)
multiplies the effective decay rate when either endpoint is graduated.
Wired into the live decay job (maintenance Step 1 + the decay command
flag); query returns graduatedIncident per edge. Pin test:
graduated-incident edge retains more weight; factor 1.0 disables.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(negfeed-cooler-001): feature doc + CHANGELOG + post (Epic 6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(sidecar-loop-001): sprint plan + recon (Epic 0) — fix-but-defer
Operator decision: fix-but-defer (keep only valuable data; build clean
dataset to train-size, defer training — rerank is default-off). Recon
live-verified: data IS teacher-labeled (rerank_scores[]) but 100%
mislabeled (collector logs unsorted input candidates vs rerank-sorted
scores; 84% len-mismatch + the 16% positionally wrong), and the trainer
reads flat vs the collector's nested schema (0 records ever parsed).
Scope: collector alignment fix + guard, trainer schema reconcile,
archive the mislabeled pre-fix data, flag honesty, readiness count;
training/A/B deferred.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sidecar-loop-001): collector logs aligned reranked candidates + guard (Epic 1)
The rerank training collector logged req.Candidates[:topN] (unsorted
input) against rerankScores (rerank-sorted output) — candidate<->score
correspondence broken in 100% of records (84% length-mismatch, the
remaining 16% positionally wrong). Now logs result.Results +
result.RerankScores, which are built together from the sorted slice and
align 1:1. Added a Collect guard that drops misaligned (len-mismatch),
empty, or all-zero-score records so the corpus is clean by construction.
Tests: aligned record written; misaligned/empty/all-zero dropped.
This is the same data-mislabeling class behind discarded retrains.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(training-audit): root-cause the 3 discarded retrains — corpus filter + eval + DPO all mislabeled
Operator-triggered audit (3 lanes + code verification). The retrain loop
is corrupted by one class of defect (label/score decoupled from its data)
in three places: (1) the mean>=0.8 distill filter over length/keyword-
biased rewards skews the corpus toward verbose output (summary_quality
was one instance of a systemic pattern; ape.reflect/summarize/synthesize
unfixed); (2) the promotion gate evals on the 99%-leaked valid_golden vs
a frozen stale-reward MLX-form baseline 0.8338 with no zero-call hard-fail
— a worse-than-baseline verdict is uninterpretable; (3) dpo_builder
assigns chosen/rejected by list position, ignoring the outcome it
computes. All file:line-verified. Recommendation: no retrain until fixed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(sidecar-loop-001): feature doc + CHANGELOG; archive mislabeled pre-fix data
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(eval-integrity-001): sprint plan + recon (Epic 0)
Lead sprint of the training-integrity remediation (operator-sequenced
eval-integrity-first). Makes the adapter promotion gate trustworthy:
clean leak-free 17-task holdout (rebuild — valid_clean is stale/wrong
shape), leak-audit preflight, GGUF serving (not the OOM-prone MLX
side-server), zero-call hard-fail, and baseline-as-recomputed-report
(not a frozen 0.8338 constant). Discloses the sequencing constraint:
the honest baseline number is re-run at the close of the reward sprint
(it must use the fixed reward), so this sprint stamps a provisional one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(eval-integrity-001): respect dynamic_prompt — recover enum-templated tasks' production rows (Epic 1)
The clean-eval builder + benchmark matcher strict-hash-matched ANY spec
with a pinned system_prompt_hash, ignoring dynamic_prompt. But
ape.reflect's prompt is built at init() by interpolating
AllowedLLMActions into the text — its hash drifts every time the action
enum changes (RSIC has added actions repeatedly). Result: ape.reflect's
71,033 production rows (under hash ef068ab) were invisible to a filter
pinned to 39b2bc (0 matches) — the single largest training target had
its entire corpus silently excluded from eval AND curation.
Fix: Spec now carries dynamic_prompt; the matcher (run_benchmark) and
build_clean_eval match dynamic/enum-templated prompts by task_name, not
by the moving hash. ape.reflect's spec corrected to dynamic_prompt=true
(was false despite being enum-templated). Proven end-to-end: clean-eval
coverage 10/17 → 12/17 (ape.reflect + hidden.reclassify recovered, 240
rows). 90 neural tests pass.
Residual: 5 tasks (consulting.synthesis, guardrail.evaluate,
metalearn.generalize, retrieval.rerank_nli, summarize.generate) have
~0 production rows — genuinely uncaptured, a separate capture-gap issue.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(eval-integrity-001): wire gate to leak-free 12-task valid_clean (Epic: gate-set)
The promotion gate evaled on the 99%-leaked valid_golden. Re-pointed
benchmark_phase10.yaml + the UBENCH spec to the rebuilt leak-free
valid_clean (240 rows / 12 tasks with real current-prompt production
data; 5 capture-gap tasks excluded as uncoverable — operator-approved
honest-12 over fake-17). Re-pinned config.sha256 + golden_holdout
sha256/expected_rows(240)/expected_tasks(12). ubench lint 100%.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(eval-integrity-001): hard-fail on zero successful calls (Epic 3)
A benchmark run that made zero successful calls — or where no task
contributed a score (weight_used==0) — reported aggregate 0.0,
indistinguishable from a genuine low score. This is the false-0.0 class
behind FT-CLASSIFY-002's four silent failures (port rot, model 404s,
llama-server answering /v1/models before load). run() now computes
specs_with_zero_successful_calls + a status field; a degenerate run is
status=error and main() exits nonzero (the gate/CI/operator rejects it
instead of mistaking a broken run for a model judgment). Pin tests on
_count_zero_call_specs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(eval-integrity-001): wire LLM recorder in ingest process — capture summarize.generate (Epic: capture-gap)
The capture-gap investigation found summarize.generate (default-ON,
LLM_SUMMARY_ENABLED=true) runs in the `mdemg ingest` subprocess where
SetDefaultRecorder was NEVER called (only `mdemg serve` wired it) — so
every summary LLM call was made and silently dropped, losing the task's
entire production corpus (the EMBED-WIRE recorder-gap class, via a
process boundary). Now wires a short-lived buffered LLM recorder in
runIngest before summarize.New (which captures defaultRecorder at
construction), guarded by LLM_INTERACTION_LOGGING, with flush+close on
exit. summarize.generate production rows will now accumulate → coverable
by the clean eval over time (13th task).
The other 4 zero-row tasks (consulting.synthesis, guardrail.evaluate,
metalearn.generalize, retrieval.rerank_nli) are correctly GATED-OFF
(SYNTHESIS/GUARDRAIL/METALEARN/RERANK_ENABLED all default-false) —
expected zeros, no fix needed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(eval-integrity-001): leak-audit gate target (Epic 2)
make test-eval-leak runs audit_eval_leakage.py over valid_clean vs the
SFT training sources, exits nonzero on ANY overlap — the gate must
re-verify the eval is disjoint from training (the valid_golden 99%-leak
class; build-time filtering isn't a substitute as the corpus grows).
The script existed but had zero callers. Verified: valid_clean CLEAN,
0/240 overlap.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(eval-integrity-001): feature doc + CHANGELOG + post (Epic 7)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(eval-integrity-001): restore ape_reflect system_prompt_source to clean file:line
The dynamic_prompt=true change is correct, but I'd also appended an
annotation to system_prompt_source — the ULTS --verify-hashes runner
parses that field AS the file:line to hash, so the annotation broke it
('source file not found'), failing the merge-blocking ULTS gate.
Restored the clean 'internal/ape/llm_reflector.go:74 (rendered with
AllowedLLMActions enum)'; dynamic_prompt=true retained. ape.reflect now
11/11 PASS locally. (UBTS --output + UOBS metric-not-found in the same
CI run are pre-existing continue-on-error soft-fails, not from this PR.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-001): sprint plan (Epic 0)
2nd training-integrity remediation sprint. The distill gate
(x9_distill_capture_v2.py:360: kept = mean(reward_vector) >= 0.8,
global) drops spec-correct-but-terse answers because coverage_score/
explanation_quality/coherence_score reward length over correctness —
gutting ape.reflect (largest target) + summarize + synthesize, then
balanced_sampler amplifies the verbose-skew. Principle: inclusion
selects for CORRECTNESS not length. Fix: length-neutral correctness
rewards + per-task inclusion thresholds + a forcing-function test (each
of the 12 covered tasks' known-correct golden rows clear their gate) +
distribution check. Closes with the eval-integrity-deferred GGUF
serving + honest baseline recompute.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(reward-correctness-001): length-neutral correctness rewards (Epic 1)
The distill inclusion gate (mean(reward_vector) >= 0.8) selected for LENGTH,
not correctness — the corpus-skew mechanism behind 3 discarded retrains. Four
reward functions used length/count ladders that dropped spec-correct-but-terse
answers below the gate and rewarded verbosity upward:
- coverage_score: <20 words→0.4 / <50→0.7 / then rising → now substantive
content scores 0.9 flat (length-neutral); empty→0.0, pure-repetition→0.3.
- explanation_quality: <20 words→0.6 cliff → now substantive→0.9.
- coherence_score: required >=2 sentences + 10 words → now any coherent
non-repetitive response→0.9; pure repetition→0.4.
- insight_count: rewarded bullet COUNT (>=5→1.0) → now >=1 genuine insight→0.9
(no upward count reward; stops bullet-spam, stops dropping single-insight
reflections to 0.5 — ape.reflect, the largest target).
Verified: terse-correct now clears the 0.8 gate; verbosity/bullet-count no
longer rewarded above concise; varied detailed content unaffected (0.9);
empty/repetition still rejected. Tests rewritten to pin the new semantics
(78 pass). Subtler keyword-bag functions (specificity/actionability) left for
the continuation — they reward content signals, not raw length.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(reward-correctness-001): per-task inclusion thresholds + live findings (Epic 2-3)
Epic 2: --reward-threshold-map JSON ({"task": float}) overrides the global
--reward-threshold per task in x9_distill_capture_v2.py, so tasks whose reward
arrays have a different natural ceiling can gate at the right bar. Records the
per-task gate in each row + the manifest. Live-verified end-to-end: real
OpenAI + TSDB run with {"consulting.classify": 0.6} applied the override
(3/3 captured, manifest per_task reward_threshold=0.6).
Epic 3 (live Tier 3, docs/development/reward-correctness-001/live_findings.md):
scored REAL production llm_interactions at the 0.8 gate, old vs new rewards.
Validated Epic 1: hidden.summarize recovered 69/72 real concise summaries the
old length ladder dropped. Surfaced three larger correctness issues the
length fix does NOT close (the real dominant suppressors for the big tasks):
1. ape.reflect (54k, largest target): json_valid mean 0.133 — ~87% of recent
responses TRUNCATED mid-JSON (prompt ~5800 + ~3000 output > 8192 per-slot
KV bound). Production serving/capture defect; gate correctly rejects.
Recommended own-sprint follow-up (raise output budget, re-capture).
2. jiminy.evaluate: explanation_quality=0.0 on correct {violations,warnings}
responses — wrong reward for the schema (no top-level explanation key).
Reward-array fix, operator-gated (changes a ULTS array + re-grades).
3. jiminy.synthesize: keyword-bag follow_rate/specificity just below gate —
the deferred Epic 1 continuation.
Also fixed 2 pre-existing lint nits in the touched file (F541, E741).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-001): CHANGELOG + sprint post (Epic 6 close)
Closes REWARD-CORRECTNESS-001 at Epic 1+2+3. Epic 4 (baseline recompute)
explicitly deferred behind the ape.reflect truncation fix per operator
sequencing — recomputing over a known-truncated corpus would bake in the
corruption. Next sprint: ape.reflect truncation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ape-prompt-budget-001): sprint plan + recon (Epic 0)
Root-caused the ape.reflect ~87% truncation (largest training target) to a
structurally-unbounded prompt: live-measured 7489 tokens (Current Assessment
~3895 + 5-cycle history ~2693), leaving only ~700 of the 8192 per-slot KV
budget for output — 191/200 invalid responses cluster at 490-520 tokens_out,
truncating mid-JSON at the ceiling. Compression already on; not a max_tokens
cap. Plan: bound the prompt to a configurable token budget (gate verbose TSDB
dataset fields, cap history cycles, final drop-oldest guard) so output always
has ~4000-token headroom, with an optional serving-slot increase as the safety
margin. Lever A (structural prompt budget) + Lever B (KV slot) proposed, picked
at execution. Tier 3 proof: fresh ape.reflect json_valid recovers 0.13 → ~1.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(ape-prompt-budget-001): bound ape.reflect prompt to protect output (Epic 1-2)
Implements the structural fix for the ape.reflect ~87% mid-JSON cutoff. The
prompt was unbounded (live 7489 tok), leaving only ~700 of the 8192 per-slot
KV budget for output, so the largest training target's responses were cut off
mid-array.
buildUserPrompt now enforces a token budget:
- gate the verbose TSDB dataset fields (LLMPerformance x17 / Retrieval /
Embedding / TrainingReadiness, ~3895 of 7489 prompt tokens) behind
RSIC_LLM_REFLECT_INCLUDE_DATASETS (default false); scalar health metrics
the detectors use are always kept;
- cap history cycles via RSIC_LLM_REFLECT_HISTORY_CYCLES (default 3, was
hardcoded 5);
- final budget guard (RSIC_LLM_REFLECT_PROMPT_BUDGET_TOKENS default 3500, 0
disables): drops history oldest-first, then trims the assessment tail,
logging loudly what was dropped (never silent). estimateTokens calibrated
to the measured 2.3 chars/tok ratio, slightly conservative.
3 config fields (range-validated, no hardcoding) wired config ->
LLMReflectorConfig -> server.go. 6 Tier-1 tests: dataset gating, history cap,
drop-history-under-budget, trim-assessment-under-budget, under-budget-unchanged,
estimator. Full ape suite + lint + config scanner (687/687) clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ape-prompt-budget-001): feature doc + CHANGELOG + post + CLAUDE.md (Epic 5)
Live Tier 3 result documented: 3/3 fresh post-restart ape.reflect rows valid
JSON (100%, up from ~13%), tokens_in ~2575 (from ~7489). Corrected the stale
CLAUDE.md ape.reflect prompt-size figure (~5800 -> ~7489 live-measured) and
added the per-slot KV "prompt+output share the budget" guidance. Closes
APE-PROMPT-BUDGET-001.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ape-prompt-budget-001): re-pin ape_reflect ULTS source line after import shift
The Epic 1-2 import (log/slog) + struct fields shifted llmReflectSystemPrompt
from line 74 to 80. The ULTS hash verifier reads from line-2 and grabs the
first backtick string; at the stale :74 the search region now included the
`"` backtick in `quoted[i] = `"` + a + `"`` → wrong hash. The system prompt
TEXT is unchanged (hash 39b2bc… still matches at :80). Updated
system_prompt_source :74 → :80. Local glob verify: ape.reflect 11/11 PASS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune-audit-001): non-destructive audit of non-conforming data (Epic 0-1)
Operator-chosen audit-first prune phase. Read-only enumeration of every
non-conforming TSDB/file target with exact counts + a backup/small-batch/verify
prune plan (each category operator-gated).
Findings — PRUNE TARGETS: (A) 2,111 invalid-JSON rows in object/array tasks
(ape.reflect 1890 in the 06-11..06-13 truncation window, rerank_cross 184,
evaluate_llm 18, query_classify 18, classify 1); (B) 21,135 error/empty rows
(mdemg data clean target); (C) rerank mislabeled archive 6,894 events/21M +
valid_golden 108 leaked + ~14 stale April baselines. ~23,246 TSDB rows total
(~22.7%).
NOT prune targets (schema/reward mismatch, data is fine, fix the definition):
hidden.summarize 72 (prose vs object schema), string-schema tasks the jsonb
check false-flags (intent_translate/codegen/synthesize emit valid bare
strings), jiminy.evaluate. Audit pitfall recorded: never run a jsonb-validity
prune predicate against string-schema tasks.
Corrects the "87% of 54k" assumption: ape.reflect corruption is 1,890 rows in
the recent truncation window (forward-fixed by APE-PROMPT-BUDGET-001), not
corpus-wide.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune-audit-001): record Category A prune execution + CHANGELOG (Epic 2 close)
Pruned 1,898 genuinely-corrupt invalid-JSON rows from llm_interactions
(ape.reflect 1,879 / jiminy.evaluate_llm 18 / consulting.classify 1),
backup-first to .mdemg-backup-20260613_195431/dataprune/ (reversible).
102,415 -> 100,517, remaining_corrupt=0, live healthy, recent ape.reflect
14/14 valid.
Small-batch verify caught that the raw pg_input_is_valid predicate over-counted
by 213 (valid JSON behind markdown fences / think-tags that production
SanitizeResponse strips); validated all candidates through a faithful replica
of llmclient.SanitizeResponse and spared the recoverable 213. Categories B
(error rows) + C (file artifacts) deferred. The backup dir is untracked
(not committed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune-audit-001): Category B+C prune execution record
B: 21,254 error/silent-failure rows removed via mdemg data clean (4 spaces),
backed up first. C: rerank prefix-archive (6,894 events/21M, no refs) moved to
backup; valid_golden + ~14 baselines RETAINED (load-bearing — leak source +
regression harness; retire during baseline recompute). Final: llm_interactions
79,461 rows, 0 non-conforming. Verification catch documented: data clean
dry-run per-task table is surviving-rows, not the delete set.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(space): delete pre-check + list panic on non-MemoryNode / null-space data
Two bugs surfaced during the space-hygiene cleanup (removing 24 junk/test/demo
spaces for live testing):
1. `mdemg space delete` gated its pre-check on `count(MemoryNode {space_id})`
but the delete itself is label-agnostic (`MATCH (n {space_id})`). A space
holding only SymbolNodes/Observations (e.g. e2e-test = 10,918 SymbolNodes)
reported "no nodes. Nothing to delete." and silently survived. Pre-check now
counts all labels, matching the delete.
2. `ListSpaces` (`mdemg space list`) panicked — `interface conversion:
interface {} is nil, not string` at the `sid.(string)` assertion — when any
MemoryNode had a null space_id (orphaned/infra artifacts). The query now
excludes null/empty space_id (such nodes are not a "space"), and the
assertion is nil-guarded defensively.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune): Neo4j space hygiene record + CHANGELOG
24 junk/test/demo spaces removed (~143k nodes, backed up); blank-space resolved
(global infra kept null, 155 test MemoryNodes staged for delete); 2 space-tool
bugs fixed (delete pre-check, list panic). Record in space_cleanup.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-002): sprint plan — schema/reward-mismatch fixes (Epic 0)
The REWARD-CORRECTNESS-001 follow-ups: (1) hidden.summarize schema object->string
(production emits prose; 72 rows mis-flagged invalid-JSON); (2) explanation_quality
schema-aware for nested violations[].reasoning (fixes jiminy.evaluate +
evaluate_llm scoring correct responses 0.0); (3) keyword-bag specificity/
actionability substantive-floored (jiminy.synthesize valid guidance dropped for
lacking magic words). Makes the 4 tasks' grading correct before the baseline
recompute.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(reward-correctness-002): schema/reward-mismatch fixes (Epics 1-3) + live validation
Three reward/schema mismatches that scored CORRECT responses wrong:
1. hidden.summarize ULTS schema object->string. Production emits bare prose
(cluster_summarizer.go), so the object schema mis-flagged 72 valid summaries
as invalid-JSON. (Reward already fixed in RC-001; this corrects the spec.)
2. explanation_quality made schema-aware: jiminy.evaluate / evaluate_llm nest
reasoning in violations[].reasoning, not a top-level field, so the flat
lookup scored every correct response 0.0. Now credits nested reasoning and
treats a valid no-violation verdict as a correct "no issues" answer (nothing
to explain). Falls back to the flat path.
3. specificity_score / actionability_score substantive-floored (0.7 floor,
keyword presence a bounded bonus, hedging/empty/repetition low) — the
keyword-bag dropped valid concise guidance below the gate for lacking ~6
magic words. follow_rate inherits it.
Live Tier 3 (real production rows, old->new kept@0.8): jiminy.evaluate 0/60->60/60
(mean 0.667->0.967), jiminy.synthesize 3/60->59/60 (0.725->0.879), ape.reflect
47/60->60/60 (0.848->0.956); evaluate_llm unchanged 60/60. New means 0.88-0.97 =
correct production output scoring correctly, no over-inflation. 87 unit tests +
609 neural tests + ruff green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-002): CHANGELOG + post (Epic 5 close)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs+fix(baseline-recompute-001): sprint plan (Epic 0) + rl_phase11 port 8101->8102 (Epic 1)
The capstone of the training-integrity arc: recompute the frozen 0.8338 baseline
through the fixed harness (valid_clean + RC-001/002 rewards + GGUF :8102). Epic 1
fixes the stale rl_phase11.yaml mlx_port (8101 mlx_lm.server decommissioned →
8102 llama-server GGUF), flagged by EVAL-INTEGRITY-001.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(baseline-recompute-001): honest baseline 0.8338->0.8655 via fixed harness (Epic 2-4)
Recomputed the adapter-promotion baseline through the fixed harness (valid_clean
leak-free eval + RC-001/002 corrected rewards + GGUF llama-server :8102) = 0.8655,
replacing the stale frozen 0.8338 (valid_golden-leaked, old length-biased rewards,
decommissioned MLX serving — not comparable). evaluate_gate_5a now derives the
target from the loaded baseline REPORT (single source of truth); the constant is
retained only as a >5pp drift tripwire. status ok, 12 tasks, 50 samples/task,
0 zero-call. ape.reflect 0.696 is an eval-harness artifact (stored ~7.5k-token
prompts bypass the runtime prompt budget and get cut off mid-JSON), not a model
regression.
Closes the training-integrity arc: trustworthy gate (EVAL-INTEGRITY-001),
correct rewards (REWARD-CORRECTNESS-001/002), sound corpus (APE-PROMPT-BUDGET-001
+ DATAPRUNE), honest baseline (this sprint).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-signal-001): sprint plan — honest Jiminy health signals (Epic 0)
P0, first of two sprints from the guidance-investigation. Fix the two lying
signals: (1) the false-positive "guidance not reaching agent" CRITICAL (fires on
a healthy Jiminy via an unassessed zero-value JiminyHealthy bool) — real probe +
SynergyAssessed guard + rename; (2) the inflated mdemg_jiminy_follow_rate gauge
(0.725 vs honest ~0.18 panels — Neo4j dedup-by-guidance_id double-credits
multi-outcome ids) — de-inflate so gauge/panels/RSIC agree. Behavioral
guidance-relevance/trust-decay fix deferred to JIMINY-EFFECTIVENESS-001 (needs
these honest signals to validate against).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-signal-001): honest Jiminy health signals (Epic 1-2)
Fix the two lying Jiminy health signals (root-caused by the guidance investigation):
Fix 1 — false-positive "guidance not reaching agent" CRITICAL. It is an RSIC
reflection insight (self_reflect.go) gated on !report.JiminyHealthy, but
JiminyHealthy is set only inside a synergy block (self_assess.go) that is
conditionally skipped — when skipped it stays at the Go zero-value false, so a
healthy, delivering Jiminy fired this CRITICAL ~8x/day. Added report.SynergyAssessed
(set true only when the block runs); guard the insight on it; renamed the alert
off the misleading "guidance not reaching agent" -> "Jiminy Service Unavailable —
catastrophic-forgetting risk". 3 unit tests pin the guard.
Fix 2 — inflated mdemg_jiminy_follow_rate gauge (0.725 vs the dashboard panels'
~0.27). The Neo4j Cypher count(DISTINCT CASE WHEN followed THEN guidance_id) /
count(DISTINCT guidance_id) double-credits multi-outcome guidance_ids. New
DatasetProvider.GuidanceEffectiveness reads constraint_outcomes TSDB with the
panels' exact math (followed=1, partial=0.5) over a config window
(RSIC_GUIDANCE_EFFECTIVENESS_WINDOW_HOURS, default 168=7d); the assessor overrides
js.FollowRate with it, so the gauge, RSIC GuidanceHealth, and the panels all
agree. Neo4j path retained as fallback with a disclosing comment. Making the gauge
honest correctly LOWERS RSIC's guidance health (it was masking the real problem).
Build + lint + config-scanner (688/688) clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jiminy-signal-001): two live-smoke bugs — synergy default + the gauge's 2nd publisher (Epic 3)
Live Tier 3 caught two bugs the unit tests couldn't:
1. The false CRITICAL fired AGAIN after the first restart. Root cause one layer
below Fix 1: ReadSynergyMetrics (synergy_reader.go) set JiminyHealthy only
`if jiminyCheck != nil`, so a nil check left it at the zero-value false
(= "Jiminy down") and the now-guarded insight still fired. Default
JiminyHealthy=true; only a real check returning false marks it down (real
outages are covered by /healthz + the watchdog). +slog.Warn on nil check.
Verified live: 0 jiminy CRITICAL fires since the fixed restart.
2. The gauge stayed inflated (0.732) despite the assessor override running
correctly (diagnostic showed tsdb_rate=0.09, n=22). Root cause: the gauge has
TWO publishers — self_assess.go AND live_collectors.go (the 15s Prometheus
collector) — and the live collector republished the un-overridden Neo4j rate,
overwriting it. Extracted the override into Assessor.applyHonestFollowRate and
called it from BOTH paths. Verified live: gauge now 0.235, matching the TSDB
7d panel value (0.2353).
Also added a diagnostic log at the insight-fire point (surfaces the runtime
JiminyHealthy/SynergyAssessed values). go build + ape tests + lint clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-signal-001): CHANGELOG + post with live Tier 3 results (Epic 4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(jiminy-signal-001): update NilJiminyCheck test to the new default-healthy semantics
TestFileSynergyReader_NilJiminyCheck asserted the OLD JiminyHealthy=false-on-nil
behavior, which the Epic-3 fix intentionally changed to default-healthy (a nil
check = "cannot determine" must not read as "down"). Updated the assertion to
pin the new correct behavior. (CI Test gate fix.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(deps): bump golang.org/x/crypto v0.49.0 -> v0.52.0 (CVE-2026-39827/8/9)
Folded into #469 per operator request. golang.org/x/crypto v0.49.0 carries HIGH
CVE-2026-39827/39828/39829 (Go SSH channel-exhaustion DoS), fixed in v0.52.0 —
flagged by the Trivy Security Scan (already failing on main). go mod tidy pulled
the standard transitive bumps (x/net v0.54.0, x/sys v0.45.0, x/text v0.37.0).
go build ./... + go vet clean; no API changes. Unrelated to the JIMINY-SIGNAL-001
logic; included here to green the scan.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(deps): bump golang.org/x/net v0.54.0 -> v0.55.0 (remaining HIGH CVEs)
The crypto bump's transitive x/net (v0.54.0) still had HIGH CVEs (CVE-2026-25680
et al., HTML-parse CPU exhaustion) fixed in v0.55.0 — Trivy showed go.mod 15->6
HIGH after the crypto bump, the remaining 6 all x/net. This completes the Go-side
remediation (Python uv.locks already scan clean). go build + vet clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-effectiveness-001): sprint plan — trust as recoverable EMA (Epic 0)
Re-scoped after live recon corrected the premise: surfaced guidance is NOT
confidence:0 junk (it's filtered >0.3, confidence 0.7-0.9, LLM-classified ~79%
ignored). The real J17-T1 blocker is the monotonic trust ratchet — 1343 ignores
floored trust at 0.04 with no recovery path. Fix: replace RecordOutcome's
ratchet with an EMA of per-outcome effectiveness (Followed=1.0/Partial=0.6/
Ignored=0.2/Contradicted=0.0, alpha config) so trust tracks recent effectiveness
and recovers when guidance is followed. Config JIMINY_TRUST_MODE (ema default,
ratchet rollback) + JIMINY_TRUST_EMA_ALPHA. Forward-only, self-healing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-effectiveness-001): trust as a recoverable EMA, not a monotonic ratchet (Epic 1)
The J17-T1 blocker: TrustScorer.RecordOutcome was a monotonic ratchet (Followed
+0.05 / Ignored -0.02), so 1343 mostly-ignored outcomes floored the live session
at 0.04 with no recovery path — trust could never climb back to the 0.75 T1
threshold even if recent guidance became effective.
Replaced the ratchet with an EMA toward per-outcome effectiveness anchors
(Followed=1.0, Partial=0.6, Ignored=0.2, Contradicted=0.0):
trust ← trust + α·(target − trust). Now trust TRACKS RECENT EFFECTIVENESS and
RECOVERS: a floored session climbs back past 0.75 once guidance is followed,
while a genuinely all-ignored session converges to ~0.2 (honestly low, still <
0.75 — correctly kept out of T1). Config JIMINY_TRUST_MODE (ema default, ratchet
rollback) + JIMINY_TRUST_EMA_ALPHA (0.1). Forward-only — existing Neo4j scores
self-heal toward their recent regime.
7 new EMA unit tests (recovers-off-floor, steady-ignored-converges-low-not-zero,
tracks-recent-regime, ema-default); legacy ratchet tests pinned to Mode:ratchet
(both modes covered). Build + config-scanner (690/690) + lint clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-effectiveness-001): EMA trust recovery — post + CHANGELOG + J17 feature doc (Epic 3)
Epic 3 of JIMINY-EFFECTIVENESS-001. The Epic 1 code (EMA trust mode in
internal/jiminy/trust.go + JIMINY_TRUST_MODE/_EMA_ALPHA config) shipped in
the prior commit; this commit documents it and records the live Tier 3 result.
- post.md: premise correction (live recon overturned the confidence:0-junk
assumption — surfaced guidance is confidence 0.7-0.9, LLM-classified ~79%
ignored), the ratchet→EMA fix, and the live recovery proof.
- CHANGELOG: Unreleased entry.
- j17-ai2ai-protocol.md §3.4.1: new "Recoverable trust — EMA mode" subsection
(formula, anchors, config table, rollback note).
Live Tier 3: the actual floored mdemg-dev session (score=0.0 over 1,445
feedbacks under the old ratchet) was sent one real Followed via the live
POST /v1/jiminy/feedback wire and moved 0.0 -> 0.10 in live Neo4j — the EMA
signature 0.1*(1.0-0), proving the deployed binary recovers the previously-
pinned session off the floor. Full T1 promotion still awaits the guidance-
relevance work (Option B, disclosed follow-up).
Gates: build OK, jiminy+config tests pass, golangci-lint 0 issues,
config-scanner 690/690.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-relevance-001): diagnostic + coordinated sprint plans (Epic 0)
The Step-1 read-only diagnostic of the ignored-guidance population, plus two
coordinated sprint plans it produced:
- jiminy-relevance-001: persist the missing training evidence (guidance text +
action text + verdict — the binding gap the diagnostic found), raise label
quality, measure "should-follow" follow-rate honestly, curate the corpus.
The operator-decided 3–6 month collection infrastructure; NOT a retrain
(that's a documented future-trigger).
- hitl-review-00…
* ci: reclaim runner disk before docker-publish builds
Docker Publish on main failed intermittently with 'No space left on
device' killing the runner worker mid-neural-sidecar build (runs
27366931196, 27367756926, 27377325965 — incl. post-PR-440; passes in
between tracked buildx cache warmth). Two multi-arch images + mode=max
gha cache + the sidecar's torch wheels exceed the hosted runner's
~14 GB free disk when caches are cold. Pre-existing infra flake, not
PR-440-caused (that merge touched no docker inputs). Dropping unused
preinstalled toolchains reclaims ~25-30 GB.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(tsdb-consume-001): feature doc + CHANGELOG + CLAUDE.md + post (Epic 8)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(tsdb-consume-001): emergence-cycle gauge writers actually land in hidden/service.go
Live-smoke catch (own fix-commit per the Phase 11.6.2 precedent): Epic
5's combined edit script died on an assert BEFORE writing
internal/hidden/service.go, so commit 86f39ee shipped the gauge, rule,
and config with NO writer — exactly the readers-with-no-writers class
this sprint exists to kill. The Tier 3 wait for the first
mdemg_emergence_cycle_duration_seconds sample (none after a completed
18:06 consolidation cycle) exposed it. All 4 RunConsolidation /
RunConversationConsolidation / RunFullConversationConsolidation
TotalDuration sites now set the gauge.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(tsdb-consume-001): record the Epic-5 live-smoke catch in post.md
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-recursive-000): recursive-retraining loop as-built audit + buildable spec
Doc-only, operator-prompted (PROMPT_ft_recursive_loop_deep_dive_v2.md,
executed with all 7 review amendments). Deliverable:
docs/development/ft-recursive-001/SPEC_recursive_retraining_loop.md —
five-stage as-built map with silent-failure (7) + dead-seam (5)
inventories on HEAD 17c283a; live-demonstrated exhibit A (3 tasks
Ready=YES → ungated insight #29 → executeAlertLog no-op, ≥9 alerts in
one session, shared-Service cooldown suppression); target state machine
(ft_training_cycles ledger single-flight, compute lease + RSIC quiesce
at 9h7m/36GB/85GB actuals, held-call canary, 5-class remediation
taxonomy, gaps-routed idempotent issue filer); 6a/6b/7/9 phased plan
honoring the FT-CLASSIFY-002 trigger; 7 operator decision forks
recommended-not-resolved. 00_README_v2.md STATUS gains a pointer only.
Governance handshake disclosed (jiminy-governance skill unregistered).
No production code.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jiminy-outcome-002): Tier-2 not_applicable + verdict provenance
The post-SUPERVISOR-002 effectiveness drop was a measurement correction
biased LOW: the Tier-2 classifier enum had no not_applicable, so
guidance that couldn't apply to an action (hooks deliver up to 10 items
per action) was scored ignored. Both prompts + the Ollama grammar
schema now offer it with the ignored-vs-not_applicable distinction
spelled out; the parser and all four persistence sinks already handled
the value, so denominators self-correct with zero stats-query changes.
ULTS jiminy_evaluate_llm prompt hashes re-pinned (11/11 PASS).
Provenance: ClassificationResult.Source (tier1|llm|heuristic|explicit)
stamped at every decision path, persisted via V0026
constraint_outcomes.classifier_source (schema 25→26) — the
heuristic-fallback artifact class (94.9% dead calls half-crediting as
partial_compliance) is forever distinguishable. Pre-19:00Z history
annotated as heuristic-dominated (Grafana + feature doc).
Live Tier 3: unrelated pairs → not_applicable with coherent reasoning,
zero rows/edges; applicable-but-unaddressed → ignored rows with
classifier_source='llm' (5/5).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(mcp-revive-001): space resolution chain across all MCP memory tools (Epic 1)
Every memory-touching tool now resolves its space as: explicit space_id
param > MDEMG_SPACE_ID env > ide-agent (back-compat). Previously
defaultSpaceID='ide-agent' was hardcoded at 8 handler sites (and was the
fallback at the 3 param-bearing tools), stranding MCP-stored
observations outside the spaces hooks recall from — fragmenting the
connection layer per client. 6 tools gain the space_id parameter
(store, recall, associate, reflect, symbols, ingest_trigger); the
repo's .mcp.json sets MDEMG_SPACE_ID=mdemg-dev so this repo's agent
channel and hook channel share one memory space.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(mcp-revive-001): contract suite for the MCP tool surface (Epic 2)
UATS-style Go contracts over the previously zero-test 1,635-line file:
each handler invoked against an httptest MDEMG backend, asserting the
HTTP mapping (method/path/body), the space-resolution precedence
(explicit > env default > ide-agent), single-space association
invariant, validation-before-HTTP, and backend-500 → tool-error (never
a Go error). 7 contracts green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(mcp-revive-001): eventgraph + strict MCP tools, plugin-orphan reaper (Epics 3+4)
Three new tools: eventgraph_reinforcement_neighborhood +
eventgraph_guidance_outcome_neighborhood (seed_node_id OR query-resolved
seed, per the EVENTGRAPH-CLI-001 precedent; hops/since/limit OMITTED
when unset so server config stays the single source of truth) and
jiminy_strict (the /strict toggle). 23 tools total, all
contract-covered (12 contracts incl. the omit-when-unset pin and the
seed-by-query two-call chain).
Plugin-orphan reaper: launchctl kickstart -k kills the server without
Manager.Stop, orphaning plugin children (3 stale generations observed
live: Apr 30 / May 1 / May 7). startModuleInstance now pgrep-reaps any
prior-generation process holding the module's socket path before
spawning — surgical (full socket path match), loud per-kill warn.
Test decoy lesson: sh -c exec-optimizes argv away; tail -f <path>
carries it like a real plugin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(mcp-revive-001): feature doc + CHANGELOG + CLAUDE.md + post (Epic 5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(tsdb): time-scope V0014's backfill + integrity assertion to its historical window
Live-caught regression (RSIC schema-drift alerts, correctly firing):
V0014's post-migration check asserted ALL-TIME prompt-hash membership
against a frozen hash set, and migrations re-run on every auto-migrate
startup — so the first legitimate prompt evolution (JIMINY-OUTCOME-002's
not_applicable classifier prompt, 8 rows with the new hash) made V0014
RAISE forever, aborting every migration run at 013 and pinning
schema_version=13 while all 26-era objects continued to exist.
V0014 is a historical repair (the Phase 11.6.x task_name swap); its
UPDATEs and the Step-4 assertion now scope to time < 2026-05-02 — the
migration asserts exactly the work it did, immune to future prompt
evolution. Verified live: full chain runs clean, schema_version
restored to 26, drift alert cause removed.
Lesson for migration authors: in a run-every-startup migration model,
integrity checks over LIVE tables must be scoped to the migration's own
target window — an open-ended assertion is a time bomb armed by normal
system evolution.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): sprint plan — distribution-matched consulting.classify distillation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(ft-classify-002): class-stratified distill capture + the real 11.5d root cause (Epic 1)
The 11.5d class skew was NOT (only) sampling: summary_quality penalized
empty summaries, but a correct 'none' verdict REQUIRES an empty summary
by spec — so the reward>=0.8 distill filter silently rejected every
correct-none teacher answer (proof: run 1 with perfect 82%-none input
stratification kept 37/200 pairs with ZERO none). Fixed: spec-compliant
{type:none, summary:''} scores 1.0 (98 reward tests green). Capture
gains --stratify-classify (production-distribution bucketed sampling,
measured live over 4,028 rows). Run 2: 200/200 kept, reward mean 0.981,
train dist none 82/must 12/must_not 3/should 3/should_not 1 — within
±2pp of production on every class, 0-leak vs all 10 sources.
NOTE for the gate epic: stored classify baselines (0.668) were computed
under the biased reward — gates recompute baselines fresh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(ft-classify-002): training config — fresh task-delta LoRA on the production fused model (Epic 2)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ft-classify-002): benchmark config still pointed at decommissioned mlx port 8101
Phase 13.5 cutover updated serving to llama-server :8102 but
benchmark_phase10.yaml kept mlx_port: 8101 — any benchmark run without
an explicit --mlx-base-url override made ZERO model calls and reported
aggregate 0.0000 (caught live launching the FT-CLASSIFY-002 fresh
baseline). UBENCH config sha re-pinned; lint green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): stage-by-stage run record (6a instrumentation input)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(doc-audit-001): amended charter per 3-lens review team (operator-approved)
Adopts the operator's doc-audit orchestrator prompt with the team's
binding amendments: push-based survival loop (commit-without-push lost
state across resets), git ls-files enumerator (find swept ~700 non-repo
files incl. .claude agent memory into a FIX_IN_PLACE authorization),
dev02 branch strategy with CLAUDE.md/CHANGELOG operator carve-outs,
pinned-snapshot convergence, claim-budget batching, JSONL ledger, CI
discipline, and the age-based drift model (96% of docs/architecture
untouched since pre-April is the real hotspot; the MoE-marker class is
already closed). Phased: 001a after FT-CLASSIFY-002, 001b after
DORMANT-CENSUS-001, 001c as a standing mechanism.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): gate results — 2/3 PASS, no-promote recommended
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): CHANGELOG + post — no-promote accepted, sprint closed (Epic 6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(doc-audit-001a): execute the approved fix batch
1. compose LLM_ENDPOINT default 8101→8102 + stale Phase-11.6 comment
(triple-confirmed audit finding; un-overridden Docker deployments
pointed at the port decommissioned 2026-05-03; both copies, parity ok)
2. 00_README_v2.md version ledger unfrozen: v5.13 catch-up entry
(13.5 cutover, MODEL-DIST-001/002, FT-RECURSIVE-000,
FT-CLASSIFY-002) — append-only, R-LT-4-clean
3. README dashboard tab count 8→10
4. pre-campaign-checklist schema v8+→26 (cites config.go as authority)
5. beta-testing version-under-test marker → v0.10.1
6. live-validation F11 framing verified already-correct (no change)
+ CHANGELOG entry for 001a (rides dev01 per charter carve-out)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(surprise-topk-001): sprint plan — honest novelty + a multiplier that can fire
Live baselines escalate the roadmap's diagnosis: the surprise chain is
flat DEAD, not noisy — all 221,504 reinforcement events ever carry
surprise_factor=1.0; node surprise_score avg 0.023 (max 0.503, n=5,808)
vs hardcoded 0.4/0.7 thresholds. Scope: vector-index top-K novelty +
config-driven thresholds recalibrated to the new scale in the same
sprint (RRF-SCALE lesson) + CoactivateSession surprise-CASE audit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(surprise-topk-001): exact top-K nearest-neighbor embedding novelty (Epic 1)
Replaces the unordered LIMIT 50 sample (no ORDER BY) whose comparison
set was dominated by EMPTY-ARRAY embeddings — 4,564 of 5,810
conversation observations (78%) carry size(embedding)=0, passing the
old IS NOT NULL guard while cosine() yields NULL; this is why node
surprise_score averaged 0.023 and every reinforcement event ever
carried surprise_factor=1.0.
New: exact ORDER BY cosine scan over real-embedding (size=dims),
non-archived, space-scoped conversation observations; config
SURPRISE_EMBEDDING_NOVELTY_TOPK (50) + _SIM_FLOOR (0=off). The
db.index.vector.queryNodes route was live-REJECTED: the label-wide
index is crowded by ~100k non-conversation nodes (top-200 near a
conversation seed were ALL emergent_concept centroids — the
HIDDEN-CHURN degeneracy), pruning role-filtered hits to zero; the exact
scan over ~1.2k real rows is deterministic and ~ms (revisit at ~50k).
Live-verified: avg sim 0.680 / count 50 over the true nearest set.
Follow-up recorded: the 78% empty-embedding backlog (embeddings
backfill) is its own item.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(surprise-topk-001): config-driven surprise multiplier thresholds at both Cypher sites (Epic 2)
CoactivateSession DOES compute the surprise CASE (audit result) — the
chain was dead purely from broken scores. The hardcoded 0.7/0.4
thresholds (unreachable: live max score 0.503, avg 0.023 under the old
noise) become SURPRISE_FACTOR_HIGH_THRESHOLD (0.5) /
SURPRISE_FACTOR_MEDIUM_THRESHOLD (0.3) — defaults calibrated to the
exact-top-K novelty scale, parameterized into BOTH ApplyCoactivation
and CoactivateSession Cypher (never recalibrate against the old scale —
the RRF-SCALE lesson). Non-positive config falls back to defaults.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(surprise-topk-001): post + CHANGELOG; threshold recalibration + loud error path (Epics 3-4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-budget-001): sprint plan
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-budget-001): budget derivation, session attribution, surface/outcome split, token floors (Epics 1-3)
Budgets: JIMINY_TIMEOUT_MS default 15s→0 (=derive from the 90s
warm-compute budget — both paths run the same synthesis work; the
independent 15s starved every fresh install, the GUIDANCE-SYNTH-001
class); /reformulate's hardcoded 10s → JIMINY_REFORMULATE_TIMEOUT_MS
(0=derive); config.Validate() warns on explicit budget incoherence.
Attribution: PersistGuidanceOutcome now receives feedbackSessionID
(was literal "" — every GUIDANCE_OUTCOME edge ever has null
session_id; forward-only fix). Surface-vs-outcome split:
mdemg_jiminy_guidance_surfaced_total{space_id} (the honest denominator
— TotalGuidanceIssued only counts guidance that RECEIVED feedback) +
mdemg_jiminy_feedback_dropped_total{space_id} on tracker-expiry drops.
Floors: JIMINY_OUTCOME_LLM_MAX_TOKENS 100→3000 (truncation risk on the
classifier reasoning field → parse fail → heuristic fallback, the
JIMINY-OUTCOME-002 artifact class), SYNTHESIS 2000→3000, EVALUATE
2000→3000 (standing ≥3000 rule; completion stops at JSON end — floors
are free insurance). Stale TTL comment fixed (1800→86400 actual).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-budget-001): CHANGELOG + post (Epic 4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dormant-census-001): sprint plan — the standing dormancy guarantee
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): route↔consumer gate + 187-route inventory skeleton (Epic 1a)
scripts/verify_route_consumers.py extracts the live route table and
fails on bidirectional drift (unlisted route / stale entry) and on any
UNREVIEWED disposition — the bootstrap marker. Gate verified failing on
the 187 fresh UNREVIEWED entries; adjudication next.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): adjudicated 187-route inventory + merge-blocking CI gate (Epic 1b)
All 187 routes carry evidence-based dispositions: 109 ACTIVE,
60 OPERATOR_SURFACE, 13 INTERNAL, 4 PRUNE_CANDIDATE, 1 DEFERRED.
171/187 matched to UATS specs. Orchestrator re-verified every
PRUNE_CANDIDATE and known false positive independently.
Census reversals vs recon (the false-positive class this gate exists
to catch): /viz/topology + /api/graph/* are LIVE Grafana consumers
(topology iframe + nodegraph datasource) — removed from the prune
list; /v1/conversation/snapshot* is NOT called by pre-compact.sh
(saves via /v1/conversation/observe) — OPERATOR_SURFACE.
Hidden consumer surfaced: embedded /ui/ dashboard consumes ~35
routes that look dormant from hooks/CLI/scripts alone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): wire SignalLearner.GetStrength into Guide() ordering (Epic 2)
The Hebbian signal learner (V0024 SignalState, supervised flush,
startup hydration, live emission/response stream since HOOKWIRE-001)
had a read side with ZERO production callers. Guide() now orders
within equal priority by (1-w)·confidence + w·GetStrength(code),
w = JIMINY_SIGNAL_STRENGTH_WEIGHT (default 0.2; 0 = off, pre-census
behavior; clamped to 1). Ordering only — selection/filtering
untouched. Unknown codes blend the learner's 0.5 neutral default.
6 Tier 1 tests pin the contract: weight-0/nil-learner pure
confidence, blend formula, neutral default, clamp, priority
dominance + within-priority strength overtake.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): prune 4 verified-dead routes + handlers (Epic 3)
Pruned (each independently re-verified zero producers + named successor):
- /v1/feedback — gap-feedback intake; live channel is /v1/jiminy/feedback.
Handler + isolated gaps.ProcessFeedback/Feedback removed.
- /v1/memory/ingest-codebase[/] — deprecated since Phase 94 (Deprecation
header); successor /v1/memory/ingest/trigger. Whole handler file removed.
- POST /v1/alerts/grafana — superseded by the native alert evaluator;
only ref was a commented-out contactpoint. Compose env
MDEMG_GRAFANA_ALERT_WEBHOOK_URL removed from both compose files.
NOT pruned (census reversals): /viz/topology + /api/graph/* are live
Grafana consumers; PREDICTS/FORESHADOWS exist nowhere in code (recon
claim was wrong — no-op).
6 UATS specs removed + capability_gaps_full /v1/feedback variants
dropped; UXTS matrix 220→214; inventory entries retained as PRUNED
with removed_in. Gate: 183 live / 187 inventoried, OK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jiminy): codegen collision-path self-deadlock wedged constraint observes
Live-smoke catch (DORMANT-CENSUS-001 Tier 3, own fix commit per
precedent): ConstraintCodeGenerator.GenerateCode's collision branch
called fallbackCode while holding g.mu; fallbackCode locks g.mu again.
sync.Mutex is not reentrant — the first LLM-returned code that
collided with a registered code deadlocked the generator permanently,
and every later constraint-typed /v1/conversation/observe queued
behind it forever (UATS conversation_observe_pinned hung 45-90s+
deterministically; goroutine dump showed the holder 18 min wedged at
codegen.go:121 with N waiters at :47).
Fix: fallbackCodeLocked (caller holds g.mu) used by the collision
branch; fallbackCode wraps it. Regression test drives the real
collision path through a fake OpenAI-compat endpoint with a 10s
deadlock tripwire + post-call usability check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dormant-census-001): feature doc + CHANGELOG + sprint post (Epic 5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dormant-census-001): re-pin capability_gaps_full spec hash after variant removal
The Epic 3 prune removed the spec's two /v1/feedback variants but did
not re-pin its integrity sha256 — the merge-blocking UNTS hash-verify
step correctly caught the drift on PR #452. 214/214 valid locally.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(context-live-001): sprint plan + recon findings (Epic 0)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): version guard + honest consensus denominator (Epic 1)
(1) Cross-version fingerprint guard: deriveQueryFingerprint returns the
active catalog version; it rides RetrieveRequest/ColumnQuery into
ContextColumn and StrictContextMode, where candidates fingerprinted
against a different catalog version score 0 — bit positions reallocate
per build, so cross-version Jaccard is noise (mdemg-dev: 76,906 v1
nodes were silently compared against v3 query bits). Version 0 =
unknown (explicit-fp callers) keeps legacy behavior.
(2) Consensus semantics: columns STRUCTURALLY unable to vote (disabled
by config, or context with no query fingerprint) are no longer
appended, so they stop deflating the denominator — the always-empty
live context column had every live query's consensus hard-capped at
0.8. Errored/timed-out columns still count (documented intent kept).
The column_context.go comment that claimed exclusion now matches code.
(3) Cache integrity: QueryContextFingerprintVersion joins CacheKey
(the reflection forcing-function caught it unprompted); scorerVersion
bumps to v2 (semantics change) and gains a deterministic hash of the
per-category context-weight + sparse-override maps — operator edits to
either JSON now flip the namespace (pointer fields dereferenced; %+v
would have hashed addresses).
Pins: TestContextColumn_VersionGuard,
TestConsensus_DenominatorExcludesAbsentColumns,
TestScorerVersion_FlipsOnCategoryMapChanges.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): stage-6 fingerprint heal + Phase-B refine wired (Epic 2)
Disclosed deviation from the plan sketch: RefineWithCoactivations is
NOT the skew healer — it merges old-catalog bits and bumps the version,
which would relabel v1-semantics bits as current. The healer is
recomputation: new conversation.RecomputeStaleFingerprints (the
backfill CLI's core as a budget-bounded, resumable library call).
Stage 6 now runs on EVERY invocation (not just rebuilds): catalog
freshness/build as before, then (1) heal pass — recompute up to
CONTEXT_FINGERPRINT_HEAL_MAX_PER_CYCLE (2000) stale-version nodes under
the existing 60s budget, flushing partial batches on ctx expiry;
(2) Phase-B refine — RefineWithCoactivations (previously ZERO callers)
over up to CONTEXT_FINGERPRINT_REFINE_MAX_PER_CYCLE (200)
current-version observations with co-activations, marked via
context_fingerprint_refined_version so cycles never re-walk. Driver
injected via SetFingerprintDriver (server.go, next to SetContextCatalog);
nil driver = old rebuild-only behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): classifier→category dispatch on live traffic (Epic 3)
Live traffic never passes ?category=, so SPARSE_GATE_CATEGORY_OVERRIDES
and RETRIEVAL_CONTEXT_COLUMN_CATEGORY_WEIGHTS only ever fired on
benchmark calls. Retrieve now derives req.Category from the
QueryClassifier's types via QUERY_CLASSIFY_CATEGORY_MAP (JSON; default
data_flow→data_flow_integration, architecture→architecture_structure,
relationship→relationship). Explicit body/param category always wins;
first mapped type wins for multi-label; empty map disables. Runs
pre-CacheKey (Category already in the key — cache-safe; classifier
already ran before the key was computed). Vocabulary gap disclosed:
service_relationships/business_logic_constraints have no classifier
equivalent and stay benchmark-only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): server-side query-fingerprint derivation default-on (Epic 4)
CONTEXT_QUERY_AUTO_DEFAULT (default true) makes derivation fire for
every retrieve with query text; per-call opt-out ?context=off|false|0;
explicit ?context=auto still forces it when the config gate is off.
Sequenced after the skew heal + version guard, so the newly-active
column scores healed current-version fingerprints, never cross-version
noise. Cost: reuses the per-(space,version) catalog-vector cache —
one cosine top-K over ≤256 refs per call.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(context-live-001): UVTS A/B artifacts + feature doc + CHANGELOG + post (Epics 5-6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(readme): coverage-gap additions per 3-lane team review (operator-approved)
Executes the corrected recommendations from the README_COVERAGE_GAPS.md
review: new Self-Improving Local LLM section (~20 lines, fact-scrubbed:
fused GGUF serving, no OpenAI auto-fallback, no hosted-SFT claim, no
volatile eval figures; Step 2b kept in place and cross-linked); 3 new
Key Features bullets (never-silent operations, closed guidance loop
with fail-closed-bash/graduated-strict wording, eventgraph federation);
2 folds (RRF/sparse/fingerprints into the Hybrid search bullet;
de-numbered the stale 'all 11 frameworks' guide pointer); 1 sentence
(TSDB telemetry governance in Observability). Rejected per review:
the report's §5.2 Step-2b 'correction' (would have written the
rolled-back Stage-1 distill as canonical) and its stale 0.85xx
figures. Drive-by count fixes: UPTS 27→28 languages, Jiminy 6s→
config-derived budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(negfeed-cooler-001): sprint plan + live-verified recon (Epic 0)
NEGFEED-001 + COOLER-001 — complete the memory lifecycle. Recon
orchestrator-verified live: CoactivateSession 244k rows/14d (73% of
reinforcement_events), apply_negative_feedback 2 rows ever, 1
CONTRADICTS edge in the whole graph — producer-wiring only (downstream
EVENTGRAPH-003/004 built). Three workstreams: CoactivateSession
off-request + delta emission + bounded window; Jiminy-contradicted →
ApplyNegativeFeedback bridge + MCP memory_reject; graduation
unification (RSIC → cooler) + decay-protection retrieval semantics.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(negfeed-001): CoactivateSession off-request + delta emission (Epic 1)
The dominant Hebbian producer (live 244k rows/14d, 73% of
reinforcement_events) ran SYNCHRONOUSLY on the Observe request path and
regenerated the full unbounded C(n,2) session clique every call —
O(n^2) per observe, and evidence_count tracked session length not
co-activation count.
(1) Off the request path: the Observe call site now fires CoactivateSession
in a panic-recovered background goroutine on a detached context
(context.WithoutCancel + 30s timeout) — observe latency decoupled from
session size.
(2) Delta emission: CoactivateSession takes the new observation's
node_id and co-activates it ONLY against the most-recent prior session
members (bounded by LEARNING_SESSION_CLIQUE_WINDOW, default 50; 0 = all
priors). Per-call work O(window) not O(n^2); a pair's edge is
created/strengthened only on genuine co-activation, so evidence_count
counts co-activation events, not session length. Cumulative graph
unchanged (sum of deltas = C(n,2)). DH-004 reinforceSessionObservations
stability raise preserved (full-session by design).
Forward-only: existing inflated evidence_count values left as-is.
Pins: TestCoactivateSession_DeltaPairCount (delta arithmetic + window
cap + cumulative invariant).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(negfeed-001): anti-Hebbian producer — Jiminy contradicted bridge + MCP memory_reject (Epic 2)
The substrate had NO automated weaken producer: ApplyNegativeFeedback
(weaken + CONTRADICTS, EVENTGRAPH-003/004 federation built) had only
the HTTP handler as caller; live, 2 apply_negative_feedback rows ever
and 1 CONTRADICTS edge in the entire graph.
Bridge A (jiminy.NegativeFeedbackApplier interface + adapter over
learning.Service, wired in server.go): on OutcomeContradicted with >1
source node, weaken co-activations AMONG the guidance's source nodes
(query==rejected; ApplyNegativeFeedback's q<>r guard skips self-pairs,
so single-source guidance is a safe no-op and unrelated memory is never
touched; weight floors at 0). Gated JIMINY_CONTRADICTED_WEAKEN_ENABLED
(default true).
Bridge B (MCP memory_reject): the explicit producer — agent rejects a
memory that surfaced wrongly for a context; resolves both sides via
retrieve (single space, mirrors memory_associate) and POSTs
/v1/learning/negative-feedback. Contract test added (MCP-REVIVE-001
rule). 24 MCP tools now.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cooler-001): unify graduation onto the Context Cooler (Epic 3)
Two divergent graduation paths existed: the cooler (threshold 0.8 +
pinned-guard + graduated_at) and RSIC executeGraduateVolatile
(hardcoded 0.7, no guard, no graduated_at). RSIC now delegates to the
cooler via a GraduationProcessor interface + coolerGraduationAdapter —
one config-driven implementation. The rollback snapshot predicate is
aligned to the cooler's (pinned-guard + COOLER_GRADUATION_THRESHOLD via
SnapshotStore.SetGraduationThreshold) so executor and snapshot can't
drift (RSIC-STORM-001 discipline; captureNodeStateP adds param support).
The cooler also runs per-space from (new Step 2 —
Graduation, between decay and prune; skipped in dry-run since it
mutates) — graduation no longer requires the default-off background
loop on its hardcoded mdemg-dev space.
Tests: NilProcessor fail-safe + DelegatesToProcessor (graduate_volatile
removed from the nil-DRIVER loop — it's no longer driver-coupled).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cooler-001): graduated-incident edges resist decay (Epic 4)
Defines what 'graduated' means to retrieval (recon: retrieval read no
graduation signal — greenfield). The lowest-risk, learning-side
interpretation: CO_ACTIVATED_WITH edges incident to a graduated
(volatile=false) node decay at a reduced rate, so stable memory's
associations persist. NOT a retrieval-scorer change — no RRF
score-scale risk, no UVTS gate.
GRADUATED_DECAY_PROTECTION_FACTOR (default 0.5; 1.0=off, 0=no decay)
multiplies the effective decay rate when either endpoint is graduated.
Wired into the live decay job (maintenance Step 1 + the decay command
flag); query returns graduatedIncident per edge. Pin test:
graduated-incident edge retains more weight; factor 1.0 disables.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(negfeed-cooler-001): feature doc + CHANGELOG + post (Epic 6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(sidecar-loop-001): sprint plan + recon (Epic 0) — fix-but-defer
Operator decision: fix-but-defer (keep only valuable data; build clean
dataset to train-size, defer training — rerank is default-off). Recon
live-verified: data IS teacher-labeled (rerank_scores[]) but 100%
mislabeled (collector logs unsorted input candidates vs rerank-sorted
scores; 84% len-mismatch + the 16% positionally wrong), and the trainer
reads flat vs the collector's nested schema (0 records ever parsed).
Scope: collector alignment fix + guard, trainer schema reconcile,
archive the mislabeled pre-fix data, flag honesty, readiness count;
training/A/B deferred.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sidecar-loop-001): collector logs aligned reranked candidates + guard (Epic 1)
The rerank training collector logged req.Candidates[:topN] (unsorted
input) against rerankScores (rerank-sorted output) — candidate<->score
correspondence broken in 100% of records (84% length-mismatch, the
remaining 16% positionally wrong). Now logs result.Results +
result.RerankScores, which are built together from the sorted slice and
align 1:1. Added a Collect guard that drops misaligned (len-mismatch),
empty, or all-zero-score records so the corpus is clean by construction.
Tests: aligned record written; misaligned/empty/all-zero dropped.
This is the same data-mislabeling class behind discarded retrains.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(training-audit): root-cause the 3 discarded retrains — corpus filter + eval + DPO all mislabeled
Operator-triggered audit (3 lanes + code verification). The retrain loop
is corrupted by one class of defect (label/score decoupled from its data)
in three places: (1) the mean>=0.8 distill filter over length/keyword-
biased rewards skews the corpus toward verbose output (summary_quality
was one instance of a systemic pattern; ape.reflect/summarize/synthesize
unfixed); (2) the promotion gate evals on the 99%-leaked valid_golden vs
a frozen stale-reward MLX-form baseline 0.8338 with no zero-call hard-fail
— a worse-than-baseline verdict is uninterpretable; (3) dpo_builder
assigns chosen/rejected by list position, ignoring the outcome it
computes. All file:line-verified. Recommendation: no retrain until fixed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(sidecar-loop-001): feature doc + CHANGELOG; archive mislabeled pre-fix data
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(eval-integrity-001): sprint plan + recon (Epic 0)
Lead sprint of the training-integrity remediation (operator-sequenced
eval-integrity-first). Makes the adapter promotion gate trustworthy:
clean leak-free 17-task holdout (rebuild — valid_clean is stale/wrong
shape), leak-audit preflight, GGUF serving (not the OOM-prone MLX
side-server), zero-call hard-fail, and baseline-as-recomputed-report
(not a frozen 0.8338 constant). Discloses the sequencing constraint:
the honest baseline number is re-run at the close of the reward sprint
(it must use the fixed reward), so this sprint stamps a provisional one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(eval-integrity-001): respect dynamic_prompt — recover enum-templated tasks' production rows (Epic 1)
The clean-eval builder + benchmark matcher strict-hash-matched ANY spec
with a pinned system_prompt_hash, ignoring dynamic_prompt. But
ape.reflect's prompt is built at init() by interpolating
AllowedLLMActions into the text — its hash drifts every time the action
enum changes (RSIC has added actions repeatedly). Result: ape.reflect's
71,033 production rows (under hash ef068ab) were invisible to a filter
pinned to 39b2bc (0 matches) — the single largest training target had
its entire corpus silently excluded from eval AND curation.
Fix: Spec now carries dynamic_prompt; the matcher (run_benchmark) and
build_clean_eval match dynamic/enum-templated prompts by task_name, not
by the moving hash. ape.reflect's spec corrected to dynamic_prompt=true
(was false despite being enum-templated). Proven end-to-end: clean-eval
coverage 10/17 → 12/17 (ape.reflect + hidden.reclassify recovered, 240
rows). 90 neural tests pass.
Residual: 5 tasks (consulting.synthesis, guardrail.evaluate,
metalearn.generalize, retrieval.rerank_nli, summarize.generate) have
~0 production rows — genuinely uncaptured, a separate capture-gap issue.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(eval-integrity-001): wire gate to leak-free 12-task valid_clean (Epic: gate-set)
The promotion gate evaled on the 99%-leaked valid_golden. Re-pointed
benchmark_phase10.yaml + the UBENCH spec to the rebuilt leak-free
valid_clean (240 rows / 12 tasks with real current-prompt production
data; 5 capture-gap tasks excluded as uncoverable — operator-approved
honest-12 over fake-17). Re-pinned config.sha256 + golden_holdout
sha256/expected_rows(240)/expected_tasks(12). ubench lint 100%.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(eval-integrity-001): hard-fail on zero successful calls (Epic 3)
A benchmark run that made zero successful calls — or where no task
contributed a score (weight_used==0) — reported aggregate 0.0,
indistinguishable from a genuine low score. This is the false-0.0 class
behind FT-CLASSIFY-002's four silent failures (port rot, model 404s,
llama-server answering /v1/models before load). run() now computes
specs_with_zero_successful_calls + a status field; a degenerate run is
status=error and main() exits nonzero (the gate/CI/operator rejects it
instead of mistaking a broken run for a model judgment). Pin tests on
_count_zero_call_specs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(eval-integrity-001): wire LLM recorder in ingest process — capture summarize.generate (Epic: capture-gap)
The capture-gap investigation found summarize.generate (default-ON,
LLM_SUMMARY_ENABLED=true) runs in the `mdemg ingest` subprocess where
SetDefaultRecorder was NEVER called (only `mdemg serve` wired it) — so
every summary LLM call was made and silently dropped, losing the task's
entire production corpus (the EMBED-WIRE recorder-gap class, via a
process boundary). Now wires a short-lived buffered LLM recorder in
runIngest before summarize.New (which captures defaultRecorder at
construction), guarded by LLM_INTERACTION_LOGGING, with flush+close on
exit. summarize.generate production rows will now accumulate → coverable
by the clean eval over time (13th task).
The other 4 zero-row tasks (consulting.synthesis, guardrail.evaluate,
metalearn.generalize, retrieval.rerank_nli) are correctly GATED-OFF
(SYNTHESIS/GUARDRAIL/METALEARN/RERANK_ENABLED all default-false) —
expected zeros, no fix needed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(eval-integrity-001): leak-audit gate target (Epic 2)
make test-eval-leak runs audit_eval_leakage.py over valid_clean vs the
SFT training sources, exits nonzero on ANY overlap — the gate must
re-verify the eval is disjoint from training (the valid_golden 99%-leak
class; build-time filtering isn't a substitute as the corpus grows).
The script existed but had zero callers. Verified: valid_clean CLEAN,
0/240 overlap.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(eval-integrity-001): feature doc + CHANGELOG + post (Epic 7)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(eval-integrity-001): restore ape_reflect system_prompt_source to clean file:line
The dynamic_prompt=true change is correct, but I'd also appended an
annotation to system_prompt_source — the ULTS --verify-hashes runner
parses that field AS the file:line to hash, so the annotation broke it
('source file not found'), failing the merge-blocking ULTS gate.
Restored the clean 'internal/ape/llm_reflector.go:74 (rendered with
AllowedLLMActions enum)'; dynamic_prompt=true retained. ape.reflect now
11/11 PASS locally. (UBTS --output + UOBS metric-not-found in the same
CI run are pre-existing continue-on-error soft-fails, not from this PR.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-001): sprint plan (Epic 0)
2nd training-integrity remediation sprint. The distill gate
(x9_distill_capture_v2.py:360: kept = mean(reward_vector) >= 0.8,
global) drops spec-correct-but-terse answers because coverage_score/
explanation_quality/coherence_score reward length over correctness —
gutting ape.reflect (largest target) + summarize + synthesize, then
balanced_sampler amplifies the verbose-skew. Principle: inclusion
selects for CORRECTNESS not length. Fix: length-neutral correctness
rewards + per-task inclusion thresholds + a forcing-function test (each
of the 12 covered tasks' known-correct golden rows clear their gate) +
distribution check. Closes with the eval-integrity-deferred GGUF
serving + honest baseline recompute.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(reward-correctness-001): length-neutral correctness rewards (Epic 1)
The distill inclusion gate (mean(reward_vector) >= 0.8) selected for LENGTH,
not correctness — the corpus-skew mechanism behind 3 discarded retrains. Four
reward functions used length/count ladders that dropped spec-correct-but-terse
answers below the gate and rewarded verbosity upward:
- coverage_score: <20 words→0.4 / <50→0.7 / then rising → now substantive
content scores 0.9 flat (length-neutral); empty→0.0, pure-repetition→0.3.
- explanation_quality: <20 words→0.6 cliff → now substantive→0.9.
- coherence_score: required >=2 sentences + 10 words → now any coherent
non-repetitive response→0.9; pure repetition→0.4.
- insight_count: rewarded bullet COUNT (>=5→1.0) → now >=1 genuine insight→0.9
(no upward count reward; stops bullet-spam, stops dropping single-insight
reflections to 0.5 — ape.reflect, the largest target).
Verified: terse-correct now clears the 0.8 gate; verbosity/bullet-count no
longer rewarded above concise; varied detailed content unaffected (0.9);
empty/repetition still rejected. Tests rewritten to pin the new semantics
(78 pass). Subtler keyword-bag functions (specificity/actionability) left for
the continuation — they reward content signals, not raw length.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(reward-correctness-001): per-task inclusion thresholds + live findings (Epic 2-3)
Epic 2: --reward-threshold-map JSON ({"task": float}) overrides the global
--reward-threshold per task in x9_distill_capture_v2.py, so tasks whose reward
arrays have a different natural ceiling can gate at the right bar. Records the
per-task gate in each row + the manifest. Live-verified end-to-end: real
OpenAI + TSDB run with {"consulting.classify": 0.6} applied the override
(3/3 captured, manifest per_task reward_threshold=0.6).
Epic 3 (live Tier 3, docs/development/reward-correctness-001/live_findings.md):
scored REAL production llm_interactions at the 0.8 gate, old vs new rewards.
Validated Epic 1: hidden.summarize recovered 69/72 real concise summaries the
old length ladder dropped. Surfaced three larger correctness issues the
length fix does NOT close (the real dominant suppressors for the big tasks):
1. ape.reflect (54k, largest target): json_valid mean 0.133 — ~87% of recent
responses TRUNCATED mid-JSON (prompt ~5800 + ~3000 output > 8192 per-slot
KV bound). Production serving/capture defect; gate correctly rejects.
Recommended own-sprint follow-up (raise output budget, re-capture).
2. jiminy.evaluate: explanation_quality=0.0 on correct {violations,warnings}
responses — wrong reward for the schema (no top-level explanation key).
Reward-array fix, operator-gated (changes a ULTS array + re-grades).
3. jiminy.synthesize: keyword-bag follow_rate/specificity just below gate —
the deferred Epic 1 continuation.
Also fixed 2 pre-existing lint nits in the touched file (F541, E741).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-001): CHANGELOG + sprint post (Epic 6 close)
Closes REWARD-CORRECTNESS-001 at Epic 1+2+3. Epic 4 (baseline recompute)
explicitly deferred behind the ape.reflect truncation fix per operator
sequencing — recomputing over a known-truncated corpus would bake in the
corruption. Next sprint: ape.reflect truncation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ape-prompt-budget-001): sprint plan + recon (Epic 0)
Root-caused the ape.reflect ~87% truncation (largest training target) to a
structurally-unbounded prompt: live-measured 7489 tokens (Current Assessment
~3895 + 5-cycle history ~2693), leaving only ~700 of the 8192 per-slot KV
budget for output — 191/200 invalid responses cluster at 490-520 tokens_out,
truncating mid-JSON at the ceiling. Compression already on; not a max_tokens
cap. Plan: bound the prompt to a configurable token budget (gate verbose TSDB
dataset fields, cap history cycles, final drop-oldest guard) so output always
has ~4000-token headroom, with an optional serving-slot increase as the safety
margin. Lever A (structural prompt budget) + Lever B (KV slot) proposed, picked
at execution. Tier 3 proof: fresh ape.reflect json_valid recovers 0.13 → ~1.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(ape-prompt-budget-001): bound ape.reflect prompt to protect output (Epic 1-2)
Implements the structural fix for the ape.reflect ~87% mid-JSON cutoff. The
prompt was unbounded (live 7489 tok), leaving only ~700 of the 8192 per-slot
KV budget for output, so the largest training target's responses were cut off
mid-array.
buildUserPrompt now enforces a token budget:
- gate the verbose TSDB dataset fields (LLMPerformance x17 / Retrieval /
Embedding / TrainingReadiness, ~3895 of 7489 prompt tokens) behind
RSIC_LLM_REFLECT_INCLUDE_DATASETS (default false); scalar health metrics
the detectors use are always kept;
- cap history cycles via RSIC_LLM_REFLECT_HISTORY_CYCLES (default 3, was
hardcoded 5);
- final budget guard (RSIC_LLM_REFLECT_PROMPT_BUDGET_TOKENS default 3500, 0
disables): drops history oldest-first, then trims the assessment tail,
logging loudly what was dropped (never silent). estimateTokens calibrated
to the measured 2.3 chars/tok ratio, slightly conservative.
3 config fields (range-validated, no hardcoding) wired config ->
LLMReflectorConfig -> server.go. 6 Tier-1 tests: dataset gating, history cap,
drop-history-under-budget, trim-assessment-under-budget, under-budget-unchanged,
estimator. Full ape suite + lint + config scanner (687/687) clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ape-prompt-budget-001): feature doc + CHANGELOG + post + CLAUDE.md (Epic 5)
Live Tier 3 result documented: 3/3 fresh post-restart ape.reflect rows valid
JSON (100%, up from ~13%), tokens_in ~2575 (from ~7489). Corrected the stale
CLAUDE.md ape.reflect prompt-size figure (~5800 -> ~7489 live-measured) and
added the per-slot KV "prompt+output share the budget" guidance. Closes
APE-PROMPT-BUDGET-001.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ape-prompt-budget-001): re-pin ape_reflect ULTS source line after import shift
The Epic 1-2 import (log/slog) + struct fields shifted llmReflectSystemPrompt
from line 74 to 80. The ULTS hash verifier reads from line-2 and grabs the
first backtick string; at the stale :74 the search region now included the
`"` backtick in `quoted[i] = `"` + a + `"`` → wrong hash. The system prompt
TEXT is unchanged (hash 39b2bc… still matches at :80). Updated
system_prompt_source :74 → :80. Local glob verify: ape.reflect 11/11 PASS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune-audit-001): non-destructive audit of non-conforming data (Epic 0-1)
Operator-chosen audit-first prune phase. Read-only enumeration of every
non-conforming TSDB/file target with exact counts + a backup/small-batch/verify
prune plan (each category operator-gated).
Findings — PRUNE TARGETS: (A) 2,111 invalid-JSON rows in object/array tasks
(ape.reflect 1890 in the 06-11..06-13 truncation window, rerank_cross 184,
evaluate_llm 18, query_classify 18, classify 1); (B) 21,135 error/empty rows
(mdemg data clean target); (C) rerank mislabeled archive 6,894 events/21M +
valid_golden 108 leaked + ~14 stale April baselines. ~23,246 TSDB rows total
(~22.7%).
NOT prune targets (schema/reward mismatch, data is fine, fix the definition):
hidden.summarize 72 (prose vs object schema), string-schema tasks the jsonb
check false-flags (intent_translate/codegen/synthesize emit valid bare
strings), jiminy.evaluate. Audit pitfall recorded: never run a jsonb-validity
prune predicate against string-schema tasks.
Corrects the "87% of 54k" assumption: ape.reflect corruption is 1,890 rows in
the recent truncation window (forward-fixed by APE-PROMPT-BUDGET-001), not
corpus-wide.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune-audit-001): record Category A prune execution + CHANGELOG (Epic 2 close)
Pruned 1,898 genuinely-corrupt invalid-JSON rows from llm_interactions
(ape.reflect 1,879 / jiminy.evaluate_llm 18 / consulting.classify 1),
backup-first to .mdemg-backup-20260613_195431/dataprune/ (reversible).
102,415 -> 100,517, remaining_corrupt=0, live healthy, recent ape.reflect
14/14 valid.
Small-batch verify caught that the raw pg_input_is_valid predicate over-counted
by 213 (valid JSON behind markdown fences / think-tags that production
SanitizeResponse strips); validated all candidates through a faithful replica
of llmclient.SanitizeResponse and spared the recoverable 213. Categories B
(error rows) + C (file artifacts) deferred. The backup dir is untracked
(not committed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune-audit-001): Category B+C prune execution record
B: 21,254 error/silent-failure rows removed via mdemg data clean (4 spaces),
backed up first. C: rerank prefix-archive (6,894 events/21M, no refs) moved to
backup; valid_golden + ~14 baselines RETAINED (load-bearing — leak source +
regression harness; retire during baseline recompute). Final: llm_interactions
79,461 rows, 0 non-conforming. Verification catch documented: data clean
dry-run per-task table is surviving-rows, not the delete set.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(space): delete pre-check + list panic on non-MemoryNode / null-space data
Two bugs surfaced during the space-hygiene cleanup (removing 24 junk/test/demo
spaces for live testing):
1. `mdemg space delete` gated its pre-check on `count(MemoryNode {space_id})`
but the delete itself is label-agnostic (`MATCH (n {space_id})`). A space
holding only SymbolNodes/Observations (e.g. e2e-test = 10,918 SymbolNodes)
reported "no nodes. Nothing to delete." and silently survived. Pre-check now
counts all labels, matching the delete.
2. `ListSpaces` (`mdemg space list`) panicked — `interface conversion:
interface {} is nil, not string` at the `sid.(string)` assertion — when any
MemoryNode had a null space_id (orphaned/infra artifacts). The query now
excludes null/empty space_id (such nodes are not a "space"), and the
assertion is nil-guarded defensively.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune): Neo4j space hygiene record + CHANGELOG
24 junk/test/demo spaces removed (~143k nodes, backed up); blank-space resolved
(global infra kept null, 155 test MemoryNodes staged for delete); 2 space-tool
bugs fixed (delete pre-check, list panic). Record in space_cleanup.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-002): sprint plan — schema/reward-mismatch fixes (Epic 0)
The REWARD-CORRECTNESS-001 follow-ups: (1) hidden.summarize schema object->string
(production emits prose; 72 rows mis-flagged invalid-JSON); (2) explanation_quality
schema-aware for nested violations[].reasoning (fixes jiminy.evaluate +
evaluate_llm scoring correct responses 0.0); (3) keyword-bag specificity/
actionability substantive-floored (jiminy.synthesize valid guidance dropped for
lacking magic words). Makes the 4 tasks' grading correct before the baseline
recompute.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(reward-correctness-002): schema/reward-mismatch fixes (Epics 1-3) + live validation
Three reward/schema mismatches that scored CORRECT responses wrong:
1. hidden.summarize ULTS schema object->string. Production emits bare prose
(cluster_summarizer.go), so the object schema mis-flagged 72 valid summaries
as invalid-JSON. (Reward already fixed in RC-001; this corrects the spec.)
2. explanation_quality made schema-aware: jiminy.evaluate / evaluate_llm nest
reasoning in violations[].reasoning, not a top-level field, so the flat
lookup scored every correct response 0.0. Now credits nested reasoning and
treats a valid no-violation verdict as a correct "no issues" answer (nothing
to explain). Falls back to the flat path.
3. specificity_score / actionability_score substantive-floored (0.7 floor,
keyword presence a bounded bonus, hedging/empty/repetition low) — the
keyword-bag dropped valid concise guidance below the gate for lacking ~6
magic words. follow_rate inherits it.
Live Tier 3 (real production rows, old->new kept@0.8): jiminy.evaluate 0/60->60/60
(mean 0.667->0.967), jiminy.synthesize 3/60->59/60 (0.725->0.879), ape.reflect
47/60->60/60 (0.848->0.956); evaluate_llm unchanged 60/60. New means 0.88-0.97 =
correct production output scoring correctly, no over-inflation. 87 unit tests +
609 neural tests + ruff green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-002): CHANGELOG + post (Epic 5 close)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs+fix(baseline-recompute-001): sprint plan (Epic 0) + rl_phase11 port 8101->8102 (Epic 1)
The capstone of the training-integrity arc: recompute the frozen 0.8338 baseline
through the fixed harness (valid_clean + RC-001/002 rewards + GGUF :8102). Epic 1
fixes the stale rl_phase11.yaml mlx_port (8101 mlx_lm.server decommissioned →
8102 llama-server GGUF), flagged by EVAL-INTEGRITY-001.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(baseline-recompute-001): honest baseline 0.8338->0.8655 via fixed harness (Epic 2-4)
Recomputed the adapter-promotion baseline through the fixed harness (valid_clean
leak-free eval + RC-001/002 corrected rewards + GGUF llama-server :8102) = 0.8655,
replacing the stale frozen 0.8338 (valid_golden-leaked, old length-biased rewards,
decommissioned MLX serving — not comparable). evaluate_gate_5a now derives the
target from the loaded baseline REPORT (single source of truth); the constant is
retained only as a >5pp drift tripwire. status ok, 12 tasks, 50 samples/task,
0 zero-call. ape.reflect 0.696 is an eval-harness artifact (stored ~7.5k-token
prompts bypass the runtime prompt budget and get cut off mid-JSON), not a model
regression.
Closes the training-integrity arc: trustworthy gate (EVAL-INTEGRITY-001),
correct rewards (REWARD-CORRECTNESS-001/002), sound corpus (APE-PROMPT-BUDGET-001
+ DATAPRUNE), honest baseline (this sprint).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-signal-001): sprint plan — honest Jiminy health signals (Epic 0)
P0, first of two sprints from the guidance-investigation. Fix the two lying
signals: (1) the false-positive "guidance not reaching agent" CRITICAL (fires on
a healthy Jiminy via an unassessed zero-value JiminyHealthy bool) — real probe +
SynergyAssessed guard + rename; (2) the inflated mdemg_jiminy_follow_rate gauge
(0.725 vs honest ~0.18 panels — Neo4j dedup-by-guidance_id double-credits
multi-outcome ids) — de-inflate so gauge/panels/RSIC agree. Behavioral
guidance-relevance/trust-decay fix deferred to JIMINY-EFFECTIVENESS-001 (needs
these honest signals to validate against).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-signal-001): honest Jiminy health signals (Epic 1-2)
Fix the two lying Jiminy health signals (root-caused by the guidance investigation):
Fix 1 — false-positive "guidance not reaching agent" CRITICAL. It is an RSIC
reflection insight (self_reflect.go) gated on !report.JiminyHealthy, but
JiminyHealthy is set only inside a synergy block (self_assess.go) that is
conditionally skipped — when skipped it stays at the Go zero-value false, so a
healthy, delivering Jiminy fired this CRITICAL ~8x/day. Added report.SynergyAssessed
(set true only when the block runs); guard the insight on it; renamed the alert
off the misleading "guidance not reaching agent" -> "Jiminy Service Unavailable —
catastrophic-forgetting risk". 3 unit tests pin the guard.
Fix 2 — inflated mdemg_jiminy_follow_rate gauge (0.725 vs the dashboard panels'
~0.27). The Neo4j Cypher count(DISTINCT CASE WHEN followed THEN guidance_id) /
count(DISTINCT guidance_id) double-credits multi-outcome guidance_ids. New
DatasetProvider.GuidanceEffectiveness reads constraint_outcomes TSDB with the
panels' exact math (followed=1, partial=0.5) over a config window
(RSIC_GUIDANCE_EFFECTIVENESS_WINDOW_HOURS, default 168=7d); the assessor overrides
js.FollowRate with it, so the gauge, RSIC GuidanceHealth, and the panels all
agree. Neo4j path retained as fallback with a disclosing comment. Making the gauge
honest correctly LOWERS RSIC's guidance health (it was masking the real problem).
Build + lint + config-scanner (688/688) clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jiminy-signal-001): two live-smoke bugs — synergy default + the gauge's 2nd publisher (Epic 3)
Live Tier 3 caught two bugs the unit tests couldn't:
1. The false CRITICAL fired AGAIN after the first restart. Root cause one layer
below Fix 1: ReadSynergyMetrics (synergy_reader.go) set JiminyHealthy only
`if jiminyCheck != nil`, so a nil check left it at the zero-value false
(= "Jiminy down") and the now-guarded insight still fired. Default
JiminyHealthy=true; only a real check returning false marks it down (real
outages are covered by /healthz + the watchdog). +slog.Warn on nil check.
Verified live: 0 jiminy CRITICAL fires since the fixed restart.
2. The gauge stayed inflated (0.732) despite the assessor override running
correctly (diagnostic showed tsdb_rate=0.09, n=22). Root cause: the gauge has
TWO publishers — self_assess.go AND live_collectors.go (the 15s Prometheus
collector) — and the live collector republished the un-overridden Neo4j rate,
overwriting it. Extracted the override into Assessor.applyHonestFollowRate and
called it from BOTH paths. Verified live: gauge now 0.235, matching the TSDB
7d panel value (0.2353).
Also added a diagnostic log at the insight-fire point (surfaces the runtime
JiminyHealthy/SynergyAssessed values). go build + ape tests + lint clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-signal-001): CHANGELOG + post with live Tier 3 results (Epic 4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(jiminy-signal-001): update NilJiminyCheck test to the new default-healthy semantics
TestFileSynergyReader_NilJiminyCheck asserted the OLD JiminyHealthy=false-on-nil
behavior, which the Epic-3 fix intentionally changed to default-healthy (a nil
check = "cannot determine" must not read as "down"). Updated the assertion to
pin the new correct behavior. (CI Test gate fix.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(deps): bump golang.org/x/crypto v0.49.0 -> v0.52.0 (CVE-2026-39827/8/9)
Folded into #469 per operator request. golang.org/x/crypto v0.49.0 carries HIGH
CVE-2026-39827/39828/39829 (Go SSH channel-exhaustion DoS), fixed in v0.52.0 —
flagged by the Trivy Security Scan (already failing on main). go mod tidy pulled
the standard transitive bumps (x/net v0.54.0, x/sys v0.45.0, x/text v0.37.0).
go build ./... + go vet clean; no API changes. Unrelated to the JIMINY-SIGNAL-001
logic; included here to green the scan.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(deps): bump golang.org/x/net v0.54.0 -> v0.55.0 (remaining HIGH CVEs)
The crypto bump's transitive x/net (v0.54.0) still had HIGH CVEs (CVE-2026-25680
et al., HTML-parse CPU exhaustion) fixed in v0.55.0 — Trivy showed go.mod 15->6
HIGH after the crypto bump, the remaining 6 all x/net. This completes the Go-side
remediation (Python uv.locks already scan clean). go build + vet clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-effectiveness-001): sprint plan — trust as recoverable EMA (Epic 0)
Re-scoped after live recon corrected the premise: surfaced guidance is NOT
confidence:0 junk (it's filtered >0.3, confidence 0.7-0.9, LLM-classified ~79%
ignored). The real J17-T1 blocker is the monotonic trust ratchet — 1343 ignores
floored trust at 0.04 with no recovery path. Fix: replace RecordOutcome's
ratchet with an EMA of per-outcome effectiveness (Followed=1.0/Partial=0.6/
Ignored=0.2/Contradicted=0.0, alpha config) so trust tracks recent effectiveness
and recovers when guidance is followed. Config JIMINY_TRUST_MODE (ema default,
ratchet rollback) + JIMINY_TRUST_EMA_ALPHA. Forward-only, self-healing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-effectiveness-001): trust as a recoverable EMA, not a monotonic ratchet (Epic 1)
The J17-T1 blocker: TrustScorer.RecordOutcome was a monotonic ratchet (Followed
+0.05 / Ignored -0.02), so 1343 mostly-ignored outcomes floored the live session
at 0.04 with no recovery path — trust could never climb back to the 0.75 T1
threshold even if recent guidance became effective.
Replaced the ratchet with an EMA toward per-outcome effectiveness anchors
(Followed=1.0, Partial=0.6, Ignored=0.2, Contradicted=0.0):
trust ← trust + α·(target − trust). Now trust TRACKS RECENT EFFECTIVENESS and
RECOVERS: a floored session climbs back past 0.75 once guidance is followed,
while a genuinely all-ignored session converges to ~0.2 (honestly low, still <
0.75 — correctly kept out of T1). Config JIMINY_TRUST_MODE (ema default, ratchet
rollback) + JIMINY_TRUST_EMA_ALPHA (0.1). Forward-only — existing Neo4j scores
self-heal toward their recent regime.
7 new EMA unit tests (recovers-off-floor, steady-ignored-converges-low-not-zero,
tracks-recent-regime, ema-default); legacy ratchet tests pinned to Mode:ratchet
(both modes covered). Build + config-scanner (690/690) + lint clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-effectiveness-001): EMA trust recovery — post + CHANGELOG + J17 feature doc (Epic 3)
Epic 3 of JIMINY-EFFECTIVENESS-001. The Epic 1 code (EMA trust mode in
internal/jiminy/trust.go + JIMINY_TRUST_MODE/_EMA_ALPHA config) shipped in
the prior commit; this commit documents it and records the live Tier 3 result.
- post.md: premise correction (live recon overturned the confidence:0-junk
assumption — surfaced guidance is confidence 0.7-0.9, LLM-classified ~79%
ignored), the ratchet→EMA fix, and the live recovery proof.
- CHANGELOG: Unreleased entry.
- j17-ai2ai-protocol.md §3.4.1: new "Recoverable trust — EMA mode" subsection
(formula, anchors, config table, rollback note).
Live Tier 3: the actual floored mdemg-dev session (score=0.0 over 1,445
feedbacks under the old ratchet) was sent one real Followed via the live
POST /v1/jiminy/feedback wire and moved 0.0 -> 0.10 in live Neo4j — the EMA
signature 0.1*(1.0-0), proving the deployed binary recovers the previously-
pinned session off the floor. Full T1 promotion still awaits the guidance-
relevance work (Option B, disclosed follow-up).
Gates: build OK, jiminy+config tests pass, golangci-lint 0 issues,
config-scanner 690/690.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-relevance-001): diagnostic + coordinated sprint plans (Epic 0)
The Step-1 read-only diagnostic of the ignored-guidance population, plus two
coordinated sprint plans it produced:
- jiminy-relevance-001: persist the missing training evidence (guidance text +
action text + verdict — the binding gap the diagnostic found), raise label
quality, measure "should-follow" follow-rate honestly, curate the corpus.
The operator-decided 3–6 month collection infrastructure; NOT a retrain
(that's a documented future-trigger).
- hitl-review-001: a general-purpose human-in-the-loop dataset review + LIVE
reinforcement platform, native in the :9999 server, applicable to all
curated datasets. jiminy-relevance-001 Epic 3 is its first consumer.
Diagnostic verdict: we cannot retrain toward the >90% goal today because the
training evidence is stored nowhere (verdicts persisted everywhere, evidence
nowhere); root cause is guidance actionability (90% of surfaced guidance is
non-actionable abstractions; 172:1 abstraction:constraint graph ratio), not
model fluency; ~51% of labels are heuristic noise.
Coordinated pair: shared v0.11.0, migration 027 (second-to-merge renumbers to
028), ordering jiminy Epic 1 -> hitl-review-001 -> jiminy Epic 3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-relevance-001): persist guidance training evidence (V0027 + writer) (Epic 1)
The binding gap from the Step-1 diagnostic (Finding 1): MDEMG persisted guidance
VERDICTS everywhere and EVIDENCE nowhere — the action_summary POSTed to
/v1/jiminy/feedback was classified then discarded, so every
(context -> surfaced-guidance -> did-the-agent-follow) training triple was
unrecoverable. This epic starts the collection clock (forward-only).
- Migration 027_guidance_training_rows.sql: new hypertable (time column = `time`,
matching constraint_outcomes + the TSDB-CONSUME-001 alert contract) carrying
EVIDENCE-ONLY columns — guidance_content snapshot, guidance_type, source
role_type/layer, action_summary, outcome_type, similarity, classifier_source,
constraint_code + CUIDv2 row_id. Retention 365d + compression after 30d
(V0025/TSDB-CONSUME-001 contract — no unbounded hypertable). NO gold-grading
columns: human/auto gold lives in HITL-REVIEW-001's review_grades, joined on
item_id == row_id. Schema 26 -> 27.
- internal/tsdb/guidance_training_rows_writer.go: buffered + CopyFrom, bounded
FIFO buffer + drop counter, registerWriterStats (joins mdemg_tsdb_writer_*),
optional Prometheus counters. Modeled on the V0022 reinforcement writer.
- 3 Prometheus counters: mdemg_guidance_corpus_rows_{enqueued,dropped}_total +
_flush_failure_total (internal/metrics).
- Config (no-hardcoding): GUIDANCE_CORPUS_ENABLED (true),
_WRITER_FLUSH_INTERVAL_SEC (30, floor 5), _WRITER_BUFFER_SIZE (1000, 0=unbounded),
_MAX_CONTENT_BYTES (8192, floor 256, UTF-8-safe truncation),
_SOURCE_LOOKUP_TIMEOUT_MS (300, 0=disable). Bump TSDB_REQUIRED_SCHEMA_VERSION 27.
- Emit from RecordOutcome per item (gated, all non-Unknown outcomes incl.
not_appli…
* feat(mcp-revive-001): space resolution chain across all MCP memory tools (Epic 1)
Every memory-touching tool now resolves its space as: explicit space_id
param > MDEMG_SPACE_ID env > ide-agent (back-compat). Previously
defaultSpaceID='ide-agent' was hardcoded at 8 handler sites (and was the
fallback at the 3 param-bearing tools), stranding MCP-stored
observations outside the spaces hooks recall from — fragmenting the
connection layer per client. 6 tools gain the space_id parameter
(store, recall, associate, reflect, symbols, ingest_trigger); the
repo's .mcp.json sets MDEMG_SPACE_ID=mdemg-dev so this repo's agent
channel and hook channel share one memory space.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(mcp-revive-001): contract suite for the MCP tool surface (Epic 2)
UATS-style Go contracts over the previously zero-test 1,635-line file:
each handler invoked against an httptest MDEMG backend, asserting the
HTTP mapping (method/path/body), the space-resolution precedence
(explicit > env default > ide-agent), single-space association
invariant, validation-before-HTTP, and backend-500 → tool-error (never
a Go error). 7 contracts green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(mcp-revive-001): eventgraph + strict MCP tools, plugin-orphan reaper (Epics 3+4)
Three new tools: eventgraph_reinforcement_neighborhood +
eventgraph_guidance_outcome_neighborhood (seed_node_id OR query-resolved
seed, per the EVENTGRAPH-CLI-001 precedent; hops/since/limit OMITTED
when unset so server config stays the single source of truth) and
jiminy_strict (the /strict toggle). 23 tools total, all
contract-covered (12 contracts incl. the omit-when-unset pin and the
seed-by-query two-call chain).
Plugin-orphan reaper: launchctl kickstart -k kills the server without
Manager.Stop, orphaning plugin children (3 stale generations observed
live: Apr 30 / May 1 / May 7). startModuleInstance now pgrep-reaps any
prior-generation process holding the module's socket path before
spawning — surgical (full socket path match), loud per-kill warn.
Test decoy lesson: sh -c exec-optimizes argv away; tail -f <path>
carries it like a real plugin.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(mcp-revive-001): feature doc + CHANGELOG + CLAUDE.md + post (Epic 5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(tsdb): time-scope V0014's backfill + integrity assertion to its historical window
Live-caught regression (RSIC schema-drift alerts, correctly firing):
V0014's post-migration check asserted ALL-TIME prompt-hash membership
against a frozen hash set, and migrations re-run on every auto-migrate
startup — so the first legitimate prompt evolution (JIMINY-OUTCOME-002's
not_applicable classifier prompt, 8 rows with the new hash) made V0014
RAISE forever, aborting every migration run at 013 and pinning
schema_version=13 while all 26-era objects continued to exist.
V0014 is a historical repair (the Phase 11.6.x task_name swap); its
UPDATEs and the Step-4 assertion now scope to time < 2026-05-02 — the
migration asserts exactly the work it did, immune to future prompt
evolution. Verified live: full chain runs clean, schema_version
restored to 26, drift alert cause removed.
Lesson for migration authors: in a run-every-startup migration model,
integrity checks over LIVE tables must be scoped to the migration's own
target window — an open-ended assertion is a time bomb armed by normal
system evolution.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): sprint plan — distribution-matched consulting.classify distillation
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(ft-classify-002): class-stratified distill capture + the real 11.5d root cause (Epic 1)
The 11.5d class skew was NOT (only) sampling: summary_quality penalized
empty summaries, but a correct 'none' verdict REQUIRES an empty summary
by spec — so the reward>=0.8 distill filter silently rejected every
correct-none teacher answer (proof: run 1 with perfect 82%-none input
stratification kept 37/200 pairs with ZERO none). Fixed: spec-compliant
{type:none, summary:''} scores 1.0 (98 reward tests green). Capture
gains --stratify-classify (production-distribution bucketed sampling,
measured live over 4,028 rows). Run 2: 200/200 kept, reward mean 0.981,
train dist none 82/must 12/must_not 3/should 3/should_not 1 — within
±2pp of production on every class, 0-leak vs all 10 sources.
NOTE for the gate epic: stored classify baselines (0.668) were computed
under the biased reward — gates recompute baselines fresh.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(ft-classify-002): training config — fresh task-delta LoRA on the production fused model (Epic 2)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ft-classify-002): benchmark config still pointed at decommissioned mlx port 8101
Phase 13.5 cutover updated serving to llama-server :8102 but
benchmark_phase10.yaml kept mlx_port: 8101 — any benchmark run without
an explicit --mlx-base-url override made ZERO model calls and reported
aggregate 0.0000 (caught live launching the FT-CLASSIFY-002 fresh
baseline). UBENCH config sha re-pinned; lint green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): stage-by-stage run record (6a instrumentation input)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(doc-audit-001): amended charter per 3-lens review team (operator-approved)
Adopts the operator's doc-audit orchestrator prompt with the team's
binding amendments: push-based survival loop (commit-without-push lost
state across resets), git ls-files enumerator (find swept ~700 non-repo
files incl. .claude agent memory into a FIX_IN_PLACE authorization),
dev02 branch strategy with CLAUDE.md/CHANGELOG operator carve-outs,
pinned-snapshot convergence, claim-budget batching, JSONL ledger, CI
discipline, and the age-based drift model (96% of docs/architecture
untouched since pre-April is the real hotspot; the MoE-marker class is
already closed). Phased: 001a after FT-CLASSIFY-002, 001b after
DORMANT-CENSUS-001, 001c as a standing mechanism.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): gate results — 2/3 PASS, no-promote recommended
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ft-classify-002): CHANGELOG + post — no-promote accepted, sprint closed (Epic 6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(doc-audit-001a): execute the approved fix batch
1. compose LLM_ENDPOINT default 8101→8102 + stale Phase-11.6 comment
(triple-confirmed audit finding; un-overridden Docker deployments
pointed at the port decommissioned 2026-05-03; both copies, parity ok)
2. 00_README_v2.md version ledger unfrozen: v5.13 catch-up entry
(13.5 cutover, MODEL-DIST-001/002, FT-RECURSIVE-000,
FT-CLASSIFY-002) — append-only, R-LT-4-clean
3. README dashboard tab count 8→10
4. pre-campaign-checklist schema v8+→26 (cites config.go as authority)
5. beta-testing version-under-test marker → v0.10.1
6. live-validation F11 framing verified already-correct (no change)
+ CHANGELOG entry for 001a (rides dev01 per charter carve-out)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(surprise-topk-001): sprint plan — honest novelty + a multiplier that can fire
Live baselines escalate the roadmap's diagnosis: the surprise chain is
flat DEAD, not noisy — all 221,504 reinforcement events ever carry
surprise_factor=1.0; node surprise_score avg 0.023 (max 0.503, n=5,808)
vs hardcoded 0.4/0.7 thresholds. Scope: vector-index top-K novelty +
config-driven thresholds recalibrated to the new scale in the same
sprint (RRF-SCALE lesson) + CoactivateSession surprise-CASE audit.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(surprise-topk-001): exact top-K nearest-neighbor embedding novelty (Epic 1)
Replaces the unordered LIMIT 50 sample (no ORDER BY) whose comparison
set was dominated by EMPTY-ARRAY embeddings — 4,564 of 5,810
conversation observations (78%) carry size(embedding)=0, passing the
old IS NOT NULL guard while cosine() yields NULL; this is why node
surprise_score averaged 0.023 and every reinforcement event ever
carried surprise_factor=1.0.
New: exact ORDER BY cosine scan over real-embedding (size=dims),
non-archived, space-scoped conversation observations; config
SURPRISE_EMBEDDING_NOVELTY_TOPK (50) + _SIM_FLOOR (0=off). The
db.index.vector.queryNodes route was live-REJECTED: the label-wide
index is crowded by ~100k non-conversation nodes (top-200 near a
conversation seed were ALL emergent_concept centroids — the
HIDDEN-CHURN degeneracy), pruning role-filtered hits to zero; the exact
scan over ~1.2k real rows is deterministic and ~ms (revisit at ~50k).
Live-verified: avg sim 0.680 / count 50 over the true nearest set.
Follow-up recorded: the 78% empty-embedding backlog (embeddings
backfill) is its own item.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(surprise-topk-001): config-driven surprise multiplier thresholds at both Cypher sites (Epic 2)
CoactivateSession DOES compute the surprise CASE (audit result) — the
chain was dead purely from broken scores. The hardcoded 0.7/0.4
thresholds (unreachable: live max score 0.503, avg 0.023 under the old
noise) become SURPRISE_FACTOR_HIGH_THRESHOLD (0.5) /
SURPRISE_FACTOR_MEDIUM_THRESHOLD (0.3) — defaults calibrated to the
exact-top-K novelty scale, parameterized into BOTH ApplyCoactivation
and CoactivateSession Cypher (never recalibrate against the old scale —
the RRF-SCALE lesson). Non-positive config falls back to defaults.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(surprise-topk-001): post + CHANGELOG; threshold recalibration + loud error path (Epics 3-4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-budget-001): sprint plan
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-budget-001): budget derivation, session attribution, surface/outcome split, token floors (Epics 1-3)
Budgets: JIMINY_TIMEOUT_MS default 15s→0 (=derive from the 90s
warm-compute budget — both paths run the same synthesis work; the
independent 15s starved every fresh install, the GUIDANCE-SYNTH-001
class); /reformulate's hardcoded 10s → JIMINY_REFORMULATE_TIMEOUT_MS
(0=derive); config.Validate() warns on explicit budget incoherence.
Attribution: PersistGuidanceOutcome now receives feedbackSessionID
(was literal "" — every GUIDANCE_OUTCOME edge ever has null
session_id; forward-only fix). Surface-vs-outcome split:
mdemg_jiminy_guidance_surfaced_total{space_id} (the honest denominator
— TotalGuidanceIssued only counts guidance that RECEIVED feedback) +
mdemg_jiminy_feedback_dropped_total{space_id} on tracker-expiry drops.
Floors: JIMINY_OUTCOME_LLM_MAX_TOKENS 100→3000 (truncation risk on the
classifier reasoning field → parse fail → heuristic fallback, the
JIMINY-OUTCOME-002 artifact class), SYNTHESIS 2000→3000, EVALUATE
2000→3000 (standing ≥3000 rule; completion stops at JSON end — floors
are free insurance). Stale TTL comment fixed (1800→86400 actual).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-budget-001): CHANGELOG + post (Epic 4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dormant-census-001): sprint plan — the standing dormancy guarantee
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): route↔consumer gate + 187-route inventory skeleton (Epic 1a)
scripts/verify_route_consumers.py extracts the live route table and
fails on bidirectional drift (unlisted route / stale entry) and on any
UNREVIEWED disposition — the bootstrap marker. Gate verified failing on
the 187 fresh UNREVIEWED entries; adjudication next.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): adjudicated 187-route inventory + merge-blocking CI gate (Epic 1b)
All 187 routes carry evidence-based dispositions: 109 ACTIVE,
60 OPERATOR_SURFACE, 13 INTERNAL, 4 PRUNE_CANDIDATE, 1 DEFERRED.
171/187 matched to UATS specs. Orchestrator re-verified every
PRUNE_CANDIDATE and known false positive independently.
Census reversals vs recon (the false-positive class this gate exists
to catch): /viz/topology + /api/graph/* are LIVE Grafana consumers
(topology iframe + nodegraph datasource) — removed from the prune
list; /v1/conversation/snapshot* is NOT called by pre-compact.sh
(saves via /v1/conversation/observe) — OPERATOR_SURFACE.
Hidden consumer surfaced: embedded /ui/ dashboard consumes ~35
routes that look dormant from hooks/CLI/scripts alone.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): wire SignalLearner.GetStrength into Guide() ordering (Epic 2)
The Hebbian signal learner (V0024 SignalState, supervised flush,
startup hydration, live emission/response stream since HOOKWIRE-001)
had a read side with ZERO production callers. Guide() now orders
within equal priority by (1-w)·confidence + w·GetStrength(code),
w = JIMINY_SIGNAL_STRENGTH_WEIGHT (default 0.2; 0 = off, pre-census
behavior; clamped to 1). Ordering only — selection/filtering
untouched. Unknown codes blend the learner's 0.5 neutral default.
6 Tier 1 tests pin the contract: weight-0/nil-learner pure
confidence, blend formula, neutral default, clamp, priority
dominance + within-priority strength overtake.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(dormant-census-001): prune 4 verified-dead routes + handlers (Epic 3)
Pruned (each independently re-verified zero producers + named successor):
- /v1/feedback — gap-feedback intake; live channel is /v1/jiminy/feedback.
Handler + isolated gaps.ProcessFeedback/Feedback removed.
- /v1/memory/ingest-codebase[/] — deprecated since Phase 94 (Deprecation
header); successor /v1/memory/ingest/trigger. Whole handler file removed.
- POST /v1/alerts/grafana — superseded by the native alert evaluator;
only ref was a commented-out contactpoint. Compose env
MDEMG_GRAFANA_ALERT_WEBHOOK_URL removed from both compose files.
NOT pruned (census reversals): /viz/topology + /api/graph/* are live
Grafana consumers; PREDICTS/FORESHADOWS exist nowhere in code (recon
claim was wrong — no-op).
6 UATS specs removed + capability_gaps_full /v1/feedback variants
dropped; UXTS matrix 220→214; inventory entries retained as PRUNED
with removed_in. Gate: 183 live / 187 inventoried, OK.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jiminy): codegen collision-path self-deadlock wedged constraint observes
Live-smoke catch (DORMANT-CENSUS-001 Tier 3, own fix commit per
precedent): ConstraintCodeGenerator.GenerateCode's collision branch
called fallbackCode while holding g.mu; fallbackCode locks g.mu again.
sync.Mutex is not reentrant — the first LLM-returned code that
collided with a registered code deadlocked the generator permanently,
and every later constraint-typed /v1/conversation/observe queued
behind it forever (UATS conversation_observe_pinned hung 45-90s+
deterministically; goroutine dump showed the holder 18 min wedged at
codegen.go:121 with N waiters at :47).
Fix: fallbackCodeLocked (caller holds g.mu) used by the collision
branch; fallbackCode wraps it. Regression test drives the real
collision path through a fake OpenAI-compat endpoint with a 10s
deadlock tripwire + post-call usability check.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dormant-census-001): feature doc + CHANGELOG + sprint post (Epic 5)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(dormant-census-001): re-pin capability_gaps_full spec hash after variant removal
The Epic 3 prune removed the spec's two /v1/feedback variants but did
not re-pin its integrity sha256 — the merge-blocking UNTS hash-verify
step correctly caught the drift on PR #452. 214/214 valid locally.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(context-live-001): sprint plan + recon findings (Epic 0)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): version guard + honest consensus denominator (Epic 1)
(1) Cross-version fingerprint guard: deriveQueryFingerprint returns the
active catalog version; it rides RetrieveRequest/ColumnQuery into
ContextColumn and StrictContextMode, where candidates fingerprinted
against a different catalog version score 0 — bit positions reallocate
per build, so cross-version Jaccard is noise (mdemg-dev: 76,906 v1
nodes were silently compared against v3 query bits). Version 0 =
unknown (explicit-fp callers) keeps legacy behavior.
(2) Consensus semantics: columns STRUCTURALLY unable to vote (disabled
by config, or context with no query fingerprint) are no longer
appended, so they stop deflating the denominator — the always-empty
live context column had every live query's consensus hard-capped at
0.8. Errored/timed-out columns still count (documented intent kept).
The column_context.go comment that claimed exclusion now matches code.
(3) Cache integrity: QueryContextFingerprintVersion joins CacheKey
(the reflection forcing-function caught it unprompted); scorerVersion
bumps to v2 (semantics change) and gains a deterministic hash of the
per-category context-weight + sparse-override maps — operator edits to
either JSON now flip the namespace (pointer fields dereferenced; %+v
would have hashed addresses).
Pins: TestContextColumn_VersionGuard,
TestConsensus_DenominatorExcludesAbsentColumns,
TestScorerVersion_FlipsOnCategoryMapChanges.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): stage-6 fingerprint heal + Phase-B refine wired (Epic 2)
Disclosed deviation from the plan sketch: RefineWithCoactivations is
NOT the skew healer — it merges old-catalog bits and bumps the version,
which would relabel v1-semantics bits as current. The healer is
recomputation: new conversation.RecomputeStaleFingerprints (the
backfill CLI's core as a budget-bounded, resumable library call).
Stage 6 now runs on EVERY invocation (not just rebuilds): catalog
freshness/build as before, then (1) heal pass — recompute up to
CONTEXT_FINGERPRINT_HEAL_MAX_PER_CYCLE (2000) stale-version nodes under
the existing 60s budget, flushing partial batches on ctx expiry;
(2) Phase-B refine — RefineWithCoactivations (previously ZERO callers)
over up to CONTEXT_FINGERPRINT_REFINE_MAX_PER_CYCLE (200)
current-version observations with co-activations, marked via
context_fingerprint_refined_version so cycles never re-walk. Driver
injected via SetFingerprintDriver (server.go, next to SetContextCatalog);
nil driver = old rebuild-only behavior.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): classifier→category dispatch on live traffic (Epic 3)
Live traffic never passes ?category=, so SPARSE_GATE_CATEGORY_OVERRIDES
and RETRIEVAL_CONTEXT_COLUMN_CATEGORY_WEIGHTS only ever fired on
benchmark calls. Retrieve now derives req.Category from the
QueryClassifier's types via QUERY_CLASSIFY_CATEGORY_MAP (JSON; default
data_flow→data_flow_integration, architecture→architecture_structure,
relationship→relationship). Explicit body/param category always wins;
first mapped type wins for multi-label; empty map disables. Runs
pre-CacheKey (Category already in the key — cache-safe; classifier
already ran before the key was computed). Vocabulary gap disclosed:
service_relationships/business_logic_constraints have no classifier
equivalent and stay benchmark-only.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(context-live-001): server-side query-fingerprint derivation default-on (Epic 4)
CONTEXT_QUERY_AUTO_DEFAULT (default true) makes derivation fire for
every retrieve with query text; per-call opt-out ?context=off|false|0;
explicit ?context=auto still forces it when the config gate is off.
Sequenced after the skew heal + version guard, so the newly-active
column scores healed current-version fingerprints, never cross-version
noise. Cost: reuses the per-(space,version) catalog-vector cache —
one cosine top-K over ≤256 refs per call.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(context-live-001): UVTS A/B artifacts + feature doc + CHANGELOG + post (Epics 5-6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(readme): coverage-gap additions per 3-lane team review (operator-approved)
Executes the corrected recommendations from the README_COVERAGE_GAPS.md
review: new Self-Improving Local LLM section (~20 lines, fact-scrubbed:
fused GGUF serving, no OpenAI auto-fallback, no hosted-SFT claim, no
volatile eval figures; Step 2b kept in place and cross-linked); 3 new
Key Features bullets (never-silent operations, closed guidance loop
with fail-closed-bash/graduated-strict wording, eventgraph federation);
2 folds (RRF/sparse/fingerprints into the Hybrid search bullet;
de-numbered the stale 'all 11 frameworks' guide pointer); 1 sentence
(TSDB telemetry governance in Observability). Rejected per review:
the report's §5.2 Step-2b 'correction' (would have written the
rolled-back Stage-1 distill as canonical) and its stale 0.85xx
figures. Drive-by count fixes: UPTS 27→28 languages, Jiminy 6s→
config-derived budget.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(negfeed-cooler-001): sprint plan + live-verified recon (Epic 0)
NEGFEED-001 + COOLER-001 — complete the memory lifecycle. Recon
orchestrator-verified live: CoactivateSession 244k rows/14d (73% of
reinforcement_events), apply_negative_feedback 2 rows ever, 1
CONTRADICTS edge in the whole graph — producer-wiring only (downstream
EVENTGRAPH-003/004 built). Three workstreams: CoactivateSession
off-request + delta emission + bounded window; Jiminy-contradicted →
ApplyNegativeFeedback bridge + MCP memory_reject; graduation
unification (RSIC → cooler) + decay-protection retrieval semantics.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(negfeed-001): CoactivateSession off-request + delta emission (Epic 1)
The dominant Hebbian producer (live 244k rows/14d, 73% of
reinforcement_events) ran SYNCHRONOUSLY on the Observe request path and
regenerated the full unbounded C(n,2) session clique every call —
O(n^2) per observe, and evidence_count tracked session length not
co-activation count.
(1) Off the request path: the Observe call site now fires CoactivateSession
in a panic-recovered background goroutine on a detached context
(context.WithoutCancel + 30s timeout) — observe latency decoupled from
session size.
(2) Delta emission: CoactivateSession takes the new observation's
node_id and co-activates it ONLY against the most-recent prior session
members (bounded by LEARNING_SESSION_CLIQUE_WINDOW, default 50; 0 = all
priors). Per-call work O(window) not O(n^2); a pair's edge is
created/strengthened only on genuine co-activation, so evidence_count
counts co-activation events, not session length. Cumulative graph
unchanged (sum of deltas = C(n,2)). DH-004 reinforceSessionObservations
stability raise preserved (full-session by design).
Forward-only: existing inflated evidence_count values left as-is.
Pins: TestCoactivateSession_DeltaPairCount (delta arithmetic + window
cap + cumulative invariant).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(negfeed-001): anti-Hebbian producer — Jiminy contradicted bridge + MCP memory_reject (Epic 2)
The substrate had NO automated weaken producer: ApplyNegativeFeedback
(weaken + CONTRADICTS, EVENTGRAPH-003/004 federation built) had only
the HTTP handler as caller; live, 2 apply_negative_feedback rows ever
and 1 CONTRADICTS edge in the entire graph.
Bridge A (jiminy.NegativeFeedbackApplier interface + adapter over
learning.Service, wired in server.go): on OutcomeContradicted with >1
source node, weaken co-activations AMONG the guidance's source nodes
(query==rejected; ApplyNegativeFeedback's q<>r guard skips self-pairs,
so single-source guidance is a safe no-op and unrelated memory is never
touched; weight floors at 0). Gated JIMINY_CONTRADICTED_WEAKEN_ENABLED
(default true).
Bridge B (MCP memory_reject): the explicit producer — agent rejects a
memory that surfaced wrongly for a context; resolves both sides via
retrieve (single space, mirrors memory_associate) and POSTs
/v1/learning/negative-feedback. Contract test added (MCP-REVIVE-001
rule). 24 MCP tools now.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cooler-001): unify graduation onto the Context Cooler (Epic 3)
Two divergent graduation paths existed: the cooler (threshold 0.8 +
pinned-guard + graduated_at) and RSIC executeGraduateVolatile
(hardcoded 0.7, no guard, no graduated_at). RSIC now delegates to the
cooler via a GraduationProcessor interface + coolerGraduationAdapter —
one config-driven implementation. The rollback snapshot predicate is
aligned to the cooler's (pinned-guard + COOLER_GRADUATION_THRESHOLD via
SnapshotStore.SetGraduationThreshold) so executor and snapshot can't
drift (RSIC-STORM-001 discipline; captureNodeStateP adds param support).
The cooler also runs per-space from (new Step 2 —
Graduation, between decay and prune; skipped in dry-run since it
mutates) — graduation no longer requires the default-off background
loop on its hardcoded mdemg-dev space.
Tests: NilProcessor fail-safe + DelegatesToProcessor (graduate_volatile
removed from the nil-DRIVER loop — it's no longer driver-coupled).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(cooler-001): graduated-incident edges resist decay (Epic 4)
Defines what 'graduated' means to retrieval (recon: retrieval read no
graduation signal — greenfield). The lowest-risk, learning-side
interpretation: CO_ACTIVATED_WITH edges incident to a graduated
(volatile=false) node decay at a reduced rate, so stable memory's
associations persist. NOT a retrieval-scorer change — no RRF
score-scale risk, no UVTS gate.
GRADUATED_DECAY_PROTECTION_FACTOR (default 0.5; 1.0=off, 0=no decay)
multiplies the effective decay rate when either endpoint is graduated.
Wired into the live decay job (maintenance Step 1 + the decay command
flag); query returns graduatedIncident per edge. Pin test:
graduated-incident edge retains more weight; factor 1.0 disables.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(negfeed-cooler-001): feature doc + CHANGELOG + post (Epic 6)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(sidecar-loop-001): sprint plan + recon (Epic 0) — fix-but-defer
Operator decision: fix-but-defer (keep only valuable data; build clean
dataset to train-size, defer training — rerank is default-off). Recon
live-verified: data IS teacher-labeled (rerank_scores[]) but 100%
mislabeled (collector logs unsorted input candidates vs rerank-sorted
scores; 84% len-mismatch + the 16% positionally wrong), and the trainer
reads flat vs the collector's nested schema (0 records ever parsed).
Scope: collector alignment fix + guard, trainer schema reconcile,
archive the mislabeled pre-fix data, flag honesty, readiness count;
training/A/B deferred.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(sidecar-loop-001): collector logs aligned reranked candidates + guard (Epic 1)
The rerank training collector logged req.Candidates[:topN] (unsorted
input) against rerankScores (rerank-sorted output) — candidate<->score
correspondence broken in 100% of records (84% length-mismatch, the
remaining 16% positionally wrong). Now logs result.Results +
result.RerankScores, which are built together from the sorted slice and
align 1:1. Added a Collect guard that drops misaligned (len-mismatch),
empty, or all-zero-score records so the corpus is clean by construction.
Tests: aligned record written; misaligned/empty/all-zero dropped.
This is the same data-mislabeling class behind discarded retrains.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(training-audit): root-cause the 3 discarded retrains — corpus filter + eval + DPO all mislabeled
Operator-triggered audit (3 lanes + code verification). The retrain loop
is corrupted by one class of defect (label/score decoupled from its data)
in three places: (1) the mean>=0.8 distill filter over length/keyword-
biased rewards skews the corpus toward verbose output (summary_quality
was one instance of a systemic pattern; ape.reflect/summarize/synthesize
unfixed); (2) the promotion gate evals on the 99%-leaked valid_golden vs
a frozen stale-reward MLX-form baseline 0.8338 with no zero-call hard-fail
— a worse-than-baseline verdict is uninterpretable; (3) dpo_builder
assigns chosen/rejected by list position, ignoring the outcome it
computes. All file:line-verified. Recommendation: no retrain until fixed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(sidecar-loop-001): feature doc + CHANGELOG; archive mislabeled pre-fix data
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(eval-integrity-001): sprint plan + recon (Epic 0)
Lead sprint of the training-integrity remediation (operator-sequenced
eval-integrity-first). Makes the adapter promotion gate trustworthy:
clean leak-free 17-task holdout (rebuild — valid_clean is stale/wrong
shape), leak-audit preflight, GGUF serving (not the OOM-prone MLX
side-server), zero-call hard-fail, and baseline-as-recomputed-report
(not a frozen 0.8338 constant). Discloses the sequencing constraint:
the honest baseline number is re-run at the close of the reward sprint
(it must use the fixed reward), so this sprint stamps a provisional one.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(eval-integrity-001): respect dynamic_prompt — recover enum-templated tasks' production rows (Epic 1)
The clean-eval builder + benchmark matcher strict-hash-matched ANY spec
with a pinned system_prompt_hash, ignoring dynamic_prompt. But
ape.reflect's prompt is built at init() by interpolating
AllowedLLMActions into the text — its hash drifts every time the action
enum changes (RSIC has added actions repeatedly). Result: ape.reflect's
71,033 production rows (under hash ef068ab) were invisible to a filter
pinned to 39b2bc (0 matches) — the single largest training target had
its entire corpus silently excluded from eval AND curation.
Fix: Spec now carries dynamic_prompt; the matcher (run_benchmark) and
build_clean_eval match dynamic/enum-templated prompts by task_name, not
by the moving hash. ape.reflect's spec corrected to dynamic_prompt=true
(was false despite being enum-templated). Proven end-to-end: clean-eval
coverage 10/17 → 12/17 (ape.reflect + hidden.reclassify recovered, 240
rows). 90 neural tests pass.
Residual: 5 tasks (consulting.synthesis, guardrail.evaluate,
metalearn.generalize, retrieval.rerank_nli, summarize.generate) have
~0 production rows — genuinely uncaptured, a separate capture-gap issue.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(eval-integrity-001): wire gate to leak-free 12-task valid_clean (Epic: gate-set)
The promotion gate evaled on the 99%-leaked valid_golden. Re-pointed
benchmark_phase10.yaml + the UBENCH spec to the rebuilt leak-free
valid_clean (240 rows / 12 tasks with real current-prompt production
data; 5 capture-gap tasks excluded as uncoverable — operator-approved
honest-12 over fake-17). Re-pinned config.sha256 + golden_holdout
sha256/expected_rows(240)/expected_tasks(12). ubench lint 100%.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(eval-integrity-001): hard-fail on zero successful calls (Epic 3)
A benchmark run that made zero successful calls — or where no task
contributed a score (weight_used==0) — reported aggregate 0.0,
indistinguishable from a genuine low score. This is the false-0.0 class
behind FT-CLASSIFY-002's four silent failures (port rot, model 404s,
llama-server answering /v1/models before load). run() now computes
specs_with_zero_successful_calls + a status field; a degenerate run is
status=error and main() exits nonzero (the gate/CI/operator rejects it
instead of mistaking a broken run for a model judgment). Pin tests on
_count_zero_call_specs.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(eval-integrity-001): wire LLM recorder in ingest process — capture summarize.generate (Epic: capture-gap)
The capture-gap investigation found summarize.generate (default-ON,
LLM_SUMMARY_ENABLED=true) runs in the `mdemg ingest` subprocess where
SetDefaultRecorder was NEVER called (only `mdemg serve` wired it) — so
every summary LLM call was made and silently dropped, losing the task's
entire production corpus (the EMBED-WIRE recorder-gap class, via a
process boundary). Now wires a short-lived buffered LLM recorder in
runIngest before summarize.New (which captures defaultRecorder at
construction), guarded by LLM_INTERACTION_LOGGING, with flush+close on
exit. summarize.generate production rows will now accumulate → coverable
by the clean eval over time (13th task).
The other 4 zero-row tasks (consulting.synthesis, guardrail.evaluate,
metalearn.generalize, retrieval.rerank_nli) are correctly GATED-OFF
(SYNTHESIS/GUARDRAIL/METALEARN/RERANK_ENABLED all default-false) —
expected zeros, no fix needed.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(eval-integrity-001): leak-audit gate target (Epic 2)
make test-eval-leak runs audit_eval_leakage.py over valid_clean vs the
SFT training sources, exits nonzero on ANY overlap — the gate must
re-verify the eval is disjoint from training (the valid_golden 99%-leak
class; build-time filtering isn't a substitute as the corpus grows).
The script existed but had zero callers. Verified: valid_clean CLEAN,
0/240 overlap.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(eval-integrity-001): feature doc + CHANGELOG + post (Epic 7)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(eval-integrity-001): restore ape_reflect system_prompt_source to clean file:line
The dynamic_prompt=true change is correct, but I'd also appended an
annotation to system_prompt_source — the ULTS --verify-hashes runner
parses that field AS the file:line to hash, so the annotation broke it
('source file not found'), failing the merge-blocking ULTS gate.
Restored the clean 'internal/ape/llm_reflector.go:74 (rendered with
AllowedLLMActions enum)'; dynamic_prompt=true retained. ape.reflect now
11/11 PASS locally. (UBTS --output + UOBS metric-not-found in the same
CI run are pre-existing continue-on-error soft-fails, not from this PR.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-001): sprint plan (Epic 0)
2nd training-integrity remediation sprint. The distill gate
(x9_distill_capture_v2.py:360: kept = mean(reward_vector) >= 0.8,
global) drops spec-correct-but-terse answers because coverage_score/
explanation_quality/coherence_score reward length over correctness —
gutting ape.reflect (largest target) + summarize + synthesize, then
balanced_sampler amplifies the verbose-skew. Principle: inclusion
selects for CORRECTNESS not length. Fix: length-neutral correctness
rewards + per-task inclusion thresholds + a forcing-function test (each
of the 12 covered tasks' known-correct golden rows clear their gate) +
distribution check. Closes with the eval-integrity-deferred GGUF
serving + honest baseline recompute.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(reward-correctness-001): length-neutral correctness rewards (Epic 1)
The distill inclusion gate (mean(reward_vector) >= 0.8) selected for LENGTH,
not correctness — the corpus-skew mechanism behind 3 discarded retrains. Four
reward functions used length/count ladders that dropped spec-correct-but-terse
answers below the gate and rewarded verbosity upward:
- coverage_score: <20 words→0.4 / <50→0.7 / then rising → now substantive
content scores 0.9 flat (length-neutral); empty→0.0, pure-repetition→0.3.
- explanation_quality: <20 words→0.6 cliff → now substantive→0.9.
- coherence_score: required >=2 sentences + 10 words → now any coherent
non-repetitive response→0.9; pure repetition→0.4.
- insight_count: rewarded bullet COUNT (>=5→1.0) → now >=1 genuine insight→0.9
(no upward count reward; stops bullet-spam, stops dropping single-insight
reflections to 0.5 — ape.reflect, the largest target).
Verified: terse-correct now clears the 0.8 gate; verbosity/bullet-count no
longer rewarded above concise; varied detailed content unaffected (0.9);
empty/repetition still rejected. Tests rewritten to pin the new semantics
(78 pass). Subtler keyword-bag functions (specificity/actionability) left for
the continuation — they reward content signals, not raw length.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(reward-correctness-001): per-task inclusion thresholds + live findings (Epic 2-3)
Epic 2: --reward-threshold-map JSON ({"task": float}) overrides the global
--reward-threshold per task in x9_distill_capture_v2.py, so tasks whose reward
arrays have a different natural ceiling can gate at the right bar. Records the
per-task gate in each row + the manifest. Live-verified end-to-end: real
OpenAI + TSDB run with {"consulting.classify": 0.6} applied the override
(3/3 captured, manifest per_task reward_threshold=0.6).
Epic 3 (live Tier 3, docs/development/reward-correctness-001/live_findings.md):
scored REAL production llm_interactions at the 0.8 gate, old vs new rewards.
Validated Epic 1: hidden.summarize recovered 69/72 real concise summaries the
old length ladder dropped. Surfaced three larger correctness issues the
length fix does NOT close (the real dominant suppressors for the big tasks):
1. ape.reflect (54k, largest target): json_valid mean 0.133 — ~87% of recent
responses TRUNCATED mid-JSON (prompt ~5800 + ~3000 output > 8192 per-slot
KV bound). Production serving/capture defect; gate correctly rejects.
Recommended own-sprint follow-up (raise output budget, re-capture).
2. jiminy.evaluate: explanation_quality=0.0 on correct {violations,warnings}
responses — wrong reward for the schema (no top-level explanation key).
Reward-array fix, operator-gated (changes a ULTS array + re-grades).
3. jiminy.synthesize: keyword-bag follow_rate/specificity just below gate —
the deferred Epic 1 continuation.
Also fixed 2 pre-existing lint nits in the touched file (F541, E741).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-001): CHANGELOG + sprint post (Epic 6 close)
Closes REWARD-CORRECTNESS-001 at Epic 1+2+3. Epic 4 (baseline recompute)
explicitly deferred behind the ape.reflect truncation fix per operator
sequencing — recomputing over a known-truncated corpus would bake in the
corruption. Next sprint: ape.reflect truncation.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ape-prompt-budget-001): sprint plan + recon (Epic 0)
Root-caused the ape.reflect ~87% truncation (largest training target) to a
structurally-unbounded prompt: live-measured 7489 tokens (Current Assessment
~3895 + 5-cycle history ~2693), leaving only ~700 of the 8192 per-slot KV
budget for output — 191/200 invalid responses cluster at 490-520 tokens_out,
truncating mid-JSON at the ceiling. Compression already on; not a max_tokens
cap. Plan: bound the prompt to a configurable token budget (gate verbose TSDB
dataset fields, cap history cycles, final drop-oldest guard) so output always
has ~4000-token headroom, with an optional serving-slot increase as the safety
margin. Lever A (structural prompt budget) + Lever B (KV slot) proposed, picked
at execution. Tier 3 proof: fresh ape.reflect json_valid recovers 0.13 → ~1.0.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(ape-prompt-budget-001): bound ape.reflect prompt to protect output (Epic 1-2)
Implements the structural fix for the ape.reflect ~87% mid-JSON cutoff. The
prompt was unbounded (live 7489 tok), leaving only ~700 of the 8192 per-slot
KV budget for output, so the largest training target's responses were cut off
mid-array.
buildUserPrompt now enforces a token budget:
- gate the verbose TSDB dataset fields (LLMPerformance x17 / Retrieval /
Embedding / TrainingReadiness, ~3895 of 7489 prompt tokens) behind
RSIC_LLM_REFLECT_INCLUDE_DATASETS (default false); scalar health metrics
the detectors use are always kept;
- cap history cycles via RSIC_LLM_REFLECT_HISTORY_CYCLES (default 3, was
hardcoded 5);
- final budget guard (RSIC_LLM_REFLECT_PROMPT_BUDGET_TOKENS default 3500, 0
disables): drops history oldest-first, then trims the assessment tail,
logging loudly what was dropped (never silent). estimateTokens calibrated
to the measured 2.3 chars/tok ratio, slightly conservative.
3 config fields (range-validated, no hardcoding) wired config ->
LLMReflectorConfig -> server.go. 6 Tier-1 tests: dataset gating, history cap,
drop-history-under-budget, trim-assessment-under-budget, under-budget-unchanged,
estimator. Full ape suite + lint + config scanner (687/687) clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(ape-prompt-budget-001): feature doc + CHANGELOG + post + CLAUDE.md (Epic 5)
Live Tier 3 result documented: 3/3 fresh post-restart ape.reflect rows valid
JSON (100%, up from ~13%), tokens_in ~2575 (from ~7489). Corrected the stale
CLAUDE.md ape.reflect prompt-size figure (~5800 -> ~7489 live-measured) and
added the per-slot KV "prompt+output share the budget" guidance. Closes
APE-PROMPT-BUDGET-001.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(ape-prompt-budget-001): re-pin ape_reflect ULTS source line after import shift
The Epic 1-2 import (log/slog) + struct fields shifted llmReflectSystemPrompt
from line 74 to 80. The ULTS hash verifier reads from line-2 and grabs the
first backtick string; at the stale :74 the search region now included the
`"` backtick in `quoted[i] = `"` + a + `"`` → wrong hash. The system prompt
TEXT is unchanged (hash 39b2bc… still matches at :80). Updated
system_prompt_source :74 → :80. Local glob verify: ape.reflect 11/11 PASS.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune-audit-001): non-destructive audit of non-conforming data (Epic 0-1)
Operator-chosen audit-first prune phase. Read-only enumeration of every
non-conforming TSDB/file target with exact counts + a backup/small-batch/verify
prune plan (each category operator-gated).
Findings — PRUNE TARGETS: (A) 2,111 invalid-JSON rows in object/array tasks
(ape.reflect 1890 in the 06-11..06-13 truncation window, rerank_cross 184,
evaluate_llm 18, query_classify 18, classify 1); (B) 21,135 error/empty rows
(mdemg data clean target); (C) rerank mislabeled archive 6,894 events/21M +
valid_golden 108 leaked + ~14 stale April baselines. ~23,246 TSDB rows total
(~22.7%).
NOT prune targets (schema/reward mismatch, data is fine, fix the definition):
hidden.summarize 72 (prose vs object schema), string-schema tasks the jsonb
check false-flags (intent_translate/codegen/synthesize emit valid bare
strings), jiminy.evaluate. Audit pitfall recorded: never run a jsonb-validity
prune predicate against string-schema tasks.
Corrects the "87% of 54k" assumption: ape.reflect corruption is 1,890 rows in
the recent truncation window (forward-fixed by APE-PROMPT-BUDGET-001), not
corpus-wide.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune-audit-001): record Category A prune execution + CHANGELOG (Epic 2 close)
Pruned 1,898 genuinely-corrupt invalid-JSON rows from llm_interactions
(ape.reflect 1,879 / jiminy.evaluate_llm 18 / consulting.classify 1),
backup-first to .mdemg-backup-20260613_195431/dataprune/ (reversible).
102,415 -> 100,517, remaining_corrupt=0, live healthy, recent ape.reflect
14/14 valid.
Small-batch verify caught that the raw pg_input_is_valid predicate over-counted
by 213 (valid JSON behind markdown fences / think-tags that production
SanitizeResponse strips); validated all candidates through a faithful replica
of llmclient.SanitizeResponse and spared the recoverable 213. Categories B
(error rows) + C (file artifacts) deferred. The backup dir is untracked
(not committed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune-audit-001): Category B+C prune execution record
B: 21,254 error/silent-failure rows removed via mdemg data clean (4 spaces),
backed up first. C: rerank prefix-archive (6,894 events/21M, no refs) moved to
backup; valid_golden + ~14 baselines RETAINED (load-bearing — leak source +
regression harness; retire during baseline recompute). Final: llm_interactions
79,461 rows, 0 non-conforming. Verification catch documented: data clean
dry-run per-task table is surviving-rows, not the delete set.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(space): delete pre-check + list panic on non-MemoryNode / null-space data
Two bugs surfaced during the space-hygiene cleanup (removing 24 junk/test/demo
spaces for live testing):
1. `mdemg space delete` gated its pre-check on `count(MemoryNode {space_id})`
but the delete itself is label-agnostic (`MATCH (n {space_id})`). A space
holding only SymbolNodes/Observations (e.g. e2e-test = 10,918 SymbolNodes)
reported "no nodes. Nothing to delete." and silently survived. Pre-check now
counts all labels, matching the delete.
2. `ListSpaces` (`mdemg space list`) panicked — `interface conversion:
interface {} is nil, not string` at the `sid.(string)` assertion — when any
MemoryNode had a null space_id (orphaned/infra artifacts). The query now
excludes null/empty space_id (such nodes are not a "space"), and the
assertion is nil-guarded defensively.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(dataprune): Neo4j space hygiene record + CHANGELOG
24 junk/test/demo spaces removed (~143k nodes, backed up); blank-space resolved
(global infra kept null, 155 test MemoryNodes staged for delete); 2 space-tool
bugs fixed (delete pre-check, list panic). Record in space_cleanup.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-002): sprint plan — schema/reward-mismatch fixes (Epic 0)
The REWARD-CORRECTNESS-001 follow-ups: (1) hidden.summarize schema object->string
(production emits prose; 72 rows mis-flagged invalid-JSON); (2) explanation_quality
schema-aware for nested violations[].reasoning (fixes jiminy.evaluate +
evaluate_llm scoring correct responses 0.0); (3) keyword-bag specificity/
actionability substantive-floored (jiminy.synthesize valid guidance dropped for
lacking magic words). Makes the 4 tasks' grading correct before the baseline
recompute.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(reward-correctness-002): schema/reward-mismatch fixes (Epics 1-3) + live validation
Three reward/schema mismatches that scored CORRECT responses wrong:
1. hidden.summarize ULTS schema object->string. Production emits bare prose
(cluster_summarizer.go), so the object schema mis-flagged 72 valid summaries
as invalid-JSON. (Reward already fixed in RC-001; this corrects the spec.)
2. explanation_quality made schema-aware: jiminy.evaluate / evaluate_llm nest
reasoning in violations[].reasoning, not a top-level field, so the flat
lookup scored every correct response 0.0. Now credits nested reasoning and
treats a valid no-violation verdict as a correct "no issues" answer (nothing
to explain). Falls back to the flat path.
3. specificity_score / actionability_score substantive-floored (0.7 floor,
keyword presence a bounded bonus, hedging/empty/repetition low) — the
keyword-bag dropped valid concise guidance below the gate for lacking ~6
magic words. follow_rate inherits it.
Live Tier 3 (real production rows, old->new kept@0.8): jiminy.evaluate 0/60->60/60
(mean 0.667->0.967), jiminy.synthesize 3/60->59/60 (0.725->0.879), ape.reflect
47/60->60/60 (0.848->0.956); evaluate_llm unchanged 60/60. New means 0.88-0.97 =
correct production output scoring correctly, no over-inflation. 87 unit tests +
609 neural tests + ruff green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(reward-correctness-002): CHANGELOG + post (Epic 5 close)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs+fix(baseline-recompute-001): sprint plan (Epic 0) + rl_phase11 port 8101->8102 (Epic 1)
The capstone of the training-integrity arc: recompute the frozen 0.8338 baseline
through the fixed harness (valid_clean + RC-001/002 rewards + GGUF :8102). Epic 1
fixes the stale rl_phase11.yaml mlx_port (8101 mlx_lm.server decommissioned →
8102 llama-server GGUF), flagged by EVAL-INTEGRITY-001.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(baseline-recompute-001): honest baseline 0.8338->0.8655 via fixed harness (Epic 2-4)
Recomputed the adapter-promotion baseline through the fixed harness (valid_clean
leak-free eval + RC-001/002 corrected rewards + GGUF llama-server :8102) = 0.8655,
replacing the stale frozen 0.8338 (valid_golden-leaked, old length-biased rewards,
decommissioned MLX serving — not comparable). evaluate_gate_5a now derives the
target from the loaded baseline REPORT (single source of truth); the constant is
retained only as a >5pp drift tripwire. status ok, 12 tasks, 50 samples/task,
0 zero-call. ape.reflect 0.696 is an eval-harness artifact (stored ~7.5k-token
prompts bypass the runtime prompt budget and get cut off mid-JSON), not a model
regression.
Closes the training-integrity arc: trustworthy gate (EVAL-INTEGRITY-001),
correct rewards (REWARD-CORRECTNESS-001/002), sound corpus (APE-PROMPT-BUDGET-001
+ DATAPRUNE), honest baseline (this sprint).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-signal-001): sprint plan — honest Jiminy health signals (Epic 0)
P0, first of two sprints from the guidance-investigation. Fix the two lying
signals: (1) the false-positive "guidance not reaching agent" CRITICAL (fires on
a healthy Jiminy via an unassessed zero-value JiminyHealthy bool) — real probe +
SynergyAssessed guard + rename; (2) the inflated mdemg_jiminy_follow_rate gauge
(0.725 vs honest ~0.18 panels — Neo4j dedup-by-guidance_id double-credits
multi-outcome ids) — de-inflate so gauge/panels/RSIC agree. Behavioral
guidance-relevance/trust-decay fix deferred to JIMINY-EFFECTIVENESS-001 (needs
these honest signals to validate against).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-signal-001): honest Jiminy health signals (Epic 1-2)
Fix the two lying Jiminy health signals (root-caused by the guidance investigation):
Fix 1 — false-positive "guidance not reaching agent" CRITICAL. It is an RSIC
reflection insight (self_reflect.go) gated on !report.JiminyHealthy, but
JiminyHealthy is set only inside a synergy block (self_assess.go) that is
conditionally skipped — when skipped it stays at the Go zero-value false, so a
healthy, delivering Jiminy fired this CRITICAL ~8x/day. Added report.SynergyAssessed
(set true only when the block runs); guard the insight on it; renamed the alert
off the misleading "guidance not reaching agent" -> "Jiminy Service Unavailable —
catastrophic-forgetting risk". 3 unit tests pin the guard.
Fix 2 — inflated mdemg_jiminy_follow_rate gauge (0.725 vs the dashboard panels'
~0.27). The Neo4j Cypher count(DISTINCT CASE WHEN followed THEN guidance_id) /
count(DISTINCT guidance_id) double-credits multi-outcome guidance_ids. New
DatasetProvider.GuidanceEffectiveness reads constraint_outcomes TSDB with the
panels' exact math (followed=1, partial=0.5) over a config window
(RSIC_GUIDANCE_EFFECTIVENESS_WINDOW_HOURS, default 168=7d); the assessor overrides
js.FollowRate with it, so the gauge, RSIC GuidanceHealth, and the panels all
agree. Neo4j path retained as fallback with a disclosing comment. Making the gauge
honest correctly LOWERS RSIC's guidance health (it was masking the real problem).
Build + lint + config-scanner (688/688) clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(jiminy-signal-001): two live-smoke bugs — synergy default + the gauge's 2nd publisher (Epic 3)
Live Tier 3 caught two bugs the unit tests couldn't:
1. The false CRITICAL fired AGAIN after the first restart. Root cause one layer
below Fix 1: ReadSynergyMetrics (synergy_reader.go) set JiminyHealthy only
`if jiminyCheck != nil`, so a nil check left it at the zero-value false
(= "Jiminy down") and the now-guarded insight still fired. Default
JiminyHealthy=true; only a real check returning false marks it down (real
outages are covered by /healthz + the watchdog). +slog.Warn on nil check.
Verified live: 0 jiminy CRITICAL fires since the fixed restart.
2. The gauge stayed inflated (0.732) despite the assessor override running
correctly (diagnostic showed tsdb_rate=0.09, n=22). Root cause: the gauge has
TWO publishers — self_assess.go AND live_collectors.go (the 15s Prometheus
collector) — and the live collector republished the un-overridden Neo4j rate,
overwriting it. Extracted the override into Assessor.applyHonestFollowRate and
called it from BOTH paths. Verified live: gauge now 0.235, matching the TSDB
7d panel value (0.2353).
Also added a diagnostic log at the insight-fire point (surfaces the runtime
JiminyHealthy/SynergyAssessed values). go build + ape tests + lint clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-signal-001): CHANGELOG + post with live Tier 3 results (Epic 4)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* test(jiminy-signal-001): update NilJiminyCheck test to the new default-healthy semantics
TestFileSynergyReader_NilJiminyCheck asserted the OLD JiminyHealthy=false-on-nil
behavior, which the Epic-3 fix intentionally changed to default-healthy (a nil
check = "cannot determine" must not read as "down"). Updated the assertion to
pin the new correct behavior. (CI Test gate fix.)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(deps): bump golang.org/x/crypto v0.49.0 -> v0.52.0 (CVE-2026-39827/8/9)
Folded into #469 per operator request. golang.org/x/crypto v0.49.0 carries HIGH
CVE-2026-39827/39828/39829 (Go SSH channel-exhaustion DoS), fixed in v0.52.0 —
flagged by the Trivy Security Scan (already failing on main). go mod tidy pulled
the standard transitive bumps (x/net v0.54.0, x/sys v0.45.0, x/text v0.37.0).
go build ./... + go vet clean; no API changes. Unrelated to the JIMINY-SIGNAL-001
logic; included here to green the scan.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* fix(deps): bump golang.org/x/net v0.54.0 -> v0.55.0 (remaining HIGH CVEs)
The crypto bump's transitive x/net (v0.54.0) still had HIGH CVEs (CVE-2026-25680
et al., HTML-parse CPU exhaustion) fixed in v0.55.0 — Trivy showed go.mod 15->6
HIGH after the crypto bump, the remaining 6 all x/net. This completes the Go-side
remediation (Python uv.locks already scan clean). go build + vet clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-effectiveness-001): sprint plan — trust as recoverable EMA (Epic 0)
Re-scoped after live recon corrected the premise: surfaced guidance is NOT
confidence:0 junk (it's filtered >0.3, confidence 0.7-0.9, LLM-classified ~79%
ignored). The real J17-T1 blocker is the monotonic trust ratchet — 1343 ignores
floored trust at 0.04 with no recovery path. Fix: replace RecordOutcome's
ratchet with an EMA of per-outcome effectiveness (Followed=1.0/Partial=0.6/
Ignored=0.2/Contradicted=0.0, alpha config) so trust tracks recent effectiveness
and recovers when guidance is followed. Config JIMINY_TRUST_MODE (ema default,
ratchet rollback) + JIMINY_TRUST_EMA_ALPHA. Forward-only, self-healing.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-effectiveness-001): trust as a recoverable EMA, not a monotonic ratchet (Epic 1)
The J17-T1 blocker: TrustScorer.RecordOutcome was a monotonic ratchet (Followed
+0.05 / Ignored -0.02), so 1343 mostly-ignored outcomes floored the live session
at 0.04 with no recovery path — trust could never climb back to the 0.75 T1
threshold even if recent guidance became effective.
Replaced the ratchet with an EMA toward per-outcome effectiveness anchors
(Followed=1.0, Partial=0.6, Ignored=0.2, Contradicted=0.0):
trust ← trust + α·(target − trust). Now trust TRACKS RECENT EFFECTIVENESS and
RECOVERS: a floored session climbs back past 0.75 once guidance is followed,
while a genuinely all-ignored session converges to ~0.2 (honestly low, still <
0.75 — correctly kept out of T1). Config JIMINY_TRUST_MODE (ema default, ratchet
rollback) + JIMINY_TRUST_EMA_ALPHA (0.1). Forward-only — existing Neo4j scores
self-heal toward their recent regime.
7 new EMA unit tests (recovers-off-floor, steady-ignored-converges-low-not-zero,
tracks-recent-regime, ema-default); legacy ratchet tests pinned to Mode:ratchet
(both modes covered). Build + config-scanner (690/690) + lint clean.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-effectiveness-001): EMA trust recovery — post + CHANGELOG + J17 feature doc (Epic 3)
Epic 3 of JIMINY-EFFECTIVENESS-001. The Epic 1 code (EMA trust mode in
internal/jiminy/trust.go + JIMINY_TRUST_MODE/_EMA_ALPHA config) shipped in
the prior commit; this commit documents it and records the live Tier 3 result.
- post.md: premise correction (live recon overturned the confidence:0-junk
assumption — surfaced guidance is confidence 0.7-0.9, LLM-classified ~79%
ignored), the ratchet→EMA fix, and the live recovery proof.
- CHANGELOG: Unreleased entry.
- j17-ai2ai-protocol.md §3.4.1: new "Recoverable trust — EMA mode" subsection
(formula, anchors, config table, rollback note).
Live Tier 3: the actual floored mdemg-dev session (score=0.0 over 1,445
feedbacks under the old ratchet) was sent one real Followed via the live
POST /v1/jiminy/feedback wire and moved 0.0 -> 0.10 in live Neo4j — the EMA
signature 0.1*(1.0-0), proving the deployed binary recovers the previously-
pinned session off the floor. Full T1 promotion still awaits the guidance-
relevance work (Option B, disclosed follow-up).
Gates: build OK, jiminy+config tests pass, golangci-lint 0 issues,
config-scanner 690/690.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* docs(jiminy-relevance-001): diagnostic + coordinated sprint plans (Epic 0)
The Step-1 read-only diagnostic of the ignored-guidance population, plus two
coordinated sprint plans it produced:
- jiminy-relevance-001: persist the missing training evidence (guidance text +
action text + verdict — the binding gap the diagnostic found), raise label
quality, measure "should-follow" follow-rate honestly, curate the corpus.
The operator-decided 3–6 month collection infrastructure; NOT a retrain
(that's a documented future-trigger).
- hitl-review-001: a general-purpose human-in-the-loop dataset review + LIVE
reinforcement platform, native in the :9999 server, applicable to all
curated datasets. jiminy-relevance-001 Epic 3 is its first consumer.
Diagnostic verdict: we cannot retrain toward the >90% goal today because the
training evidence is stored nowhere (verdicts persisted everywhere, evidence
nowhere); root cause is guidance actionability (90% of surfaced guidance is
non-actionable abstractions; 172:1 abstraction:constraint graph ratio), not
model fluency; ~51% of labels are heuristic noise.
Coordinated pair: shared v0.11.0, migration 027 (second-to-merge renumbers to
028), ordering jiminy Epic 1 -> hitl-review-001 -> jiminy Epic 3.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-relevance-001): persist guidance training evidence (V0027 + writer) (Epic 1)
The binding gap from the Step-1 diagnostic (Finding 1): MDEMG persisted guidance
VERDICTS everywhere and EVIDENCE nowhere — the action_summary POSTed to
/v1/jiminy/feedback was classified then discarded, so every
(context -> surfaced-guidance -> did-the-agent-follow) training triple was
unrecoverable. This epic starts the collection clock (forward-only).
- Migration 027_guidance_training_rows.sql: new hypertable (time column = `time`,
matching constraint_outcomes + the TSDB-CONSUME-001 alert contract) carrying
EVIDENCE-ONLY columns — guidance_content snapshot, guidance_type, source
role_type/layer, action_summary, outcome_type, similarity, classifier_source,
constraint_code + CUIDv2 row_id. Retention 365d + compression after 30d
(V0025/TSDB-CONSUME-001 contract — no unbounded hypertable). NO gold-grading
columns: human/auto gold lives in HITL-REVIEW-001's review_grades, joined on
item_id == row_id. Schema 26 -> 27.
- internal/tsdb/guidance_training_rows_writer.go: buffered + CopyFrom, bounded
FIFO buffer + drop counter, registerWriterStats (joins mdemg_tsdb_writer_*),
optional Prometheus counters. Modeled on the V0022 reinforcement writer.
- 3 Prometheus counters: mdemg_guidance_corpus_rows_{enqueued,dropped}_total +
_flush_failure_total (internal/metrics).
- Config (no-hardcoding): GUIDANCE_CORPUS_ENABLED (true),
_WRITER_FLUSH_INTERVAL_SEC (30, floor 5), _WRITER_BUFFER_SIZE (1000, 0=unbounded),
_MAX_CONTENT_BYTES (8192, floor 256, UTF-8-safe truncation),
_SOURCE_LOOKUP_TIMEOUT_MS (300, 0=disable). Bump TSDB_REQUIRED_SCHEMA_VERSION 27.
- Emit from RecordOutcome per item (gated, all non-Unknown outcomes incl.
not_applicable as a valid relevance label); source role/layer via a bounded
best-effort Neo4j lookup (never blocks the hot path; "unknown" on miss); the
writer is async. jiminy.GuidanceTrainingWriter interface + api adapter avoid
the import cycle (mirrors OutcomeWriter).
Source role/layer resolution (the diagnostic's open option): chose a bounded
best-effort inline Neo4j lookup defaulting to 300ms, with source_node_id stored
for offline re-resolution if disabled/missed — disclosed in PR.
Gate G1: go build ./... green; 6 writer + 3 truncate unit tests pass (incl.
SourceLayer nil-vs-0 distinctness + CUIDv2 row_id); golangci-lint 0 issues;
config-scanner 695/695; TSDB schema-version CI gate consistent (27 == 27 files).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-relevance-001): guidance label-quality auto-relabel job (Epic 2)
Diagnostic Finding 4: ~51% of guidance outcome labels were non-LLM heuristic
defaults — noise a retrain would learn instead of the goal. This epic makes the
evidence corpus trustworthy.
- internal/api/guidance_audit.go: a supervised (SUPERVISOR-002 via goSupervised),
periodic auto-relabel job. Samples recent guidance_training_rows whose
classifier_source is heuristic/blank, re-classifies each with the LLM
OutcomeClassifier (reuses jiminy's classifier — no separate model), and
updates classifier_source/outcome_type/similarity IN PLACE. Reports each run
to jobhealth as job_name='guidance-audit'. Runs once GUIDANCE_AUDIT_INITIAL_
DELAY_SEC after startup (first cleanup doesn't wait a full interval), then on
GUIDANCE_AUDIT_INTERVAL_HOURS.
- shouldUpgradeLabel (pure, unit-tested 11 cases): upgrade ONLY when the OLD
label was heuristic/blank AND the NEW verdict came from a real mechanism
(llm/tier1/explicit) — never downgrades a real verdict, never churns, and
leaves rows untouched when the classifier itself falls back to heuristic
(LLM unavailable → try again next run).
- Gauge mdemg_guidance_corpus_heuristic_label_fraction: share of recent rows
still on a heuristic/blank label, refreshed each run — makes the 51%→0
progress observable (no silently leaving it at 51%).
- Config (no-hardcoding): GUIDANCE_AUDIT_ENABLED (true), _INTERVAL_HOURS (24,
floor 1), _SAMPLE_SIZE (50, floor 1), _INITIAL_DELAY_SEC (60, 0=skip initial).
Design note (disclosed): the audit reuses the existing jiminy OutcomeClassifier
rather than a separate GUIDANCE_AUDIT_MODEL — same production LLM, consistent
verdicts, less config surface.
Gate G2: go build green; 11-case shouldUpgradeLabel unit test; lint 0;
config-scanner 699/699.
LIVE TIER-3: restarted on the new binary; the initial audit run upgraded
sampled=12 / upgraded=12 heuristic-or-blank rows to real LLM verdicts in 23.4s
(synthetic seed row: heuristic/partial_compliance/0.32 -> llm/followed/0.80);
jobhealth scheduled_job_events row success=true {sampled:12,upgraded:12}; the
heuristic-fraction gauge emitted and now reads 0 (corpus 100% real-labelled).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
* feat(jiminy-relevance-001): should-follow follow-rate metric + panel + rule (Epic 4)
Diagnostic TL;DR #4: ">90% follow rate" as stated is partly the wrong target —
much guidance is CORRECTLY ignored (advisory abstractions). The meaningful,
reachable metric is "follow rate on guidance that SHOULD have been followed":
restrict the denominator to the ACTIONABLE types (constraint/correction — the
ones the diagnostic found are followed ~2x better) and exclude the
pattern/learning/concept advisory class.
- internal/alert/rules.go: GuidanceShouldFollowRules factory — config-driven
rule over guidance_training_rows. SQL contract (TSDB-CONSUME-001): aggregate
(AVG) + COALESCE so an idle/fresh-corpus window returns one non-NULL row
(1.0 → no false fire), reads the `time` column (NOT recorded_at),…
Summary
Development branch changes from
reh3376_dev01.Commits
mdemg modelCLI + pluggable Fetcher interfaceAuto-generated PR from reh3376_dev01 push