Skip to content

Releases: fuwasegu/exocortex

1.2.1

Choose a tag to compare

@fuwasegu fuwasegu released this 07 Aug 06:09
ccd58ed

Fixes a regression in 1.2.0 that broke stdio proxy mode. Closes #30.

Who this affects

Anyone running --mode proxy (the recommended setup for Cursor and for multiple editor instances) on 1.2.0. Cursor showed the server as errored and no exo_* tool reached the agent:

Invalid input: expected object, received undefined
path: ["tools", N, "inputSchema"]

Direct --mode server was never affected — the SSE server always emitted correct MCP. Only the proxy corrupted the payload on its way to stdout.

Upgrade to 1.2.1. In proxy mode the server restarts itself on the version change.

What went wrong

The proxy re-serializes results from the SSE server before writing them to stdout. Under mcp 1.x the Pydantic field names were the protocol names, so a plain model_dump() happened to produce valid MCP. mcp 2.0 renamed the fields to snake_case and moved the protocol names to aliases:

mcp 1.x mcp 2.0
field inputSchema input_schema (alias inputSchema)
plain model_dump() inputSchema ✅ input_schema ❌

So the proxy started emitting a key the MCP schema does not define, and strict clients rejected the entire response. This came in with the mcp 2.x migration in 1.2.0.

inputSchema was not the only casualty — outputSchema, mimeType and _meta are aliased too, and were equally broken.

Serialization now goes through a single helper that applies by_alias=True, so no branch can omit it.

Why it was not caught

The 1.2.0 migration verified the server end thoroughly — tool registration, input schemas, concurrency, ASGI mounts — but nothing covered the proxy's re-serialization, which is the only place SDK models are re-dumped by hand. That gap is now closed by tests asserting the general rule: no field may be sent under its Python name when it has a differing wire alias. That will catch the next rename, not just this one.

Verification

Against a real stdio proxy bridging a live SSE server: all 19 tools carry camelCase inputSchema, none leak input_schema. 340 tests pass on Python 3.10, 3.11 and 3.12.

Thanks to @novr for the diagnosis, the exact root cause, and a working patch.

Full Changelog: 1.2.0...1.2.1

1.2.0

Choose a tag to compare

@fuwasegu fuwasegu released this 05 Aug 15:21
b31b685

Migrates to the mcp 2.x server API. Closes #27.

Upgrading

Nothing to do — the MCP tools and their arguments are unchanged. In proxy mode the server restarts itself on the version change.

mcp>=2.0.0,<3 is now required. 1.1.0 capped mcp at <2 as a stopgap because mcp 2.0 removed mcp.server.fastmcp; this release moves to the new API properly.

Slow tools no longer block the server

mcp 1.x invoked sync tool functions directly on the asyncio event loop, one at a time, which is why a single slow operation could stall everything — the complaint behind #25. mcp 2.x dispatches them on worker threads, so tools run in parallel:

pings served while a recall was in flight: 6244

That property is now structural rather than something Exocortex has to maintain.

The part that needed care

Parallel dispatch made several check-then-write guards reachable for the first time. exo_link_memories reads "does this link already exist?" before creating it; under 1.x nothing could interleave between those two statements, under 2.x it can. Eight concurrent identical calls produced four duplicate links.

Database-touching tools are now serialized with an operation-level lock, restoring the guarantee those guards were written against while leaving the event loop free. KùzuDB permits a single writer anyway, so little is given up. exo_ping is deliberately excluded — it never touches the database, and involving the manager would load the embedding model just to answer a health check.

Verified after the fix: eight concurrent exo_link_memories → exactly one link.

Reproducible builds

uv.lock is now committed, and mcp carries a <3 cap.

An unpinned dependency with no lockfile is what let an upstream major release break every fresh install and all of CI with no change in this repository — and it went unnoticed because main had not run CI in months. The lock also pins ruff, whose newer releases began formatting Python inside Markdown code fences and failing lint on untouched files.

The lock covers CI and local development; uvx --from git+... resolves from package metadata, so the version specifiers are what protect end users from the next major bump.

Verification

331 tests pass on Python 3.10, 3.11 and 3.12 against mcp 2.0.0. The new concurrency guard was mutation-tested: removing the serialization makes it fail with six duplicate links.

Full Changelog: 1.1.0...1.2.0

1.1.0

Choose a tag to compare

@fuwasegu fuwasegu released this 05 Aug 08:32
c08b792

Fixes a bug where writing a single memory could freeze the server for minutes and grow the database to hundreds of megabytes. Closes #25.

Upgrading

If your database directory is far larger than the data it holds, it was bloated by the old write path. Fixing the cause does not shrink an existing database — rebuild it once:

uvx --from git+https://github.com/fuwasegu/exocortex exocortex --mode compact

Or ask your assistant to call the new exo_compact tool. Your previous database is kept under ~/.exocortex/backups/.

The bug

exo_store_memory opened and closed a kuzu.Database for every write query, not once per write. Closing one runs a full synchronous StorageManager::checkpoint(), so a single store with five tags paid 13 checkpoints. Because MCP tools run directly on the event loop, that froze every other request — exo_ping included.

The RELATED_TO.reason corruption reported in #25 turned out to be the same bug, not a second one: every checkpoint rewrote relationship column chunks without reclaiming pages. Identical 700-relationship inserts:

on-disk size time
before 265 MiB 78.7 s
after 5.6 MiB 6.2 s

47× the disk from the same data. Once a column chunk outgrew the buffer pool, reads of it failed outright with Buffer manager exception: Unable to allocate memory!.

Results

before after
store_memory (5 tags) 1513 ms, 13 DB opens 209 ms, 1 open
store_memory (no tags) 314 ms, 3 opens 24 ms, 0 opens
link_memories 153 ms, 2 opens 12 ms, 0 opens
700 relationships 115.6 s, 280 MiB 6.9 s, 13.9 MiB
test suite 371 s 70 s

What's new

  • exo_compact / --mode compact — rebuilds a bloated database and reclaims the space. Relationship text that is already unreadable cannot be recovered; those columns are reported and rebuilt empty so the rest of the graph survives. Memory content is never dropped — compaction fails loudly rather than discarding memories.
  • exo_sleep now works in proxy mode. The server hands its database lock over before spawning the dream worker. This was previously documented as unsupported; the cause was the server holding a long-lived read-only database, which blocks writers.
  • --mode dashboard opens read-only, so the standalone dashboard shares the database instead of competing for the exclusive lock.
  • New settings: EXOCORTEX_DB_IDLE_TIMEOUT (default 5s — how long the database may sit idle before the lock is handed back) and EXOCORTEX_READ_ONLY.

Also in this release

  • mcp is now capped at <2. mcp 2.0.0 removed mcp.server.fastmcp, which made a fresh install fail to import entirely. Migration to the 2.x API is tracked in #27.
  • Fixes found by an adversarial review pass before merge: a stale connection could reopen a database the manager no longer tracked and leak its file lock; a lazy query result could be closed mid-consumption; compaction could leak the source handle and swallow a failed rollback.

Full Changelog: 1.0.1...1.1.0

1.0.1

Choose a tag to compare

@fuwasegu fuwasegu released this 12 Dec 06:18
94152a7

What's Changed

Full Changelog: 1.0.0...1.0.1

1.0.0

Choose a tag to compare

@fuwasegu fuwasegu released this 12 Dec 04:53
b700ed1

What's Changed

Full Changelog: 0.9.1...1.0.0

0.9.1

Choose a tag to compare

@fuwasegu fuwasegu released this 12 Dec 04:16
f92846c

What's Changed

Full Changelog: 0.9.0...0.9.1

0.9.0

Choose a tag to compare

@fuwasegu fuwasegu released this 12 Dec 02:50
c37e805

What's Changed

Full Changelog: 0.8.0...0.9.0

0.8.0

Choose a tag to compare

@fuwasegu fuwasegu released this 11 Dec 00:42
fe0402d

What's Changed

Full Changelog: 0.7.1...0.8.0

0.7.1

Choose a tag to compare

@fuwasegu fuwasegu released this 10 Dec 17:07
7b6f172

What's Changed

Full Changelog: 0.7.0...0.7.1

0.7.0

Choose a tag to compare

@fuwasegu fuwasegu released this 09 Dec 07:52
8d80f76

What's Changed

Full Changelog: 0.6.0...0.7.0