This release includes breaking changes for platform teams planning a safe upgrade.
✓ No known CVEs patched in this version
Topics
+3 more
Affected surfaces
Summary
AI summaryBroad release touches Behavior changes, Highlights, Commits, and Internal changes.
Full changelog
Install
npm install -g [email protected]
| | |
|:--|:--|
| npm | https://www.npmjs.com/package/neotoma/v/0.20.0 |
| Compare | v0.19.0 → v0.20.0 — view diff |
This release adds a safe way to re-key an entity type's identity rule after data already exists, fixes a production-freezing pagination bottleneck on deep entity queries, and moves npm publish and the hosted client-instance deploy off manual operator steps and into CI.
Highlights
- Re-key an entity type's identity without a destructive rebuild.
update_schema_incremental(MCP, HTTP, andneotoma schemas update) now acceptscanonical_name_fields, so a type whose identity collides (e.g. people deduplicated only byname) can move to a unique-field-led rule like[{composite:["linkedin_url"]},"email","name"]while itsreducer_configis preserved automatically. - Deep entity listings no longer freeze the server.
GET/POST /entities(andretrieve_entities/entities list) support an opaquecursorfor keyset pagination — O(page size) at any depth, replacing the O(offset) scan that could block the Node event loop for several seconds on a hosted instance at deep offsets. - npm publish no longer depends on one operator's laptop being awake. Tag pushes now trigger a GitHub Actions workflow that builds, verifies the tag matches
package.json, and publishes with--provenance, producing a signed supply-chain attestation that a local publish cannot produce. - The hosted client instance stays fresh automatically. A new deploy workflow mirrors the sandbox's existing "redeploy + verify on release" behavior for the separately hosted client instance, closing the gap where a stale build could go unnoticed.
What changed for npm package users
CLI (neotoma, neotoma api start, …)
neotoma schemas updategained--canonical-name-fields <json>to re-key a schema's identity rule from the CLI, matching the MCP/HTTP surfaces. A canonical-only call (no--fields/--remove-fields) is now valid.entities listgained--cursorfor keyset pagination;--offsetis still accepted but deprecated and now bounded — larger values return a structured hint pointing atcursor.
Runtime / data layer
- New pagination path in
entity_queries.tsbounds the legacy offset-scan (MAX_QUERY_OFFSET) and synchronous snapshot hydration (MAX_SNAPSHOT_PAGE_SIZE) on any single page.
Shipped artifacts
openapi.yamlanddist/include the newcursor/next_cursorandcanonical_name_fieldsfields. No other shipped-artifact changes.
API surface & contracts
GET /entitiesandPOST /entities/query: new optional request fieldcursor, new response fieldnext_cursor(both endpoints).offsetremains accepted but is markeddeprecated: trueinopenapi.yamland is now internally bounded.POST /update_schema_incremental: new optional request fieldscanonical_name_fields((string | {composite: string[]})[]) andfields_to_remove; an emptycanonical_name_fieldsarray clears the rule, omitting the field preserves the existing one. Response now echoessuccess,entity_type,schema_version,fields_added,fields_removed,canonical_name_fields,activated,migrated_existing, andscope— all additive.- MCP:
retrieve_entitiesgainedcursor;update_schema_incrementalgainedcanonical_name_fields, matching the HTTP/CLI surfaces (cross-surface parity test added). npm run openapi:bc-diff --base v0.19.0 --head HEADreports no breaking changes — 14 additive request/response fields only.
Behavior changes
- A cursor minted under one
sort_orderis rejected if reused after the sort changes mid-walk, or combined withsearch/ a non-defaultsort_by/ a non-zerooffset— callers restart the walk from the first page in that case. - Setting a type's
canonical_name_fieldsviaupdate_schema_incrementalis a major version bump (identity-rule changes are breaking to existing derivations), consistent with the existing field-removal behavior. It governs new writes only; existing rows keep their storedcanonical_nameuntil re-derived separately. offseton entity queries above the bound is now rejected with a structured hint directing the caller tocursor, rather than silently scanning further.
Agent-facing instruction changes (ship to every client)
docs/developer/mcp/instructions.md gained rules shipped to every connected MCP client on upgrade:
- Deep pagination: use the
next_cursorreturned byretrieve_entitiesinstead of growingoffset; treat the cursor as opaque, stop paging oncenext_cursoris absent, and restart from the first page on an invalid-cursor error rather than retrying the same token. - Schema evolution:
update_schema_incrementalnow documents thecanonical_name_fieldsre-key path, including the same-name-collision use case, the "new writes only" caveat, and how to verify the result viadescribe_entity_type.
Security hardening
security:classify-diff reports sensitive=true for this release (touches openapi.yaml and src/actions.ts, both on the classifier's file-identity watch list). Full-diff review confirms this is query/pagination/schema plumbing, not an auth-logic change: zero new routes, zero new proxy-trust reads, zero changes to LOCAL_DEV_USER_ID / guest-access / AAuth admission. security:lint (0 errors, 125 pre-existing warnings), security:manifest:check (in sync, 116 routes, no route-count change), and test:security:auth-matrix (18/18 passed, 1 pre-existing skip) all pass clean. Sign-off verdict: yes. See docs/releases/in_progress/v0.20.0/security_review.md for the full adversarial review.
Docs site & CI / tooling
- New
.github/workflows/npm-publish.yml: triggers onv*tag push, verifies the tag matchespackage.json, builds on pinned Node 20, and publishes with--provenance. No-ops (does not fail) if the version is already on the registry, so resuming a partial release stays safe. - New
.github/workflows/deploy-client-instance.yml: redeploys and verifies the hosted client instance onrelease: published, running the same post-deploy gates the sandbox already runs (/health, deployedgit_shamatch, non-trivial landing-page render, always-on machines). Contains no client-identifying values; skips cleanly when the client-instance app is unset. - Regenerated
src/shared/capability_manifest.jsonto recordquery_contacts_at_company'saddedInVersion: v0.19.0, unblocking thevalidate:capability-manifestCI check that had been red onmainsince the v0.19.0 tag.
Internal changes
entity_cursor.ts: new versioned, opaque base64url cursor token, scoped to the defaultentity_idsort (the only ordering with a unique, index-backed key).- Legacy-payload fixtures added for the
offsettightening per the errors.md hint-obligation rule.
Fixes
- Deep-offset entity queries no longer block the event loop. Root cause: the chunked scan in
entity_queries.tsre-read and discarded every visible row up tooffsetin JS on every page, with a deleted-entity-ids round-trip per chunk, freezing concurrent requests for the duration on a hosted instance at deep offsets. Fixed by keyset cursor pagination (see Highlights). validate:capability-manifestCI check. Was failing onmainsince the v0.19.0 tag becausequery_contacts_at_companyhad noaddedInVersionentry; regenerated the manifest.
Tests and validation
- Cursor pagination: cursor-paged pages tile the full result set with no gaps or duplicates and match legacy offset paging on the same data; invalid-cursor envelope verified over a real HTTP request and a real MCP client/transport pair.
canonical_name_fields: canonical-path contract tests, service-level coverage (ordered-precedence rule stored + major version bump,reducer_configpreserved, omit-preserves-existing, rejects unknown-field references), and a CLI cross-surface parity test.tscandeslintclean (0 errors);openapi:generateproduces no drift.- Full test suite, security gates (
security:classify-diff,security:lint,security:manifest:check,test:security:auth-matrix), and/reviewcode-review pass recorded indocs/releases/in_progress/v0.20.0/test_coverage_review.md.
Breaking changes
None. npm run openapi:bc-diff --base v0.19.0 --head HEAD reports zero breaking changes (14 additive fields only). The offset parameter is deprecated and now bounded, but remains accepted and returns a structured hint rather than being removed — existing callers under the bound are unaffected. Setting canonical_name_fields on an existing schema is a major schema version bump for that entity type (consistent with existing field-removal semantics), not a breaking change to the release itself — it is an opt-in write, not a default behavior change, and affects only new writes.
Commits (v0.19.0 → v0.20.0)
90d6e75chore(release): bump version to v0.20.0 + supplement383cb2bfix(manifest): regenerate stale capability_manifest.json for v0.19.0 (#2023)cc0413bfeat(schema): let update_schema_incremental change canonical_name_fields — closes #2018 (#2020)4ea08c0ci: auto-deploy the hosted client instance on release published (#2012)06bbc1eci: publish to npm from Actions with provenance (#2015) (#2017)b7ad5a1fix(entities): keyset cursor pagination for queryEntities (#1943) (#1946)
Full compare: v0.19.0...v0.20.0
Weekly OSS security release digest.
The CVE patches and breaking changes that affected production tools this week. One email, every Sunday.
No spam, unsubscribe anytime.
Share this release
About markmhendrickson/neotoma
Deterministic state layer for AI agents. Stores versioned entities (contacts, tasks, transactions, decisions) with immutable observations, full provenance, and schema-first extraction. Local-first SQLite, cross-client memory across Claude, Cursor, ChatGPT, and OpenClaw. Website
Related context
Related tools
Earlier breaking changes
- v0.12.1 Inspector build prepublish now exits non-zero if inspector submodule is missing, breaking ad-hoc npm pack runs without init.
- v0.12.0 Access policy source precedence: env > SchemaMetadata.guest_access_policy > config
- v0.12.0 Legacy feedback subsystem completely removed; issues subsystem is replacement
- v0.12.0 MCP submit_issue requires reporter_git_sha or reporter_app_version
Beta — feedback welcome: [email protected]