Skip to content

chore(release): v0.4.0#27

Merged
przemarzec merged 72 commits into
devfrom
release/v0.4.0
Jun 18, 2026
Merged

chore(release): v0.4.0#27
przemarzec merged 72 commits into
devfrom
release/v0.4.0

Conversation

@przemarzec

Copy link
Copy Markdown
Contributor

Milestone close: merge release/v0.4.0 into dev to fire the 0.4.0 release pipeline (semantic-release → version bump 0.3.1→0.4.0, tag v0.4.0, CHANGELOG, PyPI publish, GitHub Release).

Per RELEASE-PROTOCOL §3.5: regular merge (no-squash) so semantic-release reads the per-WS conventional commits.

Contents: bi-temporal model, MCP server, remember()/recall() convenience API, MindQL + FTS + embedding-input fixes, SQLite pragma/index perf, and the full 0.4.0 documentation pass.

Local full suite: 1922 passed, 97.18% coverage (benchmarks excluded — known Windows-host wedge, green on CI=ubuntu).

przemarzec added 30 commits June 3, 2026 11:40
chore: forward-merge dev into main (stable mirror sync)
chore: forward-merge dev into main (stable mirror sync)
chore: forward-merge dev into main (stable mirror sync)
chore: forward-merge dev into main (stable mirror sync)
chore: forward-merge dev into main (stable mirror sync)
The MCP server extra (engrava[mcp], v0.4.0) depends on the mcp SDK, which
requires pydantic>=2.11. Raise the floor uniformly so the whole package stays
on one pydantic minimum. Declaration change only — pyproject + uv.lock metadata
both declare >=2.11.0; no upper cap; no code change (already runs on >=2.12).
SqliteEngravaCore gains a public, policy-free async method that executes an
already-parsed MindQL query against the store's own connection and returns the
executor's MindQLResult. It applies no command-level filtering — callers that
expose a restricted command set (e.g. FIND-only over the wire) validate
query.command before calling. This lets consumers run MindQL without reaching
into the private connection.
Add an in-tree MCP (Model Context Protocol) server exposing engrava memory
over stdio for any MCP-capable agent. Read tools: get_thought, search_memory
(hybrid), search_keywords (FTS/BM25), query_memory (MindQL FIND-only),
memory_stats — all read-only, wrapping only the public store API. query_memory
runs via the public execute_mindql entry point and keeps a FIND-only guard at
the server boundary (rejects SELECT/COUNT/extension before execution). Ships as
the optional engrava[mcp] extra (FastMCP/stdio); a plain install is unaffected.
Entry points: python -m engrava.mcp and the engrava-mcp console script.
Brings the v0.4.0 "Connected" headline onto the release branch:
- feat(mindql): store-level execute_mindql entry point (neutral, policy-free)
- feat(mcp): MCP server with 5 read tools via the engrava[mcp] extra,
  query_memory consuming the public execute_mindql (FIND-only guard kept in
  the server boundary)
…y annotations

Expose store_thought, update_thought and link_thoughts so an MCP agent can
write to the memory graph, alongside the existing read tools. Each tool wraps
only the public store API.

Add an opt-in ENGRAVA_MCP_READ_ONLY flag (truthy: 1/true/yes): when set, the
write tools are not registered, so the server advertises a retrieval-only
surface rather than erroring on call.

Annotate every tool with MCP safety hints so clients can reason about each
call. link_thoughts is non-idempotent: an edge is unique per
(source, target, type), so repeating an identical link is rejected rather than
converging — its annotation and docstring state this explicitly.
Expose two destructive write tools that wrap the existing core delete
methods so an agent can remove a wrong thought or edge over MCP.

Both carry a destructive-but-idempotent annotation: deleting an absent
identifier is a no-op returning deleted=false and converges on the same
end state, so a retry is safe. They register only outside read-only mode,
alongside the other write tools, and stay hidden when the read-only
surface is requested.
Add three read-only MCP resources alongside the existing tools, so
clients can attach memory as context (resources are addressable URIs,
distinct from invokable tools):

- engrava://thought/{thought_id} - a single thought as JSON; an unknown
  id returns a graceful not-found payload rather than erroring.
- engrava://stats - store-health counts and size. Reuses the same
  memory_stats_impl as the memory_stats tool, so the two agree by
  construction (no duplicate stats logic).
- engrava://recent - the most-recently-updated thoughts as JSON.

Resources are reads by definition, so they are not gated by the
read-only environment flag and are advertised in both the default and
read-only deployments. Registration lives in a new register_resources()
wired into build_server() before the write-tool guard.
Register three read-oriented MCP prompts that scaffold common retrieval
workflows as client slash-commands: summarize_recent_memory (optional
limit), find_related (topic), and reflect_on_topic (topic). Each renders
a ready-to-send instruction steering the assistant to the read tools and
resources; summarize_recent_memory also embeds a read-only snapshot of
the most recent thoughts.

Prompts are reads, so like the resources they are registered outside the
write-tool guard and stay available in read-only mode.
Widen the MCP read surface to the filters and pagination the public
store API already supports, without changing any core method signature.

- search_memory: optional thought_type / lifecycle_status / priority
  filters applied as a post-filter on the ranked hybrid results
  (the hybrid ranker cannot filter). Ranking order and scores are
  preserved untouched; the unfiltered path is unchanged. When a filter
  is supplied the response adds a "filtered" block reporting the active
  criteria and scanned/matched/dropped counts, so a short or empty list
  is never mistaken for "nothing ranked".
- list_memory: new deterministic, unranked browse tool exposing the
  full list_thoughts filter matrix (type, status, priority, cycle range)
  with limit/offset pagination. Read-only tool, available in both
  default and read-only modes.
- query_memory already exposes limit; the MindQL grammar has no OFFSET,
  so pagination stays at limit only (documented).

Surface docs (module docstring, server instructions, register_tools
docstring) updated to enumerate the six read tools.
Translate the typed exceptions a tool can hit (store-not-ready, a
non-FIND query_memory command, a malformed FIND, an update of a missing
thought, and a link to a missing endpoint) into a clean ToolError with a
helpful hint, instead of letting a raw exception surface.

This is presentation only: it adds no capability and relaxes no guard.
The FIND-only contract on query_memory is preserved verbatim and the
rejection message never suggests raw SQL is runnable. Messages name only
the documented configuration environment variables and carry no
filesystem paths, stack frames, or internal symbol names.

Add tests/mcp/test_errors.py covering each mapped condition, guard
preservation, and the no-leak property over the in-process transport.
An unrecognised command verb (e.g. DROP) raised a MindQL parse error whose
raw text named the full command set ("Expected FIND, COUNT, SELECT, or
extension command"). The MCP query_memory surface exposes only FIND, so that
message leaked commands the surface deliberately hides. The parse-error
mapping now emits a generic FIND-only message with a valid FIND example and
never echoes the parser's command list. Regression tests cover unknown verbs
and malformed FIND.
Document the shipped MCP server surface: install via the `mcp` extra,
run over stdio (engrava-mcp / python -m engrava.mcp), store resolution
through ENGRAVA_MCP_CONFIG / ENGRAVA_DB_PATH, the 11 tools (6 read +
5 write), 3 engrava:// resources, 3 prompts, and ENGRAVA_MCP_READ_ONLY.
Add copy-paste mcpServers client configs for Claude Desktop, Claude
Code, Cursor, Windsurf, and VS Code, plus sample config files under
examples/. Link the guide from the README and examples index.
The OpenAI-compatible embedding provider issued a single request and
propagated any transient failure (read timeout, network blip, or a
transient HTTP status) straight to the caller, aborting the whole
ingest. _request_embeddings now retries with bounded exponential
backoff on httpx timeouts/network errors and on transient statuses
(408, 409, 425, 429, 500, 502, 503, 504), re-raising once attempts are
exhausted so a persistent error still surfaces. Non-transient statuses
(400/401/403/404, ...) still raise immediately with no retry.

Two keyword-only constructor arguments tune the policy, max_attempts
(default 3) and base_retry_delay_s (default 1.0); the defaults keep the
success path at a single request, so existing callers are unchanged.
The raise/log path carries only the status code and response body,
never the API key or Authorization header. Other providers untouched.
The execute layer (Layer 1) only ran an explicit allowlist of self-contained
blocks, so compile-only coverage missed examples that span several consecutive
blocks — in particular the tutorial, whose one worked example (imports ->
ingest -> link -> search_hybrid -> main + asyncio.run) is not runnable as any
single block.

Add an allowlist-driven page-concatenation capability: CONCATENATED_PAGES opts
a page in via (path, first_anchor, last_anchor), the inclusive contiguous block
range is joined in document order and run in a bounded subprocess asserting
exit 0. First target: docs/tutorial.md, which now executes end-to-end against
the installed package — running the search_hybrid round-trip whose return-shape
claims would otherwise rot silently under compile-only checks.

The opt-in stays entirely in the test module (no fence syntax or marker leaks
to the public docs). Documented in the module docstring so future docs authors
know how to opt a page in. tests/docs: 252 -> 253.
Introduce a second time axis ("valid time" — when a fact is true in the
world) alongside the existing transaction time. Adds nullable ISO-8601
valid_from / valid_until columns to ThoughtRecord and EdgeRecord, sharing
one ISO-8601 validator (UTC-normalised for correct TEXT ordering).

Schema migrates v12 -> v13 automatically on open: additive ADD COLUMN on
both tables, three partial indexes per table, and an asymmetric backfill
(thought.valid_from seeded from created_at where present; edges have no
calendar timestamp to source from, so they stay open-lower-bound NULL).
The migration tolerates a thought-only partial schema (skips edge work
when the table is absent) and is idempotent. A fresh v13 database is
structurally identical to a migrated one.

Existing behaviour is unchanged: the new fields are optional and queries
without a temporal filter return the same rows as before. Query-time
temporal predicates are a follow-up.
Set synchronous=NORMAL (the documented-safe companion to WAL) and a 5s
busy_timeout on every connection, cutting per-commit fsync cost and the
instant SQLITE_BUSY a second connection used to hit. Add four indexes
backing the equality filters and sort hit on every common read
(edge.to_thought_id, embedding.owner_id, thought.updated_cycle,
thought.thought_type), turning previous full scans into index lookups. The
schema migrates v13->v14 automatically on open: purely additive (indexes
only), idempotent, zero data loss; absent lazy tables and legacy-missing
columns are skipped rather than erroring.
synchronous=NORMAL + busy_timeout on every connection; four read-path indexes; additive v13->v14 migration.
Map the duplicate link_thoughts edge, invalid field value, and illegal
lifecycle transition in the MCP _tool_errors handler to curated, actionable
messages so internal SQLite schema names, Pydantic model internals, and the
status-type name never reach the client; an unrecognised integrity error is
re-raised unchanged. Add client-level tests asserting no internal symbol leaks.

Also complete the 0.4.0 documentation pass: announce the bi-temporal model,
the MCP server, and execute_mindql in the changelog and API reference; document
the FTS keyword query model, the MindQL quoting/fullmatch rules and FIND row
cap, the SQLite tuning and hot-path/valid-time indexes, the OpenAI-compatible
embedding retry and max_seq_length behaviour, and the user_version 12->14
upgrade; correct the concurrency busy_timeout/synchronous notes; and add
valid-time glossary and concepts coverage.
Update the architecture overview for 0.4.0 — add the bi-temporal valid-time
surface and the MCP server to the Layer Model and Core Components, and rewrite
the upgrade-path sections (REFLECTION clustering correctly attributed to 0.3.0;
v0.3->v0.4 now documents bi-temporal + MCP). Also document the gc --expired
flag and a bi-temporal query in the CLI guide, and drop an internal reference
from a module docstring.
Correct seven low/medium doc inaccuracies vs shipped 0.4.0: created_cycle/
updated_cycle optional (default 0) not required; fix three dead "& multi-tenancy"
anchor links; add the MCP server to the quickstart Next Steps; type the
subclassing example param as aiosqlite.Row; qualify search_keywords as an MCP
tool not a store method; the priority signal shipped in 0.3.0 not v0.2.1; and
point the core scope at infrastructure/sqlite.
bumped ``confirmation_count`` when deduplication hits).

"""
...
backends that contributed.

"""
...
@przemarzec przemarzec changed the title Release v0.4.0 release: v0.4.0 (bi-temporal, MCP, remember/recall) Jun 18, 2026
@przemarzec przemarzec changed the title release: v0.4.0 (bi-temporal, MCP, remember/recall) chore(release): v0.4.0 Jun 18, 2026
@przemarzec przemarzec merged commit fcc41c1 into dev Jun 18, 2026
11 of 19 checks passed
@przemarzec przemarzec deleted the release/v0.4.0 branch June 18, 2026 17:33
engrava-release Bot pushed a commit that referenced this pull request Jun 18, 2026
## 0.4.0 (2026-06-18)

* Merge bi-temporal documentation and 0.3 to 0.4 upgrade notes into v0.4.0 ([a04b75c](a04b75c))
* Merge bi-temporal valid-time storage into v0.4.0 ([534cb5c](534cb5c))
* Merge chore/release-trigger-to-dev: switch release-trigger to dev + align docs + CI on dev ([3ce07b2](3ce07b2))
* Merge convenience API (remember/recall) and cycle defaults into v0.4.0 ([bd6402f](bd6402f))
* Merge doc-block execution (tutorial end-to-end + search round-trip) into v0.4.0 ([c582bf0](c582bf0))
* Merge docs accuracy fix + documentation-example tests ([f4e1e28](f4e1e28))
* Merge documented-defaults verification and metadata-helper docs into v0.4.0 ([8ff4145](8ff4145))
* Merge embedding-input repair into v0.4.0 ([825a9fc](825a9fc))
* Merge embedding-provider transient-error retry into v0.4.0 ([f959844](f959844))
* Merge full-text query repair into v0.4.0 ([ba1b288](ba1b288))
* Merge MCP delete tools (delete_thought, delete_edge) into v0.4.0 ([76598bb](76598bb))
* Merge MCP guided memory prompts into v0.4.0 ([2afef80](2afef80))
* Merge MCP memory filters and pagination into v0.4.0 ([62b2b60](62b2b60))
* Merge MCP resources (thought, stats, recent) into v0.4.0 ([62c7639](62c7639))
* Merge MCP server (read tools) + MindQL execution entry point into v0.4.0 ([82c65f0](82c65f0))
* Merge MCP server guide and client-config examples into v0.4.0 ([7ccdbd8](7ccdbd8))
* Merge MCP structured tool errors into v0.4.0 ([8c6811b](8c6811b))
* Merge MCP write tools, read-only mode and safety annotations into v0.4.0 ([3723d10](3723d10))
* Merge MindQL correctness fixes into v0.4.0 ([3f3fbeb](3f3fbeb))
* Merge pull request #13 from sovantica/dev ([f2d4f8b](f2d4f8b)), closes [#13](#13)
* Merge pull request #14 from sovantica/chore/commitlint-ignore-merge-commits ([fbfe701](fbfe701)), closes [#14](#14)
* Merge pull request #15 from sovantica/dev ([f93d026](f93d026)), closes [#15](#15)
* Merge pull request #18 from sovantica/dev ([3db5530](3db5530)), closes [#18](#18)
* Merge pull request #21 from sovantica/dev ([f07fef4](f07fef4)), closes [#21](#21)
* Merge pull request #23 from sovantica/dev ([b2f1691](b2f1691)), closes [#23](#23)
* Merge pull request #27 from sovantica/release/v0.4.0 ([fcc41c1](fcc41c1)), closes [#27](#27)
* Merge pull request #28 from sovantica/fix/branch-guard-semver ([0598155](0598155)), closes [#28](#28)
* Merge pull request #29 from sovantica/ci/release-app-token ([5432de6](5432de6)), closes [#29](#29)
* Merge pull request from dev: docs accuracy fix + documentation-example tests (WS docs) ([7d1118d](7d1118d))
* Merge quickstart remember()/recall() short path into v0.4.0 ([1a89476](1a89476))
* Merge README extras list + drop no-op dreaming extra into v0.4.0 ([13c2448](13c2448))
* Merge README quickstart remember()/recall() into v0.4.0 ([22bf027](22bf027))
* Merge reflection temporal-extent inheritance into v0.4.0 ([e220f0a](e220f0a))
* Merge search functional contract suite into v0.4.0 ([30027f4](30027f4))
* Merge session-wide test thread-pinning into v0.4.0 ([114e85a](114e85a))
* Merge sqlite pragma tuning and hot-path indexes into v0.4.0 ([66afaf0](66afaf0))
* Merge temporal query predicates and invalidate primitive into v0.4.0 ([c160736](c160736))
* Merge temporal-query performance gate into v0.4.0 ([4779c9c](4779c9c))
* ci: add secret scan + dependency audit; verify wheel data on publish (#22) ([7cfaec3](7cfaec3)), closes [#22](#22)
* ci: align branch-name guard allowed types with BRANCHING.md ([a7f3e98](a7f3e98))
* ci: allow semantic-version release and hotfix branch names ([26261b4](26261b4))
* ci: cache HuggingFace models and pip to stop HF 429 (#20) ([e43f3cc](e43f3cc)), closes [#20](#20)
* ci: use GitHub App token for semantic-release push to protected dev ([1a282bd](1a282bd))
* ci(ci): ignore merge commits in commitlint via JS config ([3d54f61](3d54f61))
* ci(ci): run CI on dev branch as well as main ([d28ace2](d28ace2))
* docs: add bi-temporal model guide and 0.3 to 0.4 upgrade notes ([036e225](036e225))
* docs: bring architecture + CLI docs up to 0.4.0 (bi-temporal + MCP) ([cd5df19](cd5df19))
* docs: capitalize the Engrava brand name in README prose ([1254830](1254830))
* docs: correct mindql, extensions, extension-hooks, and configuration examples ([54149ef](54149ef))
* docs: correct README, quickstart, and api-reference examples to match shipped API ([36cf61d](36cf61d))
* docs: correct reflection_boost default to 1.0; add test verifying documented config defaults ([d42a852](d42a852))
* docs: document percept/utterance/thought metadata helpers in api-reference ([2e80d04](2e80d04))
* docs: drop reference to non-existent purity-check script in CONTRIBUTING ([59dcf5d](59dcf5d))
* docs: expand and correct the engrava documentation set (#17) ([2c3fa82](2c3fa82)), closes [#17](#17)
* docs: fix 0.4.0 drift found in the full doc audit ([bb5dff1](bb5dff1))
* docs: fix quickstart cycle note — remember() takes no created_cycle; use ThoughtRecord for write-sid ([8c00368](8c00368))
* docs: lead quickstart with remember()/recall() short path ([edfe620](edfe620))
* docs: lead README Basic Usage with remember()/recall() ([3f415e2](3f415e2))
* docs: list all installable extras in README (mcp + ollama/hf embeddings); note dreaming needs no ext ([c55f0ad](c55f0ad))
* docs: trim README Basic Usage to the core create-and-read example ([b2bf7bf](b2bf7bf))
* docs(mcp): add MCP server guide and client-config examples ([b2bc18b](b2bc18b))
* docs(release): align branching guide with dev release-trigger model ([7619127](7619127))
* docs(tests): de-reference internal principle name in doc-test rationale ([ed8b184](ed8b184))
* build: drop no-op 'dreaming' extra (empty deps; dreaming is in the base install) ([c6fa946](c6fa946))
* build(deps): raise pydantic floor to >=2.11 ([d7eaff0](d7eaff0))
* feat: add bi-temporal valid-time to thoughts and edges ([456bcb6](456bcb6))
* feat: add temporal query predicates and invalidate primitive ([86de77f](86de77f))
* feat: reflections inherit temporal extent from members ([8fba769](8fba769))
* feat(api): add remember() and recall() convenience methods on the store ([be9b110](be9b110))
* feat(mcp): add delete_thought and delete_edge tools ([84b87f6](84b87f6))
* feat(mcp): add guided memory prompts ([1f6fa36](1f6fa36))
* feat(mcp): add MCP server with read tools (engrava[mcp] extra) ([68a8085](68a8085))
* feat(mcp): add memory filters and pagination ([57357b7](57357b7))
* feat(mcp): add write tools, opt-in read-only mode, and per-tool safety annotations ([79d7604](79d7604))
* feat(mcp): expose memory as resources (thought, stats, recent) ([c54dcf7](c54dcf7))
* feat(mcp): map known failures to typed, actionable tool errors ([8b615cc](8b615cc))
* feat(mindql): add store-level execute_mindql entry point ([1c8ffb4](1c8ffb4))
* fix: assert plan-shape invariant for temporal queries, not scan-vs-index ([0e4e176](0e4e176))
* fix: embed full thought content without duplication or silent truncation ([36e08e7](36e08e7))
* fix: keep quoted MindQL values as strings and reject malformed conditions ([d88043d](d88043d))
* fix: let natural-language queries reach the full-text index ([bb6b729](bb6b729))
* fix: match exact table token in query-plan helpers ([cd4ecc2](cd4ecc2))
* fix(embeddings): retry transient errors with bounded backoff ([897c46f](897c46f))
* fix(mcp): keep query_memory parse errors FIND-only ([5f4ea20](5f4ea20))
* fix(mcp): map write-tool errors and complete the 0.4.0 documentation ([6794436](6794436))
* test: add documentation-example test suite ([75b977e](75b977e))
* test: add functional contract suite for search behavior ([862e7bc](862e7bc))
* test: bound temporal query overhead and confirm index use ([035e860](035e860))
* test: isolate subprocess examples (offline, single-thread, no stdin) ([c3cf339](c3cf339))
* test: pin native thread pools session-wide to stop full-suite hang ([3a0eaa3](3a0eaa3))
* test(docs): execute the tutorial end-to-end and a search round-trip ([730c2bb](730c2bb))
* perf: tune sqlite pragmas and add hot-path indexes ([1256303](1256303))
* chore: back-merge main into dev after v0.3.0 release ([4769d22](4769d22))
* chore: back-merge main into dev after v0.3.1 release ([40e45b6](40e45b6))
* chore(ci): switch release-trigger branch from main to dev ([cd2bb24](cd2bb24))
* chore(deps): bump actions/cache from 4 to 5 (#25) ([c84fb8f](c84fb8f)), closes [#25](#25)
* chore(deps): bump actions/setup-python from 5 to 6 (#26) ([0a69457](0a69457)), closes [#26](#26)

[skip ci]
@engrava-release

Copy link
Copy Markdown

🎉 This PR is included in version 0.4.0 🎉

The release is available on GitHub release

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant