Skip to content

feat(phase5): epistemic confidence model - #137

Merged
salishforge merged 1 commit into
mainfrom
feat/phase5-epistemic-confidence
May 21, 2026
Merged

feat(phase5): epistemic confidence model#137
salishforge merged 1 commit into
mainfrom
feat/phase5-epistemic-confidence

Conversation

@salishforge

Copy link
Copy Markdown
Owner

Summary

Second of six PRs splitting #129. Ships Feature 1: Epistemic Confidence Model — calibrated uncertainty levels on warm-tier memories with auto-promotion via sleep cycle.

What lands

  • New columns on warm_tier: epistemic_status TEXT NOT NULL DEFAULT 'provisional', evidence_count INTEGER NOT NULL DEFAULT 1, last_corroborated_at TIMESTAMPTZ. Index warm_tier_epistemic_idx on (agent_id, epistemic_status).
  • Sleep cycle Phase 5.12 (Epistemic Promotion) — runs in the run() loop after deprecated decay, before drift snapshot. Promotes provisional → established when both evidence_count >= 3 AND positive retrievals come from ≥2 distinct namespaces (cross-context corroboration required). Stamps last_corroborated_at. Does NOT touch contested rows.
  • Query filter: query({ epistemic: 'established' }) or array form filters retrieval by status.
  • REST endpoint: GET /memory/:agentId/epistemic returns per-status counts.
  • MCP tools: memforge_certainty (filtered query) and memforge_epistemic_profile (counts).
  • SDK: TypeScript + Python clients both gain epistemic query param and epistemicProfile() method.

Migration: schema/migration-v3.9.sql.

Notes

Part of the #129 split

Test plan

  • `npm run type-check` — clean
  • `npm run build` — clean
  • `npm run lint` — clean
  • `npm run test:epistemic-confidence` — 30/30 pass (7 unit + 12 integration + 5 e2e + 6 migration)
  • `npm audit --omit=dev` — 0 vulnerabilities
  • CI green on push

🤖 Generated with Claude Code

Calibrated uncertainty levels (established/provisional/contested/inferred)
on warm-tier memories with auto-promotion via new sleep cycle Phase 5.12.
New columns: epistemic_status, evidence_count, last_corroborated_at on
warm_tier. New endpoint GET /memory/:id/epistemic and MCP tools
memforge_certainty + memforge_epistemic_profile. Phase 5.12 promotes
provisional rows with evidence_count >= 3 and multi-namespace positive
retrievals to established, stamps last_corroborated_at, and demotes stale
established rows back to provisional. Epistemic filter on query() restricts
results to the desired confidence tier. TypeScript SDK, Python SDK,
tool-definitions, and OpenAPI spec updated.

Migration: schema/migration-v3.9.sql
Split from #129 (PR-B of 6).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@salishforge
salishforge merged commit 8d2b16c into main May 21, 2026
12 checks passed
salishforge added a commit that referenced this pull request Jul 26, 2026
The final Phase 5 slice — the Feature 1 remnant that never landed with
PR #137. Phase 2.5 conflict resolution stops encoding noise as fact.

- When the multi-factor score gap between two conflicting live memories
  is at most one point (one factor's worth of noise on the quantized
  integer scale), both are marked epistemic_status='contested' and the
  conflict resolves with a contested(...) strategy and no winner. The
  umbrella used a relative 20% threshold, which inverts on small
  integer scores (pure-noise 1-vs-0 reads as decisive, well-evidenced
  6-vs-5 as contested) and is distorted by the mutual +100 supersession
  offset — the absolute delta is immune to both.
- Superseded rows never contest: supersession is an absolute signal,
  and marking stale facts contested would resurface them as live
  disputes.
- Contested is not terminal (the trap this same stack fixed for
  'inferred'): Phase 5.12 promotes contested rows that earn the
  corroboration bar, and a decisive re-resolution of the dispute clears
  the winner's badge back to provisional.
- tests/contested-conflicts.test.ts: 9 tests — close-call semantics,
  the exact one-vs-two-point boundary, winner/loser path preservation
  incl. untouched winner confidence, both-superseded decisiveness,
  contested recovery via decisive re-resolution. Conflict pairs share
  an identical relative timestamp (separate INSERTs see different
  now(), silently handing one side the +3 recency factor).
- tests/epistemic-confidence.test.ts: the promotion tests now pin the
  new behavior (corroborated contested rows promote; uncorroborated
  ones stay).


Claude-Session: https://claude.ai/code/session_011xCqQo49d3CEbn6oEvb3Ru

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
salishforge added a commit that referenced this pull request Jul 26, 2026
* feat(phase5): explainable memory operations (v3.10)

Feature 2 of the Phase 5 split (umbrella #129): every retrieval can say
why, and any warm memory can be asked where it stands.

- query(): opt-in explain=true attaches ExplanationFactor[] per result
  (rank_score, epistemic_status weight, search_mode, temporal_decay)
- explainMemory() + GET /memory/:id/explain?warm_id= reports scores,
  access patterns, and standing against the sleep-cycle score
  thresholds. Flags are scoped honestly (would_evict_by_threshold,
  would_flag_low_confidence): capacity eviction and the outcome /
  contradiction revision channels depend on live cross-table state and
  are deliberately not predicted here.
- memforge_explain MCP tool wired to the real client method; TS +
  Python SDKs including both resilient wrappers; OpenAPI entries
- query cache key extracted to queryKey() in cache.ts so the explain
  flag's participation in the key is unit-testable without Redis
- warm_id validated to int8 range at REST and MCP boundaries; invalid
  agent ids on /explain return 400 like sibling routes

No schema migration — pure code feature.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xCqQo49d3CEbn6oEvb3Ru

* feat(phase5): causal memory graph (v3.10)

Feature 3 of the Phase 5 split (umbrella #129): MemForge learns which
events tend to follow which, and can answer "what led here" and "what
happens next".

- causal_edges table (migration-v3.10.sql + canonical schema, RLS,
  agent-scoped FKs to warm_tier with CASCADE)
- Sleep Phase 6.1 mines memory_sequences for repeated content-level
  A→B patterns (md5 of 50-char prefixes, the Phase 5.5 technique) —
  the umbrella's row-pair grouping was unsatisfiable under the table's
  UNIQUE constraint and could never mine an edge. One edge per
  pattern, anchored to the earliest surviving pair so re-mining
  updates in place and FK cascade self-heals evicted anchors. Not
  skip-gated: the phase's input accumulates independently of its own
  change history, so the zero-change gate would permanently disable
  mining for young agents. Count surfaces as
  SleepCycleResult.causal_edges_updated.
- getCausalChain(): recursive CTE traversal (causes|effects, depth
  clamped 1-10, 50-row cap, ordered depth then strength)
- predict(): context match → outgoing edges → ranked events.
  probability = confidence × (1 − e^(−strength/30)) — monotonic,
  never saturated (raw strength × confidence clamps to 1.0 for
  virtually every mined edge). Shared-pool matches are excluded from
  the cause-id list (different id space than warm_tier), and effects
  are confined to the requested namespace.
- GET /memory/:id/causal + POST /memory/:id/predict, memforge_causal_chain
  + memforge_predict MCP tools wired to real client methods, TS +
  Python SDKs incl. resilient wrappers, tool-definitions, OpenAPI
- tests/causal-graph.test.ts: 39 tests (traversal incl. cycles/clamps/
  cap/ordering, predict incl. namespace + pool-collision, Phase 6.1
  mining incl. cv weighting + anchor stability + true re-confirmation
  step, pruning, HTTP status paths, migration shape + RLS)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xCqQo49d3CEbn6oEvb3Ru

* feat(phase5): hierarchical abstraction engine (v3.11)

Feature 4 of the Phase 5 split (umbrella #129): Sleep Phase 5.11
distills cross-cutting principles from accumulated meta-reflections
into a queryable abstractions store.

- abstractions table (migration-v3.11.sql + canonical schema): level
  CHECK (principle|strategy|mental_model), stored md5 content_hash
  with UNIQUE (agent_id, level, namespace, content_hash) — the
  umbrella's ON CONFLICT DO NOTHING had no conflict target and would
  have duplicated every principle every cycle. RLS + grants.
- Phase 5.11: gated on LLM presence, token budget, and >= 3
  meta-reflections; prompt carries the codebase-standard injection
  guard, wraps reflection content via wrapUserContent, and states the
  schema's own limits (max 10, under 2000 chars, empty-array escape)
  so one overlong item does not sink the batch silently. Re-derivation
  is corroboration: ON CONFLICT keeps GREATEST confidence and revives
  a deactivated principle only when the fresh derivation is confident
  (>= 0.5); the conditional WHERE keeps change counts honest. Age-based
  deactivation of stale sub-0.2 principles. Not skip-gated (input
  accumulates independently). Cancellation checkpoint before the LLM
  call so canceled dream runs stop paying. runPhase now records real
  per-phase token spend in sleep_phase_analytics; count surfaces as
  SleepCycleResult.principles_extracted.
- getAbstractions()/getPrinciples(), GET /principles + /abstractions
  routes, memforge_principles + memforge_mental_models MCP tools wired
  to real client methods (honest description: only 'principle' is
  auto-extracted today), TS + Python SDKs incl. resilient wrappers,
  tool-definitions, OpenAPI with shared Abstraction component.
- tests/abstractions.test.ts: 39 tests — read paths, extraction incl.
  dedup/revival/confidence-raise/skip-gate immunity/token attribution/
  budget + window gates/malformed + schema-invalid + missing-key LLM
  output, deactivation age and scope boundaries, ordering tiebreak,
  HTTP status paths, migration shape + RLS.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xCqQo49d3CEbn6oEvb3Ru

* feat(phase5): cross-agent transfer learning bootstrap (v3.12)

Feature 7 of the Phase 5 split (umbrella #129), the final feature:
bootstrap a new agent from an experienced one.

- bootstrapAgent(): one atomic transaction copying established warm
  memories (as epistemic 'inferred', 0.5x importance/confidence,
  _transferred_from provenance), active procedures, and active
  principles from source to target. Set-based INSERT..SELECT with
  dedup BEFORE each LIMIT so already-carried knowledge frees its cap
  slot (the umbrella looped N+1 with no-op ON CONFLICTs — procedures
  has no unique constraint at all). Memory dedup is namespace-scoped
  and content-exact (organic rows hash content differently, so
  equality is the only honest check). Warm-tier creates carry
  hash-chained audit records ('bootstrap' trigger) like consolidation
  and cold-restore do.
- Phase 5.12 promotion now also considers 'inferred' rows meeting the
  same evidence bar — bootstrap is the first mass writer of inferred
  status, which was previously terminal: excluded by every epistemic
  filter except 'all' with no path out regardless of corroboration.
- POST /memory/:id/bootstrap (path agent = target; Zod-validated body
  incl. agent-id regex and 0-1000/0-100 caps), memforge_bootstrap MCP
  tool with its own agent-pair validation and a real executor, TS +
  Python SDKs incl. resilient wrappers, tool-definitions, OpenAPI.
  Docs state honestly: principles carry no provenance marker (no
  metadata column); transferred rows are FTS-searchable immediately,
  semantic after embedding backfill, filter-visible after promotion.
- No schema migration (audit trigger comment updated in schema.sql).
- tests/bootstrap.test.ts: 22 tests — transfer semantics per category,
  discounts + provenance, namespace scoping incl. the cross-namespace
  dedup regression, per-category caps incl. principle slot-starvation
  regression, idempotency, organic-content dedup, transaction rollback
  via sabotage trigger, inferred->established promotion, auto-register,
  HTTP status paths.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011xCqQo49d3CEbn6oEvb3Ru

* feat(phase5): contested status for close-call conflict resolution (#166)

The final Phase 5 slice — the Feature 1 remnant that never landed with
PR #137. Phase 2.5 conflict resolution stops encoding noise as fact.

- When the multi-factor score gap between two conflicting live memories
  is at most one point (one factor's worth of noise on the quantized
  integer scale), both are marked epistemic_status='contested' and the
  conflict resolves with a contested(...) strategy and no winner. The
  umbrella used a relative 20% threshold, which inverts on small
  integer scores (pure-noise 1-vs-0 reads as decisive, well-evidenced
  6-vs-5 as contested) and is distorted by the mutual +100 supersession
  offset — the absolute delta is immune to both.
- Superseded rows never contest: supersession is an absolute signal,
  and marking stale facts contested would resurface them as live
  disputes.
- Contested is not terminal (the trap this same stack fixed for
  'inferred'): Phase 5.12 promotes contested rows that earn the
  corroboration bar, and a decisive re-resolution of the dispute clears
  the winner's badge back to provisional.
- tests/contested-conflicts.test.ts: 9 tests — close-call semantics,
  the exact one-vs-two-point boundary, winner/loser path preservation
  incl. untouched winner confidence, both-superseded decisiveness,
  contested recovery via decisive re-resolution. Conflict pairs share
  an identical relative timestamp (separate INSERTs see different
  now(), silently handing one side the +3 recency factor).
- tests/epistemic-confidence.test.ts: the promotion tests now pin the
  new behavior (corroborated contested rows promote; uncorroborated
  ones stay).


Claude-Session: https://claude.ai/code/session_011xCqQo49d3CEbn6oEvb3Ru

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
@salishforge
salishforge deleted the feat/phase5-epistemic-confidence branch July 27, 2026 20:20
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.

1 participant