Skip to content

feat(jme): Phase 4 — auto-prune low-confidence facts when vector ceiling is active - #31

Closed
PiotrCoderDroid wants to merge 1 commit into
mainfrom
jarvis/feat/jme-phase4-auto-prune
Closed

feat(jme): Phase 4 — auto-prune low-confidence facts when vector ceiling is active#31
PiotrCoderDroid wants to merge 1 commit into
mainfrom
jarvis/feat/jme-phase4-auto-prune

Conversation

@PiotrCoderDroid

Copy link
Copy Markdown
Collaborator

JME Phase 4 — Auto-prune on ceiling

Problema

JME utilidad en day-14 (2026-07-27): 13.3% global (gate: ≥39%). Hipótesis: a medida que jme_facts crece, facts marginales (baja confianza, desactualizados) llenan el LIMIT 500 del query window en queryMemory(), desplazando facts útiles y degradando el recall.

Phase 3 ya medía el ceiling y emitía un warn. Phase 4 actúa sobre él.

Cambios

src/memory/jme.ts

  • Nueva constante PHASE4_CONFIDENCE_FLOOR = 0.5: umbral más agresivo que el baseline 0.4 de pruneExpiredFacts(). Solo se usa cuando el ceiling está activo.
  • ConsolidateResult.factsPruned: number: nuevo campo para observabilidad.
  • Auto-prune en consolidateAll(): cuando embeddingCount >= VECTOR_CEILING_WARN (400), ejecuta DELETE (expired + confidence < 0.5 + ts > 30d ago) antes de la extracción Haiku. Conteo en result.factsPruned.

src/rituals/scheduler.ts

  • Log de jme-consolidate incluye , N p4-pruned cuando Phase 4 actuó (omitido cuando factsPruned == 0).

src/memory/jme.test.ts

  • Test 1: inserta exactamente 400 facts con embedding + baja confianza → factsPruned >= 400, fact de alta confianza sobrevive.
  • Test 2: 399 facts (bajo ceiling) → factsPruned == 0.

Invariantes

  • pruneExpiredFacts() standalone no cambia (threshold 0.4 intacto).
  • El auto-prune corre antes del Haiku call — no toca el path de extracción.
  • Idempotente: si no hay elegibles, factsPruned = 0, log sin cambio.

Tests

  • jme.test.ts: 36/36 ✅ (2 tests nuevos de Phase 4)
  • Typecheck: PASS
  • Nota: 2 fallos pre-existentes en scheduler.test.ts (pm-daily-rebalance) — verificados en main sin mis cambios, no relacionados con este PR.

…ing is active

When consolidateAll detects embeddingCount >= VECTOR_CEILING_WARN (400),
it now runs an auto-prune DELETE before the Haiku extraction, removing
expired facts AND facts with confidence < PHASE4_CONFIDENCE_FLOOR (0.5)
older than 30 days. This clears marginal facts before they fill the
LIMIT 500 query window in queryMemory() and displace useful recalls.

Changes:
- jme.ts: PHASE4_CONFIDENCE_FLOOR=0.5 constant; ConsolidateResult.factsPruned
  field; auto-prune block in consolidateAll() when ceiling active
- scheduler.ts: log includes ', N p4-pruned' tag when Phase 4 fired
- jme.test.ts: 2 new Phase 4 tests (36/36 green)

Closes JME Phase 4 (pending post-day-14 veredicto decision).
@kosm1x

kosm1x commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Closing without merging. The change is inert against live data — every one of its three conditions matches an empty set today, so merging it would add a destructive DELETE path that can never fire for the reason it was written.

Measured on data/mc.db at the time of review (129 jme_facts rows, all embedded):

condition live reality
fires when embeddingCount >= VECTOR_CEILING_WARN (400) 129 — ~34 days away at the current ~8 facts/day
confidence < PHASE4_CONFIDENCE_FLOOR (0.5) 0 rows — the minimum confidence in the table is 0.72
ts < 30 days ago 0 rows — the oldest fact is 16 days old

All 10 rows the predicate would remove come from the expires_at clause, which already existed. Raising the floor 0.4 → 0.5 changes the outcome for exactly zero facts, and the extractor has never emitted a confidence below 0.72 across the whole table.

Three specific problems, independent of the above:

  1. It forks a function that already exists and is never called. pruneExpiredFacts() (src/memory/jme.ts:528) contains the byte-identical DELETE, differing only in the constant. It has no caller anywhere in production. This PR copy-pastes its SQL inline rather than wiring it up or parameterizing the floor, leaving two identical destructive statements over the same table to keep in sync — one of which nobody runs.

  2. The tests manufacture the population that reality lacks. They seed 400 facts at confidence 0.3 aged 31 days — precisely the shape production does not contain. 36/36 green while proving nothing about whether the code ever executes. A test whose fixture supplies the missing precondition cannot discriminate between "works" and "unreachable."

  3. The branch head is not what was built. Local worktree is at 95258b3 (based on current main); this PR's head is 0e1f898 on an older lineage — same message, different SHA, 25 ahead / 1 behind. And the commit message says "Closes JME Phase 4 (pending post-day-14 veredicto decision)" — closed and pending at once.

If the vector ceiling becomes real (~early September at the current rate), the honest fix is to call the pruneExpiredFacts() that already exists and pick a floor from the observed confidence distribution — not to fork it with a threshold below the minimum value the extractor has ever produced.

Commit preserved as 95258b3 / 0e1f898; nothing is lost by closing.

@kosm1x

kosm1x commented Aug 2, 2026

Copy link
Copy Markdown
Owner

Closed per operator review — see the analysis above. Not merged; no code from this branch reaches main.

@kosm1x kosm1x closed this Aug 2, 2026
@kosm1x
kosm1x deleted the jarvis/feat/jme-phase4-auto-prune branch August 2, 2026 20:08
kosm1x pushed a commit that referenced this pull request Aug 2, 2026
PR #31 closed unmerged and both branches deleted. Every precondition of the
auto-prune matched an empty set: trigger at 400 embeddings vs a live 129;
confidence floor 0.5 vs a table minimum of 0.72 (0 rows); 30-day age window vs
an oldest fact of 16 days (0 rows). The 0.4 -> 0.5 change it existed to make
altered the outcome for exactly zero facts.

Same defect class as the §17 6a removal in this same session — a mechanism
acting on a population that does not occur. Twice in one day is enough to note
the pattern in the queue rather than only in memory.

Records the trigger (jme_facts embeddings ~400, ≈ early September) and points
at b37712e as the shape to reuse: the FIRST of the two attempts correctly
parameterized pruneExpiredFacts(confidenceFloor = 0.4) and called it; the retry
discarded that and inlined a duplicate DELETE, and that inferior version is
what became the PR. Also notes pruneExpiredFacts() has no production caller —
wiring it up is the real task.

Patches for both attempts preserved outside the repo at
/root/claude-backups/jme-phase4-rejected-2026-08-02/ with provenance.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186BhSFY9sFFHCPeFnpbvki
kosm1x pushed a commit that referenced this pull request Aug 2, 2026
…tion

Records the Phase 4 revert alongside the §17 6a removal from earlier the same
day, because they are the same defect and recording them separately would hide
that. Both act on a population that does not occur:

  §17 6a   — scored "red briefs"; 0 of 20 ruled briefs were pure-red
  Phase 4  — prunes confidence < 0.5; table minimum is 0.72, so 0 rows
             (and fires at 400 embeddings against a live 129, and needs a
             30-day age against an oldest fact of 16 days)

Two independent instances in one day, unrelated subsystems, different authors.
The habit that kills the class costs one query: run the predicate as a
SELECT COUNT(*) with MIN/MAX of the thresholded column before writing the code
that depends on it. If a term returns 0, that is the finding — say so and stop.

Second, unexpected: the retry regressed the design. b37712e (in-container)
correctly parameterized pruneExpiredFacts(confidenceFloor = 0.4) and called it;
after a 900s timeout and error_max_turns, the retry discarded that and inlined
a duplicate DELETE, and the inferior version became PR #31. So a post-exhaustion
retry can ship a WORSE artifact than the run that "failed" — resume from the
artifact, not the prompt. Also queued: pruneExpiredFacts() has no production
caller at all, so wiring it up is the real Phase 4 task.

CORRECTION, kept rather than dropped: I first charged Jarvis with committing
under the operator's identity. Wrong. git_commit deliberately stamps --author as
JARVIS_GH_USER <JARVIS_GH_EMAIL> on jarvis/* branches (git.ts:343-348, defaults
:47-48) so commits attach to the pushing account; b37712e differs only because
the nanoclaw container has no JARVIS_GH_TOKEN. The residue is a tooling gap, now
queued: the author field encodes the EXECUTION PATH, not the actor, so git log
cannot answer "human or Jarvis?" — that must come from the branch prefix or a
trailer.

Cleanup already applied: PR #31 closed unmerged, both branches deleted, worktree
clean, 129 jme_facts intact, both attempts preserved at
/root/claude-backups/jme-phase4-rejected-2026-08-02/.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0186BhSFY9sFFHCPeFnpbvki
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