Skip to content

dev: reh3376_dev01 -> main#469

Merged
reh3376 merged 369 commits into
mainfrom
reh3376_dev01
Jun 23, 2026
Merged

dev: reh3376_dev01 -> main#469
reh3376 merged 369 commits into
mainfrom
reh3376_dev01

Conversation

@github-actions

Copy link
Copy Markdown
Contributor

Summary

Development branch changes from reh3376_dev01.

Commits

  • docs(jiminy-signal-001): CHANGELOG + post with live Tier 3 results (Epic 4)
  • fix(jiminy-signal-001): two live-smoke bugs — synergy default + the gauge's 2nd publisher (Epic 3)
  • feat(jiminy-signal-001): honest Jiminy health signals (Epic 1-2)
  • docs(jiminy-signal-001): sprint plan — honest Jiminy health signals (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • feat(baseline-recompute-001): honest baseline 0.8338->0.8655 via fixed harness (Epic 2-4)
  • docs+fix(baseline-recompute-001): sprint plan (Epic 0) + rl_phase11 port 8101->8102 (Epic 1)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(reward-correctness-002): CHANGELOG + post (Epic 5 close)
  • fix(reward-correctness-002): schema/reward-mismatch fixes (Epics 1-3) + live validation
  • docs(reward-correctness-002): sprint plan — schema/reward-mismatch fixes (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(dataprune): Neo4j space hygiene record + CHANGELOG
  • fix(space): delete pre-check + list panic on non-MemoryNode / null-space data
  • docs(dataprune-audit-001): Category B+C prune execution record
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(dataprune-audit-001): record Category A prune execution + CHANGELOG (Epic 2 close)
  • docs(dataprune-audit-001): non-destructive audit of non-conforming data (Epic 0-1)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • fix(ape-prompt-budget-001): re-pin ape_reflect ULTS source line after import shift
  • docs(ape-prompt-budget-001): feature doc + CHANGELOG + post + CLAUDE.md (Epic 5)
  • feat(ape-prompt-budget-001): bound ape.reflect prompt to protect output (Epic 1-2)
  • docs(ape-prompt-budget-001): sprint plan + recon (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • Merge remote-tracking branch 'origin/reh3376_dev01' into reh3376_dev01
  • docs(reward-correctness-001): CHANGELOG + sprint post (Epic 6 close)
  • feat(reward-correctness-001): per-task inclusion thresholds + live findings (Epic 2-3)
  • feat(reward-correctness-001): length-neutral correctness rewards (Epic 1)
  • docs(reward-correctness-001): sprint plan (Epic 0)
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • fix(eval-integrity-001): restore ape_reflect system_prompt_source to clean file:line
  • Merge branch 'reh3376_dev01' of https://github.com/reh3376/mdemg into reh3376_dev01
  • docs(eval-integrity-001): feature doc + CHANGELOG + post (Epic 7)
  • feat(eval-integrity-001): leak-audit gate target (Epic 2)
  • fix(eval-integrity-001): wire LLM recorder in ingest process — capture summarize.generate (Epic: capture-gap)
  • feat(eval-integrity-001): hard-fail on zero successful calls (Epic 3)
  • feat(eval-integrity-001): wire gate to leak-free 12-task valid_clean (Epic: gate-set)
  • fix(eval-integrity-001): respect dynamic_prompt — recover enum-templated tasks' production rows (Epic 1)
  • docs(eval-integrity-001): sprint plan + recon (Epic 0)
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • Merge branch 'reh3376_dev01' of https://github.com/reh3376/mdemg into reh3376_dev01
  • docs(sidecar-loop-001): feature doc + CHANGELOG; archive mislabeled pre-fix data
  • docs(training-audit): root-cause the 3 discarded retrains — corpus filter + eval + DPO all mislabeled
  • fix(sidecar-loop-001): collector logs aligned reranked candidates + guard (Epic 1)
  • docs(sidecar-loop-001): sprint plan + recon (Epic 0) — fix-but-defer
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • Merge branch 'reh3376_dev01' of https://github.com/reh3376/mdemg into reh3376_dev01
  • docs(negfeed-cooler-001): feature doc + CHANGELOG + post (Epic 6)
  • feat(cooler-001): graduated-incident edges resist decay (Epic 4)
  • feat(cooler-001): unify graduation onto the Context Cooler (Epic 3)
  • feat(negfeed-001): anti-Hebbian producer — Jiminy contradicted bridge + MCP memory_reject (Epic 2)
  • feat(negfeed-001): CoactivateSession off-request + delta emission (Epic 1)
  • docs(negfeed-cooler-001): sprint plan + live-verified recon (Epic 0)
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • Merge branch 'reh3376_dev01' of https://github.com/reh3376/mdemg into reh3376_dev01
  • docs(readme): coverage-gap additions per 3-lane team review (operator-approved)
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(context-live-001): UVTS A/B artifacts + feature doc + CHANGELOG + post (Epics 5-6)
  • feat(context-live-001): server-side query-fingerprint derivation default-on (Epic 4)
  • feat(context-live-001): classifier→category dispatch on live traffic (Epic 3)
  • feat(context-live-001): stage-6 fingerprint heal + Phase-B refine wired (Epic 2)
  • feat(context-live-001): version guard + honest consensus denominator (Epic 1)
  • docs(context-live-001): sprint plan + recon findings (Epic 0)
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • fix(dormant-census-001): re-pin capability_gaps_full spec hash after variant removal
  • docs(dormant-census-001): feature doc + CHANGELOG + sprint post (Epic 5)
  • fix(jiminy): codegen collision-path self-deadlock wedged constraint observes
  • feat(dormant-census-001): prune 4 verified-dead routes + handlers (Epic 3)
  • feat(dormant-census-001): wire SignalLearner.GetStrength into Guide() ordering (Epic 2)
  • feat(dormant-census-001): adjudicated 187-route inventory + merge-blocking CI gate (Epic 1b)
  • feat(dormant-census-001): route↔consumer gate + 187-route inventory skeleton (Epic 1a)
  • docs(dormant-census-001): sprint plan — the standing dormancy guarantee
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(jiminy-budget-001): CHANGELOG + post (Epic 4)
  • feat(jiminy-budget-001): budget derivation, session attribution, surface/outcome split, token floors (Epics 1-3)
  • docs(jiminy-budget-001): sprint plan
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(surprise-topk-001): post + CHANGELOG; threshold recalibration + loud error path (Epics 3-4)
  • feat(surprise-topk-001): config-driven surprise multiplier thresholds at both Cypher sites (Epic 2)
  • feat(surprise-topk-001): exact top-K nearest-neighbor embedding novelty (Epic 1)
  • docs(surprise-topk-001): sprint plan — honest novelty + a multiplier that can fire
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • fix(doc-audit-001a): execute the approved fix batch
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • Merge branch 'reh3376_dev01' of https://github.com/reh3376/mdemg into reh3376_dev01
  • docs(ft-classify-002): CHANGELOG + post — no-promote accepted, sprint closed (Epic 6)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(ft-classify-002): gate results — 2/3 PASS, no-promote recommended
  • docs(doc-audit-001): amended charter per 3-lens review team (operator-approved)
  • docs(ft-classify-002): stage-by-stage run record (6a instrumentation input)
  • fix(ft-classify-002): benchmark config still pointed at decommissioned mlx port 8101
  • feat(ft-classify-002): training config — fresh task-delta LoRA on the production fused model (Epic 2)
  • feat(ft-classify-002): class-stratified distill capture + the real 11.5d root cause (Epic 1)
  • Merge branch 'reh3376_dev01' of https://github.com/reh3376/mdemg into reh3376_dev01
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(ft-classify-002): sprint plan — distribution-matched consulting.classify distillation
  • fix(tsdb): time-scope V0014's backfill + integrity assertion to its historical window
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(mcp-revive-001): feature doc + CHANGELOG + CLAUDE.md + post (Epic 5)
  • feat(mcp-revive-001): eventgraph + strict MCP tools, plugin-orphan reaper (Epics 3+4)
  • test(mcp-revive-001): contract suite for the MCP tool surface (Epic 2)
  • feat(mcp-revive-001): space resolution chain across all MCP memory tools (Epic 1)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • fix(jiminy-outcome-002): Tier-2 not_applicable + verdict provenance
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(ft-recursive-000): recursive-retraining loop as-built audit + buildable spec
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(tsdb-consume-001): record the Epic-5 live-smoke catch in post.md
  • fix(tsdb-consume-001): emergence-cycle gauge writers actually land in hidden/service.go
  • docs(tsdb-consume-001): feature doc + CHANGELOG + CLAUDE.md + post (Epic 8)
  • ci: reclaim runner disk before docker-publish builds
  • fix(tsdb-consume-001): remove the 4 reader-without-writer ft_* dashboard panels (Epic 7)
  • feat(tsdb-consume-001): V0020 writer-or-drop — decided: writer (Epic 6)
  • feat(tsdb-consume-001): guidance_conflicts counter + emergence-cycle duration tripwire (Epic 5)
  • feat(tsdb-consume-001): scorer-drift tripwires over retrieval_audit (Epic 4)
  • feat(tsdb-consume-001): writer flush stats → metrics + flush-failure rule (Epic 3)
  • fix(tsdb-consume-001): honest metrics plane — windowed percentiles, real pool gauges, latency rules over real wall-time (Epic 2)
  • feat(tsdb-consume-001): V0025 retention+compression for unbounded hypertables (Epic 1)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(config-deadflag-001): CHANGELOG + post — sprint complete
  • feat(config-deadflag-001): full triage — 28 wired, 29 deleted, 0 allowlisted (Epic 2) + CI guard (Epic 4a)
  • feat(config-deadflag-001): enforce EVENTGRAPH_MAX_PAIRS_PER_EVENT_BATCH (first triage wire)
  • docs(config-deadflag-001): sprint plan (Epic 0)
  • feat(config-deadflag-001): strict getBool + un-swallow LoadYAMLConfig + consumer scanner (Epics 1+3)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(doc-truth-002): MoE-as-current residual cleanup (modified per review)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • fix(uxts-ci-001): UBENCH CI gate → lint mode; holdout absence tolerated, mismatch still fatal
  • fix(uxts-ci-001): add tiktoken + pyyaml to the neural CI job deps
  • docs(uxts-ci-001): CHANGELOG + CLAUDE.md CI-gate inventory + post — sprint complete (Epics 5-6)
  • feat(uxts-ci-001): drift checker covers all 15 frameworks, matrix refreshed, gate un-zombied (Epic 4)
  • feat(uxts-ci-001): neural pytest+ruff CI job — first run caught two silent failures (Epic 3b)
  • feat(uxts-ci-001): TSDB in CI, un-zombie UOTS, delete UVTS step, ULTS hash gate live (Epics 1-3a)
  • docs(uxts-ci-001): sprint plan (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • fix(uats-gap-001): tag breaker reset round-trip llm_required — CI has no openai-embeddings breaker
  • feat(uats-gap-001): 8 contract specs for the revived channels + suite hygiene (Epics 1-6)
  • fix(transfer): nil-guard edge identity assertions — one bad edge panicked the whole server (UATS-caught P0)
  • fix(jiminy): reformulate returns 400 (not 500) for missing context
  • docs(uats-gap-001): sprint plan (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • fix(tsdb): initial backup on start — restart-resetting ticker meant zero backups ever
  • fix(rsic-storm-001): commit ExecuteTombstoneStaleForTest wrapper missed from Epic F
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(rsic-storm-001): feature doc + verification + rollback drill test + CHANGELOG + CLAUDE.md — sprint complete (Epic F)
  • fix(rsic-storm-001): attributable archival + unified tombstone predicate (Epics B+C)
  • fix(rsic-storm-001): atomic trigger admission — reserve-on-allow (Epic A)
  • docs(rsic-storm-001): sprint plan with corrected burst attribution (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(backup-restore-verify-001): feature doc + verification + CHANGELOG + CLAUDE.md — sprint complete (Epic 7)
  • fix(transfer): omit empty path/name on import — restores with observations always failed (drill-caught)
  • fix(backup-restore-verify-001): retention was deleting every backup it just made (drill-caught)
  • feat(backup-restore-verify-001): initial backup on start (rule honesty)
  • feat(backup-restore-verify-001): neo4j-backup jobhealth + generalized staleness rules (Epic 5)
  • feat(backup-restore-verify-001): harden the restore path (Epics 1-4)
  • docs(backup-restore-verify-001): sprint plan (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(supervisor-002): feature doc + verification + CHANGELOG + CLAUDE.md — sprint complete (Epic 6)
  • fix(supervisor-002): streak-relative global-outage discriminator (drill-caught)
  • fix(jiminy): detach feedback outcome processing from the hook's connection lifetime
  • feat(supervisor-002): aggregate evaluator-degraded alert for global outages
  • fix(supervisor-002): nolint G118 on legacy-fallback loop launches
  • fix(supervisor-002): recency-gate the RSIC llm_error_rate_spike insight (Epic 4)
  • feat(supervisor-002): rule-health meta-alert on evaluator query failures (Epic 3)
  • feat(supervisor-002): register the 12 unsupervised background loops (Epic 2)
  • feat(supervisor-002): sliding-window restart budget + late registration (Epic 1)
  • docs(supervisor-002): sprint plan + background loop inventory (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(hidden-churn-001): PR-B verification + CHANGELOG + CLAUDE.md — sprint complete (Epic B5)
  • fix(hidden-churn-001): live-smoke fixes — noise pool was structurally empty, clustering included archived debris, coverage gauge gated on min-obs
  • feat(hidden-churn-001): surface themes_updated + noise_assigned in consolidate API + periodic log (Epic B3)
  • feat(hidden-churn-001): mdemg concepts repair + trace — grounding audit CLI (Epic B2)
  • feat(hidden-churn-001): PR-B coverage retune — config ratio, density assignment, gauge + rule (Epic B1)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(hidden-churn-001): PR-A verification + CHANGELOG (Epics A3-A4)
  • chore(hidden-churn-001): remove the dead global-detach helper — the churn mechanism itself
  • feat(hidden-churn-001): stable theme identity — centroid match-or-create replaces the 5-minute churn (Epic A2)
  • fix(hidden-churn-001): automated consolidation no longer skips LLM emergence (Epic A1)
  • docs(hidden-churn-001): sprint plan — stable concept identity, two-PR delivery (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(rrf-scale-002): live-calibrated suggest floor + CHANGELOG + close (Epics 5-6)
  • fix(rrf-scale-002): CacheKey covers ALL result-affecting fields + two forcing functions (Epics 3-4)
  • fix(rrf-scale-002): config-driven score thresholds — suggest revival, MCP tiers, guardrail floor (Epic 2)
  • fix(rrf-scale-002): persistent rerank clients — failure alerting re-armed on the hottest LLM path (Epic 1)
  • docs(rrf-scale-002): sprint plan — finish the score-scale contract (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • test(rsic-validate-001): integration seeds carry session linkage for the scoped tombstone contract
  • docs(rsic-validate-001): Tier 3 verification + CHANGELOG + close (Epics 5-6)
  • fix(rsic-validate-001): counter-free confidence calibration — RSIC stops polluting its own signal (Epic 4)
  • fix(rsic-validate-001): tombstone_stale scoped to correction-linked nodes; refresh_stale_edges decays for real (Epic 3)
  • fix(rsic-validate-001): honest criteria evaluation — populated keys + fail-closed mutations (Epics 1-2)
  • docs(rsic-validate-001): sprint plan — fail-closed self-improvement (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(doc-truth-001): last stale --adapter help string (sweep straggler)
  • docs(doc-truth-001): grep-sweep proof + CHANGELOG + close (Epic 4)
  • docs(doc-truth-001): 00_README STATUS block + AGENT_HANDOFF retired (Epic 3)
  • fix(doc-truth-001): operator-facing text matches the Phase 13.5 reality (Epic 2)
  • docs(doc-truth-001): CLAUDE.md FT section rewritten to post-pivot reality (Epic 1)
  • docs(doc-truth-001): sprint plan — documentation matches reality (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(embed-wire-001): live verification + CHANGELOG + CLAUDE.md + close (Epics 3-4)
  • fix(ingest-exec-001): server-triggered ingest resolves the mdemg binary — was hardcoded ./bin/mdemg (Epic 2)
  • fix(embed-wire-001): breaker + recorder reach the real embedder through the wrapper chain (Epic 1)
  • docs(embed-wire-001): sprint plan — embedder wiring + ingest exec resolution (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs: CLAUDE.md architecture note for MAINT-LIVE-001
  • docs(maint-live-001): first live run verification + feature doc + CHANGELOG + close (Epics 4b-5)
  • fix(prune): orphan sweeps use implicit transactions for batched deletes
  • feat(maint-live-001): context-dependent orphan policy — --exclude-role-types (Epic 4a)
  • feat(maint-live-001): mdemg upgrade refreshes installed LaunchAgents + hooks (darwin) (Epic 3)
  • feat(maint-live-001): maintenance_no_live_run evaluator rule (Epic 2)
  • fix(maint-live-001): scheduled maintenance runs live — plist passes --dry-run=false (Epic 1)
  • docs(maint-live-001): sprint plan — scheduled maintenance actually runs (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • docs(hidden-weight-001): Tier 3 verification + corpus restoration + UVTS harness audit + close (Epics 4-5)
  • fix(ingest): config-driven consolidation timeout — was sharing the 300s batch budget
  • feat(hidden-weight-001): null-weight gauge + regression alert rule (Epic 3)
  • feat(hidden-weight-001): mdemg graph backfill-weights — heal 56k NULL abstraction weights (Epic 2)
  • fix(hidden-weight-001): abstraction-edge weights — vector.similarity.cosine replaces point.distance (Epic 1)
  • docs(hidden-weight-001): sprint plan — real weights on the abstraction hierarchy (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • fix(ci): track .claude/hooks/pre-write-check.py so hook-parity check passes
  • fix(uats): jiminy_guide_sanitized timeout 30s → 90s — stale vs synthesis latency
  • docs(hooksync-001): Tier 3 verification + feature doc + CHANGELOG + close (Epics 7-8)
  • fix(hooksync-001): PORT-TRUTH — loopback bind defaults + sidecar zombie replaced (Epic 6)
  • feat(hooksync-001): mdemg hooks doctor — one-shot hook-channel triage (Epic 5)
  • feat(hooksync-001): hook-channel absence detection — the channel now self-reports outages (Epic 4)
  • feat(hooksync-001): alert Cleared lifecycle — display once, then delivered (Epic 3)
  • ci(hooksync-001): hook-template parity gate — live hooks must match templates (Epic 2)
  • fix(hooksync-001): reconcile bidirectional hook drift — alert delivery restored to live (Epic 1)
  • docs(hooksync-001): sprint plan — drift-proof + self-monitoring hook channel (Epic 0)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • Merge remote-tracking branch 'origin/reh3376_dev01' into reh3376_dev01
  • docs(hookwire-001): Tier 3 verification + CHANGELOG + CLAUDE.md contract pin + close (Epics 4-5)
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • fix(hookwire-001): pre-compact transcript extraction reads the real line shape (Epic 3)
  • fix(hookwire-001): post-tool-observe reads tool_response — end blind "succeeded" observations (Epic 2)
  • fix(hookwire-001): prompt-context.sh reads .prompt — revive the per-prompt channel (Epic 1)
  • docs(hookwire-001): sprint plan — fix hook stdin contract, reconnect per-prompt channel (Epic 0)
  • docs(roadmap): Q3 2026 vision-derived roadmap from 26-agent codebase deep-dive
  • chore: sync reh3376_dev01 with main (post-merge base advance)
  • ci: auto-sync dev branch with main after each squash-merged PR
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • Merge origin/main into reh3376_dev01 (resolve PR dev: reh3376_dev01 -> main #419 conflicts)
  • docs(eventgraph-004): feature doc + CHANGELOG + UATS pin + close (Epic 3)
  • docs(eventgraph-004): Tier 3 live verification — contradict create/re-match + weaken unchanged (Epic 2)
  • feat(eventgraph-004): wire ApplyNegativeFeedback contradict path → reinforcement_events (Epic 1)
  • docs(eventgraph-004): sprint plan + CoactivateSession post-revival health review (Epic 0)
  • docs(eventgraph-003): Tier 3 verification + feature doc + CHANGELOG + close (Epic 4)
  • fix(conversation): inject learning service so CoactivateSession actually runs
  • feat(eventgraph-003): wire ApplyNegativeFeedback weaken path → reinforcement_events (Epic 3)
  • feat(eventgraph-003): wire ApplySymbolCoactivation into reinforcement_events (Epic 2)
  • feat(eventgraph-003): wire CoactivateSession into reinforcement_events (Epic 1)
  • docs(eventgraph-003): sprint plan — reinforcement coverage for other Hebbian paths
  • chore(submodule): bump homebrew-mdemg to v0.10.1 formula
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • docs: governance system doc + bring cli/api references current
  • release: cut v0.10.1
  • feat(hooks): add pre-write-check.py to the tracked installer
  • feat(hooks): per-conversation SessionID — installed hook copies
  • feat(hooks): per-conversation SessionID across hooks + skill
  • docs(jiminy-governance): commit install-ready skill + install README
  • feat(jiminy-governance): ship the J17 governance skill + register MDEMG MCP
  • docs(jiminy-governance): resolve skill wire-up against the real instance
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • docs(roadmap): add jiminy-governance skill build-out (Workstream C, Action 7)
  • fix(nosilent-001): sync embedded launchd server plist with source (CI)
  • docs(nosilent-001): feature doc + CHANGELOG + CLAUDE.md + close (Epic 4)
  • fix(nosilent-001): distinct services for job rules so neither masks the other
  • feat(nosilent-001): scheduled-job staleness + failure alert rules (Epic 3)
  • feat(nosilent-001): record + alert on scheduled-job outcomes (Epic 2)
  • feat(nosilent-001): V0024 scheduled_job_events + writer (Epic 1)
  • docs(nosilent-001): sprint plan — fail-loud scheduled jobs (Epic 0)
  • fix(metrics,backup): resolve docker binary robustly under minimal launchd PATH
  • docs(eventgraph-002): feature doc + CHANGELOG + CLAUDE.md + close (Epic 7)
  • docs(eventgraph-002): Tier 3 live verification (Epic 6)
  • test(eventgraph-002): UATS contract spec for guidance-outcome federation (Epic 5)
  • feat(eventgraph-002): mdemg eventgraph guidance-outcome-neighborhood CLI (Epic 4)
  • feat(eventgraph-002): guidance-outcome federation handler + route (Epic 3)
  • feat(eventgraph-002): GuidanceOutcomesInNeighborhood federation method (Epic 2)
  • feat(eventgraph-002): V0023 constraint_code index on constraint_outcomes (Epic 1)
  • docs(eventgraph-002): sprint plan — guidance-outcome federation (Epic 0)
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • fix(eventgraph-cli-001): tag UATS spec 'tsdb' so CI skips it without TSDB
  • docs(eventgraph-cli-001): live verification + feature doc + CHANGELOG + close (Epic 3)
  • test(eventgraph-cli-001): UATS contract spec for federation API (Epic 2)
  • fix(eventgraph): neighbor_node_ids serializes as [] not null for empty neighborhood
  • feat(eventgraph-cli-001): mdemg eventgraph reinforcement-neighborhood (Epic 1)
  • docs(eventgraph-cli-001): sprint plan — federation consumer CLI + UATS backfill
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • fix(jiminy): /guide 30s timeout sibling + single-source config defaults (GUIDANCE-SYNTH-001 fix-commit)
  • docs(followup-c): close JSON control-char escaping as NON-ISSUE (no fix)
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • test+docs(guidance-synth-001): Tier 2/3 verification + docs + close (Epic 3)
  • feat(jiminy): config-drive warm-compute timeout (GUIDANCE-SYNTH-001 Epic 2)
  • feat(consulting): parallelize per-node constraint classifier (GUIDANCE-SYNTH-001 Epic 1)
  • docs(guidance-synth-001): sprint plan — fix guidance synthesis timeout (Follow-up B)
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • docs(jiminy-outcome-001): CHANGELOG + CLAUDE.md + post.md (Epic 3)
  • test(jiminy-outcome-001): Tier 2 integration + Tier 3 live verification (Epic 2)
  • feat(jiminy): embedding-similarity constraint-code matching (JIMINY-OUTCOME-001 Epic 1)
  • docs(jiminy-outcome-001): sprint plan — revive Neo4j GUIDANCE_OUTCOME sink
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • test(rrf-scale-001): skip guidance integration tests on empty environment (CI fix)
  • docs(rrf-scale-001): CHANGELOG + CLAUDE.md score-scale contract + post.md (Epic 5)
  • test(rrf-scale-001): Tier 2 integration + Tier 3 live verification (Epic 4)
  • docs(rrf-scale-001): Epic 3 — remaining LOW findings reviewed + decided
  • fix(consulting): RRF-calibrate score gates + confidence sigmoid (RRF-SCALE-001 Epic 2)
  • docs(rrf-scale-001): Epic 1 audit findings — 12 sites cataloged
  • docs(rrf-scale-001): sprint plan — RRF score-scale consumer remediation
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • fix(eventgraph-001): Grafana panel uses TSDB instead of unconfigured Prometheus datasource
  • Merge branch 'main' into reh3376_dev01
  • docs(eventgraph-001): feature doc + CHANGELOG + CLAUDE.md + sprint close (Epic 8)
  • docs(eventgraph-001): Tier 3 live e2e verification transcript (Epic 7)
  • fix(retrieval): set Activation on RRF RetrieveResult (EVENTGRAPH-001 fix-commit)
  • fix(eventgraph-001): restore full GRAFANA-AUDIT-001 audit_results.json
  • feat(observability): Grafana panel + Prometheus counters for reinforcement events (EVENTGRAPH-001 Epic 6)
  • feat(eventgraph): federation query helper + API endpoint (EVENTGRAPH-001 Epic 5)
  • feat(learning): record reinforcement events to TSDB (EVENTGRAPH-001 Epic 4)
  • refactor(learning): expose per-pair telemetry from Hebbian Cypher (EVENTGRAPH-001 Epic 3)
  • feat(tsdb): buffered reinforcement_events writer (EVENTGRAPH-001 Epic 2)
  • feat(tsdb): V0022 reinforcement_events hypertable (EVENTGRAPH-001 Epic 1)
  • docs(eventgraph-001): sprint plan (Pattern Y1 TSDB-federation)
  • docs(model-dist-002): flip adapter section to shipped + sprint close
  • feat(cli): enable mdemg model pull --adapter (MODEL-DIST-002 Epic 5+6)
  • feat(model-dist-002): Epic 4 local — Modelfile.adapter + ollama create
  • feat(model-dist-002): Epic 1-3 — MLX adapter → PEFT → GGUF LoRA + live verify
  • feat(model-dist-002): Epic 0 — sprint plan + workspace prep
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • docs(grafana-audit): Epic 4 + 7 — feature doc + sprint close
  • fix(grafana): Epic 3 — 5 panels recovered (3 FAIL + 2 schema-drift)
  • feat(grafana-audit): Epic 1 + 2 — full audit + findings
  • feat(grafana-audit): Epic 0 — sprint plan + audit harness
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • docs(api): document 19 previously-undocumented endpoints (follow-up Implement Learning Loop - ApplyCoactivation #2)
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • feat(cli): add mdemg model run wrapper (follow-up Edge Weight Decay CLI Command #1 to MODEL-DIST-001)
  • chore(submodule + docs): bump homebrew-mdemg to v0.10.0 + cli-reference Model Distribution section
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • docs(release): promote Unreleased -> v0.10.0
  • merge: resolve quant_manifest.json conflicts (Epic 3 closeout vs squashed main)
  • docs(model-dist-001): sprint close — post.md
  • feat(model-dist-001): Epic 3 closeout — Ollama Library push complete
  • docs(model-dist-001): Epic 8 — Documentation Update (main repo)
  • docs(model-dist-001): Epic 7 — local-model-distribution feature doc
  • feat(model-dist-001): Epic 5 — V0021 model_install_events hypertable + writer
  • feat(model-dist-001): Epic 4 — mdemg model CLI + pluggable Fetcher interface
  • feat(model-dist-001): Epic 3 — 3 Modelfiles + local ollama create (push pending)
  • docs(model-dist-001): Epic 2 — defer adapter to MODEL-DIST-002
  • feat(model-dist-001): Epic 1 — built Q4_K_M + Q8_0 fused GGUFs
  • docs(sprint): MODEL-DIST-001 sprint plan + quant manifest skeleton
  • fix(service): replace decommissioned mlx-server LaunchAgent with llama-server
  • fix(api): /healthz returns build-time version, not stale literal "0.6.0"
  • chore(submodule): bump homebrew-mdemg to v0.9.0 formula + docs
  • Merge remote-tracking branch 'origin/main' into reh3376_dev01
  • docs(release): promote Unreleased -> v0.9.0

Auto-generated PR from reh3376_dev01 push

rhenley1958 and others added 30 commits June 10, 2026 21:31
…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>
github-actions Bot and others added 12 commits June 15, 2026 13:10
…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>
@github-actions github-actions Bot requested a review from reh3376 as a code owner June 23, 2026 15:43
@reh3376

reh3376 commented Jun 23, 2026

Copy link
Copy Markdown
Owner

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 !report.JiminyHealthy, which is set only inside a synergy block that's conditionally skipped; when skipped, the Go zero-value false (= "down") fired the CRITICAL. Added report.SynergyAssessed + guarded the insight; renamed the alert to "Jiminy Service Unavailable — catastrophic-forgetting risk".

Live Tier 3 caught a second instance one layer belowReadSynergyMetrics defaulted JiminyHealthy=false when jiminyCheck was nil. Fixed by defaulting to healthy (real outages covered by /healthz + watchdog). Verified: 0 fires post-fix.

Fix 2 — inflated follow-rate gauge

mdemg_jiminy_follow_rate = 0.725 vs the dashboard panels' ~0.24 — the Neo4j 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 over a config window (RSIC_GUIDANCE_EFFECTIVENESS_WINDOW_HOURS, default 7d); Assessor.applyHonestFollowRate overrides it.

Live Tier 3 caught the gauge has two publishers (the assessment path + the 15s live_collectors.go Prometheus collector) — the live collector was overwriting it back to 0.732. Both now apply the override. Verified: gauge 0.732 → 0.235, matching the TSDB panel (0.2353).

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.

Notes

3 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

internal/ape/{self_reflect,self_assess,task_dispatch,types_rsic,synergy_reader,live_collectors}.go, internal/jiminy/stats.go, internal/tsdb/dataset_builder.go, internal/config/config.go, docs/development/jiminy-signal-001/{sprint_plan,post}.md, CHANGELOG.md.

…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>
@reh3376 reh3376 self-assigned this Jun 23, 2026
reh3376
reh3376 previously approved these changes Jun 23, 2026
…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>
reh3376
reh3376 previously approved these changes Jun 23, 2026
…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>
@reh3376 reh3376 merged commit 13268ff into main Jun 23, 2026
18 checks passed
reh3376 pushed a commit that referenced this pull request Jul 1, 2026
* 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…
reh3376 pushed a commit that referenced this pull request Jul 3, 2026
* 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…
reh3376 pushed a commit that referenced this pull request Jul 3, 2026
* 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…
reh3376 pushed a commit that referenced this pull request Jul 3, 2026
* 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),…
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants