CHANGELOG.md
Rama main · réplica de lectura del repositorio original.
# Changelog
All notable changes to Titen are recorded here.
The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and
this project follows [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
Titen is in `0.x`: per SemVer clause 4, the public API is not yet stable. Below
`1.0.0` a **minor** bump carries breaking changes and a **patch** carries
everything else — any breaking entry appears under **Changed** and names the
migration. Released versions live on npm under `latest`; prereleases
(`0.2.0-rc.1`) under `next`. `main` may be ahead of both; see
[versioning and channels](./docs/engineering/release.md#versioning-and-channels).
The word **stable** around Titen — the npm `latest` dist-tag and
`"channel": "stable"` in [`titen.dev/version.json`](https://titen.dev/version.json)
— names the release *channel*, meaning a deliberate release rather than a
prerelease. It never describes API stability; the paragraph above does.
Every release heading below is dated in **UTC**, matching the npm registry
`time` field for that version. A heading date and
`https://registry.npmjs.org/titen-memory` must agree.
The published npm package is [`titen-memory`](https://www.npmjs.com/package/titen-memory).
The **CLI command is `titen`** regardless; see [Package name](#package-name).
## [Unreleased]
## [0.10.15] — 2026-11-11
schema 32
This release changes enrichment readiness: only a failure that an operator can fix reports `terminal_error`. A rejected model output gets one automatic second run. It adds no migration.
### Changed
- `/readyz` now reports `enrichment_jobs: terminal_error` only for a failed job that an operator can fix. These are the classes `provider_unavailable`, `provider_rejected`, and `provider_protocol`. A failed job with the class `unsafe_output`, `invalid_output`, or `source_changed` no longer marks readiness. The job stays `failed`. `titen jobs list`, `retry`, `ack`, and `purge` still select every failed job.
- A job that fails with `unsafe_output` on its first attempt now returns to `pending` one time. The model output varies between runs, so the second run often passes. The first failed run records no output hash. A second `unsafe_output` failure is terminal and records the hash of the second output.
## [0.10.14] — 2026-11-11
schema 32
This release fixes `titen jobs retry`: a retried enrichment job can now finish. It adds migration 32.
### Fixed
- A retried enrichment job can now record a new output. `titen jobs retry` clears the output hash of the job. Before this fix, the next run recorded a different hash and the database rejected it, so the job stayed leased and stopped the pass. Migration 32 replaces the trigger `enrichment_jobs_output_immutable`. The new trigger allows only the retry reset to `NULL`. Migration 32 also clears the output hash of each `pending` or `leased` job that an earlier retry left behind.
## [0.10.13] — 2026-11-11
schema 31
This release closes 1 issue: the dashboard Approvals queue now shows what an approver decides. It adds no migration.
### Fixed
- Dashboard Approvals now shows what an approver is deciding and why a decision is refused. Policies list their claim kind, minimum trust, and independent-approval setting, and disabled policies are de-emphasized. The approval queue shows each submission's reason and policy, and **Review evidence** opens the claim's Evidence trace in Atlas. The decision confirmation shows the submitter and submission reason. Under an independent-approval policy, the submitter no longer sees an **Approve** or **Reject** that the server always refuses; the row says another approver must decide. If the server does refuse a submitter's decision, the message names that rule instead of the generic "not authorized". The adapter maps that refusal to its own fixed wording. ([#407](https://github.com/RamaAditya49/titen/issues/407))
## [0.10.12] — 2026-11-10
schema 31
This release closes 3 issues: the purge cascade no longer redacts a claim that only cites the purged evidence as a contradiction, and the dashboard fixes the Releases approval view and the reference panes. It adds no migration.
### Fixed
- `DELETE /v1/observations/:id` no longer redacts a claim that cites the observation only as `contradicts`. Before, a purge redacted and revoked every claim that cited the observation in any relation. A disputed claim lost its statement when someone purged its contradicting evidence, although its supporting evidence was intact. Now only claims that cite the observation as `supports` or `qualifies` are redacted and revoked. The `dependent_claims_redacted` count follows the same rule. A legal hold on any claim that cites the observation, in any relation, still blocks the purge. The fix works forward only. A claim that an earlier purge already redacted cannot be restored. ([#404](https://github.com/RamaAditya49/titen/issues/404))
- Dashboard Releases now shows what an approver is publishing. The list has a Content column and channel labels, and the approve, activate, and revoke confirmation shows the full released text with its locale, validity window, proposer, and proposal reason. A release's proposer no longer sees an **Approve** that the server always refuses; the row says another approver must approve. If the server does refuse for separation of duties, the message names that rule instead of the generic "not authorized". The adapter maps the release proposer refusal to its own fixed wording. ([#401](https://github.com/RamaAditya49/titen/issues/401))
- Dashboard Subjects and Projects reference panes no longer hang on "Loading authorized references…". The dashboard adapter encoded the ID a second time, so a subject such as `user:alice` was requested as `user%253Aalice` and got a 404. The adapter now decodes the path segment once before re-encoding it, and rejects a malformed escape with 400. A failed request now shows an error in the pane, and a subject or project with no references says so by name. ([#403](https://github.com/RamaAditya49/titen/issues/403))
## [0.10.11] — 2026-11-09
schema 31
This release closes 5 issues: dashboard usability in Access, Models, Work, and Federation, SDK types for the workspace graph, and the event index on stores with statistics. It adds no migration.
### Added
- `GET /v1/keys` accepts `issued_by`, which returns the keys a principal issued, including derived keys clamped by that principal's grants. The SDK's `listKeys` passes it through.
### Fixed
- The SDK's `compileView` types now match the server. `ViewLens` includes `workspace_graph`, and the options accept `workspace_id` and `max_nodes`, so TypeScript callers can request the workspace graph without casting to `any`. The SDK type contract now pins `ViewLens` to the server's lens list, and `expire`, `acquireLease`, `releaseLease` and `compileView` gained round-trip tests against a real server. ([#392](https://github.com/RamaAditya49/titen/issues/392))
- Webhook and event scans now always read `event_order` through the per-organization `event_order_org_seq` index. Once SQLite had statistics (`ANALYZE`), it preferred the global `seq` rowid range. Each maintenance pass, webhook scan, `/v1/events` listing and federation pull for a quiet organization then walked every other organization's events, which #361 had meant to stop. The queries pin the index with `INDEXED BY`, which also fails loudly if the index is renamed. A query-plan test now checks the plans on an analyzed store with a busy neighbouring organization. ([#396](https://github.com/RamaAditya49/titen/issues/396))
- The dashboard's Access key clamp preview no longer lists every key the organization ever issued. It appears once a principal is selected and requests the active keys the selected principal holds or issued, through the `status`, `principal_id`, and `issued_by` filters of `GET /v1/keys`, which the adapter now forwards. It shows a Status column, starts with ten rows, and offers "Include expired and revoked (N)". The grant form and the access simulator stay within reach instead of sitting thousands of pixels down the page. ([#391](https://github.com/RamaAditya49/titen/issues/391))
- Dashboard Models no longer offers probes that cannot succeed or reports the wrong cause when one fails. Probe buttons are disabled for a group that is not enabled and name the variables that enable it. A ten-second countdown follows each probe. A probe that is rate-limited now says to wait instead of "Too many sign-in attempts", and a probe of a provider that is not enabled says so instead of "Titen is not ready". The adapter maps these known server codes to its own fixed wording and still forwards no upstream text. Its generic 429 message no longer mentions sign-in. When no model is configured, the generated restart configuration reads FTS-only instead of a lone response-mode variable. ([#395](https://github.com/RamaAditya49/titen/issues/395))
- Dashboard Work and Federation lookups no longer wipe the area's lists. **Find checkpoint** and **Load peer log** render in their own region, with a heading that echoes the query, so leases, handoffs, and peers stay visible. A miss reads "No current checkpoint for subject …, agent …, kind …" or "No exchange log for peer …" instead of "The authorized resource was not found". Checkpoint kind is now a choice of the four supported kinds. Handoff rows offer **Find checkpoint** and peer rows offer **View log**, and the Agent ID and Peer ID fields suggest IDs already loaded. ([#399](https://github.com/RamaAditya49/titen/issues/399))
## [0.10.10] — 2026-11-08
schema 31
This release closes 29 issues: query cost on large stores, dashboard usability and accessibility, and correctness fixes.
### Upgrade notes
- Migrations 28 to 31 run on start, or run `titen migrate --db <path>`. Take a backup first. Migration 29 backfills `event_order.org_id` for every event, and migration 30 indexes every active channel release, so the first start on a large store takes longer.
- Migration 28 adds `enrichment_jobs.source_observation_id`. Migration 29 adds `event_order.org_id` and `webhooks.scanned_seq`. Migration 30 adds the `channel_release_fts` index. Migration 31 adds the `observations_project` and `claims_project` indexes.
- Webhook scans now move forward only. An event that the webhook owner cannot read when it is first examined is not delivered later.
### Changed
- Context compile no longer scans a subject's entire observation history to report `budget.unconsolidated_observations`. One statement now inspects the 500 newest caller-readable observations at or before the compile's `as_of` and sets `budget.unconsolidated_observations_capped: true` when that window is full. On a subject with 200,000 unconsolidated observations, median compile latency on Bun dropped from 382 ms to 2.9 ms (2.4 ms for a subject with none). Observations ingested after a historical `at` are no longer counted (#339).
- The derivation backfill that runs on every enrichment drain no longer scans every derivation job for each observation. Migration 28 adds a `source_observation_id` column and an index, so a steady-state pass over 20,000 observations takes about 12 ms. ([#376](https://github.com/RamaAditya49/titen/issues/376))
- Event scans now cost the new events of one organization, not every event ever written in the deployment. `event_order` carries `org_id` with an `(org_id, seq)` index, and each webhook keeps a `scanned_seq` mark that moves forward with each delivery pass. `GET /v1/events`, webhook delivery, and federation pull read only the caller's organization above their cursor or mark. An event the webhook owner cannot read when it is first examined is not delivered later. The API and cursor rules are unchanged. ([#361](https://github.com/RamaAditya49/titen/issues/361))
- `GET /v1/projects` and `GET /v1/subjects` choose the page first and count records only for the returned entries. Cost now follows the page size, not the organization size. Counts, `last_write`, and omission of entries with no readable records are unchanged. Migration 31 adds the `observations_project` and `claims_project` indexes, which also serve the project reference checks ([#338](https://github.com/RamaAditya49/titen/issues/338)).
- `POST /v1/channels/:id/context/compile` now ranks eligible releases by relevance to `task`, falls back to newest activation first, applies the 200-release bound after ranking, and reports `omitted_items` and `budget_exhausted`. Migration 30 adds a release search index and backfills active releases ([#381](https://github.com/RamaAditya49/titen/issues/381)).
- `GET /v1/keys` can reach every key in the organization. It was capped at the 500 oldest keys with no cursor, so once dashboard sign-ins had filled that window, newly issued keys never appeared. The response now carries `data.next_cursor`, and the endpoint accepts `cursor`, `limit` (1-500, default 500), `status` (`active|pending|expired|revoked|all`) and `principal_id`. Filters run before the limit, and fences are loaded only for the returned page. The default first page is unchanged. The SDK's `listKeys(options)` passes these through and returns `next_cursor` (#340).
- Dashboard destructive actions confirm in one shared dialog instead of the browser's `confirm()` and `prompt()`. The dialog names the target record, its impact, and whether it can be undone. Cancel is focused by default, and required reasons go in a labelled multi-line field. Release activation now asks for confirmation too. The API keys table shows Created and Last used columns and a key-id suffix, and marks **This session**, so identical "Dashboard session" rows and their Revoke buttons are distinguishable. Revoking your own session warns first and then says you revoked it (#356).
- The dashboard Context form is laid out like the other forms, with labels above shared-style inputs at every width. A compiled pack now says why it is empty or short. It shows unconsolidated observations with the API's `titen_consolidate` hint, items that did not fit the token budget with their reasons, or that no authorized claims matched. The status line reports the item count. The Subject ID field suggests authorized subjects, and Subjects rows offer **Compile context** (#363).
- Dashboard System status no longer shows failing readiness checks in the same plain style as passing ones. Checks render as a status list with failing checks first, each with a text status and a non-colour marker. The service notice names the failing checks. `enrichment_jobs` reads as words instead of raw JSON and points to `titen jobs list --status terminal_error` on a terminal error. Each optional capability is listed once. ([#383](https://github.com/RamaAditya49/titen/issues/383))
### Fixed
- `tz` local-time fields no longer fail on historical Local Mean Time offsets that carry seconds. Such offsets are rounded to the minute with the wall clock shifted, so each `*_local` value keeps the UTC instant, and a value that cannot be presented becomes `null`. Before this, `POST /v1/observations` with an archival `occurred_at` returned 500 after the write committed, and a retry stored duplicate evidence (#337).
- Dashboard collection tables (API & Keys, Access) no longer hide their row actions at desktop widths. A collection narrower than its table now shows each row as a label/value record, as on phones, so "Revoke" stays visible in half-width split columns from 961px to 1440px. Status and trust values no longer break mid-word. Any table that still scrolls sideways is a named, keyboard-focusable region with edge shadows (#343).
- `GET /v1/memories` applies the claim validity window per row: `active` and `disputed` mean currently valid, whichever other statuses are requested. `status=active,superseded` no longer brings back expired or not-yet-valid active claims, and `facets` now use the same predicate, so `facets.status.active` matches what `status=active` lists. Facets and the validity rule are documented (#347).
- On laptop-height screens the dashboard sidebar shows every navigation group. At 1280x720, Administration and Governance used to sit below a 190 px credential footer with no scroll cue. The footer is now one row with identity and Disconnect, and the credential explanation sits in a disclosure. The nav rhythm tightens below 820 px of height, keeping targets at least 26 px. The nav shows scroll shadows when it overflows and scrolls the active area into view (#350).
- The dashboard Memories list shows one empty state that matches the situation. With no search or filter, it says no memory is visible yet and that agents add memory through the REST API or MCP. With filters, it names each active search, status, and kind filter and offers **Clear search and filters**. The live status line now reports counts only, so it no longer contradicts the visible message, and the pagination footer is hidden while the first page is empty (#352).
- Text with Windows line endings is accepted on every write path (REST, `titen_remember`, and the MCP compatibility tools) instead of failing with "unsafe control character". Each CRLF is stored as LF before validation and hashing, so the same note sent with LF or CRLF and the same `source.id` replays to one record. `titen import` applies the same rule, so markdown sources saved with Windows line endings import exactly like their LF copies. A carriage return not followed by a line feed is still rejected, now with a message that names U+000D. All other #108 controls stay rejected (#354).
- `titen import` no longer splits a long entry inside a surrogate pair or grapheme cluster. Entries over 4,000 characters with no nearby whitespace are now cut at the last grapheme boundary, so emoji and ZWJ sequences stay whole. Each chunk is also checked during prepare, so the preview reports a bad chunk instead of `--apply` stopping halfway with "must contain well-formed Unicode" (#358).
- A wrong password on the dashboard sign-in screen now says "Username or password is invalid." instead of "Your dashboard session is no longer valid", and a used or mistyped recovery code says so. The failed field is marked `aria-invalid`, linked to the message, and focused. After three failures in the tab, a line repeats the documented delay rule without revealing the account's state. Switching to the passkey or recovery-code panel clears the old message. The session-expiry message still appears for a 401 on an authenticated request (#360).
- Memory Atlas `workspace_graph` no longer returns 500 on Cloudflare D1 when the view holds 50 or more claims or 100 or more subjects. The subject and link lookups are now chunked under D1's 100-parameter limit, and links are filtered to the selected claims in memory, with the same output and order. Bun was not affected (#364).
- Dashboard Audit & Events now loads when opened and shows the newest audit entries and events first. It follows the API cursor in a bounded walk, pages with Newer and Older, and says when a list is partial instead of presenting the oldest page as the whole log. The filter form submits on Enter or through "Apply filters" and suggests known action names and event kinds. ([#367](https://github.com/RamaAditya49/titen/issues/367))
- `GET /v1/audit/export` now pages on the same `(created_at, id)` key it orders by. Because audit ids are random, the old id-only cursor skipped some entries and repeated others across pages while still reporting `complete: true`. Following `next_cursor` now returns every entry of the organization exactly once, in the same order as `GET /v1/audit`. An `after` value that names no audit entry in the caller's organization now returns `400 VALIDATION_ERROR` instead of a page that could read as a finished export. ([#368](https://github.com/RamaAditya49/titen/issues/368))
- The dashboard no longer offers passkey and recovery-code controls that cannot work. When readiness reports WebAuthn disabled, Profile hides passkey registration, last-passkey removal, and recovery-code regeneration and names the `TITEN_WEBAUTHN_*` settings that enable them, and the sign-in screen hides "Use passkey or recovery code". A passkey or recovery request that fails because WebAuthn is unavailable now says so instead of "The operation conflicts with current canonical state." ([#371](https://github.com/RamaAditya49/titen/issues/371))
- The `titen mcp` stdio bridge now relays JSON-RPC batches the way `POST /mcp` answers them. A batch on stdin produces the server's response array as one line. A notification-only batch produces nothing. An `initialize` inside a batch sets the negotiated `mcp-protocol-version` for later requests, and a failed batch yields one error per request id instead of a single `id: null` error the client cannot match. ([#372](https://github.com/RamaAditya49/titen/issues/372))
- The dashboard's "Search or jump to" dialog now does what it says. Enter no longer closes the dialog and discards the query. The dialog lists the areas the principal can open, filtered as you type, and offers to search Memories only when that area is available. Closing it returns focus to the trigger. The shortcut hint reads ⌘K on Apple platforms and Ctrl K elsewhere and is exposed through `aria-keyshortcuts`. At 620px and below the trigger becomes an icon button and no longer covers the breadcrumb. ([#375](https://github.com/RamaAditya49/titen/issues/375))
- Dashboard row actions (Inspect, Revoke, Release, Approve and similar) meet WCAG 2.2 target size (2.5.8). They were 14px tall and sat 8–10px apart. They are now at least 24px tall (44px with a coarse pointer such as touch), and a destructive action starts at the far end of the cell, away from Inspect. Standalone text buttons such as Disconnect also get a 24px minimum. ([#378](https://github.com/RamaAditya49/titen/issues/378))
- Dashboard Work, Federation, Approvals and Release policy now load their authorized data when opened, like the other areas, instead of showing "No live data loaded for this principal" until Refresh. The Subjects and Projects reference panes keep their "Select a … to inspect its references" prompt instead of the shared reset text. ([#380](https://github.com/RamaAditya49/titen/issues/380))
- Webhook delivery now reads the clock for each attempt instead of reusing the start of the drain pass. `X-Titen-Timestamp` is the attempt time, as documented, so a receiver applying the 300-second rule no longer rejects deliveries sent late in a pass with a slow endpoint. Each lease now covers the whole HTTP attempt, so a concurrent drain cannot send the same in-flight delivery again. ([#384](https://github.com/RamaAditya49/titen/issues/384))
- The dashboard's phone navigation rail can be closed without choosing an area. It has a visible "Close navigation" button and also closes on Escape or a tap outside it, returning focus to the toggle. While it is open, the page behind it is inert and focus moves into the rail. Choosing an area moves focus to the main region instead of a button that has slid off-screen. The toggle's `aria-controls` now points to the rail. ([#385](https://github.com/RamaAditya49/titen/issues/385))
- The Bun store now waits up to its 5-second busy timeout when another process holds a lock while it opens. Before, a second `titen` process that started during WAL recovery or a WAL switch failed at once with `database is locked`.
- Regenerating recovery codes now deletes the previous codes explicitly in the same batch, instead of relying on a foreign-key cascade that a connection without `PRAGMA foreign_keys` would skip. Contract tests on Bun and D1 now cover regeneration (old codes rejected, new codes accepted, a passkey required), the passkey list, `GET /v1/principals`, and project and subject references, including 404 for records the caller cannot read. ([#386](https://github.com/RamaAditya49/titen/issues/386))
- The dashboard topbar wordmark now loads Noto Sans Javanese from the dashboard origin, so it renders correctly shaped without a system Javanese font. Assistive technology skips it as decoration. ([#345](https://github.com/RamaAditya49/titen/issues/345))
### Security
- Development dependencies move to patched versions: `astro` 7.3.3 with its `vite`, `devalue`, `smol-toml`, and `source-map-js`, plus `undici` 7.30.1 and `sharp` 0.35.5 through workspace overrides. The published package does not ship these dependencies.
## [0.10.9] — 2026-11-03
schema 27
Import, request bodies, webhook signatures, and the audit cursor now close the open findings of [GHSA-27j7-xc9x-322c](https://github.com/RamaAditya49/titen/security/advisories/GHSA-27j7-xc9x-322c).
### Security
- Import applies the direct-route authority rules. A membership record needs an organization owner or admin, and only an owner may import the `owner` role. A credential without `*` may import keys only for its own principal.
- Request bodies are bounded while they are read. A chunked body above 1 MiB returns `PAYLOAD_TOO_LARGE` without buffering the rest. The Bun server and the dashboard adapter also set a runtime body limit.
- Webhook attempts add `X-Titen-Timestamp` and `X-Titen-Signature-V2`, an HMAC of the timestamp, the delivery id, and the body. Verify V2 and reject a timestamp older than 300 seconds. `X-Titen-Signature` stays for existing receivers.
- An audit cursor from another organization now returns no entries, the same as an unknown cursor.
### Changed
- ADR 0007 records `mcp:call` as a tool-bundle scope. Its behavior does not change.
## [0.10.8] — 2026-11-03
schema 27
A flood of sign-in attempts with new usernames can no longer lock every operator out of the dashboard ([GHSA-5f6j-4xcw-58px](https://github.com/RamaAditya49/titen/security/advisories/GHSA-5f6j-4xcw-58px)).
### Security
- Dashboard sign-in no longer locks out every operator when the shared throttle table is full. Admission evicts unblocked rows with the fewest recorded failures first and keeps active blocks. Only a table full of active blocks returns `LOGIN_RATE_LIMITED`.
- The Bun client limiter replaces its oldest client when it tracks 4096 clients, instead of refusing every new client.
## [0.10.7] — 2026-10-14
schema 27
Compile includes each result that fits the reported token remainder, and names every omitted authorized candidate.
### Fixed
- Compile charges item estimates against the reported `max_tokens`. A result that fits in `max_tokens - used_tokens` is included. Each omitted authorized candidate is listed in `budget.omissions` with `claim_id`, `tokens`, and `reason` (`result_too_large`, `budget_exhausted`, or `count_bound`).
## [0.10.6] — 2026-10-07
schema 27
### Upgrade notes
- Migration 27 adds nullable `enrichment_jobs.acked_at`. A failed job with that timestamp no longer marks `/readyz` `terminal_error`. Restart so migrate runs, or run `titen migrate --db <path>`.
- `titen version` prints `schema_version` on a second line. `titen --version` stays the bare package version.
### Added
- Write-fence and trust denials name the failed check and the values the key may use. `VALIDATION_ERROR` lists every missing field. `subject` and `query` are deprecated aliases of `subject_id` and `task`.
- A write or compile that omits `project_id` fills it when the write fence lists exactly one project and reports `project_id_source: "key_fence"`. An unscoped compile on a multi-project fence includes a scope hint.
- `titen_whoami` and `GET /v1/principal` report allowed trust and fenced projects. `titen_project_resolve` accepts a subject id and suggests close project references. The response never includes the raw key.
- Compile reports unconsolidated observation ids, a budget hint when nothing fits, `meta.degraded.reason`, and `meta.consistent_as_of`. SQL and FTS include commits visible at that timestamp. Vector matches follow the index.
- `titen jobs list|retry|ack|purge` manages failed enrichment jobs. `titen principal reassign` moves private observation and claim ownership and writes an audit event. `POST /v1/principals/reassign` does the same for an owner or admin with `keys:manage`.
- Optional `tz` or `TITEN_DISPLAY_TIMEZONE` adds parallel `*_local` timestamps. UTC fields stay unchanged.
- `/readyz` reports `version` and `schema_version`. The dashboard unit uses `PartOf=` and `Wants=` so a titen restart brings it back.
## [0.10.5] — 2026-10-06
schema 26
Share one memory service across agents with an optional one-call claim, a per-project visibility default, and optional key target fences.
### Upgrade notes
- Migration 25 adds nullable `projects.default_visibility`. Null keeps the private default, so a v0.10.4 database can migrate in place. Set a shared project with `titen project update --db <path> --org-id <org> --reference <owner/repo> --default-visibility organization`.
- Migration 26 adds `api_key_fences` and does not rewrite existing keys. Restart the v0.10.4 process on this build so migrate runs, or run `titen migrate --db <path>`. Existing keys remain unrestricted. The raw secret cannot be shown again, so revoke a key and create a replacement: `titen key create --db <path> --org-id <org> --principal <agent> --subjects castle:profile:alice,castle:shared`. JSONL export does not carry fences; a SQLite backup does.
### Added
- `titen_remember` accepts optional `consolidate: true`, which records an observation and one supporting claim in the same call. Trust and visibility are copied. MCP `initialize` instructions say to consolidate after remember, or to pass that flag, when no enrichment model is configured.
- Projects accept `default_visibility` at create and through `PATCH /v1/projects/:id` or `titen project update`. A write that omits `visibility` uses that default. Existing projects stay private until an operator sets one. An explicit private write into a wider project returns `visibility_warning`.
- API keys accept optional read and write fences for project ids and subject patterns at `POST /v1/keys` and `titen key create` (`--subjects`, `--projects`, and the explicit `--read-*` / `--write-*` flags). A write outside the fence returns 403, including through MCP as a tool error. `titen key list`, key creation, `GET /v1/principal`, and the `key.create` audit detail show the fences. Keys with no fence rows stay unrestricted.
## [0.10.4] — 2026-10-05
Protect dashboard sign-in and provide account recovery during password blocks.
### Upgrade notes
- No schema migration is required.
- Configure verified client headers only through a trusted loopback proxy. See the VPS sign-in runbook.
### Added
- Host account list, unlock, and password-reset commands support operator recovery.
- Password-independent passkey and recovery-code sign-in remains available during password blocks.
- Sign-in audit records and successful-session failure counts show preceding failed attempts.
### Fixed
- Bun limits client attempts and concurrent sign-in password checks.
- Persistent client and account throttles preserve active blocks and reject full storage before password work.
- The API rejects ambiguous raw paths before route matching.
### Security
- Session transactions reject stale account or revoked-session state after a host password reset.
## [0.10.3] — 2026-09-25
### Changed
- Requests to OpenRouter for extraction, embeddings, and the System One gate now
carry app attribution: `HTTP-Referer: https://titen.dev` and the app name
`Titen.dev`. Other providers receive no extra headers.
## [0.10.2] — 2026-09-25
Jev now decides reflection links. On a live store it handled 87% of reflection
jobs without the generative model and found more duplicates.
### Added
- The decision gate can now decide reflection links. Set
`TITEN_EXTRACT_GATE_LANES=reflection` to ask one choice question per premise
pair in a single request. Confident relations become a link-only proposal
that passes the usual validator. Code orders supersession links by date.
Claim synthesis, uncertain answers, and gate failures continue to the
generative call. `TITEN_EXTRACT_GATE_LINK_MIN_CONFIDENCE` (default `0.8`) sets
the threshold, and a conflict needs at least `0.9`.
## [0.10.1] — 2026-09-25
Titen is now Jev-ready. An optional decision gate lets a structured decision
model skip the generative extraction call when an observation holds no durable
fact.
### Added
- An optional decision gate runs before derivation. It asks one yes/no question
in the System One wire format. It works with TypeSafe Jev
(`https://api.typesafe.ai/v1/systemone`, `jev-1.13.0`) and with Jev on
OpenRouter (`https://openrouter.ai/api/v1/systemone`, `typesafe/jev-1.13`).
Set `TITEN_EXTRACT_GATE_URL`, `TITEN_EXTRACT_GATE_MODEL`, and the secret
`TITEN_EXTRACT_GATE_API_KEY` on Bun or Cloudflare.
- Only a yes probability below `TITEN_EXTRACT_GATE_ABSTAIN_BELOW` (default
`0.1`) records an abstain without the generative call. Every other answer,
and every gate failure, continues to the generative call. The gate never runs
for reflection and never adds, links, or raises trust.
- Partial gate configuration, a `latest` model alias, a gate without complete
extraction, or an out-of-range threshold or timeout reports
`configured_error`. A gate change re-keys the enrichment pipeline
fingerprint.
### Security
- The gate sends observation content to the gate provider. It stays off until
an operator sets its URL and model.
## [0.10.0] — 2026-08-30
### Added
- Optional WebAuthn passkeys now protect dashboard accounts on Cloudflare and
Bun/SQLite with exact RP ID and origin verification.
- First passkey enrollment returns eight recovery codes once. Profile can list,
revoke, and add passkeys or replace the recovery set.
- The dashboard completes passkey assertions through fixed same-origin routes.
A real browser WebAuthn test uses a virtual authenticator.
- A public-artifact release gate rejects private deployment identifiers in the
source tree and generated package assets.
### Changed
- Migration 24 adds persistent login throttles, explicit API-key authentication
stages, WebAuthn credentials and challenges, and recovery generations.
- Password login now applies progressive SQL-backed delays before verification.
The fifth failure waits 30 seconds. Later delays reach at most 30 minutes.
- Accounts with passkeys receive a 15-minute staged key until passkey or
recovery verification creates a full dashboard session.
- Dashboard sessions are no longer part of credential portability exports.
- Account-specific Cloudflare configuration and private runtime evidence are no
longer part of the public repository or npm release surface.
### Security
- Login throttle rows store only a hashed account bucket and bounded timing
state. Passwords and submitted usernames do not enter throttle storage.
- Challenges bind to organization, account, session, purpose, and expiry.
Conditional SQL claims prevent challenge and recovery-code replay.
- First passkey enrollment revokes older dashboard sessions. Removing the last
passkey requires the current password.
- Staged keys have no product scopes. Central route authorization permits only
the required password-change or second-factor completion operations.
## [0.9.1] — 2026-08-30
### Added
- REST and MCP errors now include bounded support guidance that classifies an
error as expected, requiring investigation, or a defect candidate.
- Missing project references keep `404 NOT_FOUND` and now identify the expected
setup action, normalized caller-supplied reference, and create capability.
- Distributed agent skills now reproduce and sanitize a defect, search existing
issues, and use a draft when GitHub write access is unavailable.
- Codex, Claude, Cursor, OpenClaw, Hermes, and Pi plugin manifests now publish
the updated issue-triage instructions under new patch versions.
### Security
- Error guidance uses allowlisted constants and never copies request bodies,
exception details, provider responses, credentials, prompts, or memory content.
- Titen keeps GitHub authentication in the host agent. Security findings use the
private disclosure path instead of a public issue.
## [0.9.0] — 2026-08-29
The operator dashboard now presents each data area for its specific task. The
REST, MCP, storage, and authorization contracts do not change.
### Added
- Task-specific tables and fact views cover Projects, Subjects, Work, Audit &
Events, Federation, Access, API & Keys, Approvals, Releases, Models, System,
and Context.
- Each collection includes an authorized record count, selected-record
inspector, empty state, and closed technical payload disclosure.
- Projects and Subjects load normalized references in the selected area.
- Desktop and 320 pixel mobile screenshots document the Projects directory.
### Changed
- Work, grant, key, approval, and release actions now appear in the affected
row. Successful mutations still reload state from the server.
- Federation peers and exchange records now use separate schemas. Audit entries
and domain events also keep separate operator labels.
- Mobile tables now become linear record lists without horizontal page
scrolling. The dashboard bundle remains below its 80 KiB gzip limit.
### Security
- The new renderer uses text nodes for authorized response data. It does not
render response HTML or save private data in browser storage.
- Disconnect, denial, logout, and capability changes clear the new tables,
inspectors, payload disclosures, and row actions.
## [0.8.7] — 2026-08-16
The Level 6 sidebar now restores the approved workspace-selection hierarchy
without changing the live authorization or request contract.
### Fixed
- The exposed native workspace select is replaced by the mockup-aligned
two-line trigger, workspace icon, animated chevron, active checkmark, and
bounded menu explanation on desktop and mobile.
- Selecting an authorized workspace still clears stale results and scopes
Memories and Workspace Graph requests with the same live `workspace_id`.
### Security
- Workspace names and selection state are cleared when the principal signs out
or the adapter disconnects; no workspace or credential state enters browser
storage.
## [0.8.6] — 2026-08-16
The complete Level 6 operator surface now runs on live, scoped contracts in
both runtimes, with package and Cloudflare regressions closed before release.
### Added
- The Astro operator dashboard now implements the complete fifteen-destination
product map from the approved Level 6 mockup: Atlas, Memories, Context,
Subjects, Work, Audit & Events, System, Models, Federation, Access, API &
Keys, Projects, Approvals, Releases, and Profile.
- Atlas adds a bounded `workspace_graph` lens over authorized claim/subject
nodes and canonical relationships, with stable layout, selection, legend,
truncation state, and direct Evidence Trace navigation.
- New principal, project, subject, reference, scoped-grant, access-simulation,
and masked model-diagnostic routes run through the shared Bun/Cloudflare
contract. Model probes are bounded, rate-limited, metadata-audited, and make
no canonical write.
- Work, grant/key management, approval, and release controls now execute their
existing versioned server operations with confirmation and refreshed live
state. Context shows its token budget; System shows readiness detail.
### Changed
- Canonical reads, writes, approvals, events, Atlas, context, federation, and
directories now share a visibility-plus-additive-grant boundary. Derived API
keys are clamped to their declared target and their issuer's current grants
on every request; `admin` delegation remains bounded to its grant target.
- Memories and Workspace Graph keep organization-visible records available in
every selected workspace while limiting team-visible records to the matching
workspace.
- Dashboard navigation, responsive rail, local typography, loading/empty/error
states, search, pagination, and action surfaces now match the approved
warm-paper desktop and 320 px mobile system without new runtime dependencies.
### Fixed
- The npm package includes `dist/index.html`, so an installed `titen dashboard`
serves the root redirect as well as `/dashboard/` (#303).
- Atlas fallback routing remains in the shared API root, preserving the
authenticated Cloudflare administrator compile path (#304).
### Security
- Migration 23 backfills compatibility grants without rewriting evidence;
denied resources remain absent from labels, counts, facets, cursors, graph
topology, disputes, citations, and directory summaries.
- Model configuration exposes only masked immutable tuples; dashboard secrets,
provider payloads, prompts, and embeddings never enter browser-visible state.
## [0.8.5] — 2026-08-15
Atlas Evidence Trace now matches the approved relationship-graph direction:
the focus claim is centered, relationship paths are labeled, and only live
authorized relationships are drawn.
### Added
- Evidence Trace may include complete caller-readable context runs through
`selected-in` edges and current eligible channel releases through
`released-as` edges.
- The dashboard renders distinct observation, claim, context, and release nodes
with `supports`, `contradicts`, `qualifies`, `selected-in`, and `released-as`
path styles.
### Changed
- The generic Atlas card grid is replaced by a deterministic claim-centered
Evidence Trace canvas with labeled SVG paths and an accessible node inspector.
- Opening a memory in Atlas immediately compiles and renders that claim's live
Evidence Trace; it does not show mock records when no trace exists.
### Security
- A context node is returned only when the caller owns or receives the context
and can still read every item in the pack. Active release nodes are rechecked
against claim authorization, lifecycle, version, channel status, and validity.
## [0.8.4] — 2026-08-15
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.
### Added
- `GET /v1/memories` lists authorized canonical claims with bounded lexical
search, lifecycle/visibility filters, and stable keyset pagination.
- The same-origin dashboard adapter exposes the protected Memories list route.
- Memories now has real loading, empty, error, search, and pagination states;
a selected record opens its evidence graph in Atlas.
### Changed
- Atlas is a separate navigation destination with its existing graph, inspector,
compile trace, and explicit administrator boundary.
- Global dashboard search now searches Memories instead of filling an unrelated
Atlas subject field.
### Security
- Memory list authorization and retention checks run in canonical SQL before
FTS or pagination. Hidden records never contribute to a count or cursor.
## [0.8.3] — 2026-08-15
Memory Atlas now gives an explicitly authorized owner a safe recovery view,
while ordinary operators get clearer principal-scoped results. Normal semantic
index catch-up no longer removes a canonically healthy service from traffic.
### Added
- Memory Atlas now supports an explicit, same-organization
`organization_admin` mode gated by `views:compile:all`, active root/owner
authority, a bounded audit reason, and metadata-only audit evidence.
- Every Atlas result identifies its active principal and access mode; the
dashboard exposes administrator mode only to eligible operators and explains
empty principal-scoped results without implying global memory is empty.
### Changed
- Normal pending semantic projection work now keeps `/readyz` at HTTP 200 with
`index_projection_pending` and an enabled vector capability. Observed
dependency, fingerprint, metadata, migration, and signing-secret failures
remain fail-closed at 503.
## [0.8.2] — 2026-08-15
The npm package now carries the same production dashboard that is checked into
the repository, so a fresh install cannot silently fall back to an older static
dashboard bundle.
### Added
- `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.
### Security
- The package includes only built dashboard assets and the adapter entrypoint;
credentials, mockup source, memory content, and runtime state remain excluded.
## [0.8.1] — 2026-08-15
The live operator dashboard now follows the approved Memory Atlas visual system
while keeping the authenticated adapter and six-area contracts unchanged.
### Changed
- Reworked the Astro dashboard rail, header, lens controls, status metadata,
topology workspace, compile trace, and inspector with local Titen icons and
the approved warm paper palette.
- Added a live, bounded SVG topology that is derived only from authorized Atlas
nodes and edges, with a synchronized accessible record list and inspector.
- Added a native search dialog that fills the authorized Atlas query without
moving credentials or memory data into browser storage.
- Wired every visible rail item to a real destination: Memories aliases Atlas,
System shows live health/readiness, Access shows the effective principal
policy, and Releases loads authorized release data.
- Added a private Profile view with session-backed password rotation, and fixed
the readiness revision display when the upstream reports it in response
metadata.
### Fixed
- Dashboard mobile layout now keeps the Atlas graph inside an internal scroll
region and preserves a single-column reading path.
## [0.8.0] — 2026-08-13
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.
### Upgrade notes
- **Breaking:** service commands without `--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.
- The website installer now exits non-zero when `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.
### Added
- `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.
- Context-pack budgets now report `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.
### Fixed
- An accepted observation no longer looks silently lost: both Bun/SQLite and
Cloudflare/D1 expose the scoped pending-evidence count without leaking hidden
organizations, projects, workspaces, or private principals (#297).
- `bootstrap` and `serve` no longer choose different databases when launched
from different directories, and `serve` refuses to create an empty store by
accident (#298).
- The installer no longer reports success when the verified binary is outside
`PATH`; it prints a predictable `TITEN_BIN=...` recovery value and exits 1,
while `--print-path` provides a scripting-safe mode (#299).
### Security
- Source import rejects secrets before target access, symlinks, non-UTF-8 and
unsafe-control text, unknown or incomplete structured exports, populated
AgentFile environment values, dangling block references, more than 10,000
entries, or more than 64 MiB of selected input. Rule links and vendor
frontmatter remain inert evidence and are never dereferenced or executed.
## [0.7.4] — 2026-08-10
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.
### Fixed
- **A bridge started without its environment answered every lookup from the
wrong store and said so nowhere.** With neither `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.
- **A failed bridged request was one sentence for every cause.** A revoked key,
a wrong port and a restarted server all produced exit 0, empty stderr, and
the same `-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.
## [0.7.3] — 2026-08-08
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.
### Fixed
- **Switching from `@modelcontextprotocol/server-memory` imported nothing, and
said nothing.** The search covered `MEMORY_FILE_PATH` and the working
directory. That server writes **beside its own module** when the variable is
unset, so anyone who ran it the documented way — `npx -y
@modelcontextprotocol/server-memory` — has their graph inside a hashed
directory in npm's `_npx` cache, which was never searched; and an MCP server
launched by a desktop client inherits the client's working directory, so the
one fallback was weakest exactly where it was relied on. Both populations
imported zero entities and were told nothing, 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 the current `memory.jsonl` name
and the older `memory.json`. A `MEMORY_FILE_PATH` that points at nothing now
says so on stderr.
- **`memory://knowledge-graph` is served.** `resources/list` returned `-32601`,
so a client that reads the graph as an MCP resource rather than through
`read_graph` broke on the switch however well the nine tool names matched.
`resources/list` and `resources/read` now answer with the same JSON body
`read_graph` returns. `initialize` declares `resources` with
`subscribe: false`: the graph is readable, and no change notifications are
sent.
- **Every authenticated request paid a durable write to record that it had
read.** `authenticate` compared `last_used_at` against `now`, so the `CASE`
fired on any request whose clock had advanced a millisecond — in production,
all of them. Measured on WAL with `synchronous = FULL`: fifty updates whose
`CASE` is a no-op grow the WAL by **0 bytes**, fifty that change the value
grow it by **206,032** — one page and one `fsync` each, and one billed write
on D1. 25 of the 27 `GET` routes paid it. Now bounded to one write per key
per minute. `last_used_at` stays monotonic and loses resolution, which
`GET /v1/keys` documents.
- **An imported credential colliding with another organization told the
operator to retry forever.** `api_keys.id` is a global primary key, but the
credential preflight is scoped `WHERE org_id = ?` and so cannot see a
collision elsewhere. The constraint surfaced through the concurrent-write
handler as *"Import collided with a concurrent write; retry after exporting
current state"* — advice that can never succeed, because the collision is
with another organization's row. The foreign-id preflight now covers
`api_key`, so it reports what actually happened, as every other record type
does.
### Changed
- **`score` is comparable across queries (#227).** The relevance term was
rescaled against the candidate set, which pinned the best candidate to
exactly `1` however poor the match; with every other component constant on a
uniformly ingested corpus, rank 1 returned the same number on every query and
threshold-based abstention was arithmetically impossible. Relevance is now
`strength / (strength + 3.7)` where `strength` is the `bm25` magnitude
divided by the query's term count, and the semantic arm uses raw cosine.
Measured on the 424,168-claim anchor store, 500 questions: rank-1 `score`
went from **1 distinct value** to **498**, spanning 0.4875–0.6632.
Pre-registered before any cell was scored, then measured on both bench
conditions with the baselines reproduced first: 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% and +0.67%.
**That gate could not have failed, and the report says so.** Both stores were
ingested in one pass, so all five non-relevance components are constant
across 89,467 packed items; with those constant the order is identical by
arithmetic. Where they vary the behaviour does change: at `bm25` −60 against
−45 the relevance gap compresses from 0.250 to 0.018 and the order flips to
the `verified` claim over the marginally better-matching `asserted` one. That
is intended, it is pinned by contract, and it is bounded — a genuinely weak
match still loses to a strong one however trusted.
Two limits: `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; and the vector arm's move off set-relative scaling is
**unmeasured**, because both benchmark lanes ran with vectors disabled.
### Removed
- Five exports that shipped in the tarball with no caller anywhere in the
source, tests, scripts or dashboard: `ftsQuery`, `canReadRecord`,
`recordEvent`, `param`, `FEEDBACK_ENDPOINT`.
### Internal
- `@types/node` and `@types/bun` were never installed, so `tsc` could not
resolve `process`, `Bun`, or any `node:`/`bun:` import and the tree reported
791 errors. With them, and with `Prepared` typed at the untrusted-import
boundary, `src/` is clean under `tsc --noEmit`. A `typecheck` script now
exists so the number cannot drift unobserved. No `any`, `@ts-ignore`, or new
cast was added.
## [0.7.2] — 2026-08-08
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.
### Fixed
- **`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](https://github.com/RamaAditya49/titen/issues/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](./docs/testing/2026-08-08-pooled-compile-latency.md), protocol
[pre-registered](./docs/testing/2026-08-08-pooled-compile-latency-prereg.md)
before the A/B.
### Added
- **Plan-shape guards for the retrieval queries**
(`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.
## [0.7.1] — 2026-08-07
> **Superseded by [0.7.2](#072--2026-08-08). Do not run 0.7.1 on a store with
> many claims under one subject: one context compile takes a median 74.5
> seconds.**
The measurement release: the pooled-store condition on LongMemEval-S, with
two pre-registered falsifiers fired against Titen and published, plus the
#291 disputed-signal authorization fix and the evidence-depth tie-break.
### Added
- **The pooled-store measurement: quality, latency, and build cost at
production store shape.** All 19,829 distinct LongMemEval-S sessions in one
single-subject store, all 500 questions against it, at four store sizes —
the condition every published number in this field (ours included) avoids by
giving each question its own ~50-session haystack. Pre-registered with five
falsifiers before the first scored run
([prereg](./docs/testing/2026-08-07-pooled-store-prereg.md),
[report](./docs/testing/2026-08-07-pooled-store.md)). The system under test
is the published npm tarball, not a checkout.
- The [2026-08-07 agent-memory landscape survey](./docs/research/2026-08-07-memory-agent-landscape.md),
superseding 2026-08-04 on the fame roster and correcting two of its claims,
and [the performance-axis answer](./docs/research/2026-08-07-performance-axis.md)
recording which competitive axes were adversarially killed and why.
### Changed
- **A ranking dead heat now breaks on authorized evidence depth before the
arbitrary statement fallback** ([#288](https://github.com/RamaAditya49/titen/pull/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](./docs/testing/2026-08-07-evidence-ranking.md)).
- **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](https://github.com/RamaAditya49/titen/issues/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](./docs/engineering/release.md#versioning-and-channels) — the
minor slot signals shape breakage, and only values that were disclosing hidden
rows move here.
### Fixed
- **The Memory Atlas review queue no longer scans every observation in the
organization to decide `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.
**Corrected in [0.7.2](#072--2026-08-08):** what shipped here was a join
*inside* the `EXISTS`, not the nested form this entry describes, and SQLite
3.53.0 reordered it straight back into the 79-second shape — a median 74.5 s
per compile on a large single-subject store.
Found by benchmarking the change above; the dual-runtime contract suite passed
on both query shapes, because its stores hold tens of rows.
### Evidence
- [`docs/testing/2026-08-07-disputed-authorization.md`](./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.
## [0.7.0] — 2026-08-07
### Changed
- **Breaking: `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.
### Added
- **Zero-config local mode.** `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.
- **Drop-in compatibility with `@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. A primary-key lookup against the `context_runs` row
the compile already wrote, 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.
- **Concurrent-writer durability suite** on both runtimes, with the invariants
published before the run and each re-run with its primitive removed to prove
the suite can fail. Closes #282.
### Evidence
- [`docs/testing/2026-08-07-titen-audit-self-report.md`](./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`](./docs/testing/2026-08-07-durability.md)
— invariants held on both runtimes under concurrent writers.
## [0.6.1] — 2026-08-05
### Fixed
- 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.
### Measured, not built
- The reranking stage recorded as strategic debt was measured before being
written, and the measurement says do not write it. The oracle ceiling over
Titen's own top-10 is +10.2 points (recall@1 0.880 to 0.982), but every cheap
reranking signal tested captured at most 0.6 of those points at p=0.61, and
two were significantly *worse* than no reranking at all. MemPalace, which
ships one, scored lower with it than without it in both embedding
configurations. The gain is real and lexical signals cannot reach it.
Closes #269.
### Added
- In-process mode for Bun hosts. `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.
### Documentation
- Measured the memory-agent field on an externally authored corpus
(LongMemEval-S, MIT) and recorded where Titen actually stands:
[landscape note](./docs/research/2026-08-04-memory-agent-landscape.md).
`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](./docs/deployment/vps.md) 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.
## [0.6.0] — 2026-08-04
### Evidence
- First published head-to-head against a comparator on an **externally authored**
corpus: [neutral head-to-head](./docs/testing/2026-08-04-neutral-head-to-head.md),
Mr.TyDi Indonesian, 25 queries over 100 documents, 10 repeats, against
self-hosted `mem0ai` 2.0.13. The result is **parity, not superiority**. Point
estimates favour Titen on every metric, but both systems are deterministic on
this harness, so disjoint across-repeat ranges prove nothing; paired sign tests
over the same 25 queries give p >= 0.500 on every metric, and the whole margin
is two queries (20 of 25 rank-1 hits against 18). Titen loses one query to both
Mem0 configurations. What the run does establish: ~1.9x lower query latency
with vector search and ~400x without it, ~9.6x faster ingest, and deterministic
ranking **when vector search is enabled**. The FTS-only lane still produces a
different ordering on every repeat, exactly as 0.5.7 did.
- Correction to the record: the five-repeat figures published earlier the same
day for 0.5.7 — recall@1 0.680, MRR@10 0.807, query p50 170 ms — were the
**top** of their ranges, not medians. Re-scoring the same artifact per repeat
gives a 0.62 median for recall@1. Do not requote them.
- [Scale and concurrency](./docs/testing/2026-08-04-scale-and-concurrency.md):
the service uses one core at every concurrency level, so a single client
saturates it at and above 10,000 claims, and retrieval quality falls with
corpus size on a synthetic corpus (recall@1 1.00 / 0.81 / 0.49 at 10^3 / 10^4 /
10^5). Cold start stays at ~53 ms and resident memory grows 2.9% across a
hundredfold corpus. Tracked as #259 and #260.
- [Operational lifecycle](./docs/testing/2026-08-04-operational-lifecycle.md):
nine published releases rehearsed through in-place upgrade with zero canonical
rows lost or mutated, plus a real backup, destroy and restore drill and a JSONL
export/import round trip. The **data-usable upgrade floor is 0.2.0**: a 0.1.x
store migrates without error and then answers 404 on every claim (#257), and
its export is rejected by the importer (#258).
### Added
- `POST /v1/context/compile` accepts an optional `top_k`, a hard ceiling of 1
through 1,000 on returned items, exposed by the TypeScript SDK, the Python
client, and the `titen_compile` MCP tool. It bounds the answer where
`max_candidates` bounds what retrieval considers, and is applied to the ranked
list before the token budget, so a bounded pack costs fewer tokens instead of
being truncated client-side. Omitting it changes nothing. Closes #229.
- `TITEN_EMBED_PROFILE=raw-unit-v1-model-mismatch-acknowledged` sends raw
embedding input on a model whose id claims a prompt convention. The forced
rule that rejects `raw-unit-v1` for an EmbeddingGemma model is unchanged and
still fails closed; this is the one deliberate way past it, for a model served
without the prompts and for a fair head-to-head against a system that embeds
raw text. It is a distinct profile in the index fingerprint, so switching to
or from it answers `503` with `index_fingerprint_mismatch` until the index is
rebuilt rather than mixing prefixed and raw vectors. The retrieval harness
accepts `TITEN_EVAL_EMBED_PROFILE` to use it. Closes #250.
### Documentation
- The README states up front that Titen's default memory model is
caller-authored claims: `consolidate()` takes explicit statements with explicit
source links, and model-assisted derivation remains activation-gated with no
candidate model past the gate.
- The README states the client surface exactly: a TypeScript SDK, the `titen`
CLI, and the standard-library-only Python client in `clients/python/` that is
installed from a checkout and is not published to PyPI. Callers in any other
language are pointed at the REST reference.
- Project status leads with pre-1.0 and defines *stable* as the release channel
(npm `latest`, `"channel": "stable"` in `version.json`), never as API
stability. The changelog header and the release guide carry the same wording.
- The SDK quickstart now works by copy-paste from `npm init -y`. `titen-memory`
is ESM-only, so the quickstart adds `npm pkg set type=module` and names the
file `titen-example.js`; previously the documented steps ended in
`SyntaxError: Cannot use import statement outside a module`.
- `TITEN_EMBED_REVISION`, `TITEN_EMBED_PROFILE`, and `TITEN_EMBED_MIN_COSINE`
are documented in the README and the VPS guide with their absence behavior,
the model-forced profile rule, and a worked EmbeddingGemma example.
`TITEN_EMBED_MIN_COSINE` has no shipped default and semantic retrieval fails
closed without it.
- Vectorize status is stated identically everywhere: verified live only on the
maintainer's isolated the maintainer release stack stack, which is test production and not
general availability.
### Fixed
- Hybrid ranking no longer decides a tied top result with a random identifier.
Because lexical and vector relevance are each min-max normalized inside the
candidate set, each signal's own best scores exactly `1`; when those are
different claims the two tie on the whole weighted score, and the tie-break
was `claim_id` over UUIDs minted at ingest. Equal scores now break on the
stronger vector similarity first, then on `claim_id`. Two cosines are only
ever compared with each other, so no constant and no cross-scale comparison
enters the ordering, no returned score changes, and lexical-only ranking is
untouched. On the release retrieval harness, 10 repeats, the vector lane's
recall@1 median rises from 0.7143 to 0.8571, MRR@10 from 0.8333 to 0.9048 and
nDCG@3 from 0.8758 to 0.9286, and every range collapses to zero width. The
ceiling is unchanged: this is a higher median with the variance eliminated,
not a higher best case. Part of #226; #226 and #227 both remain open.
- Temporal polarity reaches lexical retrieval. "Mulai Juli 2026" and "Sebelum
Juli 2026" previously scored identically for any query that did not repeat one
of those words verbatim, because a surviving marker matched only a claim using
the identical word. A marker now expands inside the MATCH to the other markers
naming the same window boundary **in the same language**, so "sejak Juli 2026"
reaches "Mulai Juli 2026". Groups never span languages, and markers that are
also function words (`dari`, `from`) stay in the stoplist and are not
expanded. `query_terms_used` still counts only the caller's terms, and no
ranking term was added. On the release retrieval harness, 10 repeats, the
FTS-only lane's recall@1 median rises from 0.5714 to 0.7143 and nDCG@3 from
0.7517 to 0.8044, both with the variance eliminated and the maximum unchanged.
MRR@10 rises from a median of 0.7381 to 0.8061 but keeps a residual range of
[0.8061, 0.8095] across four independent 10-repeat runs, so its variance is
reduced rather than removed. Closes #228.
- `budget.omitted_items` counts the ranked candidates a `top_k` bound discarded,
not only those the token budget dropped. A count-bounded pack previously
reported zero omissions with `budget_exhausted: false`, so a caller could not
tell a truncated answer from a complete one.
- Release headings for 0.5.7, 0.5.6, 0.5.5, and 0.2.0 now carry their UTC
publish dates and agree with the npm registry `time` field; they had recorded
the maintainer's local (UTC+7) date.
- The full-fit context contract no longer pins an absolute as-of date, so it
stops passing or failing on the calendar. Shipped code is unchanged: the
fixture asked for context as of a date that had moved into the past, and
`valid_from <= at` correctly excluded the claims it had just written.
## [0.5.7] — 2026-08-01
### Documentation
- The npm README now gives one ordered path from CLI install through bootstrap,
agent-specific keys, service readiness, and verified MCP setup for Codex,
Claude Code, OpenClaw, Hermes, and generic stdio clients.
- The host guide separates direct Streamable HTTP from the bundled `titen mcp`
bridge, keeps credentials in host environments, and includes a nine-tool
connection check.
### Fixed
- Dashboard integration and browser gates no longer share hard-coded port
ranges with unrelated development servers.
## [0.5.6] — 2026-08-01
### Added
- `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.
### Changed
- MCP initialization now tells compatible hosts to resolve the repository and
compile authorized memory once at each task or scope boundary while keeping
durable writes explicit and typed.
### Security
- The stdio bridge accepts its endpoint and revocable key only through the
inherited environment, rejects credential-bearing or ambiguous URLs, emits no
notification reply, and sanitizes upstream failures without exposing the key.
## [0.5.5] — 2026-08-01
### Upgrade notes
- The canonical schema advances from 20 to 21. Take a verified backup and run
`titen migrate --dry-run` before starting 0.5.5. The migration is additive;
rollback restores the backup with the 0.5.4 binary.
- Dashboard replicas that must preserve sessions across restarts share one
base64url-encoded 32-byte `TITEN_DASHBOARD_SESSION_KEY`. Without it, each
process uses an ephemeral key and restart signs everyone out.
### Added
- Context compilation accepts an explicit historical `at` instant and a
bounded `max_candidates` value through REST, MCP, and the TypeScript SDK.
- Stable source IDs and canonical claim hashes converge exact re-ingestion after
the request idempotency window while changed evidence remains append-only.
- `POST /v1/index/verify` checks a bounded set of active claims against
Vectorize or `sqlite-vec` and queues repairs without reading embedding values.
- The publishable OpenClaw bundle now includes remote Streamable HTTP MCP, and
the Cursor package includes the metadata and documentation expected by its
public plugin repository.
### Changed
- Dashboard login state is an authenticated AES-GCM HttpOnly cookie instead of
process-local memory, so a shared key supports replicas without a session
database.
- Confirmed semantic statement hashes avoid duplicate embedding calls; explicit
reconciliation still forces a provider repair when an index record is absent.
- Context budget units now use deterministic UTF-8 bytes across runtimes, and
Vectorize queries retain the native 100-result ceiling.
- The README now leads with Titen's Level 6 collaboration model and distinguishes
evidence-grounded context from storage-only and similarity-only memory.
### Security
- Public Cloudflare login failures consume the canonical account throttle and a
native Rate Limiting binding. The key contains no password or client IP.
- Password changes reject common and account-context values before a verifier is
stored, while retaining the 15-character minimum and forced first change.
- Sealed dashboard sessions reject forged, expired, or undecipherable cookies;
raw short-lived API credentials remain outside browser storage.
## [0.5.4] — 2026-08-01
### Added
- A checked-in the maintainer release stack Wrangler profile provisions the isolated live
Worker, D1, Vectorize, Workers AI, and Cron contract without storing an
account API token in the Worker.
### Changed
- Cloudflare schema readiness verifies the complete migration contract in one
D1 read instead of issuing one remote read per required object and column.
- New dashboard passwords use six serial PBKDF2-HMAC-SHA-256 stages of 100,000
iterations each, preserving the 600,000-operation work factor within the
Workers Web Crypto per-call limit. Bun retains legacy verifier compatibility.
- The live semantic verifier uses an explicit bounded index drain; Cron remains
a separately observed production reconciler instead of a timing dependency.
### Fixed
- The dashboard login username example is now the canonical bootstrap account
`owner`, not a maintainer-specific placeholder.
- The Cloudflare runbook provisions the three Vectorize metadata indexes needed
for scope-before-search filtering before the first vector upsert.
### Security
- Live unauthenticated and cross-organization probes fail with non-disclosing
`401`/`404`, while native D1, Vectorize, and Workers AI bindings require no
account credential inside the deployed Worker.
## [0.5.3] — 2026-08-01
### Added
- Bootstrap creates default username `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.
### Changed
- Schema 20 adds canonical operator accounts. Temporary-password login is
restricted to password replacement; established login issues an eight-hour
revocable API credential only to the server-side adapter.
### Fixed
- The per-principal sign-in state now uses a focused responsive login surface;
the private product sidebar and operator topbar appear only after a valid
session instead of framing the unauthenticated form.
### Security
- Password verifiers use unique salts and PBKDF2-HMAC-SHA-256 with 600,000
iterations on both D1 and Bun/SQLite. Login failures are non-disclosing and
locally throttled; password change and logout revoke short-lived dashboard
credentials.
## [0.5.2] — 2026-08-01
### Added
- The operator dashboard now wires Memories, Context, Work, Audit, Governance,
and Federation to fixed authenticated API routes, with capability-gated
navigation and no fixture fallback.
- Per-principal dashboard login exchanges an API key for an opaque HttpOnly
session, and authorized owners/admins can atomically create one human
membership plus its one-time API key.
- The deployment guide now covers private Tailscale Serve and Cloudflare Tunnel
protected by Cloudflare Access while both Titen listeners remain loopback-only.
### Changed
- The public SDK now types all six Memory Atlas lenses and the optional Add User
fields on key creation.
### Security
- Dashboard routes enforce exact Host/Origin, bounded bodies and list results,
credential isolation, revocation, expiry, logout/restart invalidation, and
server-side authorization independent of browser navigation.
## [0.5.1] — 2026-08-01
### Fixed
- The dashboard content security policy now permits its bundled Titen mark data
URI, so the release logo renders without allowing remote image sources.
## [0.5.0] — 2026-08-01
### Upgrade notes
- **Breaking:** the canonical schema advances from 17 to 19. Take a verified
backup and run `titen migrate --dry-run` before starting 0.5.0; rollback
restores that snapshot with the previous binary rather than running a down
migration.
- Configure `TITEN_SECRET_KEYS` only when encrypted channels or federation are
used. The lexical memory core remains available without it.
- A dashboard principal using governance lenses needs an active organization
membership as well as the documented bounded key scopes.
### Added
- Enterprise governance now covers organization roles, typed approval and
retention policies, claim review, encrypted channel assertions, versioned
channel releases, legal holds, and external identity mappings.
- Opt-in canonical federation exchanges signed, filtered claim bundles with
their evidence and provenance. Imports bind the source organization, preserve
disagreement, reject unsafe trust elevation, and remain idempotent on both
supported runtimes.
- The read-only dashboard now uses the live same-origin adapter for six
authorized Memory Atlas lenses, including governance scope and knowledge
release views. It has explicit loading, empty, denied, and disconnected
states instead of a synthetic fallback.
### Changed
- Context compilation reports selected, omitted, and deduplicated candidate
counts plus an explicit budget-exhaustion flag without exposing unauthorized
records.
### Fixed
- Local key commands fail cleanly for a missing database, schema, organization,
or key and no longer report a false revocation success.
- `titen backup` reapplies owner-only permissions after atomic replacement, so
container bind mounts cannot leave the finished backup group- or world-readable.
- `titen migrate --dry-run` no longer changes file permissions or requires a
writable database directory, so it works on a read-only pre-upgrade mount.
### Security
- Governance and federation mutations now enforce role, resource, retention,
replay, peer-source, and cross-organization boundaries in the shared
Bun/SQLite and workerd/D1 contract.
## [0.4.1] — 2026-08-01
### Added
- `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.
### Fixed
- MCP initialization now reports the package SemVer as `serverInfo.version`
instead of mislabeling a deployment revision such as `dev`, `test`, or a Git
SHA as the server implementation version.
- Published SDK declarations now stay inside the package and exactly type claim
inputs, readiness diagnostics/capabilities, key lifecycle fields, and a
bounded event iterator that terminates on preserved cursors.
- Extraction now supports explicit strict-schema, JSON-object, and custom modes,
rejects redirects and incomplete provider finishes, and uses the same model
proposal validator as the locked release gate.
- Cloudflare liveness no longer waits on D1 preparation, Vectorize queries stay
within the platform's `topK` limit, and the documented Wrangler recovery
command uses a real temporary SQL file.
- Bun allows the documented extraction timeout and releases every semantic
lease acquired before or during bounded SIGTERM shutdown, so a restart can
recover work immediately.
- Context packing preserves rank when every item fits, applies diversity only
under budget pressure, and no longer awards disputed claims a positive score.
- Live semantic verification carries project scope, while historical comparison
tooling refuses to present a current deployment as the frozen `0.3.0` target.
### Security
- D1 diagnostic redaction now covers secrets split across streamed byte and
text chunks, and Bun creates the canonical SQLite database plus sidecars with
owner-only permissions.
- API keys now enforce immutable not-before and expiry windows, update
`last_used_at` monotonically, preserve lifecycle metadata across supported
operator surfaces, reject unknown creation fields, and cannot gain authority
through credential import.
## [0.4.0] — 2026-07-31
### Fixed
- Global CLI installs now run directly on Bun and report `titen --version`
instead of requiring a Node shim.
- Successful SDK responses now reject array, `null`, and primitive JSON
envelopes with an `INVALID_RESPONSE` `TitenError` while preserving the HTTP
status, request ID, and safe response metadata.
- Bun HTTP, Cloudflare Workers AI, and injected embedding-provider results now
require exact output cardinality, ordered provider indices when present,
dense configured dimensions, and finite numeric coordinates before vector
query or indexing.
- Semantic readiness now distinguishes intentional FTS-only operation from
partial configuration, unavailable vector initialization, legacy untracked
vectors, missing requeue work, unsafe storage aliasing, empty restored
projections, incompatible fingerprints, and locally observed indexing
dependency failures; configured failures return a fixed local diagnostic
without probing providers.
- Semantic retrieval now discards sub-threshold cosine hits before canonical
hydration, so relative ranking cannot turn a best bad neighbor into useful
context. Bun and Cloudflare share the same validated unit-vector boundary.
- Manual and background semantic-index drains now fence each outbox row before
provider I/O, so an expired losing attempt cannot add stale failure evidence
after another attempt completes the row. Lease eligibility and expiry now use
the database clock at each conditional claim, so caller clock skew and earlier
work cannot create an expired or stranded owner.
- Semantic-index upserts and removals now persist canonical reconciliation
before external mutation and recreate it after stale or apply-then-throw
outcomes, so a losing owner cannot resurrect a purged vector, erase a newer
projection, or report unowned work as complete.
### Changed
- Capability contract version 1 reports embedding, extraction, and background
enrichment separately while retaining `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.
- Context compilation now treats a missing `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.
- **Breaking:** Semantic configuration now requires an immutable model revision, a named
role-aware preprocessing profile, and an operator-calibrated cosine floor in
the existing index fingerprint. EmbeddingGemma uses its official asymmetric
query/document prompts; Titen ships no universal threshold.
- Migration 16 adds nullable owner and expiry fields to the rebuildable semantic
index outbox; canonical observations and claims are unchanged.
## [0.3.0] — 2026-07-31
### Added
- Native Claude/ZCode and Cursor marketplace bundles, a ClawHub/OpenClaw skill
bundle plus native MCP config, a Hermes skill plugin, a Pi skill package, and
OpenCode/Windsurf/TRAE host kits now package the same nine-tool Titen MCP
contract without duplicating the server or embedding an endpoint or
credential. The standalone Titen Memory skill is public on ClawHub; its
bundle-plugin package is staged while an upstream inspector incident blocks
live package publication.
- The TypeScript SDK now covers project resolution, evidence-linked claims,
feedback, checkpoints, leases, and handoffs with typed results, bounded
timeout/signal handling, structured API errors, and mutation retry keys. The
same nine operations are available through MCP with truthful schemas.
- Handoff recipients can read the exact delegated checkpoint and currently
authorized context pack; operators can page organization leases and active
organization-level owners/admins can force-release a failed agent's lease.
- `titen migrate --dry-run` prints the pending forward-only SQL without creating
or changing the SQLite database.
- `titen serve --quiet`, bounded cleanup of expired execution state, and
content-free audits for credential, portability, collaboration, webhook, and
federation changes use the existing runtime and SQL primitives.
- The README is now a concise international open-source entrypoint with a
runnable SDK example, explicit maturity boundaries, and a prominent link to
[titen.dev](https://titen.dev).
### Changed
- **Breaking:** JSONL export format v2 adds workspace and membership streams,
actor mappings, supersession pointers, deployment-scoped export authority,
and byte-bounded pages. Import remains backward-compatible with v1, but code
that parses export headers must accept format version 2.
- **Breaking:** MCP now exposes nine tools and uses normalized JSON-RPC tool
results with structured content. Clients that hard-code the previous
seven-tool list or result shape must update for `0.3.0`.
- Migration 11 atomically rebuilds the derived observation and claim FTS tables
with Porter stemming and encoded scope terms; canonical SQL is unchanged.
- Migration 12 repairs unsafe collaboration pointers, adds database fences for
handoff resolution, scopes idempotency to principals across key rotation, and
assigns monotonic event order without changing public event IDs.
- The default npm install no longer downloads `sqlite-vec`. SDK and lexical-only
users install only `titen-memory`; vector-enabled VPS and container paths add
`sqlite-vec@0.1.9` explicitly.
### Security
- JSON depth, unsafe controls, malformed Unicode, non-sortable timestamps, and
inverted validity windows now fail before canonical mutation. Each returned
memory item is marked untrusted, feedback is limited to the context actor or a
currently authorized intended delegate, and evidence purge removes FTS and
vector projections without exposing a general MCP deletion tool.
- Whole-organization export now requires the separate `export:all` scope and
writes a metadata-only audit record. Portable actor ownership survives only
through explicit, preflighted source-to-destination mappings; importing on
behalf of another principal additionally requires `keys:manage`.
### Fixed
- Lexical retrieval now stems multilingual terms, removes stopword noise,
applies organization and subject scope before ranking and candidate limits,
preserves useful tail terms, and fills token budgets without duplicate
statements or an arbitrary three-item-per-kind ceiling.
- Context compilation bounds correlated SQL work before evidence hydration, so
large authorized corpora do not turn small requested result sets into
unbounded query work.
- Checkpoint saves and handoff resolutions now have database-enforced single
winners under D1 latency, with deterministic duplicate repair and safe
handoff foreign keys during migration. Handoff preflight and migration also
reject incomplete, foreign, mismatched, or unauthorized context packs.
- Idempotent retries now follow the acting principal across API-key rotation
while retaining the original credential ID for audit and preserving
cross-principal isolation.
- Event polling and federation pulls now page by a database-assigned monotonic
sequence without changing public event-ID cursors, preventing equal-timestamp
UUID ordering from skipping committed events; exhausted polling preserves the
caller's cursor.
- An explicitly scoped REST tombstone removes readable observation and
dependent-claim text while retaining hashes, provenance, and audit history.
- Validation errors distinguish missing values, identify nested field paths,
and keep raced purge/consolidation and lifecycle writes fail-closed on both
SQLite and D1.
- Versioned JSONL v2 restores workspaces, active memberships, team-scoped
records, actor provenance, claim evidence, and supersession pointers. Export
pages are UTF-8 byte-bounded so every emitted page fits the import boundary;
v1 imports remain supported, and a purge racing current-claim import rolls the
whole import back while revoked tombstones remain portable.
- `titen backup` refuses a missing source, verifies a non-empty current schema
plus integrity and foreign keys, and atomically refreshes a fixed output path
without exposing an internal stack. `titen schema` output is deterministic
and safely repeatable.
- The installed CLI now explains when Bun is missing, while SDK-only installs
no longer download the optional `sqlite-vec` native package.
- No-vector deployments avoid unused index-outbox work. Configured vector
deployments retain claim upserts and purge deletes; SQLite uses explicit
`synchronous=FULL`, and CLI startup failures are short and actionable.
## [0.2.1] — 2026-07-31
### Fixed
- Key creation now returns the canonical `principal_id`, so SDK callers can use
a newly generated agent identity for handoffs without confusing it with the
credential's `key_id`.
- Contributor setup now documents a temporary writable `HOME` and
`XDG_CONFIG_HOME` for pnpm/Wrangler checks in restricted containers.
## [0.2.0] — 2026-07-31
### Added
- **An authorized reviewer queue in Memory Atlas.** The read-only lens derives
deterministic review work, counts, ownership, evidence, and opaque pagination
from canonical state without introducing a second queue database.
- **Durable, signed Bun webhooks and rootless deployment artifacts.** Delivery
uses bounded leases, stable delivery IDs, retries, terminal failure, TLS
address pinning, and externally keyed secret encryption; the checked-in
Quadlet keeps the public binding on loopback by default.
- **Broader SDK and CLI coverage.** Typed claim lifecycle/reviewer operations,
generic authenticated JSON/raw requests, mutation idempotency, strict flag
validation, and installed-tarball verification now cover the shipped surface.
- **Current agent integration guidance.** Claude Code, Codex, OpenClaw, Hermes,
Pi, and generic clients share one MCP/REST contract; deliberately deferred
native adapters are recorded in `PONYTAIL-DEBT.md`.
### Security
- **Authorization now precedes every protected projection.** Team records are
workspace-bound and require active membership across context, evidence,
exports, events, Atlas, vectors, and webhook delivery; checkpoint and webhook
state is principal-bound within an organization.
- **Signing secrets are no longer canonical plaintext.** Webhook and federation
secrets use versioned AES-256-GCM envelopes backed by an external keyring;
missing or wrong key material keeps readiness closed, while unrecoverable
legacy integrations without a secret are terminalized for safe replacement.
- **The MCP transport validates its trust boundary.** Cross-origin browser
requests and unsupported protocol revisions fail before tool execution, while
tool annotations let hosts apply read/write approval policy correctly.
### Fixed
- **Schema and mutation races now have one winner.** Migrations recover from
faults and concurrent startup, leases and claim lifecycle transitions are
fenced atomically, and idempotency binds the credential plus full request
identity on both SQLite and D1.
- **Imports are fully preflighted and atomic.** Orphan evidence, collisions, and
invalid late rows roll back canonical, history, FTS, event, audit, and outbox
effects even beyond one database batch.
- **Retrieval cannot lose authorized tail terms or top-K slots.** Lexical term
selection is position-independent, and vector organization/subject/project
filters execute before ranking on both runtimes.
- **Webhook delivery survives retries, timeouts, crashes, and restart.** Queue
claims are atomic, expired leases recover, pending age is observable, and
caller-visible queue state excludes other principals. Membership revocation
terminalizes queued delivery before any further outbound request.
- **Federation peers and cursors are principal-bound.** Same-organization
credentials cannot discover, mutate, advance, or replay another principal's
peer or private event stream. Signed push data is stored as an owner-visible
untrusted wrapper, so remote actor and resource pointers grant no local
authority.
- **Runtime and package gates reflect real operation.** Maintenance freshness,
bounded WAL checkpoints, isolated workerd contract execution, rootless restart
behavior, README links, SDK error parsing, CLI help, and npm install smokes are
checked through the actual runtime paths.
### Changed
- **Breaking:** incomplete policy, channel, release, and caller-selected customer
context routes were removed until their authorization contracts are ready.
Pre-`1.0`, this requires the `0.2.0` minor release.
- MCP tools now delegate to the same validated application operations as REST,
negotiate through protocol `2025-11-25`, and expose seven wire tools across six
semantic families.
## [0.1.2] — 2026-07-30
### Added
- **A five-minute small-team golden path.** The documented researcher, writer,
operator, and reviewer flow uses four scoped keys and the public SDK to prove
evidence, conflicting claims, context, checkpoint, lease, handoff, feedback,
citations, and freshness against a real Bun/SQLite service.
- **Optional loopback live dashboard data.** Conflict & Freshness can read one
subject-scoped Atlas view through a same-origin adapter that keeps the Titen
key server-side. Static serving rejects traversal, encoded/backslash/NUL, and
symlink escapes; every other dashboard lens stays visibly synthetic.
- **More SDK coverage.** The published client now exposes the shipped
checkpoint, lease, handoff, evidence, and Atlas-view operations used by the
golden path.
- **One canonical live verifier.** `pnpm verify:live` replaces the duplicate
script name and remains explicit about requiring a provisioned deployment.
### Fixed
- **Semantic ranking now works with real narrow-band cosine scores.** Vector
similarity is normalized inside the authorized candidate set and confidence
is an explicit weighted component, so a semantically correct lower-confidence
claim no longer ranks last behind lexical decoys.
- **Index dependency outages are retryable.** Embedder and vector-store failures
return bounded `503 UNAVAILABLE` metadata while leaving pending outbox work
intact.
- **Canonical imports are order-independent within a request.** Imports preflight
missing parents before mutation, accept child-before-parent lines atomically,
and return `UNRESOLVED_REFERENCE` instead of mislabeling a missing dependency
as a conflict.
- **Readiness reports background repair honestly.** Bun reports whether its
maintenance timer was actually created; Cloudflare reports external scheduler
ownership without pretending the request isolate can observe Cron state.
### Changed
- The API reference is verified against the router's 58 implemented routes and
clearly separates proposed endpoints from callable ones.
- Dashboard screenshots, mobile disclosure, and the capability matrix identify
synthetic, locally verified, and planned regions consistently.
## [0.1.1] — 2026-07-30
### Fixed
- **`./package.json` is reachable again.** An `exports` map hides every subpath
it does not list, so `require("titen-memory/package.json")` failed with
`ERR_PACKAGE_PATH_NOT_EXPORTED`. Bundlers and tooling read that file. It is
now listed explicitly, and `scripts/verify-pack.sh` fails if it stops
resolving.
## [0.1.0] — 2026-07-30
First published release. Titen was previously reachable only by cloning the
repository.
### Added
- **`titen` CLI on the registry.** `bunx titen-memory serve` starts the memory
service with no clone and no build step. Also `bootstrap`, `migrate`,
`key create|list|revoke`, `backup`, and `schema`.
- **Agent SDK for every runtime.** `import { TitenClient } from "titen-memory"`
is plain `fetch` — Node 22+, Bun, Deno, and edge workers. The `titen-memory/sdk`
subpath resolves to the same client.
- **`scripts/verify-pack.sh`** — the release gate. It installs the real tarball
into a throwaway directory and exercises bootstrap, serve, and the SDK, because
every path resolves in a working tree even when `files` forgot to ship it.
- **`docs/engineering/release.md`** — the manual release procedure.
- **`CLAUDE.md`** — agent guidance that points at `AGENTS.md` instead of
restating it.
### Changed
- **`astro` moved to `devDependencies`.** It only builds the dashboard, but it
was declared as a runtime dependency, so installing Titen pulled a full build
toolchain onto every consumer's disk. A consumer tree is now three packages:
`titen-memory`, `sqlite-vec`, and its platform binary.
### Notes
- The tarball is 41 files / ~73 kB: the Web-Standards kernel, the Bun runtime,
and a type-stripped SDK for Node. The Astro dashboard, the Cloudflare adapter,
tests, and docs are not shipped — deploy the Worker from a clone.
- `sqlite-vec` is an `optionalDependency` loaded lazily. Without it retrieval
degrades to lexical FTS5 and `/readyz` reports the vector capability as
disabled rather than failing.
- **The CLI requires Bun** — it uses `bun:sqlite`, and the published `bin`
carries a `#!/usr/bin/env bun` shebang. `npx titen-memory` works only with Bun
on `PATH`. The SDK has no such constraint.
- 0.1.0 carries no git tag. It was published from a staging tree assembled
before the packaging work was committed, and was superseded by 0.1.1 the same
day. Prefer 0.1.1.
## Package name
npm refuses to register `titen`:
```
403 Forbidden - PUT https://registry.npmjs.org/titen
Package name too similar to existing package vite
```
That is npm's typosquat filter, not a collision — `npm view titen` still returns
404. The package is therefore `titen-memory`. The **command stays `titen`**,
because that comes from the `bin` field rather than from the package name:
```console
$ bunx titen-memory serve
titen — self-hosted memory service
```
## Releasing
Releases are cut by hand from a maintainer's machine. GitHub Actions stays
disabled so the repository has no hosted automation cost; manual publication
also keeps the npm token out of repository secrets. See
[`docs/engineering/release.md`](./docs/engineering/release.md).
[Unreleased]: https://github.com/RamaAditya49/titen/compare/v0.10.15...HEAD
[0.10.15]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.15
[0.10.14]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.14
[0.10.13]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.13
[0.10.12]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.12
[0.10.11]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.11
[0.10.10]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.10
[0.10.9]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.9
[0.10.8]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.8
[0.10.7]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.7
[0.10.6]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.6
[0.10.5]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.5
[0.10.4]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.4
[0.10.3]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.3
[0.10.2]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.2
[0.10.1]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.1
[0.10.0]: https://github.com/RamaAditya49/titen/releases/tag/v0.10.0
[0.9.1]: https://github.com/RamaAditya49/titen/releases/tag/v0.9.1
[0.9.0]: https://github.com/RamaAditya49/titen/releases/tag/v0.9.0
[0.8.7]: https://github.com/RamaAditya49/titen/releases/tag/v0.8.7
[0.8.6]: https://github.com/RamaAditya49/titen/releases/tag/v0.8.6
[0.8.5]: https://github.com/RamaAditya49/titen/releases/tag/v0.8.5
[0.8.4]: https://github.com/RamaAditya49/titen/releases/tag/v0.8.4
[0.8.3]: https://github.com/RamaAditya49/titen/releases/tag/v0.8.3
[0.8.2]: https://github.com/RamaAditya49/titen/releases/tag/v0.8.2
[0.8.1]: https://github.com/RamaAditya49/titen/releases/tag/v0.8.1
[0.8.0]: https://github.com/RamaAditya49/titen/releases/tag/v0.8.0
[0.7.4]: https://github.com/RamaAditya49/titen/releases/tag/v0.7.4
[0.7.3]: https://github.com/RamaAditya49/titen/releases/tag/v0.7.3
[0.7.2]: https://github.com/RamaAditya49/titen/releases/tag/v0.7.2
[0.7.1]: https://github.com/RamaAditya49/titen/releases/tag/v0.7.1
[0.7.0]: https://github.com/RamaAditya49/titen/releases/tag/v0.7.0
[0.6.1]: https://github.com/RamaAditya49/titen/releases/tag/v0.6.1
[0.6.0]: https://github.com/RamaAditya49/titen/releases/tag/v0.6.0
[0.5.7]: https://github.com/RamaAditya49/titen/releases/tag/v0.5.7
[0.5.6]: https://github.com/RamaAditya49/titen/releases/tag/v0.5.6
[0.5.5]: https://github.com/RamaAditya49/titen/releases/tag/v0.5.5
[0.5.4]: https://github.com/RamaAditya49/titen/releases/tag/v0.5.4
[0.5.3]: https://github.com/RamaAditya49/titen/releases/tag/v0.5.3
[0.5.2]: https://github.com/RamaAditya49/titen/releases/tag/v0.5.2
[0.5.1]: https://github.com/RamaAditya49/titen/releases/tag/v0.5.1
[0.5.0]: https://github.com/RamaAditya49/titen/releases/tag/v0.5.0
[0.4.1]: https://github.com/RamaAditya49/titen/releases/tag/v0.4.1
[0.4.0]: https://github.com/RamaAditya49/titen/releases/tag/v0.4.0
[0.3.0]: https://github.com/RamaAditya49/titen/releases/tag/v0.3.0
[0.2.1]: https://github.com/RamaAditya49/titen/releases/tag/v0.2.1
[0.2.0]: https://github.com/RamaAditya49/titen/releases/tag/v0.2.0
[0.1.2]: https://github.com/RamaAditya49/titen/releases/tag/v0.1.2
[0.1.1]: https://github.com/RamaAditya49/titen/releases/tag/v0.1.1
[0.1.0]: https://www.npmjs.com/package/titen-memory/v/0.1.0