The Level 6 sidebar now restores the approved workspace-selection hierarchy without changing the live authorization or request contract.
workspace_id.Every release, dated in UTC, matching the npm registry time field. Keep a Changelog format, SemVer clause 4 discipline. Below 1.0.0 the minor slot is the only breaking-change signal you get.
Nine of the twenty landed on 1 Aug in the original governance run. The tall bar is not velocity, it is a day of small corrections shipped honestly.
0.5.7 → 0.6.0 may break you, and ^0.5.0 does not match 0.6.0. Pin an exact version. The word stable around Titen names the release channel, never API stability.
The Level 6 sidebar now restores the approved workspace-selection hierarchy without changing the live authorization or request contract.
workspace_id.The complete Level 6 operator surface now runs on live, scoped contracts in both runtimes, with package and Cloudflare regressions closed before release.
workspace_graph lens over authorized claim/subject
nodes and canonical relationships, with stable layout, selection, legend,
truncation state, and direct Evidence Trace navigation.admin delegation remains bounded to its grant target.dist/index.html, so an installed titen dashboard
serves the root redirect as well as /dashboard/ (#303).The dashboard now renders live Atlas evidence using the approved claim-centered Evidence Trace structure.
The dashboard now separates the actionable Memories inventory from the read-only visual Atlas. Memory records are visible immediately after login, without a compile step.
GET /v1/memories lists authorized canonical claims with bounded lexical search, lifecycle/visibility filters, and stable keyset pagination.The 0.8.3 release closes the Atlas authorization and semantic-index readiness issues.
index_projection_pending; dependency failures remain HTTP 503.titen-memory 0.8.2
titen dashboard [--port 4322] serves the packaged Astro dashboard through
the existing same-origin adapter. Live mode remains opt-in and requires the
documented private service configuration.prepack rebuilds the dashboard and SDK before creating the npm tarball.The live operator dashboard now follows the approved Memory Atlas visual system while keeping the authenticated adapter and six-area contracts unchanged.
Titen can now bootstrap curated memory from sixteen agent and memory-system exports through one bounded, deterministic importer. This release also closes the three usability gaps that could make accepted evidence look missing, split one service across working directories, or leave an apparently successful installer without a runnable command.
--db now use the absolute per-user
store ~/.titen/service.db, not ./titen.db. Existing deployments should
keep passing their current absolute --db path or move the database once.
When a legacy cwd titen.db exists and the user store does not, Titen refuses
to mutate either store and prints the explicit compatibility command.titen is installed but still
cannot be resolved by name. Automation that intentionally consumes the
verified absolute binary path should call install.sh --print-path; its
stdout contains that path only.titen import-source previews by default and applies only with an explicit
local database or exact served origin. Versioned profiles cover Mem0,
OpenClaw, Hermes, Claude Code, Codex, Gemini CLI, Qwen Code, ByteRover,
Amazon Q, Replit, Honcho, Letta AgentFile, MemoMind, agent rule files, Basic
Memory, and explicit Markdown. Imports use the existing observation and
consolidation contracts, deterministic source and idempotency identities,
private/unverified defaults, and exact replay accounting.unconsolidated_observations: the count of
readable evidence in the requested subject/project scope that has not yet
produced a claim. Returned context items remain claim-only.bootstrap and serve no longer choose different databases when launched
from different directories, and serve refuses to create an empty store by
accident (#298).PATH; it prints a predictable TITEN_BIN=... recovery value and exits 1,
while --print-path provides a scripting-safe mode (#299).Three fixes found while wiring a live Titen into Claude Code as a memory server, and every one of them cost hours before it was understood.
TITEN_MCP_URL nor
TITEN_API_KEY set, titen mcp falls back to ~/.titen/memory.db. That is
correct, and a host configuration can reach it by accident: on the machine
where this was found, a project-scoped MCP registration with an empty env
shadowed the user-scoped one that carried both variables. The session looked
healthy — connected, eighteen tools — while titen_compile returned zero
items for a subject whose claim the served instance returned over HTTP in the
same minute. Local mode now names the store it opened and both unset
variables twice: once on stderr, and once appended to the instructions in
the initialize result, which is the copy the model reading an empty context
pack can actually see. A served deployment appends nothing.-32000 Titen MCP request failed.; a notification produced no reply
and no trace at all. The endpoint and the caught reason now go to stderr for
requests and notifications alike, with the API key redacted as it already was
in a response body, and an upstream answer that is not JSON-RPC reports its
HTTP status. The body is still never printed, because it carries memory..gitignore covered databases and no key material. titen.key,
keys/owner.key and secrets/x.pem were all committable in the repository
whose CLI prints an API key and a dashboard password, and whose default
--db is relative to the working directory. *.key, *.pem, *.p12 and
secrets/ are now ignored.Prompted by an external audit of b2d2fba. 61 of its claims were verified against HEAD before any of them was acted on: 32 held, 4 were already fixed, 18 held in part, and 7 were wrong. Everything below survived that check; the rejections are in PR #296 so nobody spends a day on them.
@modelcontextprotocol/server-memory imported nothing, and said nothing. That server writes beside its own module when MEMORY_FILE_PATH is unset, so anyone who ran it the documented way has their graph in a hashed directory inside npm’s _npx cache — never searched. An MCP server launched by a desktop client also inherits the client’s working directory, so the one fallback was weakest exactly where it was relied on. Both populations imported zero entities in silence, which reads as Titen losing their memories. The search now covers the working directory, node_modules/@modelcontextprotocol/server-memory/dist beneath it, and every such install in the _npx cache, under both memory.jsonl and the older memory.json.memory://knowledge-graph is served. resources/list returned -32601, so a client reading the graph as an MCP resource broke on the switch however well the nine tool names matched. initialize declares resources with subscribe: false: readable, no change notifications.synchronous = FULL: fifty no-op CASE updates grow the WAL by 0 bytes, fifty real ones by 206,032 — one page and one fsync each, one billed write on D1. 25 of 27 GET routes paid it. Now bounded to one write per key per minute, still monotonic.score is comparable across queries (#227). Relevance was rescaled against the candidate set, pinning the best candidate to exactly 1 however poor the match, so rank 1 returned one distinct value across all 500 benchmark questions and threshold-based abstention was arithmetically impossible. It is now strength / (strength + 3.7) where strength is the bm25 magnitude per query term; rank 1 now returns 498 distinct values spanning 0.4875–0.6632. Pre-registered before any cell was scored: anchor recall@1 0.8800 → 0.8800, pooled 0.2460 → 0.2460, sign tests W0/L0/T500 at p = 1.0, compile p95 +1.21% / +0.67%.bm25 −60 against −45 the relevance gap compresses 0.250 → 0.018 and the order flips to the verified claim over the marginally better-matching asserted one. Intended, pinned by contract, and bounded — a genuinely weak match still loses to a strong one however trusted.3.7 is calibrated on LongMemEval-S and BM25 is not portable across corpora, so the absolute band shifts with the corpus while the ordering does not. The vector arm’s half of the change is unmeasured: both benchmark lanes ran with vectors disabled.ftsQuery, canReadRecord, recordEvent, param, FEEDBACK_ENDPOINT.@modelcontextprotocol/server-memory and saw an empty store should restart titen mcp: the import runs on a store that has not yet imported, and it now looks where that server actually writes.@types/node and @types/bun were never installed, so tsc could not resolve process, Bun, or any node:/bun: import — 791 reported errors, none of them bugs. src/ is now clean under tsc --noEmit with no any, @ts-ignore, or new cast added.An urgent fix: 0.7.1 cannot serve a large single-subject store. Upgrade from 0.7.1 without delay; 0.7.0 is unaffected.
titen-memory@0.7.1 cannot serve a large single-subject store: one context
compile takes a median 74.5 seconds where 0.7.0 takes under half a second. The 0.7.1
fix for #291 wrote the
disputed predicate as a join inside EXISTS while its own comment claimed
the nested form, and a join inside EXISTS is still a join the planner may
reorder. SQLite 3.53.0 — the version Bun 1.3.14 links — reorders it to
SEARCH o USING INDEX observations_workspace_scope (org_id=?), scanning every
observation in the organization once per candidate row: the exact shape
src/core/authorization.ts documents as the historical 79-second failure,
shipped in the release that believed it had prevented it.
Who is affected: anyone whose store holds many claims under one subject
and who compiles with a large max_candidates. The cost is the product of
candidates and organization-wide observations, so small and per-subject stores
are unaffected — which is why no test and no published benchmark caught it.
Every published pooled quality figure was measured on 0.7.0; every published
pooled or scoped-anchor latency figure was measured on 0.7.0 or on this
release, never on 0.7.1.
Measured, 342,129-claim / 19,829-observation store, one subject, real
statement, EXPLAIN captured from bun:sqlite rather than a pasted copy:
| candidate query | served compile | |
|---|---|---|
| 0.7.1 as published | 73,439 ms | 74,474 ms |
| this release | 232 ms | 417 ms p50 / 864 ms p95 |
This restores 0.7.0’s behaviour; it does not make compile fast. 864 ms p95 still fails the pre-registered 250 ms gate, so the 2026-08-07 latency falsifier stands — the pre-registration predicted exactly that outcome in writing before the run.
The ranked output is byte-identical to the published 0.7.0 pooled run — equal sha256 over all 500 instances — so this restores the shipped answer rather than changing it. Full method and both plans in the report, protocol pre-registered before the A/B.
tests/integration/query-plan.test.ts). The regression above was invisible
to every existing test because contract stores hold tens of rows and still
return the right answer quickly. The load-bearing discovery is that the bad
plan reproduces on an empty store — SQLite picks the join order from the
schema, not from row counts — so a cheap deterministic test could have caught
this and the 2026-08-07 occurrence before either shipped. The guards assert
the plan of the candidate query, the by-id hydration, and authorized-source
loading; reverting the fix fails two of the three. They assert the plan, never
a duration: a timing assertion on this hardware would be flaky, and the plan
is what regressed.⚠️ Superseded — do not install. Replaced by
0.7.2. On a store with many claims under
one subject, titen-memory@0.7.1 takes a median 74.5 seconds to compile
one context, where 0.7.0 and 0.7.2 take under half a second. Cause and
measurement are in the
0.7.2 notes.
A ranking dead heat now breaks on authorized evidence depth before the arbitrary statement fallback (#288). Order changes only where weighted score and vector similarity are both exactly tied; measured byte-identical on all 500 LongMemEval-S instances, and published as capturing 0.0 of the reranking ceiling on that corpus (report).
The disputed signal now resolves through the caller’s own authorization,
so a contradicting observation the caller may not read no longer marks the
claim (#291). The flag
was computed from a bare EXISTS over claim_sources with no join to
observations, while the citations beside it were filtered correctly. A
principal who could not read the contradicting source still saw the claim
demoted by the 0.05 conflict term and still received a conflicts[] entry
whose evidence_ids omitted the source that caused it — told a contradiction
existed and told they could not see it. Fixed at all four query sites:
POST /v1/context/compile (lexical and vector candidates), GET /v1/context/:id, and the Memory Atlas conflict_freshness lens. The
governance review queue was already correct and now shares the same predicate.
Visible consequence: in a store that already holds a cross-scope
contradiction, claims that were demoted for callers who cannot read the
contradicting source stop being demoted. Their score rises by up to 0.05,
their score_components.conflict reads 1 instead of 0, they leave
conflicts[], and the resulting order can change. Nothing changes for a
caller who can read the source, and nothing changes for a claim whose own
status is disputed — that is the claim’s own field, not an inference about
hidden evidence. There is no migration and no flag: the previous numbers were
the leak.
Response shapes, routes, and field names are unchanged, so this is a patch under the table in release.md — the minor slot signals shape breakage, and only values that were disclosing hidden rows move here.
has_contradiction. It expressed the predicate as
claim_sources JOIN observations, and SQLite drove from observations,
evaluating the membership and retention subqueries for all of them once per
candidate. On a 424,168-claim store that is 79 s per compile against 17.8
ms. The shared predicate added above uses a nested EXISTS so
claim_sources seeks its own primary key, and the review queue now uses it.
Found by benchmarking the change above; the dual-runtime contract suite passed
on both query shapes, because its stores hold tens of rows.docs/testing/2026-08-07-disputed-authorization.md
— n=500, ranked output byte-identical before and after (0/0/500, p = 1.0),
compile latency flat within repeat spread. It also states what it cannot show:
the corpus holds zero contradicting sources, so it cannot measure the fix
where the fix fires.titen-memory 0.7.0
source.ref is now required on every observation write. A
caller that omitted it received 201; it now receives 400 VALIDATION_ERROR. The MCP tool spec already stated the obligation, so MCP
callers are unaffected; direct HTTP callers must add a pointer back to where
the content came from. Migration: add "ref" to the source object on
POST /v1/observations — any stable identifier for the origin (a commit sha,
a ticket id, a URL, a tool invocation id). There is no compatibility flag: an
entry that cannot be traced to an origin cannot be told apart from junk, which
is the whole point of the audit work below. Part of #280.npx titen-memory mcp with no environment opens
or creates ~/.titen/memory.db, provisions an organization, workspace,
project and owner as real rows, and serves MCP over stdio in-process — no HTTP
hop, no key to paste, no outbound call, FTS-only. The served mode and its auth
path are unchanged; this is an additional entry point, not a relaxation.
Closes #278.@modelcontextprotocol/server-memory. The nine
reference-server tool names are served alongside the native ones, search_nodes
is routed through Titen retrieval rather than a substring scan, and an existing
memory.json is imported on first run. The switching cost is one line of MCP
config. Closes #279.recalled provenance is server-issued. POST /v1/context/compile returns
a signed context token; an observation written while carrying it is stamped
source.type: "recalled" by the server, and a caller that merely declares
recalled is refused. Stateless HMAC, so no new table and no migration.
Known ceiling, stated in the code: the stamp proves the write was made while
holding a Titen-issued pack, not that its content came from that pack, so it
is a sound lower bound on the recall loop and never an upper one. Closes #280.titen audit. Reports exact-duplicate, near-duplicate, recall-loop,
secret-pattern and stale rates over a memory.json, a Mem0 export, or a Titen
store. No network, no LLM, no upload, no composite score, no leaderboard. Its
first published run is against Titen’s own store and opens by naming two
defects in this product. Closes #281.docs/testing/2026-08-07-titen-audit-self-report.md
— 17.9% byte-identical duplicates six seconds after write, 96.7% never read
back, and the compatibility surface turning one entity into six. It also names
the number it cannot report: there is no 32-day Titen store, so nothing in it
bounds long-run accumulation.docs/testing/2026-08-07-durability.md
— invariants held on both runtimes under concurrent writers.Webhooks no longer silently drop events written in their own registration
millisecond. Eligibility compared w.created_at < e.created_at, a strict
comparison on a millisecond wall-clock string, so on a fast host the
registration and the next write shared a millisecond and those events were
never queued, never delivered, and never retried. Delivery now pages on the
event_order.seq watermark that /v1/events and federation already use:
migration 22 adds webhooks.created_seq, backfilled to the current head so an
upgrade delivers only future events rather than replaying history. Both
eligibility sites are converted — processWebhooks and the background
deliverPending selector — because the second one would otherwise leave an
organization unwoken until another event arrived. Measured on a 16-core host:
the contract file went from 8 of 12 runs failing to 12 of 12 passing.
Closes #265.
Webhooks no longer silently drop events written in their own registration
millisecond. Eligibility compared w.created_at < e.created_at, a strict
comparison on a millisecond wall-clock string, so on a fast host the
registration and the next write shared a millisecond and those events were
never queued, never delivered, and never retried. Delivery now pages on the
event_order.seq watermark that /v1/events and federation already use:
migration 22 adds webhooks.created_seq, backfilled to the current head so an
upgrade delivers only future events rather than replaying history. Both
eligibility sites are converted — processWebhooks and the background
deliverPending selector — because the second one would otherwise leave an
organization unwoken until another event arrived. Measured on a 16-core host:
the contract file went from 8 of 12 runs failing to 12 of 12 passing.
Closes #265.
Retrieval ranking is now reproducible in the FTS-only lane. Exactly-tied
scores previously fell through to claim_id, a fresh uuid per ingest, so the
same corpus ranked differently on every run. Ties now break on the claim
statement — content-derived, and compared by code unit so Bun and Workers
agree — before falling back to the id. This makes rank reproducible, not
better: it picks an arbitrary-but-stable winner among genuine ties. What
causes the ties is untouched and stays open as #227. Closes #226.
Upgrading a pre-0.2.0 store now refuses instead of succeeding into silence.
Migration 10 scopes team visibility to a workspace and nothing backfills the
column, so a 0.1.x store migrated cleanly and then answered 404 on every
claim. migrate now counts legacy team rows first and fails closed, naming
the count and the 0.2.0 data floor. Rebinding is deliberately not attempted: a
legacy row’s real workspace is unknowable, and inventing one would invent an
authorization boundary. Closes #257.
The logical export refuses a pre-workspace team row rather than writing a
backup its own importer rejects. export_import is advertised as enabled, so
an artifact that cannot be restored is worse than an error. Closes #258.
serve() returns the handler it serves, and a
titen-memory/bun subpath exports it, so a benchmark or embedding host can
drive TitenClient through an injected fetch with no loopback hop. The
transport is removed; auth, scopes, and response envelopes are unchanged
because it is the same handler. An ephemeral socket is still bound and unused,
tracked in PONYTAIL-DEBT.md. Closes #230.Measured the memory-agent field on an externally authored corpus
(LongMemEval-S, MIT) and recorded where Titen actually stands:
landscape note.
docs/testing/EVALS.md now marks recall@5/@10 as saturated on that corpus so
only recall@1 and MRR@10 are quoted as discriminating, and blueprint.md no
longer plans a LoCoMo run — LoCoMo is CC BY-NC 4.0 and a launch is commercial
use, which an SPDX check misses because GitHub reports it as NOASSERTION.
Closes #267, #270, #271.
The single-core throughput ceiling is published as an operator sizing rule in
VPS deployment and deploy/README.md: at and
above 10,000 claims one client saturates one process, so shard by subject
across processes rather than adding clients. Cloudflare records that this is a
bun:sqlite property that does not transfer to per-request isolates. Closes
#259.
docs/testing/EVALS.md carries the FTS-only degradation curve, so no
FTS-only quality figure is quoted without the corpus size it was measured at.
Closes #260.
The cross_language:en zero is explained rather than left open: the stratum
rotates document language past query language, so it is three language pairs,
and English queries never retrieve a non-English document (2,000 of 2,000
top-10 hits). A provider embedding property, not a fixture defect. The fixture
is unchanged because its hashes pin the locked holdout and pre-hoc threshold.
Closes #245.
docs/engineering/release.md states why the tarball carries no provenance
attestation — it requires a supported CI’s OIDC token and this repository
publishes by hand on purpose — and gives consumers the registry checks that do
work, with what they do not prove. Closes #242.
One product sentence across the GitHub description, README, and
package.json. Closes #225.
Stable channel · npm latest · pre-1.0, the public API is not yet stable
mem0ai 2.0.13 in library mode.top_k on context compile, a standard-library-only Python client,
a container image, and the release-bound retrieval harness that the evaluation
spec had been waiting for.Stable channel · npm latest · pre-1.0, the public API is not yet stable
titen mcp
bridge, keeps credentials in host environments, and includes a nine-tool
connection check.Stable channel · npm latest · pre-1.0, the public API is not yet stable
titen mcp bridges newline-delimited stdio MCP clients to an existing
authenticated Titen /mcp endpoint, so hosts without native remote MCP can
use the same nine tools without another memory implementation.Stable channel · npm latest · pre-1.0, the public API is not yet stable
titen migrate --dry-run before starting 0.5.5. The migration is additive;
rollback restores the backup with the 0.5.4 binary.TITEN_DASHBOARD_SESSION_KEY. Without it, each
process uses an ephemeral key and restart signs everyone out.at instant and a
bounded max_candidates value through REST, MCP, and the TypeScript SDK.POST /v1/index/verify checks a bounded set of active claims against
Vectorize or sqlite-vec and queues repairs without reading embedding values.Stable channel · npm latest · pre-1.0, the public API is not yet stable
titen-test-* Wrangler profile provisions the isolated live
Worker, D1, Vectorize, Workers AI, and Cron contract without storing an
account API token in the Worker.owner, not a maintainer-specific placeholder.401/404, while native D1, Vectorize, and Workers AI bindings require no
account credential inside the deployed Worker.Stable channel · npm latest · pre-1.0, the public API is not yet stable
owner with a random temporary password;
human operators sign in with username/password, and authorized owners/admins
can atomically add another account, membership, role, and one-time password.
API keys remain unchanged for agents, services, SDKs, CLI recovery, and
existing integrations.Stable channel · npm latest · pre-1.0, the public API is not yet stable
Patch release for the live dashboard shipped in 0.5.0.
Stable channel · npm latest · pre-1.0, the public API is not yet stable
titen version --check explicitly reads the stable CLI/plugin release
manifest from titen.dev and points users to the manual install guide without
background polling or remote command execution.serverInfo.version
instead of mislabeling a deployment revision such as dev, test, or a Git
SHA as the server implementation version.topK limit, and the documented Wrangler recovery
command uses a real temporary SQL file.0.3.0 target.last_used_at monotonically, preserve lifecycle metadata across supported
operator surfaces, reject unknown creation fields, and cannot gain authority
through credential import.titen-memory 0.4.0
titen --version
instead of requiring a Node shim.null, and primitive JSON
envelopes with an INVALID_RESPONSE TitenError while preserving the HTTP
status, request ID, and safe response metadata.model as a deprecated 0.3.x
embedding alias. Migration 13 persists the claim-index provider, model,
revision, dimensions, metric, preprocessing, and schema fingerprint and
requires an explicit reindex after incompatibility. Migration 14 retains only
safe embedder/vector-store failure timestamps in semantic metadata until a
later complete embed/upsert proves recovery.sqlite-vec@0.1.9 is a pinned optional peer: default installs remain
dependency-light while the documented vector install is machine-verifiable.project_id as unscoped-only;
explicit cross-project recall requires cross_project: true plus the separate
context:compile:all capability and reports its effective scope and grant
reason across REST, SDK, and MCP.Canonical source
These entries are generated from the versioned release pages. Full source history remains available in CHANGELOG.md.