Changelog¶
All notable changes to Engrama will be documented here.
Format: Keep a Changelog Versioning: Semantic Versioning
[Unreleased]¶
Added¶
- Streamable HTTP transport for the MCP server. Switchable via
ENGRAMA_TRANSPORT=http(default staysstdio, so existing Claude Desktop setups are untouched). Binds loopback127.0.0.1:8000with the MCP endpoint at/mcp, runs stateful so conversational clients keep anMcp-Session-Id, and validatesOrigin/Host(DNS-rebinding guard, configurable viaENGRAMA_ALLOWED_ORIGINS). Adds a/healthprobe and a/.well-known/oauth-protected-resource(RFC 9728) stub wired for the upcoming OAuth phase viaENGRAMA_AUTH_ISSUER. No authentication yet — intended for local use only. See the Streamable HTTP guide.
[0.12.0] — 2026-05-22¶
Two latency fixes on the MCP server path plus the documentation site overhaul (bilingual docs, MkDocs landing page, modern GitHub Pages deployment). No API or schema-breaking changes.
Fixed¶
- MCP vault I/O no longer blocks the asyncio event loop (#66). The
MCP server runs on a single-threaded event loop, but ~15 synchronous
ObsidianAdaptercalls (list_notes,read_note,add_relation) and directPath.write_textwrites were made straight from insideasynctool handlers. While any one of those waited on a slow vault write — common on cloud-sync drives like Proton Drive or OneDrive, and more frequent since 0.11.0 added automatic note creation inengrama_relate— every other tool call queued behind it and hit the client timeout. The server looked half-alive (engrama_statusstill replied instantly) whileengrama_relate/engrama_surface_insightstimed out. Every sync vault call inside a tool handler now runs throughasyncio.to_thread, freeing the loop to service other tools concurrently. Insight.statusrange index added toinit-schema.cypher(#67).Insightis the highest-cardinality status-bearing label — everyengrama_reflectrun MERGEs new pending Insights — but it was the one status-indexed label missing from the schema's RANGE INDEXES block.engrama_surface_insights(MATCH (i:Insight {status: $status})) therefore degraded to a label scan once the pending queue grew to a few hundred rows. The index applies hot to existing deployments viaCREATE INDEX … IF NOT EXISTS.
Changed¶
- Documentation site overhaul. Bilingual docs (English / Castilian
Spanish) served from a dedicated MkDocs landing page (
index.md) in place of the README fallback, GitHub Pages now deployed via the modern Actions artifact flow rather than the legacy branch push, and the commercial contact points at the website. (#69, #70, #71, #72, #73, #74)
Dependencies¶
- Bumped
idna(#68) and the GitHub Actions toolchain —actions/cache(#64),actions/upload-artifact(#63),actions/attest-build-provenance(#62).
[0.11.0] — 2026-05-16¶
Dual-vault contract hardening (#52 Phases A–D), engine + MCP
canonicalisation of the merge key against TITLE_KEYED_LABELS (#51,
53, #59), and the one-shot engrama migrate keys command (#54) for¶
healing rows that pre-date the canonicalisation fix.
Fixed¶
- MCP
engrama_remembernow canonicalises the merge key the same way the engine does (#51 / #53). The handler writes to the async store directly and was carrying its own pre-#51if "name" in props else "title"selection, so an agent callingengrama_remember(label="Experiment", properties={"name": "X"})via MCP still landed on aname-keyed row even after the engine was fixed. Now the handler runs the sameTITLE_KEYED_LABELScanonicalisation asEngramaEngine.merge_node; the non-canonical alias is silently dropped (matching the sanitiser pattern). Newtests/test_mcp_remember_merge_key.pypins the MCP-boundary contract that #51's engine-level tests didn't cover. engrama_sync_vaultiteration shape.ObsidianAdapter.list_notes()returns[{"path": ..., "name": ...}, ...]but the MCP handler was iterating those entries as if they were strings, so every note path reachingobsidian.read_note()was a dict — the resultingWindowsPath / dictTypeErrorwas logged at WARNING level and the note was bumped into theskippedbucket. Vault sync had been a no-op end-to-end for some time without a regression test. Surfaced while writing the Phase D dry-run tests; iteration now unpacks the dict at the top of the loop.
Added¶
engrama migrate keysCLI command — one-shot migration that heals rows whosekey_fielddoesn't matchTITLE_KEYED_LABELS, the residue from pre-#53 / pre-#59 writes that picked the wrong merge key from the caller's property bag. Default is a dry-run that prints the plan and exits;--applyrewrites the rows. On SQLite the migration is a rename-in-place becauseUNIQUE(label, key_value)already collapsed the two writes onto a single row at write time. On Neo4j the migrator detects sibling-collision cases (where a canonical-keyed node already carries the same identity) and skips them with an actionable error rather than tripping the uniqueness constraint. Optional--labels Foo,Barscopes the run;--report PATHwrites a full JSON audit trail. Closes #54.dry_runparameter onengrama_sync_vaultandengrama_sync_note. Whentrue, neither tool writes to the graph or injectsengrama_idinto note frontmatter. They return the same JSON envelope as a real run, withcreated/updatedcounts replaced bywould_create/would_update. The vault response also includesfiles_would_receive_engrama_id— the explicit list of notes that would be modified. Gives operators a cheap way to confirm a sync targets the intended vault before it commits any writes, and is the natural pair toengrama_status(Phase C) when reasoning about a multi-MCP setup. Phase D of #52.engrama_statusMCP tool — read-only introspection that returns the running server's vault path, backend, embedder, search mode and engrama version. Designed to be called at session start when Engrama coexists with other Obsidian-capable MCPs (e.g.obsidian-mcp) so the agent can disambiguate which server "the vault" refers to before any sync or ingest call — a server-side signal that doesn't rely on docstrings being read or onengrama-system-prompt-v0.5.mdbeing installed. JSON contract documented inarchitecture.md. Phase C of #52.
Changed¶
- MCP tool descriptions for sync/ingest declare their vault scope.
engrama_sync_vault,engrama_sync_noteandengrama_ingestnow state that they operate against Engrama's internal vault (VAULT_PATH), distinct from any user-managed Obsidian vault exposed by a separateobsidian-mcpserver. Pure docstring change — no behavioural difference, but gives the agent a server-side signal to disambiguate when multiple Obsidian-capable MCPs are connected. First step of the multi-phase contract hardening tracked in #52.
Fixed¶
- Engine
merge_nodehonoursTITLE_KEYED_LABELSregardless of the caller's property bag. Previously the engine picked the merge key from whichever ofnameortitlewas present inproperties, so a caller that putnamein the bag for a title-keyed label (notably the MCPengrama_remembertool, which forwardsparams.propertiesverbatim) bypassed the schema convention. The result was duplicate rows on Neo4j and silent property-key divergence on SQLite between SDK and MCP writes of the same logical node. The engine now canonicalises the merge key after sanitisation; the non-canonical alias is silently dropped (matching the sanitiser's behaviour with reserved keys). Existing duplicate rows from earlier writes are not healed — a one-shotengrama migrate keyscommand is tracked as a follow-up. (#51) Insightadded toTITLE_KEYED_LABELS. The auto-injectedInsightdataclass usestitleas its merge key (Neo4j constraint enforcesn.title IS UNIQUE, everyreflect/proactivequery filters bytitle), but the codegen only added user-defined nodes toTITLE_KEYED_LABELS, soInsightwas missing. The engine fix above exposed the mismatch by canonicalisingtitle → namefor any label not in the set, breaking every CypherMATCH (i:Insight {title: ...}). Patched bothengrama/core/schema.pyand the generator scripts so future regenerations includeInsight.
[0.10.0] — 2026-05-14¶
CI maturity + supply-chain hardening for the first public PyPI publish. The library APIs are unchanged from 0.9.0; this release is about making engrama installable, auditable, and migratable for users outside the dev environment.
Added¶
engrama export/engrama import— backend-agnostic NDJSON dump and restore for the graph + vectors, enabling cross-backend migrations (SQLite ↔ Neo4j) as a first-class CLI path. Format is one JSON object per line: envelope, thennode/relation/vectorrecords. Vectors only restore when source and target embedding dimensions match; mismatches are reported andengrama reindexrebuilds them under the active embedder. (#30)- Release pipeline (
.github/workflows/release.yml) — six-stageguardian → build → sbom → attest → publish → release-notestriggered byv*tags. PyPI trusted publishing (OIDC, no API key in secrets), CycloneDX + SPDX SBOMs attached to the GitHub Release, SLSA build-provenance attestations on the wheel and sdist, and aworkflow_dispatchdry-run path. Guardian fails fast on version drift betweenpyproject.toml,engrama/__init__.pyand the topmost CHANGELOG entry. (#27) - PR-level vulnerability gate —
audit-depsjob runspip-auditon PRs that touchpyproject.tomloruv.lock(always on push tomain). Blocks on CVSS ≥ 7.0 (looked up from OSV) or any advisory with an upstream fix; warns on LOW/MEDIUM with no fix. (#27) - CI matrix across Python 3.11 / 3.12 / 3.13 for
import-smokeandtest-sqlite. Newtest-neo4jintegration job uses aneo4j:5.26.4-communityservice container with the committedscripts/init-schema.cypherapplied via the Python driver. (#26) - Phase-1 CI baseline —
lint(ruff format + check),test-sqlite(SQLite-only suite, no Docker),import-smoke(DDR-004 promise gate). Dependabot weekly forpipandgithub-actions, monthly fordocker. (#7–#12) - Phase-4 repo hygiene —
.github/CODEOWNERS,pull_request_template.md, structured issue templates,security.md(disclosure via GitHub private advisories),lychee-actionweekly + per-PR link checker,DDR-template.md. (#22)
Changed¶
- README embedder section — expanded to a six-provider matrix
(Ollama, OpenAI, LM Studio, vLLM, llama.cpp, Jina) with
copy-pasteable
.envblocks, recommended models + dimensions, start commands where relevant, and provider-specific gotchas. Mirrored inREADME_ES.md. (#29) - Misconfig surfacing in the CLI/MCP —
GRAPH_BACKEND=neo4jwithout the[neo4j]extra (#23) andengrama-mcpwithout the[mcp]extra (#28) now both emit a single-line install hint to stderr and exit 1, replacing the prior raw Python tracebacks. - Documentation — install instructions aligned with source-only reality, Codex + ChatGPT Desktop MCP setup snippets added in both READMEs. (#15, #23, #25)
- License metadata —
pyproject.tomlcorrected toApache-2.0.
Fixed¶
- Base install no longer eagerly imports
neo4j—engrama/core/client.pydefers the import toEngramaClient.__init__, soimport engramaworks on apip install engramabase install with no extras (DDR-004 promise gate). (#11) - FTS5 MATCH sanitization on SQLite — hyphenated queries like
engrama-mcp-serverare wrapped as phrases instead of being treated as FTS5 grammar, restoring the fulltext path. 14 tests added. (#16) - Hybrid search degraded-mode signal — when the embeddings provider
is unreachable, the search engine exposes
last_mode = {mode, degraded, reason}and the MCPengrama_searchresponse carries asearch_modefield, so callers can distinguish a fulltext-only fallback from a healthy hybrid result. (#20) - Degenerate embeddings caught at write time —
engrama/embeddings/health.is_degenerate_vectorflagsneeds_reindex=trueon the node and skips vector storage;list_nodes_for_embedding(force=False)now pulls those nodes back soengrama reindexheals them. (#21) under_connectedreflect pattern excludes stub neighbours — both backends now filter outstatus='stub'nodes when counting connections. (#19)- Ruff import grouping repo-wide cleanup. (#24)
[0.9.0] — 2026-05-10¶
Portable storage — SQLite + sqlite-vec as the default backend, Neo4j moved to an opt-in extra, single OpenAI-compatible embedder, full async contract parity (DDR-004, PR #5).
Added¶
- SQLite backend (
engrama/backends/sqlite/) — full implementation of theGraphStoreandVectorStoreprotocols on top ofsqlite3and thesqlite-vecextension. Modules:store.py(sync graph store, 36+ methods, FTS5 fulltext),async_store.py(async wrapper that mirrorsNeo4jAsyncStore's rich return shapes),vector.py(SqliteVecStoreusing thevec0virtual table),schema.sql(auto-applied on first connect). Default DB path:~/.engrama/engrama.db(override viaENGRAMA_DB_PATH). - OpenAI-compatible embedding provider (
engrama/embeddings/openai_compat.py) — single client speaking the OpenAI/v1/embeddingsshape, drives Ollama, OpenAI proper, LM Studio, vLLM, llama.cpp, Jina, and any future compatible service. Sync (embed,embed_batch) and async (aembed,aembed_batch) methods. - Backend factory (
engrama/backends/__init__.py) —create_stores()andcreate_async_stores()dispatch the engine, CLI, SDK, and MCP server through a single entry point.GRAPH_BACKENDenv var (or explicit config dict) selects the implementation. Skills and tools no longer hardcode any backend. engrama-mcpCLI flags —--backend {sqlite,neo4j}(defaultsqlite),--db-path,--neo4j-uri,--neo4j-password,--neo4j-database,--vault-path. Defaults read from environment.- Async GraphStore contract suite (
tests/contracts/test_async_graphstore_contract.py) — parameterised oversqlite-asyncandneo4j-async. Pins the rich response shapes the MCP server depends on (merge_nodereturns{"node": ..., "created": ...}, neighbours come back as{label, name, via, properties}, etc.). 12 tests × 2 backends. get_approved_titleson every store layer (Neo4j async, SQLite async, SQLite sync) — used by reflect to skip patterns the user has already approved (see Fixed below).- 76 SQLite-only tests (
tests/backends/test_sqlite*.py,tests/contracts/test_graphstore_contract.pySQLite branch) — pass on a fresh checkout with no.envand no Docker. CI runs them by default. - backends.md — newcomer-facing decision guide: when to pick SQLite, when to pick Neo4j, how to switch, FAQ. Linked from README and ARCHITECTURE.
- DDR-004 — formal record of the portable storage decision, including the three regressions found and fixed during e2e testing.
Changed¶
- Default
GRAPH_BACKENDis nowsqlite(wasneo4j). Existing installs that rely on Neo4j must setGRAPH_BACKEND=neo4jexplicitly. neo4jdriver moves to an opt-in extra (uv sync --extra neo4j, or theengrama[neo4j]extra once Engrama ships on PyPI). Base install ships withsqlite-vec,httpx,pydantic,python-dotenv,pyyamlonly.Neo4jGraphStorereturns plain dicts at the boundary (Phase 1 of the spec).EngramaEngine,recall.pyand other internal callers consume Pythondictrather than driver-specific types.HybridSearchEnginecopies enrichment fields (summary,tags,confidence,updated_at) ontoSearchResultfor vector-only hits. Both backends'search_similarnow project these fields.engrama initis a no-op on SQLite for Cypher schema statements (the schema is inbackends/sqlite/schema.sql, applied automatically) but still seeds domain nodes from the active profile.- Documentation overhaul — README, README_ES, ARCHITECTURE, ROADMAP,
VISION, GRAPH-SCHEMA, CONTRIBUTING and GLOSARIO_ES all updated to
reflect the dual-backend reality.
.env.examplenow defaults to the zero-dep SQLite path with Neo4j commented as opt-in.
Fixed¶
- Async store contract drift on SQLite.
SqliteAsyncStorewas forwarding sync calls via__getattr__, leaking the legacy[{"n": ...}]shape to the MCP server which expected the richNeo4jAsyncStore-style{"node": ..., "created": ...}. Crashed anyengrama_remembercall against the SQLite backend. Replaced with explicit method-by-method delegation that translates shapes; locked in by the new async contract suite. - Reflect overwriting approved Insights. Each
_build_*helper inengrama_reflectcalledmerge_nodewithstatus="pending", silently undoing user approvals on re-runs. Reflect now filters candidates againstdismissed | approved. The output payload also exposesapproved_countnext todismissed_count. - Search dropping enrichment on pure-semantic hits. The hybrid
scorer only copied
summary/tagsfrom fulltext results, so nodes ranked solely by vector similarity surfaced with empty fields.search_similarnow projects the enrichment fields and the scorer copies them on the vector path.
Removed¶
- Implicit assumption that Neo4j is required to use Engrama. Neo4j is
still fully supported via the
neo4jextra; nothing about its feature surface has changed.
[0.8.0] — 2026-04-14¶
Temporal reasoning — confidence decay, fact supersession, and time-travel queries (DDR-003 Phase D).
Added¶
- Confidence decay:
decay_confidence()inNeo4jAsyncStoreapplies exponential decay (confidence × exp(-rate × days_old)) to stale nodes. Supports dry-run mode, label filtering, and auto-archival of nodes below a confidence threshold. Sync equivalent was already inNeo4jGraphStore.decay_scores(). valid_tosupport:merge_node()now acceptsvalid_toto mark facts as superseded. Settingvalid_toauto-halves confidence. Updating a superseded node clearsvalid_to(revival) and returns a conflict warning.- Temporal queries:
query_at_date(date, label?)in both async and sync stores — returns nodes valid at a specific date (valid_from <= date AND (valid_to IS NULL OR valid_to >= date)). - Enhanced CLI
engrama decay:--dry-runnow shows a sample table of nodes that would be affected with current vs projected confidence values. - Enhanced stale knowledge detection:
reflectstale_knowledge pattern now also considers nodes withconfidence < 0.3as stale regardless of age. Insight body includes confidence value for severity assessment. - Async store tests:
TestAsyncDecayConfidence(5 tests),TestAsyncValidTo(3 tests),TestAsyncQueryAtDate(3 tests) added totest_temporal.py.
Changed¶
- architecture.md: rewritten 5-layer diagram (Adapters → Skills → Engine → Protocols → Backends), added temporal reasoning section, configuration reference table, updated directory structure to match reality (15 test files).
- graph-schema.md: added temporal fields (
valid_to,decayed_at,embedding) to all-nodes section. temporal.py:days_since()now handles Neo4jDateTimeobjects via.to_native().search.py: min-max normalization returns1.0for single-result sets (was0.0).
Fixed¶
- Search normalization bug: a single search result normalized to score 0.0 instead of 1.0.
- Neo4j
DateTimeincompatibility indays_since():TypeErrorwhen subtractingneo4j.time.DateTimefromdatetime.datetime. valid_toand caller-suppliedvalid_fromnow stored as Neo4jdatetime()instead of raw strings, fixingquery_at_datecomparisons.
[0.7.0] — 2026-04-13¶
Async embedding providers and hybrid search (DDR-003 Phase B + C).
Added¶
- Async embedding methods:
OllamaProvidernow hasaembed(),aembed_batch(),ahealth_check(),aclose()usinghttpx.AsyncClient. Sync methods (urllib) remain for CLI/SDK backward compatibility.NullProvideralso has async counterparts. - Async hybrid search:
HybridSearchEngine.asearch()— async counterpart ofsearch(). Usesaembed()and async store methods. Deployed in MCP server for non-blocking search. - Embed-on-write:
engrama_rememberandengrama_sync_noteMCP tools now embed nodes automatically whenEMBEDDING_PROVIDERis configured. Uses asyncaembed()to avoid blocking the event loop. core/text.py— re-export ofembeddings/text.pyfor import convenience.httpx>=0.27added as optional dependency (embeddingsandmcpextras).- Test suite:
test_hybrid_search.py(unit tests with mock stores for sync+async search, scoring formula, graceful degradation, integration tests with real Neo4j+Ollama). Async embedding tests added totest_embeddings.py(NullProvider + OllamaProvider async methods).
Changed¶
- MCP server lifespan: simplified — no longer creates redundant sync stores for hybrid search. The async store serves as both
GraphStoreandVectorStoreforHybridSearchEngine.asearch(). - MCP
engrama_search: usesHybridSearchEngine.asearch()with the async store directly, eliminating the sync→async impedance mismatch. - MCP embed-on-write: uses
await embedder.aembed()instead of syncembedder.embed(), preventing event loop blocking. - architecture.md: added "Embedding and hybrid search" section documenting dual-mode providers, embed-on-write, vector index strategy, and scoring formula.
Fixed¶
- Event loop blocking in MCP server: embedding and hybrid search previously called sync methods from async context, blocking the event loop. Now fully async.
[0.6.0] — 2026-04-13¶
Protocol extraction and bug fixes (DDR-003 Phase A). Zero Cypher in server.py.
Added¶
- DDR-003 Phase A — Protocol layer: abstract
GraphStore,VectorStore,EmbeddingProviderprotocols incore/protocols.py. All storage operations route through backend implementations — no adapter, skill, or tool writes Cypher directly. Neo4jAsyncStore(backends/neo4j/async_store.py) — async backend for the MCP server. Contains all Cypher that was previously inline inserver.py. Methods:merge_node,get_node,delete_node,merge_relation,get_neighbours,get_node_with_neighbours,fulltext_search,count_labels,run_pattern,lookup_node_label, plus vector ops (store_embedding,search_similar,delete_embedding,count_embeddings) and Insight ops (get_dismissed_titles,get_pending_insights,get_insight_by_title,update_insight_status,mark_insight_synced,find_insight_by_source_query,list_existing_nodes).create_async_store()factory inbackends/__init__.py— readsEMBEDDING_DIMENSIONSfrom config/env, returns configuredNeo4jAsyncStore.count_labels()andclose()methods on syncNeo4jGraphStore.NullGraphStoreandNullVectorStore— no-op implementations for testing and dry-run mode.- Test suites:
test_protocols.py(protocol conformance for all stores, NullGraphStore/NullVectorStore behaviour, async store method inventory) andtest_neo4j_store.py(integration tests against real Neo4j: merge, dedup, update, relations, fulltext COALESCE, neighbours, count_labels, run_cypher, get/delete node, health_check). .env.example:HYBRID_ALPHA(fulltext vs vector weight) andHYBRID_GRAPH_BETA(graph topology boost).
Fixed¶
- BUG-006:
engrama_searchreturnednullnames for title-keyed nodes (Decision, Problem, Vulnerability, etc.). Fulltext search and neighbour queries now useCOALESCE(node.name, node.title). - BUG-007:
engrama_reflectgenerated duplicate under-connected Insights on repeated runs._detect_under_connectednow checks for existing pending/approved Insight bysource_querybefore creating; updates in place if found; respects previously dismissed Insights. - BUG-008:
engrama_contextshowed duplicate relation entries in theviaarray. Deduplicated inget_node_with_neighbours.
Changed¶
- server.py: rewired from inline Cypher to
Neo4jAsyncStoremethod calls. Contains zero Cypher strings (was ~2053 lines, now ~1753). MCP tools handle orchestration, validation, vault I/O, and response formatting only. - server.py lifespan: creates
Neo4jAsyncStoreviacreate_async_store(driver, database, config)and stores it in context alongside the raw driver. - architecture.md: updated stack table, directory structure, and added "Protocol layer (DDR-003 Phase A)" section documenting sync/async stores, null implementations, and factory pattern.
[0.5.0] — 2026-04-12¶
Three core features that make Engrama valuable beyond a raw Neo4j wrapper. System prompt v0.5.
Added¶
- Phase 1 — Ingestion:
engrama_ingestMCP tool. Reads a vault note, raw text, or conversation transcript and returns content with entity extraction guidance. The agent extracts entities and callsengrama_rememberfor each one. Includes graph deduplication hints (existing nodes listed in response). - Phase 2 — Adaptive Reflect:
engrama_reflectnow inspects the graph before querying. Selects only applicable patterns based on what labels have data. Four new detection patterns: technique transfer (cross-domain technique applicability), concept clustering (3+ entities sharing a Concept), stale knowledge (90+ day old nodes linked to active Projects), under-connected nodes (<2 relationships). Previously dismissed Insights are never re-surfaced. Confidence scoring based on connection strength and entity count. - Phase 3 — Proactivity: Session state tracks
engrama_remembercalls. After 10+ entities stored since last reflect,engrama_rememberreturns aproactive_hintsuggesting the agent run reflect.engrama_searchchecks for pending Insights related to the search query and surfaces them inline.engrama_reflectresets the counter. - Reference docs: Extracted v0.4 detailed content into
docs/reference/(faceted-classification, query-patterns, node-schema, sync-contract). System prompt v0.5 is lean; reference docs are the "workshop manual". - DDR-001: Design decision record for the faceted classification system.
Fixed¶
- Phase 3 proactivity counter not firing:
_proactive_statemoved from FastMCP lifespan context (not reliably mutable across tool calls) to a module-level dict. Counter now persists correctly acrossengrama_rememberinvocations. - training_opportunity never activating: query only matched
Problem {status: "open"}but real graphs haveVulnerabilitynodes (status: "demonstrated"). Broadened WHERE clause:(issue:Vulnerability) OR (issue:Problem AND issue.status = $open_status). - shared_technology skipped in most graphs: required both
ProjectANDTechnologylabels, but many graphs have Courses or Decisions sharing technologies. Broadened: matches any entity viaUSES/TEACHES/COMPOSED_OF, activation requires onlyTechnologylabel. - stale_knowledge skipped when only Courses exist: activation required
Projectbut the query also checksCourseconnections. Broadened: activates when eitherProjectORCourseexists. _run_patterntoo rigid for OR-logic: addedany_labelsparameter — each entry is an OR-group where at least one label must have data. Used bytraining_opportunity(Problem OR Vulnerability + Course) andstale_knowledge(Project OR Course).
Changed¶
- System prompt v0.5: shorter, token-efficient. Adds dual-vault routing (obsidian-mcp vs engrama). References
docs/reference/for details. engrama_searchresponse now wraps results in{"results": [...]}object (was bare array) to accommodate optionalpending_insightsandproactive_hintfields.- Reflect skill confidence scores adjusted: cross-project 0.85, shared-tech 0.7, training 0.65, technique-transfer 0.5–0.9 (scaled by related entities), concept-clustering 0.5–0.9 (scaled by count), stale 0.5, under-connected 0.4.
[0.4.0] — 2026-04-12¶
Bug-fix sprint + schema expansion. System prompt v0.4.
Fixed¶
- BUG-001: CLI
initdropped fulltext index — comment lines inside;-split chunks caused the entire CREATE FULLTEXT INDEX statement to be silently discarded. - BUG-002:
engrama_remembernever created vault notes — nodes had noobsidian_path, breaking the DDR-002 contract. Now creates full YAML frontmatter with engrama_id, type, properties, and empty relations block. - BUG-003:
engrama_relatefailed to write to vault becauseobsidian_pathwas always null (cascading from BUG-002). Added fallback: if source node has no vault note, create one on-the-fly. - BUG-005:
engrama_remembercrashed whenrelationsdict was passed insideproperties— Neo4j rejects Map values as node properties. Now extracts relations before MERGE and merges both input paths (top-level field and nested in properties) into a single processing loop.
Added¶
- BUG-004: Domain seed data for all modules —
engrama initnow seeds domain nodes and key concepts for hacking, teaching, photography, and AI modules. - BUG-005: Inline relations in
engrama_remember— passrelations: {TEACHES: [Python]}and targets are found/created (with stub creation) + relationships merged + vault frontmatter written, all in one call. - FIX-008:
Materialnode type for teaching artifacts (cheatsheets, slides, exercises, reference cards). Properties: name, type, format, status, notes. New relation:HAS_MATERIAL(Course → Material). - DDR-001: Design decision record for the faceted classification system (was referenced but missing).
Changed¶
- FIX-006: System prompt section 4 — relaxed "immediately call relate" to reflect that
remembernow supports inline relations. - FIX-007: System prompt section 3 —
INSTANCE_OFis now mandatory only for Problem, Decision, Vulnerability. Recommended for all other types when it adds discovery value. - System prompt version bumped to 0.4.0. File renamed from
v0.3tov0.4.
Removed¶
_obsidian_mcp_ref/— development reference folder, not part of the codebase..claude/— session working directory artifact.
[0.3.0] — 2026-04-12¶
Bidirectional sync and vault portability (DDR-002).
Added¶
- DDR-002: Bidirectional sync — all graph relations are serialized into each note's YAML frontmatter
relationsmap. Vault and graph are co-equal sources of truth. ObsidianAdapter.add_relation(),remove_relation(),set_relations()— idempotent frontmatter relation management.NoteParserextractsrelationsfrom frontmatter, normalises scalars to lists, uppercases relation types.ObsidianSync.full_scan()three-pass strategy: nodes → wiki-links → frontmatter relations._infer_stub_label()— maps relation types to likely target labels for stub node creation.AssociateSkillwrites relations to vault frontmatter (dual-write contract).- DDR-002 test suite:
TestParserRelations(3 tests),TestAdapterRelations(9 tests).
[0.2.0] — 2026-04-11¶
Faceted classification system (DDR-001).
Added¶
- DDR-001: Six-facet classification adapted from Ranganathan's PMEST + BFO. Facets: identity, composition, action, purpose, context, domain.
- Composable profiles:
base.yaml+ domain modules (hacking, teaching, photography, ai). generate_from_profile.py— merges profiles, deduplicates relations, generatesschema.pyandinit-schema.cypher.- CLI:
engrama init --profile base --modules hacking teaching photography ai. - System prompt v0.2 with full faceted classification documentation.
[0.1.0] — 2026-04-10¶
Initial release. Phases 0–7: core engine, MCP adapter, Obsidian sync, skills, reflect, proactive insights, SDK, CLI.
Added¶
- Neo4j 5.26 LTS with Docker Compose setup.
- Core engine: MERGE semantics, timestamps, fulltext search.
- FastMCP server with 10 tools: search, remember, relate, context, sync_note, sync_vault, reflect, surface_insights, approve_insight, write_insight_to_vault.
- Obsidian adapter: vault ↔ graph sync via engrama_id.
- Four skill classes: RememberSkill, RecallSkill, AssociateSkill, ForgetSkill.
- ReflectSkill: cross-project solution transfer, shared technology detection, training opportunity discovery.
- ProactiveSkill: surface/approve/dismiss/write Insights to vault.
- Python SDK:
Engramaclass wrapping all skills. - CLI:
engrama init,engrama verify,engrama reflect,engrama search. - 100 integration tests across 9 test files.