Skip to content

feat(plugins): optional yantrik_memory — semantic memory + supersession (opt-in) - #16

Open
spranab wants to merge 3 commits into
mpfaffenberger:mainfrom
spranab:feat/yantrik-memory
Open

feat(plugins): optional yantrik_memory — semantic memory + supersession (opt-in)#16
spranab wants to merge 3 commits into
mpfaffenberger:mainfrom
spranab:feat/yantrik-memory

Conversation

@spranab

@spranab spranab commented Aug 18, 2026

Copy link
Copy Markdown

Re-homed from mpfaffenberger/code_puppy#465, which was closed because plugins moved here. Thanks for the pointer — the port turned up three real bugs, so this is not a straight copy.

What it is

An opt-in plugin giving Code Puppy durable memory across sessions, backed by YantrikDB. A local distiller reads each user turn and extracts durable facts, so the user never hand-tags anything; a later turn that contradicts an earlier fact supersedes it rather than piling up. Default OFF, enabled with /yantrik enable.

Purely additive: new package, new test, two registration lines. No existing code changes.

Conventions followed

  • code_puppy_core_plugins/yantrik_memory/
  • test flat at tests/test_yantrik_memory.py
  • registered in pyproject.toml entry-points and plugin-names.txt
  • yantrikdb import is guarded, matching how aws_bedrock treats boto3 — absent dependency makes the plugin inert, never fatal

Three bugs the port exposed

The test could not fail. Its pytest entrypoint returned PASS when yantrikdb was absent. CI here does not install it, so this would have been green forever. It now SKIPs — and that is what surfaced the next two.

Supersession was broken. YantrikDB.correct() gained a required reason parameter, so every call raised TypeError; the fallback caught it and inserted a duplicate, leaving the stale value in the current band. From outside that is indistinguishable from working memory.

Facts were written twice. The passive distiller and the agent's yantrik_remember tool both wrote, and only the distiller de-duplicated. One "remember X" turn stored the same fact in two phrasings. Fixed at both ends; measured 4 stored facts → 1 for the same two turns.

Also now forwards the engine's per-hit trust signals (superseded_by, disputed_with, current_status, staleness in why_retrieved) into the recall block, so the agent can tell a disputed fact from a good one.

Verification

Live, not just unit-tested: two separate code-puppy -p processes against engine 0.15.3, with qwen3.8:27b as both agent and distiller. Turn one states a fact; turn two, in a fresh process, recalls it and grounds it correctly. Current band ends as a single clean entry.

  • ruff clean
  • tests/test_yantrik_memory.py passes 3/3 with the engine present, SKIPs without it
  • full suite 1950 passed / 15 failed — all 15 reproduce on a clean main checkout (Windows file-locking in aws_bedrock, steer_queue, web_retriever_skill), unrelated to this change

Known limitations

  • The integration test needs Ollama plus a local distiller model, so it skips in CI rather than running. It asserts real end-to-end behaviour when those are present.
  • think() flags redundancy as an advisory trigger without applying it, so de-duplication is handled in the plugin rather than relying on a maintenance pass.
  • Distillation costs one local LLM call per user turn; disabled entirely when the plugin is off.

…on (opt-in)

Ports the plugin from code_puppy#465, which was closed because plugins now
live in this repository. No behaviour change from the reviewed version; only
the packaging moves.

  code_puppy/plugins/yantrik_memory/ -> code_puppy_core_plugins/yantrik_memory/
  plugin-local test_integration.py   -> tests/test_yantrik_memory.py
  registered in pyproject entry-points and plugin-names.txt

Opt-in and fail-soft: the yantrikdb import is guarded, so the plugin is inert
rather than fatal when the dependency is absent — matching the convention
aws_bedrock already uses for boto3.

Verified on this repo: ruff clean; tests/test_yantrik_memory.py passes; the
remaining 235 tests pass. tests/test_aws_bedrock.py has one Windows
file-locking failure that reproduces on a clean main checkout and is unrelated
to this change.
Supersession was silently broken. yantrikdb's correct() gained a required
`reason` parameter, so every call raised TypeError, and the recorder's
fallback caught it and inserted the new fact as a DUPLICATE — leaving the
superseded value in the current band. From the outside that looks exactly like
working memory, which is why it survived unnoticed.

Verified against yantrikdb 0.15.3 / yantrikdb-mcp 0.19.2: 3/3 runs now show
the new value present and the superseded one absent, and the chatter probe
drops from 2 durable facts to 1 because the correction replaces rather than
appends.

Two things that hid it, both fixed here:

  * the pytest entrypoint returned PASS when yantrikdb was absent, so CI (which
    does not install it) would have reported green forever. It now SKIPs, and
    that is what surfaced the failure.

  * the correct() fallback swallowed the exception silently. It still keeps the
    fact rather than losing it, but now says why it fired.

The skip check also has to import through _bootstrap_imports(): config.py
resolves DB_PATH at import time, so touching substrate any earlier binds the
developer's real store and the test accumulates facts across runs.
…e's trust signals

Three defects a live two-turn session made obvious.

DUPLICATES. Two writers reach the store — the passive distiller and the
agent's own yantrik_remember tool — and only the distiller de-duplicated. A
single "remember X" turn fired both, so the same fact landed twice in
different phrasings ("Deploy key for node4 is X" / "Node4 deploy key is X").
think() notices the redundancy but its trigger is ADVISORY: the originals stay
`active` with `consolidated_into` unset, so there is nothing to filter on
afterwards. Fixed at both ends — the tool checks before writing, and prefs()
collapses restatements when building the band. Measured on the same two turns:
4 stored facts -> 1.

TRUST SIGNALS. The engine returns superseded_by, disputed_with,
current_status and why_retrieved on every hit; the recall block forwarded only
`text`, so an agent could not tell a disputed or superseded fact from a good
one. Now surfaced as a short parenthetical, keeping only the warning entries
in why_retrieved rather than the routine "semantically similar" reasons.

Verified live, not just in tests: two separate code-puppy processes against
engine 0.15.3 with qwen3.8:27b as agent and distiller. Turn two recalls the
fact in a fresh process, and the current band is a single clean entry.
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