Skip to content

Releases: jarmstrong158/context-keeper

v0.10.0: Abstention + supersession-as-ranking

Choose a tag to compare

@jarmstrong158 jarmstrong158 released this 03 Jul 18:26

Two ideas adapted from studying Curion, a competitor MCP memory tool — kept dependency-free. Curion's LLM-controller architecture (an API call on every store and recall) was deliberately not adopted; context-keeper stays zero-dependency and offline by default.

Abstention: get_context can now say "I don't have anything relevant"

Previously it always returned its top-scored entries. But the composite score banks ~55 points from recency/status/origin regardless of query relevance, so a query with no relevant memory silently got a confident-looking top result. Measured confabulation was 100% on no-answer queries (evals/abstention.py).

Now the response carries top_relevance, and when the top entry's tag/text relevance falls below min_relevance (config, default 0.20), no_confident_match: true with guidance telling the agent not to present the entries as established fact. It annotates, never suppresses — weak matches are still returned, so the vocabulary-mismatch recall that retrieval_hints and the semantic blend preserve survives. 0.20 is the highest floor with 0% false-abstention on the eval set (TNR 38%). Honest limit documented: hard-negatives sharing real topic vocabulary still slip through — a field-wide hard problem for lexical and dense retrieval.

Supersession as a ranking signal, not just a filter

record_decision accepts supersedes: [ids]: the prior decisions become supersededdemoted in ranking but still recallable ("why did we change from X?"), distinct from deprecate_entry which removes an entry from retrieval entirely. Superseded entries are skipped by prune_stale/verify_quality (intentional history, not stale work) and marked **SUPERSEDED** by dec-NNN in the DECISIONS.md projection. Reuses infrastructure already present — score_entry already demoted superseded, get_context already only filtered deprecated; the gap was that nothing ever set the status.

146 tests passing (15 new). tools/list payload 2493/2500. Retrieval eval unchanged.

v0.9.0: Clustering, more embedding backends, critical summary fix

Choose a tag to compare

@jarmstrong158 jarmstrong158 released this 03 Jul 17:14

Critical fix: empty session-start injection for large stores

The get_project_summary truncation loop evaluated the original text in its condition, so any store whose summary exceeded the token budget (~30+ entries) silently popped every line and injected an empty summary at session start. Large stores were getting ~0 tokens of memory briefing. Found by measurement (see below); fixed with a regression test.

Topic clustering

Above 8 decisions, get_project_summary groups decisions by their most-frequent shared tag instead of one flat list -- a 59-decision store reads as a dozen topics.

OpenAI-compatible embedding backends

semantic.api: "openai" points the semantic blend at any /v1/embeddings endpoint -- LM Studio, llama.cpp server, or OpenAI itself (api_key_env names the env var holding the key). Ollama stays the default; same fail-safe lexical fallback. nomic task prefixes now apply only to nomic models.

Trust-aware conflict guidance

similar_entries matches now carry each entry's origin, and the guidance states the precedence: user-stated overrides agent-inferred overrides imported.

Token-reduction measurement published

evals/token_reduction.py, run against four real stores: 94-97% reduction on large stores (e.g. 78 entries: ~75k tokens dumped vs ~2k injected), with the by-construction caveat stated in-script. Running this measurement is what caught the empty-injection bug.

Six more MCP clients documented

OpenCode, GitHub Copilot CLI, Antigravity, OpenClaw, Hermes, and pi/oh-my-pi config snippets in the README. The server is standard stdio MCP; only the hooks are Claude Code specific.

136 tests passing (5 new). Tool schemas unchanged (2481/2500 token budget). Held-out retrieval eval unchanged.

v0.8.0: DECISIONS.md projection

Choose a tag to compare

@jarmstrong158 jarmstrong158 released this 03 Jul 10:30

DECISIONS.md projection (opt-in, render-on-write)

Mirror the canonical decisions store into a human-readable DECISIONS.md:

{ "markdown_export": { "enabled": true, "path": "DECISIONS.md" } }
  • Render-on-write. Every decision mutation (record_decision, update_entry, deprecate_entry) regenerates the entire file from decisions.json after the JSON write and before the tool returns -- so a subsequent git commit captures JSON and markdown in the same commit. Deliberately on the write path, not a git/PostToolUse hook: rendering after the commit snapshot is taken would reintroduce drift.
  • JSON stays canonical; markdown is derived and read-only. Regenerated whole every time, never appended to or parsed back. Hand edits are not preserved -- a regenerated projection has no drift surface.
  • New export_markdown tool regenerates on demand (optional custom path), so existing repos can backfill without enabling the flag.
  • Entry layout mirrors the field-tested convention: ### <summary> -- MM-DD (dec-NNN) headings with Why / Tried / Tradeoff / Rejected bullets; pre-v0.4 legacy entries render their freeform rationale as Why.
  • Pure stdlib. Default behavior with the flag off is byte-for-byte unchanged.

131 tests passing (10 new). Smoke-tested against a real 59-decision store.

v0.7.1: Tool-schema token budget

Choose a tag to compare

@jarmstrong158 jarmstrong158 released this 03 Jul 01:55

Tool-schema token budget

Every connected MCP client loads the full tools/list payload into model context at every session start. This release trims the schema descriptions from ~2800 to ~2370 estimated tokens (record_decision alone was 738) and adds a regression test capping the payload at 2500 -- future fields must fit the budget or consciously raise it.

Nothing behavioral changed: rich field guidance still reaches the model where it is lazy-loaded (validation rejection messages via _FIELD_GUIDANCE, and CLAUDE.md), so capture quality enforcement is unchanged. 121 tests passing; held-out retrieval eval unchanged (hit@5 80%, MRR 0.700).

v0.7.0: Anticipated queries, origin trust, timeline filters

Choose a tag to compare

@jarmstrong158 jarmstrong158 released this 03 Jul 01:43

retrieval_hints (anticipated queries)

All record_* tools accept retrieval_hints: 2-4 alternate phrasings a future session might search for (synonyms, symptom descriptions, error messages). Indexed for both lexical and semantic retrieval -- the zero-dependency fix for vocabulary-mismatch queries. Field-proven on release day: two eval queries that store growth had crowded out of the top-5 recovered to rank 1 after their gold entries got hints (held-out hit@5 66.7% -> 80%, MRR 0.567 -> 0.700).

origin + trust weighting

Entries record who authored them: user (explicitly stated), agent (inferred, default), or import (backfilled). Retrieval scoring weights them 10/5/2, so a rule the user actually stated outranks one the agent guessed. Pre-v0.7 entries score as agent -- a uniform shift that preserves their relative order.

since / before temporal filters

get_context accepts ISO since/before bounds against each entry's verified/created timestamp. "What did we decide this month" is now a query.

Other MCP clients documented

The server is standard stdio MCP; README now has config snippets for Cursor, Codex CLI, Gemini CLI, and Windsurf. Only the hooks are Claude Code specific.

120 tests passing (8 new).

v0.6.0: Capture-time guardrails

Choose a tag to compare

@jarmstrong158 jarmstrong158 released this 03 Jul 01:21

Scoped constraint injection

New hooks/scope_guard.py (PostToolUse on Edit|Write|NotebookEdit): the moment the agent edits a file covered by a constraint's scope, that constraint is injected into context via additionalContext. Session-start injection briefs the rules once at turn one; this enforces them at the exact moment they are about to matter. Each constraint fires at most once per session (state in .context/scope_guard_state.json). Constraints scoped global never fire here.

Similar-entry surfacing at record time

record_* now compares the new entry against all active entries (word-set Jaccard, threshold configurable via similar_threshold, default 0.30) and returns similar_entries + resolution guidance when existing entries overlap heavily -- catching restatements and contradictions at capture instead of relying on MMR to mitigate duplicates at retrieval. Advisory only: the write always proceeds, and related_to-linked entries are exempt. CLAUDE.md documents the resolution protocol (deprecate restatements, supersede contradictions, link the genuinely distinct).

112 tests passing (9 new, including subprocess tests that exercise the hook end to end).

v0.5.0: Data integrity, semantic retrieval, hook fixes

Choose a tag to compare

@jarmstrong158 jarmstrong158 released this 02 Jul 23:55

Data integrity

  • Atomic writes: entry files are written to a temp file and swapped in with os.replace, so a crash mid-write can no longer corrupt the store.
  • Corrupt-store protection: record_* / update_entry / deprecate_entry refuse to write when an entry file exists but cannot be parsed. Previously a corrupt file read as empty and the next record silently replaced the entire history with one entry.
  • update_entry enforces the v0.4 schema: structured fields are min-length validated on update, so entries cannot be hollowed out after recording.

Retrieval

  • Opt-in semantic blend (semantic.enabled in config, local Ollama + nomic-embed-text): hit@5 80% -> 93%, MRR 0.63 -> 0.88 on the held-out eval split. Fail-safe: falls back to lexical ranking if Ollama is unreachable.
  • Opt-in MMR diversity reordering so near-duplicate entries do not crowd the token budget.
  • evals/: retrieval eval harness, labeled datasets, and results.
  • Budget packing skips oversized entries instead of stopping at the first one that does not fit; recency scoring clamped against future timestamps.

Hooks

  • SessionStart now runs the post-compaction snapshot comparison itself (SessionStart fires with source compact before any Stop, so the injected report was previously one compaction stale) and injects a one-line quality-scan nudge -- the model-visible surface for verify_quality, since PreCompact stdout never reaches the model.
  • pre_compact.py / post_compact.py import resolution and the quality scan from server.py, removing duplicated keep-in-sync copies.

Packaging & CI

  • semantic_index.py and hooks/commit_capture_reminder.py now ship in the wheel/sdist (both were missing from 0.4.0 -- semantic retrieval silently fell back to lexical on pip installs).
  • CI matrix extended to Windows (atomic-write path is OS-sensitive).
  • 104 tests passing (14 new).