Skip to content

Real decay write-back + contradiction supersede + token budget + typed LLM errors (Q-1…Q-5)#8

Merged
tcconnally merged 2 commits into
mainfrom
fix/decay-and-contradiction
Jun 13, 2026
Merged

Real decay write-back + contradiction supersede + token budget + typed LLM errors (Q-1…Q-5)#8
tcconnally merged 2 commits into
mainfrom
fix/decay-and-contradiction

Conversation

@tcconnally

Copy link
Copy Markdown
Collaborator

Implements PROMPT 1 from the pre-submission fix plan — the MemoryAgent Track requirements that were claimed in docs but absent in code, plus one discovery that changed the approach (see "Engram backend" below).

Track-requirement fixes

  • Q-1 — demo crash: MemoryEntry.id now defaults to "" (the handoff doc said this was already applied — it was not on the remote). The tools layer constructed entries without id and crashed Session 1 with a verified TypeError.
  • Q-2 — "timely forgetting" is now real: _decay_confidence writes decayed confidence back (was a literal pass). New facts default to 0.9 so they can decay; 1.0 is pinned for explicitly verified facts. Age is measured from created_at so the write-back doesn't reset the decay clock. end_session reports and prints every decay (~ forgotten: "…" 90% → 20% (14d old)).
  • Contradiction supersede (the differentiator): detect_contradictions() (same-category token-overlap) + supersede() — old fact demoted to 0.2 (below the 0.3 recall floor), new fact stored at 0.9. The demo announces it:

    🔄 Updating — I had "We use Pinecone for vector search" at 90% confidence from earlier; superseding it (now 20% — effectively forgotten).

  • Q-3 — token-budgeted context: recall_context keeps highest-confidence memories within max_context_chars (default 8000, env-overridable) with an explicit truncation note. Compounding can no longer grow the prompt until the context window 400s.
  • Q-4 — typed LLM errors: LLMError with status/retryable; 429/5xx/network retried with 1s/2s/4s backoff, 4xx fail fast. process_message returns a clean sentence instead of [LLM Error…] text, and _auto_store skips extraction on failure — error strings can never be persisted as memories.
  • Q-5 — per-session counts: session_memories resets in start_session.

Engram backend: rewritten as direct SQLite (discovery)

The installed engram-rs (v0.5.0) exposes only engram serve — the CLI verbs the old backend shelled out to (store/recall/delete/health) don't exist, so the demo could never have run against the real binary. Per the fix plan's "update it via the CLI or direct DB access", the backend now reads/writes engram's own facts/facts_fts schema directly (stdlib sqlite3, WAL, FTS5 with operator-safe quoting, soft-invalidation forget, id-upsert for confidence write-back). An engram serve pointed at the same db file serves these memories over MCP/REST — same store, two access paths. Timestamps round-trip, so age-based decay has real ages.

Also fixed while verifying: get_project_context searched for the literal words "conventions"/"tech stack", which stored content never contains — now wildcard recall scoped by category, split by tags ("4 conventions found" in the demo, previously 0).

Verification (end-to-end, real store)

$ python -m pytest tests/ -q
23 passed
$ DASHSCOPE_API_KEY=test MEMORY_BACKEND=engram ENGRAM_DB_PATH=/tmp/demo.db python -m agent.main
  Memory health: ok
  Existing memories: 0 → 5 across sessions   (persistence)
  Project context recall: 4 conventions found
  🔄 Updating — I had "We use Pinecone…" … superseding it
  (LLM 401s degrade to clean messages; nothing error-shaped stored)

Tests cover: decay write-back values + forgotten floor + pinned-1.0 + fresh-facts, supersede demote/unrelated/identical cases, context budget keep-best/truncation-note, LLM error non-storage, FTS operator injection inertness, project isolation, and the engram round-trip with tags/timestamps. CI workflow added.

🤖 Generated with Claude Code

…yped LLM errors

MemoryEntry (Q-1): id defaults to "" — tools constructed entries
without it and crashed the demo in Session 1 with TypeError.

Decay (Q-2): _decay_confidence now writes decayed confidence back
(was a pass statement). New facts default to 0.9 so they CAN decay;
1.0 is pinned for explicitly verified facts. Age measured from
created_at so the write-back doesn't reset the clock. end_session
reports and prints decayed/forgotten memories.

Contradiction supersede (differentiator): detect_contradictions()
finds same-topic/same-category facts via token overlap; supersede()
demotes the old fact to 0.2 (below the 0.3 recall floor) and stores
the new one at 0.9. Demo: Session 1 stores "We use Pinecone...",
Session 3 supersedes with pgvector and announces it.

Context budget (Q-3): recall_context keeps highest-confidence
memories within config.max_context_chars (default 8000) and appends
a truncation note — compounding no longer grows the prompt unboundedly.

LLM errors (Q-4): QwenClient raises typed LLMError; 429/5xx/network
retried with backoff (1s,2s,4s, max 3 retries), 4xx fail fast.
process_message returns a clean message instead of "[LLM Error...]"
text; _auto_store skips extraction on LLM failure so error strings
are never persisted as memories.

Session accounting (Q-5): session_memories reset in start_session.

Engram backend rewritten as direct SQLite (the installed engram 0.5.x
exposes only `engram serve` — the old CLI verbs store/recall/delete
never existed, so the demo could not run against the real binary).
The backend reads/writes engram's own facts/facts_fts schema (WAL,
quoted FTS queries, soft-invalidation forget, confidence write-back),
so `engram serve` on the same db serves these memories over MCP/REST.

get_project_context now uses wildcard+category recall split by tags —
keyword queries ("conventions") never matched stored content.

requirements.txt documents stdlib-only. 23 tests + CI.

Verified end-to-end: MEMORY_BACKEND=engram demo runs against a real
SQLite store — health ok, memories persist across sessions, "4
conventions found", contradiction beat prints, LLM 401s degrade
cleanly without polluting memory.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Resolved merge conflicts from shared library extraction:
- Updated imports: agent.memory → perseus_agent_core.memory
- Updated imports: agent.tools → perseus_agent_core.tools
- Removed deleted memory/tools files (now in perseus-agent-core)
- Kept all PR features: decay write-back, contradiction detection,
  token-budgeted recall, typed LLMError with 429 retry, session reset

Test: import paths verified, no remaining agent.memory references.
@tcconnally tcconnally merged commit df31659 into main Jun 13, 2026
@tcconnally tcconnally deleted the fix/decay-and-contradiction branch June 13, 2026 00:21
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