Tages
Integrates with Git hooks to automatically extract architectural decisions, conventions, and context from commit messages for automatic memory indexing and codebase learning.
Integrates with local Ollama LLM instances to process and extract decisions from Git commits for auto-indexing without requiring external API calls.
Uses Supabase as the backend database for persistent memory storage, with support for self-hosted Supabase instances enabling team sharing and data ownership.
Click on "Install Server".
Wait a few minutes for the server to deploy. Once ready, it will show a "Started" state.
In the chat, type
@followed by the MCP server name and your instructions, e.g., "@Tageswhat are our coding standards for this project?"
That's it! The server will respond to your query, and you can continue using it as needed.
Here is a step-by-step guide with screenshots.
Tages
Team memory for AI coding agents.
Your AI agents forget everything between sessions. Every decision re-litigated. Every convention re-explained. Every past mistake repeated by the next agent that touches the same code.
Personal memory tools patch the symptom: store some facts, retrieve them later. That works for solo developers who remember what they stored. It breaks down when teams ship with AI — when three developers are using agents on the same codebase, when onboarding requires transferring institutional knowledge, when the wrong memory survives a refactor because no one audited it.
Memory isn't storage. It's a team practice.
Tages treats codebase memory as a managed artifact: structured types, quality scoring, audit trails, sharpen passes that rewrite vague notes into imperative agent instructions. One developer's architecture decision becomes every agent's context. A bad memory gets flagged and corrected before it misleads the next session.
Start in under 60 seconds with one command. Add team features when your workflow demands them.
claude mcp add tages -- npx -y @tages/serverWhy Tages?
Tages | Zep | Mem0 | |
Install | One line ( | Docker + API key | API key + SDK |
Local-only mode | Yes (SQLite, zero config) | Self-hosted only | No (cloud-only) |
Team sharing | Yes (RBAC, federation) | Yes (cloud) | No |
Dashboard | Yes (Next.js, analytics) | Yes | Basic |
Quality control | Audit, sharpen, enforce | No | No |
Memory types | 11 structured types | Knowledge graph (Graphiti) | Structured |
MCP tools | 56 | N/A | N/A |
Search | Trigram + semantic + decay | Temporal knowledge graph | Vector |
Workflow integration | Git hooks, CI/CD, briefs | SDK calls | SDK calls |
Pricing | Free local / $14 Pro | Open-source / Cloud | $19-$249/mo |
Related MCP server: Agent Memory Bridge
What It Remembers
Source code tells agents what exists. Tages tells them why it was built, how to work with it, and what not to do.
Type | Example |
Convention | "Always use snake_case for API routes" |
Decision | "Chose Postgres over MongoDB for pg_trgm fuzzy search" |
Architecture | "Auth middleware in lib/auth.ts, JWT in httpOnly cookies" |
Lesson | "Don't cache the Supabase mock — tests need fresh state" |
Anti-pattern | "Never pass id in upsert with onConflict — causes FK violation" |
Pattern | "All API errors return { error, code, status }" |
Plus: entity, execution, operational, environment, preference (11 types total).
How It Works
Install —
tages initconnects to your project, or install the Claude Code plugin for zero-config setupRemember — Store decisions and conventions manually, via git hooks, or by importing CLAUDE.md
Recall — Every session gets full project context in <10ms from local SQLite cache
Zero-Config Auto-Detection
When running as a Claude Code plugin or MCP server, Tages automatically detects which project you're in:
.tages/config.json— explicit marker file (created bytages link)Git remote — matches the repo name against registered projects
Directory name — matches the folder name against registered project slugs
Auto-create — if authenticated, creates a new cloud project automatically; otherwise uses local-only mode
No tages init required per directory. Use tages link [slug] to explicitly bind a directory to a project.
Claude Code Plugin
Install Tages as a Claude Code plugin for automatic session memory:
/plugin https://github.com/ryantlee25-droid/tagesWorks With
Claude Code, Cursor, Codex, Gemini — anything that speaks MCP.
Features
56 MCP tools — remember, recall, audit, sharpen, import, federation, analytics, and more
53 CLI commands — full control from the terminal
Web dashboard — browse, search, and edit memories with dark-mode UI
Auto-indexing — git hooks extract decisions from commits via Ollama or Claude Haiku
Import — seed from existing CLAUDE.md, ARCHITECTURE.md, or JSON files
tages brief— generate a cached context document for system prompt injectiontages audit— score your memory coverage and get suggestions for improvementtages sharpen— rewrite memories into imperative form for better agent consumptionLocal-first — SQLite cache for sub-10ms queries, works offline
Hybrid search — pg_trgm trigram matching + pgvector semantic search
Team sharing — multiple developers share one codebase memory (Pro)
Benchmarks
Reproducible LongMemEval and coding-memory benchmark results are published under eval/ with full methodology, judge configuration, and run notebooks. Results are reproducible against the published harness; raw numbers are in each eval's results/ directory.
Setup Guides
Architecture
packages/
server/ MCP server (56 tools, stdio transport, 818 tests: 805 passing, 13 skipped)
cli/ CLI (45 top-level commands, npm global install, 331 tests)
shared/ TypeScript types + Supabase client
apps/
dashboard/ Next.js 16, Supabase Auth, Tailwind, shadcn/ui
supabase/
migrations/ 64 migrations (tables, RLS, pgvector, RBAC, encryption)Security
Encryption at rest — AES-256-GCM for memory values (opt-in)
RBAC — Owner/admin write, member read-only
Row Level Security — All tables enforce project membership at the database layer
Auth — Supabase Auth + GitHub OAuth; API tokens SHA-256 hashed with expiration
Secret detection — Memories scanned for API keys, credentials, PII before storage
Audit logging — Auth events, exports, and token validation tracked
See SECURITY.md for our full security policy and responsible disclosure process. See PRIVACY.md for our privacy policy.
Pricing
Plan | Price | Includes |
Free | $0 | 1 project (cloud sync), 10,000 memories, 20 core MCP tools, local SQLite |
Pro | $14/mo | Up to 10 projects, 50K memories, all 56 tools, cloud sync |
Team | $19/seat/mo | Up to 20 projects, 100K memories, federation, RBAC, audit logging |
Self-hosted | Free forever | Bring your own Supabase, no limits, MIT license |
Contributing
See CONTRIBUTING.md for development setup and guidelines.
Release Notes
2026-08-18 — Recall relevance floor: recall can now answer "nothing"
tages recall <anything>returned the whole project. The recall RPCs filter on an absolute cosine threshold (p_threshold, 0.7 or 0.3), which against the hosted embedding model filters nothing: measured, pure nonsense (wibbleflux ganthorpe zzzqx) scores 0.775 against unrelated memories and "what is the best recipe for sourdough bread" scores 0.725 — both clear 0.7. Agents were receiving irrelevant memories presented as answers, and every positive recall result was therefore uninformative.No absolute cutoff can fix this. The model's cosines occupy a narrow high band (~0.69–0.95), and any constant tuned to it breaks when the embedding model changes — which is exactly how the existing 0.7 became inert.
The signal is distributional. Calibrated over 12 memories × 13 queries (8 answerable, 5 not): a query the corpus can answer makes one result stand out; one it cannot produces a flat pack. The top result's z-score separated the two cleanly (answerable 2.27–3.22, unanswerable 1.12–1.99, margin 0.277) and is scale-free, unlike the absolute top (margin 0.060), top−2nd (0.008), or top−mean (0.003).
The rule is deliberately conservative. Suppression is destructive — hiding a memory someone needed is worse than showing one they did not — so results are dropped only when both signals agree: a top score below 0.80 and either a flat distribution or too few candidates to judge. A tightly focused corpus, where everything is legitimately about one subject, keeps its high absolute scores and survives. Scoped to the semantic and chunk channels only; the trigram channel already has a real floor (measured 0 rows for the same nonsense queries) and is left alone.
The suite is now 137/137, green three runs consecutively, with the paraphrase check still passing — the floor rejects nonsense without suppressing genuine semantic matches. Constants come from n=12 on one model: a defensible starting point, not a tuned optimum.
TAGES_DEBUG_RECALL=1prints the verdict and its reason.
2026-08-18 — v0.5.3: republish with @tages/shared bumped
v0.5.2shipped a broken MCP server.evidenceWeightwas added to@tages/sharedwithout bumping that package, so the publish workflow correctly skipped it as already-published at0.2.0— and@tages/server@0.3.2went out pinned to@tages/shared@0.2.0, a version with no such export. Every MCPrecallfailed with(0, shared_1.evidenceWeight) is not a function. Local builds were unaffected because the workspace link resolves to the source tree, so nothing caught it until the end-to-end suite ran against the public registry: 134/137 published vs 136/137 local, with two MCP checks failing only from npm.Fix is versions, not code:
@tages/shared0.2.1,@tages/server0.3.3,@tages/cli0.5.3.Lesson worth keeping: a monorepo package whose exports changed must be bumped even when its consumers were. Testing only the workspace build cannot catch this class of defect — the suite's npm mode is what did.
2026-08-18 — Release readiness: CLI secret gate, working authorship, created_by pinned in the database (migration 0074)
The CLI never scanned for secrets.
tages rememberpersisted an AWS key that the MCP path already blocked (tools/remember.ts:65) — and it was the wrong way round, since the human-typed path is where a pasted config snippet arrives, and a stored secret is then readable by every project member and pulled into every agent's context by recall. The CLI now runs the same scan, fails closed before any write, and exposes the same--forceoverride.Authorship never worked.
memories.created_by/updated_byand the sync mapper have existed since0048, but the local SQLite cache had neither column — and the CLI syncs from SQLite, so every write dropped its author on the way out andget_memory_authorsreturned"Unknown"for everything. Columns added to the cache schema with an upgrade path for existing caches.created_byis now pinned by the database (migration0074), because no client can get it right.remoteInsertupserts withonConflict: 'project_id,key', and a Supabase upsert overwrites every column it is given — so an editor either sendsnull(erasing the author) or its own id (stealing authorship). Choosing correctly needs to know whether the row exists remotely, which a client cannot know: the CLI's pull is rate-limited, so a teammate's row may legitimately not be cached yet. Measured — the owner editing a teammate's memory rewrotecreated_byto the owner. ABEFORE INSERT OR UPDATEtrigger now fills it fromauth.uid()on insert and pins it to its existing value on update.updated_byis left to the client, since that field is supposed to move.End-to-end 136/137 against prod, up from 132: the four authorship and secret-gate checks now pass. The one remaining failure is recall's missing relevance floor, a ranking-quality issue tracked separately. 916 server + 505 CLI + 40 shared tests pass.
2026-08-17 — Evidence levels: how well established a memory is (migration 0070)
A memory carried three fields circling "how much should I trust this" and none that answered it.
typesays what it is about,sourcesays how it was captured, andconfidenceis a float that conflates the two —0.8could mean a test proved it or a model guessed it, and those warrant opposite behaviour from a reader. Newevidencecolumn:verified(checked against something executable),declared(asserted by a human as policy),observed(seen once),inferred(reasoned, not checked),disputed(contradicted). Namedevidencebecausestatusis alreadylive/pending/archived. Adapted from YAIML's evidence discipline.The defaults are the design. A human
tages rememberrecordsdeclared; an agent's own MCP write recordsinferred. Nothing becomesverifiedunless explicitly claimed, and the MCP tool description tells the model so outright. Defaulting the flattering way would let every unexamined guess arrive dressed as a fact, which is the failure the field exists to prevent. Existing rows are not backfilled —NULLmeans unknown, and inventing an assessment nobody made is the same failure in another form.The level changes retrieval order, in both ranking implementations. The end-to-end suite caught that applying it only in the MCP server left
tages recallordering purely on lexical fit, so an inferred guess that matched the query text slightly better was served ahead of the verified fact answering the same question. Both paths now weight by evidence.disputedis demoted hardest but never suppressed — a contradicted claim is exactly what someone re-litigating a decision needs to find, and it renders asDISPUTED — contradicted, re-check before actingrather than as a quiet tag.Known cost, made explicit rather than hidden: the four recall RPCs return a fixed table shape predating this column, so the CLI issues one extra primary-key lookup to fetch levels for the fused candidates. Widening those
SECURITY DEFINERreturn types would meanDROP+CREATEon four functions that0066,0068and0072have each already had to patch. A test asserts that exactly one such call is made, so a future change adding a third still trips.Also found while testing this: when two memories tie on score, recall order is nondeterministic — six identical runs produced two different orders. Unrelated to this change and not fixed here.
2026-08-17 — Post-merge hardening: three more PR #75 review findings closed
Migration
0072closes three more SECURITY DEFINER RPCs that0066/0068missed.0066's header claimed to close "the same class of hole" but only closed four of seven.revert_memory_to_versionandget_memory_versions(both0015) andcontextual_recall(0016) share the same shape: SECURITY DEFINER, a caller-suppliedp_project_id, noauth.uid()check, and no explicit GRANT so EXECUTE defaults to PUBLIC.revert_memory_to_versionis the serious one — it issues an UPDATE onmemories, so any authenticated caller who could guess a project UUID could overwrite that project's memory, and since0069that UPDATE also writes a forged entry into the audit trail via the snapshot trigger. The guard is copied verbatim from0068so all seven functions now share one implementation; reads return empty on denial to match the existing recall RPCs, the write raises42501.Migration
0073makeshybrid_recallfilterstatus = 'live'. The other three recall RPCs already did.memories.statusis one of('live','pending','archived')per0054—pendingis theobservestaging queue awaiting approval,archivedis a soft delete.hybrid_recallis the default path fortages recalland what the MCPrecalltool uses, so it had been returning memories the user never approved and memories they explicitly forgot. The filter is added inside each fusion leg rather than around the fused result, since RANK() is computed per leg.auto-reconcileis now time-bounded (default 3s,TAGES_SYNC_TIMEOUT_MSoverrides). It runs from a commanderpreActionhook in front of nearly every command, and no call underneath it carries an AbortSignal — a captive portal or a firewall that completes the TCP handshake and then never answers fell through to undici's 300sheadersTimeout, hanging commands liketages recallfor five minutes on background work the user never asked for. The abandoned promise gets a no-op catch so a late rejection can't surface after the command has moved on, and the sync handle is held in an object so it stays closable infinallywhen the timeout wins.Migrations are numbered
0072/0073rather than0070/0071because another concurrent session independently claimed0070_memory_evidence_level.sql; both files were renumbered and their verifications re-run against the renamed files before this commit.Verified:
0072applied to a throwaway Postgres with a fixture matching prod's ownership shape —get_memory_versions/contextual_recallreturn the row for the owner and forservice_role, 0 rows for an outsider, and an outsider'srevert_memory_to_versionis blocked with42501while the owner's revert still succeeds.0073applied to a throwaway pgvector instance —hybrid_recallwent from returningarchived-one, live-one, pending-oneto onlylive-one, with the outsider still getting 0 rows. 37 tests passing in this worktree acrossreconcile-timeout,auto-reconcile,auth-store, andlogin(5 new).A fourth review finding from the same round — an anchored-regex tightening of the destructive sweep in
e2e/run.mjs— is not in this commit; it was picked up inadvertently by a concurrent session's commit while both sessions shared a working tree, and is filed under an unrelated message onfeat/evidence-levels.
2026-08-17 — Version bump to actually ship the fixes, and the auth.json write path the last fix missed
The version bump was the actual blocker, not a formality. Two independent re-reviewers blocked this PR on it.
@tages/cli@0.5.1and@tages/server@0.3.1are already live on npm, and.github/workflows/publish.ymlchecks each package against the registry and prints SKIP + exits 0 when the version already exists — so merging and tagging without a bump would have produced a green pipeline that published nothing. Verified against the live registry rather than by reading the workflow:npm pack @tages/server@0.3.1then grepping the bundleddist/index.jsstill showsawait deps.tracker?.endSession()as a bare sequential await with noallSettledanywhere, andnpm pack @tages/cli@0.5.1shows zerochmodcalls onauth.json. Both published packages still carry the shutdown data-loss bug and the world-readableauth.json, while the never-reviewed auto-reconcile feature is already live in0.5.1. Bumped to@tages/cli@0.5.2and@tages/server@0.3.2.loginwasn't the only writer ofauth.json— the silent refresh inauth/session.tswas the other, and it mattered more. Both usedwriteFileSync(path, data, { mode: 0o600 }), andmodethere is theopen(2)creation mode: ignored on a file that already exists. The prior fix covered onlylogin, which a user hits occasionally. It missed the refresh path inauth/session.ts, which now runs on nearly every command since auto-reconcile is wired into a commanderpreActionhook — the common case, not the rare one. Newpackages/cli/src/auth/store.tsexports a singlewriteAuthFile()used by both call sites: it sets the directory to0700and the file to0600unconditionally, and opens+truncates before correcting the mode and writing, so a fresh token never sits on disk at looser permissions even momentarily.Also corrects the "published as
@tages/cli@0.5.1/@tages/server@0.3.1" claim in the 2026-08-13 sync-fixes entry below — those versions predate this release and never carried those fixes; it now names0.5.2/0.3.2.Verified: 3 new tests in
auth-store.test.ts, mutation-checked — revertingwriteAuthFileto the plain creation-mode write fails "tightens a pre-existing 0644 auth.json instead of inheriting its mode". Full suite: 1,483 passing / 16 skipped across 8 packages (cli495,server907).pnpm typecheckclean.
2026-08-17 — Shutdown drain fix and CLI login permission fix (two PR #75 review blockers)
Shutdown dropped the entire dirty memory queue when analytics failed.
packages/server/src/index.ts's drain step ranawait deps.tracker?.endSession()thenawait deps.sync?.flush()as two sequential awaits in one async function.endSession()writes an analytics row and rejects on any network blip, which aborted the function beforeflush()ever ran — so every memory in the dirty queue was lost on theexit(0)that follows, the exact loss this block exists to prevent, over the least important thing in it. NowPromise.allSettledruns both regardless, logs either failure, and rethrows only a failed flush, since only that means data was left behind.tages logincould not tighten permissions on an existingauth.json.writeFileSync'smodeoption is the open(2) creation mode — applied only when the file is actually created.loginis a re-run command by design (switch accounts, refresh an expired session), so the common case is an existing file keeping whatever bits it already had, and this file holds a live Supabase refresh token. It has been observed at0644on real machines. Now followed by unconditionalchmodSync(getConfigDir(), 0o700)andchmodSync(getAuthPath(), 0o600).Both were flagged by independent reviewers on PR #75; the shutdown fix was a merge blocker. Each has a new regression test, and both were mutation-checked: reverting the shutdown fix to sequential awaits fails "still flushes queued memories when the analytics endSession rejects"; removing the chmods fails "tightens permissions on an auth.json that already exists at 0644". Full suite after restoring both: 1,480 passing / 16 skipped across 8 packages,
pnpm typecheckclean.Ships in the already-published
@tages/cli0.5.0 — no version bump in this commit; a follow-up release is needed to distribute the login fix.
2026-08-17 — Security fix: CLI login redirect could send live session tokens to any host
Session tokens could be redirected to any attacker-chosen host.
apps/dashboard/src/app/auth/callback/route.tsread thecli_redirectquery param raw and redirected to it withaccess_token,refresh_token, anduser_idattached — no host validation. An attacker could craft a provider authorize URL withredirect_to=<site>/auth/callback?cli_redirect=https://evil.com; the victim signs in normally and their live refresh token is delivered to the attacker. That is full account takeover, and the refresh token is long-lived. The sibling route/auth/clialready had the correct loopback-only guard, but/auth/callbacknever called it, and an attacker doesn't need to go through/auth/clito reach the callback at all. The guard is now extracted to a shared module,apps/dashboard/src/lib/safe-redirect.ts(isLocalRedirect), and used by both routes instead of duplicated — a copy per route is how the two drifted apart in the first place./auth/cli's local copy was deleted in favor of the import; its behavior is unchanged.Open redirect via the
nextparam, same file.${origin}${next}looks origin-locked but is not:next=//evil.comandnext=/\evil.comare protocol-relative and leave the site. Now validated by the newisSafeRelativePath, falling back to/app/projects.accept_pending_invitesfailures were invisible. The call sat in atry/catch, butsupabase.rpc()resolves with{ data, error }rather than throwing, so the catch caught nothing and a failed invite sweep logged nowhere. Now the result is destructured anderror.code/error.messageare logged. Still non-fatal by design — a failed invite sweep must not block the sign-in that triggered it.Removed three internal audit/handoff documents from this public repository:
FINDINGS-RELEASE-AUDIT.md,HANDOFF-ONBOARDING-DAY.md,PLAN-HOSTED-EMBEDDING.md. Verified they contain no key material, but they document that the productionservice_rolekey is unrotated and world-readable, give a working privilege-escalation call against the named production deployment, name the prod project ref, and pair real user emails withauth.usersUUIDs..gitignorenow blocks the whole class (FINDINGS-*.md,HANDOFF-*.md,PLAN-*.md,SPLIT-*.md). These files were public on this branch and remain in git history — removal stops further exposure but does not un-publish what was already visible.Verified:
isLocalRedirect/isSafeRelativePathexecuted directly against 22 cases includinghttp://localhost.evil.com,http://127.0.0.1.evil.com,http://user@evil.com,javascript:,//evil.com, and/\evil.com— all correctly rejected, whilehttp://127.0.0.1:<port>andhttp://localhost:<port>are still accepted so the CLI login flow keeps working.tsc --noEmitclean,next buildcompiled successfully.
2026-08-17 — End-to-end suite, the memory_versions sync blocker (migration 0069), and bidirectional CLI sync
Every edit permanently killed that user's cloud sync, silently (migration
0069).memory_versionswas created in0006with RLS enabled and exactly one policy,for select; noINSERTpolicy has ever existed in any of the 69 migrations. Thesnapshot_memory_version()BEFORE UPDATE trigger onmemorieswas plainplpgsqlwith noSECURITY DEFINER, so it ran as the invoking user and its insert was denied — and a BEFORE trigger that raises takes the wholeUPDATEwith it. Worse, the rejected row stayed dirty in the local sync queue, so every subsequent flush retried it, hit the same error, and aborted, taking brand-new unrelated memories down with it. From a user's first correction onward, nothing they wrote reached their team. Exit code stayed0and stdout stayed empty; only stderr carried the warning. Fixed by making the triggerSECURITY DEFINERwith a pinnedsearch_path(the pattern0051already uses forcheck_seat_limit_on_update) and populatingchanged_by_user_id. Safe because the function takes no caller-supplied arguments — every value comes fromOLD/NEWof a row the caller was already authorized to update. Applied to prod; the end-to-end suite went from 85/109 to 115/122 on that change alone.The CLI now reconciles with the cloud on its own (
packages/cli/src/sync/auto-reconcile.ts). Sync was previously push-only, with the local store refreshed just once at MCP server startup — so a teammate's memory was invisible until you restarted your agent. A commanderpreActionhook now pushes local changes and pulls remote state before any command that reads or writes memories. Best-effort throughout: it never changes a command's exit status or output. Rate-limited to one round trip per 60s (TAGES_SYNC_TTL_MS;TAGES_NO_AUTO_SYNC=1disables). Skipsinit,link,login,logout,whoami,doctor,token,harness,dashboard,onboard—doctorespecially, since a doctor that repairs what it is diagnosing hides the bug.The database is authoritative, without that meaning data loss. Order is push-then-pull, always, and
hydrateFromRemotenow skips keys still marked dirty. Both are load-bearing:upsertMemory(mem, false)overwrites the value and clears the dirty flag, so a pull-first reconciliation would revert an unsynced local edit to the cloud's older copy and drop it from the retry queue, with no error anywhere.hydrateFromRemotealso reports what it withheld, andSupabaseSync.hydrate()holds itslast_synced_atwatermark when anything was skipped — advancing past a withheld revision would push it outside every futureupdated_at > lastSyncedwindow, so that key could never be refreshed again while the "cache is current" fast path reported it up to date forever.New end-to-end suite at
e2e/, 126 assertions across 11 phases, driving the real CLI and the real MCP server as five separately authenticated identities with isolated$HOMEs and their own git work repos, against real Supabase. Covers create, retrieve, update, invite/join, bidirectional sharing, roles and seat limits, cold-cache persistence, tenant isolation, retraction, and reconciliation. Phase 99 asserts the suite itself can fail. Teardown runs in afinallyand onSIGINT/SIGTERM, then verifies zero fixture rows remain; a start-of-run sweep clears residue from any earlier run killed where teardown could not run.node e2e/run.mjs --set-credentialsstores keys in the macOS Keychain — thesecurity -wprompt cannot be used, as itsreadpassphrase(3)buffer is 128 bytes and truncates a Supabase JWT silently at exit 0.Known and unfixed, surfaced by the suite: the CLI never calls
scanForSensitiveData, sotages rememberpersists a secret the MCP server would block (tools/remember.ts:65);memories.created_by/updated_byare never populated by any write path, soget_memory_authorsreturns"Unknown"; and recall has no relevance floor, returning top-k for nonsense queries.Published as
@tages/cli@0.5.2and@tages/server@0.3.2. Both are required — the CLI carries the reconcile hook, and the server carries thepreserveDirtyguard and the watermark hold that the MCP startup hydration depends on. The other five packages are unchanged since their last release. Migration0069is already applied to prod, so the database half of the fix protects everyone regardless of client version.
2026-08-17 — Marketing site: mobile overflow, button shadow, pill rendering, nav a11y
Fixed horizontal overflow on every phone-width viewport. At 390px the document laid out 523px wide, pushing the nav hamburger off-screen and running section headings off the right edge. Root cause was
min-width: auto, the default on grid/flex children, refusing to shrink below content width — onenowrapcommand line (tages link --project-id <project-id>) set a width floor for the whole page. Fixed withmin-w-0in four places: theCommandcode element andCodePanelroot inui.tsx, both grid children insearch.tsx, and the step body inquickstart.tsx. Verified zero horizontal overflow across 8 routes at 5 viewport widths (360/390/430/768/1024) against a production build.Removed the accent-tinted halo from the primary button's shadow, keeping only the neutral hairline contact shadow. The fill (
signal-600#2563eb) is identical to every other blue on the site, but the blue-tinted shadow darkened its own edge so the button read heavier than the same blue reads as text. A lighter fill (signal-500#3b82f6) was rejected — it gives white label text only 3.68:1 contrast, under the 4.5:1 AA floor;signal-600is 5.17:1.Fixed the
conventionmemory type rendering as bare text on/exampleswhile the other seven types rendered as pills. The tint was built by string concatenation (`${color}15`), which works for a bare hex but producedvar(--color-signal-600)15for the one type whose color was a token, silently invalidating the background and border declarations. Replaced withcolor-mix(in srgb, <color> N%, transparent), which works for hex andvar()alike.Mobile nav menu now locks body scroll while open, closes on Escape, and closes when the viewport crosses the 768px breakpoint — the panel is
md:hidden, so without that last case rotating to landscape would hide the menu while leaving the scroll lock stranded on.
Verified with npx tsc --noEmit (clean), pnpm build (clean), and a 9-check browser suite against next start covering scroll lock on/off, Escape, resize release, button fill, absence of the halo, 5.17:1 label contrast, and all 22 type pills painting a capsule.
2026-08-17 — Recall RPCs: restore service-role access, a zero-row regression from 0066 (migration 0068)
tages recallsilently returned zero rows for every query, on every project, since0066_recall_rls_guards.sql. 0066 added anauth.uid()access guard to all four recall RPCs (recall_memories,semantic_recall,hybrid_recall,chunk_semantic_recall). All four areSECURITY DEFINER, so the guard is an ordinary predicate rather than RLS, and the service role does not bypass it: underTAGES_SERVICE_KEY,auth.uid()isNULL, both branches of the guard evaluate false, and every recall RPC returned zero rows.tages recall --allkept working the entire time because it is a plain PostgREST table select, which the service role does bypass RLS on — that asymmetry, identical credentials producing a full table read but zero RPC rows, was the whole bug, and it looked like a broken search index rather than an access-control regression. 0066's own header predicted this exact failure, labelled it a known behavior change, and deliberately deferred the service-role decision to a later migration;0068is that migration.Fix adds a
service_roleexemption to the guard, reading the role from the request-scoped JWT GUC rather thancurrent_user(which underSECURITY DEFINERbecomes the function owner and would silently exempt every caller, not just service_role). It fails closed: an absent GUC yieldsNULL, andNULL is distinct from 'service_role'is true, so the guard still applies to every other caller. This is not a new hole — a service-role caller can already readmemoriesdirectly with RLS bypassed, so the exemption only restores parity between the RPC path and the table path. The case 0066 actually closed, an authenticated non-member reading another project's memories by guessing its UUID, is untouched. Everything else in all four functions carries forward from 0066 verbatim, generated by textual substitution of the guard block alone.Applied to dev and prod (
wezagdgpvwfywjoxztfs) viasupabase db push --linked;supabase migration list --linkedconfirms0068applied on both. Verified against prod post-deploy:tages recall "shadow"returnsno-accent-tinted-shadowsat 0.85 semantic similarity, and previously-dead queries ("migration", "supabase", "vercel", "how do I deploy the site", "why did mobile break") all return sensible hits.
2026-08-17 — CLI: login, logout, whoami
The GitHub OAuth loopback (
runGithubOAuth) was already built and working — it spins up a local server on127.0.0.1, opens the browser to the dashboard's/auth/cliroute, and receives session tokens back — but it was only reachable frominit,link, andmigrate, each of which also creates or looks up a project and rewrites config. There was no way to sign in, sign out, or switch accounts on their own; switching identity meant hand-pasting tokens from the/auth/cli-tokenpage into~/.config/tages/auth.json.tages loginruns the OAuth flow and writes~/.config/tages/auth.jsonat mode0600, reporting the account it left and the one it landed on.tages logoutremoves the credentials file.tages whoamireports the signed-in identity and flags an expired access token.Identity is read by decoding the Supabase access token's JWT claims rather than making a network call, avoiding threading the anon key through this module to answer a question the token already answers.
All three warn when
TAGES_SERVICE_KEYis set. That env var takes precedence overauth.jsonincreateAuthenticatedClient(packages/cli/src/auth/session.ts:15-18), so with it exported a successful login silently has no effect and every command keeps running as the service role. That exact trap cost a debugging session; it now announces itself.Verified with 10 new tests in
login.test.ts, the full CLI suite at 473 passing across 36 files, andtsc --noEmitclean. Mutation-checked: neutering the service-key warning and removing theauth.jsonwrite each made 4 tests fail, confirming the tests assert behavior rather than just passing.Not yet published to npm — the shipped
@tages/cliis 0.4.0 and does not include these commands.
2026-08-13 — Onboarding release: three security fixes, a working join path, a bundled CLI, and zero-install semantic search (@tages/cli 0.4.0 · @tages/server 0.3.0 · @tages/shared 0.2.0 · @tages/harness-claude-code 0.1.0 · the three editor plugins 0.1.0)
A full-repo audit against the goal "a teammate signs up, joins, and recalls a colleague's memory" found the v0.3.0 tag was never distributed and the documented onboarding path did not work end to end. This release closes that. Verified by an automated end-to-end suite driving the real CLI as three distinct authenticated identities against production: 28/28.
Three security fixes, all found in this audit and applied to dev and prod (migrations
0065-0067).accept_pending_inviteswasSECURITY DEFINER, granted toauthenticated, and took the email and user id as caller-supplied parameters bound to nothing. Any signed-in user could claim anyone's pending invite and read that project's memories. Now zero-argument, deriving identity fromauth.uid()and the JWT email claim.All four recall RPCs bypassed RLS (
recall_memories,semantic_recall,hybrid_recall,chunk_semantic_recall), filtering only on a caller-suppliedp_project_id. The project UUID was effectively a bearer token for every memory in the project, while the documented join flow was sharing that UUID. Guarded with the sameowner OR is_project_memberpattern0051already applied to two other RPCs. The leak was reproduced on both databases before the fix and confirmed closed after; owner and active-member results are byte-identical.tages team invite --role ownerhad no guard, and the RLS policy checked only the caller's role, never the role being granted, so any admin could mint an owner. Closed at the CLI and with aBEFORE INSERT OR UPDATEtrigger, which also covers the dashboard's service-role write path.
The join path now works.
tages link --project-idpreviously bound a project and configured nothing. It now writes a Claude Code project-scoped.mcp.json(init and link both targeted Claude Desktop's config before this), installs the git hook, points the MCP config at the locally built server instead of the four-month-stale published package, and adds.mcp.jsonto the repo's local git excludes since it carries the project id and anon key.The CLI is bundled with tsup.
rootDir: "../../"emitted CJS copies of sibling packages into an ESM package, so the published tarball threwexports is not defined. This was not tarball-only, as first assumed:link.tsloaded the emitted copy rather than the real siblingdist/, so the join path crashed on source installs too. Tarball is now 3 files instead of 229, andtages templatesworks from an npm install for the first time.Silent failures made visible. Both
tages rememberand the MCPremembertool reported success when the cloud write had failed, leaving memories local-only and invisible to teammates forever.observe/verifypromoted remote rows by a local id that never matches, so promotions silently stayedpending.tages doctorreported a correct setup as broken and then advised the one command that wedges a joiner. Project resolution fell back to the alphabetically-first config, so a command run from a subdirectory could target the wrong project.Housekeeping: SIGTERM handling (MCP clients send it; only SIGINT was handled, losing embeddings on shutdown), a runnable lint for the first time (the root
lintscript had noeslintdependency at all), vitest no longer globs into stale worktrees, and the invite integration tests re-plumbed onto real per-user JWTs — they could never have run before, since the fixture project omitted aNOT NULLslug.Semantic search now needs nothing installed. Embedding used to run client-side, so every developer had to install Ollama or hold an OpenAI key — and
embedOneprobed Ollama unconditionally, so a teammate who happened to have it running for an unrelated project silently wrote vectors from a different model into the shared index. Similarity across models is meaningless, so those results looked confident and were noise. Embedding now runs through a hosted Supabase edge function (gte-small, JWT-gated, project-membership checked), and provider selection is a deterministic switch resolved once per process with no fallthrough. An unreachable endpoint degrades to trigram; it never silently switches model. All 135 existing prod memories were re-embedded onto the new provider.
2026-08-17 — Release: @tages/cli 0.5.0
Releasing @tages/cli 0.5.0, which packages the login, logout, and whoami commands added in commit 2529355 (see the entry above). No other workspace package is changing in this release.
Known limitation (superseded): embeddings were previously opt-in. Without Ollama or TAGES_OPENAI_EMBED, memories store with a null embedding and recall silently degrades to trigram matching. See docs/team-onboarding.md.
2026-07-19 — Team-readiness release (@tages/cli 0.3.0 · @tages/server 0.2.0 · @tages/shared 0.1.2)
First npm release since April; ships the three months of merged retrieval/harness work to users and adds the team-join path. See CHANGELOG.md for the rolled-up detail.
tages link --project-id <uuid>— an invited teammate can bind their machine to an existing shared project without having created it (the gap that previously made "team memory" impossible for anyone but the project owner). Membership enforced by theis_project_memberSECURITY DEFINER RPC (fail-closed); refuses to clobber a local link on a slug collision; expired sessions route to re-auth. Newdocs/team-onboarding.mdwalks the full install → auth → join → harness-opt-in path.Cross-encoder rerank is now opt-in. The local
@huggingface/transformersONNX model (~90MB) is dropped as a runtime dependency; rerank runs only withOPENAI_API_KEY+TAGES_OPENAI_EMBED(OpenAI-judge, fail-open), on both CLI and server, so default recall fires no per-query API call. It measured net-neutral on the eval. Lighternpx/global-install footprint.Quality gate: White + Gray + two high-effort
/code-reviewpasses on the combined diff caught 12 defects that 1,100+ passing tests and standard review cleared — silent config-clobber (data loss), an untimed CLI fetch (multi-minute hang), a server-side rerank firing on every recall, a service-role membership bypass, and an expired-session misroute among them. All fixed and re-verified READY. Final: 1,228 tests passing, typecheck clean.Harness (Milestone 1) and PRIVACY disclosure of its marker-gated redaction limitation ship for the Mersive dogfood; drift wiring (M2) is deferred.
2026-07-10 — Two-stage retrieval: RRF fusion, cross-encoder rerank, multi-vector chunk storage (Tier 1 + Tier 2)
Phase 1 (Tier 1): candidate-pool widening + Reciprocal Rank Fusion (k=60) replacing raw-score merge across trigram, semantic, and temporal channels (CLI merge path + SQL
hybrid_recall, migration0062); local cross-encoder rerank (Xenova/ms-marco-MiniLM-L-6-v2, ONNX/CPU) with an OpenAI-judge fallback; new temporal date-range retrieval channel; opt-in--assembled-context/assembledContextbudget-fitted output.Phase 2 (Tier 2): new
memory_chunkschild table + HNSW index (migration0063); per-chunk embedding write path;chunk_semantic_recallRPC returning winning-chunk citations (migration0064); chunk channel wired in as a 4th RRF list; single-project backfill script for existing memories.Measured results (LongMemEval 50q, seed 42, dev project) vs. the pre-Phase-1/2 baseline (migration 0061): overall accuracy 72%→80% (+8), recall@k 90%→94% (+4), temporal-reasoning 38.5%→61.5% (+23), single-session-preference 33%→67% (+34), zero-hit questions 5/50→3/50. All targets from the plan's recalibrated expectations were met. The gains come from the chunk + temporal channels; the cross-encoder rerank is net-neutral on this 50q sample since retrieval already surfaces the gold memory into top-k (consistent with the reader-is-the-bottleneck finding). A 500q run is pending as the headline number.
Quality gate: White + Gray + a high-effort
/code-reviewpass on the combined diff found 10 confirmed defects that 1,191 passing tests and a medium review both cleared — all fixed and White-re-verified (0 blockers). Notable catches: the cross-encoder rerank was a silent no-op (text-classification pipeline saturated every score to 1.0; fixed to raw logits); an embedding-space mismatch between OpenAI-only chunks and Ollama-first queries; unencryptedchunk_textdefeating at-rest encryption; the reranker scoring ciphertext; a flush/write concurrency clobber of dirty flags; and chunk sync keyed on local ids that never match remote (would have shipped Phase 2 as a silent no-op).Reviewer decision (resolved): the local
@huggingface/transformerscross-encoder (ONNX, ~90MB model cached on first use) that this phase originally added to CLI + server has since been dropped as a runtime dependency; cross-encoder rerank is now opt-in and requiresOPENAI_API_KEY+TAGES_OPENAI_EMBEDrather than shipping a bundled local model. Migrations0062–0064are now applied to prod (prod is current through0064).Known scope boundaries: MCP rerank only fires on the remote-hybrid fallback path (warm local cache returns early); temporal channel only helps regex-resolvable dates and its nearest-date guarantee is heuristic above ~1000 date-carrying memories/project; a pre-existing transient double-insert window on concurrent chunk writes self-heals (follow-up: route through the flush mutex or add a
(memory_id, chunk_index)unique constraint). Phase 3 (ingestion-time observation distillation + knowledge-update supersedence relations) is deferred, not in this change.
2026-07-10 (precision + recall)
Reader temporal fix + recall widening. The eval reader now receives the question's reference date: temporal-reasoning +15.4pt, knowledge-update +25pt, overall 54%→62% on the 50q LongMemEval calibration (mechanism row-verified: "28 weeks ago"→"2 weeks ago"). Added
word_similarity()torecall_memories/hybrid_recall(migration 0061) to recover long-single-session recall dilution. CLI recall: surfaces referenced/relative dates, tunableTAGES_RECALL_THRESHOLD(clamped [0,1]), conservative content dedup. Migration 0061 validated on dev; recall-lift measurement + prod apply pending.
2026-07-10
fix(cli): one-shot
tages remembernow generates + persists a durable embedding. The CLI write path never embedded (only the long-lived MCP server did via fire-and-forget), so CLI-stored memories were invisible to semantic search (trigram-only). Also fixed: embedding was dropped on re-remember of an existing key (id vs project_id+key mismatch), and dropped in sync because rowToMemory never reconstructed it.
2026-07-09 — Long-input embedding fix + 3-date temporal anchoring (Tier-1 retrieval quality)
Embedding silent-drop fix (Tasks A+B): memories over ~8192 tokens got NO embedding at all — OpenAI's 400 was swallowed, so the memory was invisible to semantic search with no error surfaced.
generateEmbedding()(newchunking.tsin bothpackages/cliandpackages/server) now token-aware chunks long input and mean-pools the resulting vectors; HTTP error bodies are logged instead of discarded; 429s are retried with a fresh per-attempt timeout and total backoff capped at 2s so the recall read path can't hang.Temporal 3-date anchoring (Task C): new
referenced_date/relative_datecolumns (migration0060_temporal_date_anchoring, drops and recreateshybrid_recall/semantic_recallto return them with every original clause preserved verbatim), a rule-based date extractor, a narrowed temporal-query classifier, and relevance-preserving date-proximity reordering in recall. Targets temporal reasoning, the universal weak spot (23–54%) across every LongMemEval run to date.Quality gate: 1,089 tests passing, typecheck clean. White (Opus) review + Gray + a high-effort
/code-reviewworkflow pass; the workflow caught real blockers White/Gray missed (temporal reorder was discarding relevance, the classifier over-fired on "may"/"after", and the retry hardening had re-introduced the silent-drop and could hang recall) — all fixed and adversarially re-verified READY.Must-do before prod: migration
0060is SQL and not exercised by the test suite — apply it against a Supabase dev branch and confirmhybrid_recall/semantic_recallreturn the new columns and existing recall still works before it reaches prod.Follow-ups (non-blocking): classifier still misses bare relative phrases like "last week"/"last month" (pre-existing, not a regression); previously-silently-dropped long memories need a manual per-project embedding backfill via
packages/server/scripts/backfill-embeddings.ts; the CLI'sremembercommand still never generates embeddings at all (separate gap, out of scope here, needs its own ticket).
2026-07-09 — Instrumented Claude Code hook capture for behavioral drift (Milestone 1)
New
packages/harness-claude-codecapture package: an opt-in, local-first Claude Code hook (bin) that parses stdin tool-call events, redacts secrets, and appends them to a local SQLite log. Fail-closed by design — any parse/write error is swallowed and the process still exits 0, so a broken hook can never block or crash an agent's tool call. Additive to the existing MCP path; ships ahead of a Mersive engineering team dogfooding it for a baseline data window.Secret redaction extracted to
@tages/shared: newredactSensitiveDatahelper, shared between the MCP server's existingsafety.tsand the new hook capture package, redacting before persistence rather than after.Migration
0059_harness_tool_events: new table for captured tool-call events; RLS policies copied verbatim fromtool_call_log(both the owner andteam_membersbranches) to avoid the RLS-drift class of bug.CLI
tages harness enable|disable|status|sync: per-developer opt-in.enablewrites an absolute-path hook entry into the developer's gitignored.claude/settings.local.json(never into shared repo config);syncbatch-uploads redacted rows to Supabase.PRIVACY.mdamended to disclose the opt-in harness and its 90-day retention window.Quality gate: 971 tests passing, typecheck clean across all 9 packages. A high-effort review plus 3 adversarial review rounds found and fixed 8 defects, including 3 distinct secret/PII redaction leaks (nested-object, numeric, and argv-split forms), a silent-no-op hook path, and a slug-misroute regression. A subprocess smoke test confirmed end-to-end capture + redaction against the real compiled bin.
Coverage warnings (non-blocking):
harness.ts~76% line coverage,packages/harness-claude-code/src/index.ts~90% — flagged for follow-up, not a merge blocker.Deferred to Milestone 2 / follow-up:
harness syncis currently at-least-once (needs a dedup/unique constraint or upsert onharness_tool_events); redaction is still marker-gated regex (brittle — a bare secret with no adjacent marker is indistinguishable from a SHA/base64, worth an entropy-based or structural deny-by-default pass);harness_tool_eventsstill needs to be merged into the drift computation indrift.ts; and the server'sembeddings.tsOpenAI fallback (pre-existing, from PR #65) should be gated behind an env flag the same way the CLI already is.
2026-07-09 — LongMemEval-driven memory-quality fixes (product + eval harness)
Product fix: document embeddings were never written (the #1 bug) —
remembernever populated the pgvectorembeddingcolumn, so semantic recall had been silently trigram-only since it shipped.packages/server/src/tools/remember.tsandpackages/server/src/embeddings.tsnow generate and persist the embedding on write;packages/server/src/sync/supabase-sync.tssyncs it to Supabase narrowly, serialized against concurrent writes/deletes so a late embedding upsert can no longer revert a newer value, resurrect aforget-ed memory, or strand a dirty flag.CLI/server embedding parity:
packages/cli/src/lib/embedding.tsandpackages/cli/src/commands/recall.tsgain the same Ollama-primary embedding path the server uses, with the OpenAI fallback made opt-in (TAGES_OPENAI_EMBED) rather than a blocking, billable per-recall call — closes both the hot-path cost/latency issue and the Ollama(768-dim)/OpenAI(1536-dim) vector-space mismatch.Structured, citable recall output:
packages/server/src/tools/recall.tsreshapes passages for the client-agent reader (source, updated-at citation, formatted body), and now guards against undefinedupdatedAt/sourceon legacy/backfilled rows instead of throwing.Migration 0058 DDL fix:
supabase/migrations/0058_drop_provenance_user_id.sqlswitched fromCREATE OR REPLACEtoDROP+CREATEso the function signature change actually applies.Embedding backfill script:
packages/server/scripts/backfill-embeddings.ts— sandbox-scoped, one-time backfill for memories written before the embedding-on-write fix.Truncate-renormalize guard:
normalizeTo1536now renormalizes after truncating embeddings over 1536 dims, so an over-1536-dim future embedding can't silently corrupt cosine-similarity rankings.Eval harness (Phase 1, EVAL-ONLY): per-type judge, type-aware answer prompt, retrieved-memory logging, a real
recall@kretrieval metric, turn-level ingest, a Chain-of-Note reader, and a correction toeval/longmemeval/README.md.Validation: 918 tests passing (669 server + 204 CLI + 45 eval), monorepo typecheck clean. Quality gate ran White + Gray + a high-effort
/code-review; the high-effort pass caught 6 concurrency/correctness blockers (embedding-sync races, a recall crash on null dates, CLI blocking-paid-embed cost, vector-space mismatch) — all fixed and re-verified READY before this PR.
2026-07-08 — LongMemEval eval-backend expansion + memory-quality fix plan
Pluggable LongMemEval embedder backends:
eval/longmemeval/src/memory.tsgains three newBackendvariants —tages-semantic(nomic/pgvector via Tages' own semantic store,semantic-store.ts),openai-cosine(OpenAI embeddings + cosine similarity,openai-store.ts), andvoyage-cosine(Voyage AI embeddings,voyage-store.ts) — alongside the existingtages-cliandin-memorybackends. Also fixes thetages remembermemory type passed byTagesCliStorefromfacttoentity.Benchmark results captured: nine result JSONs from tonight's runs across all backends (baseline in-memory, Tages local/cloud-dev/semantic v1+v2, OpenAI small/large, Voyage 4-large/code-3) landed in
eval/longmemeval/results/.Memory-quality fix plan:
PLAN-MEMORY-FIXES.mdlays out a two-phase plan — Phase 1 (EVAL-ONLY) fixes the harness's judge/prompting/ingestion issues; Phase 2 (PRODUCT) fixes the real bug found during this run: the pgvectorembeddingcolumn is never populated on therememberwrite path, so semantic search has been silently trigram-only since it shipped. This commit is the base Phase 1 builds on.
2026-04-29 — Week 1 housekeeping (governance unghost, action-setup v6, drop provenance user_id)
A1 — Governance page indexed: removed
robots: 'noindex, nofollow'from/governancemetadata. The page is now crawlable and eligible for Google Search Console indexing. Added/governancelink to both the desktop nav (after Security, before GitHub) and the mobile menu using the same styling as adjacent links.A4 follow-up — publish.yml pnpm/action-setup aligned to v6: bumped
pnpm/action-setup@v5to@v6in.github/workflows/publish.yml. PR #31 (Dependabot) is bringingci.ymlto v6; without this bumppublish.ymlwould be left on v5 — functional but inconsistent and easy to forget before the v0.3.1 tag push.B1 —
get_memory_provenanceno longer returns raw auth.users UUID (migration 0058):create or replace functioninsupabase/migrations/0058_drop_provenance_user_id.sqlremoves theuser_id uuidcolumn from the function'sreturns table(...)and the correspondingm.updated_by as user_idfrom the SELECT. All other columns retained. The function still returnsuser_display(full_name → email-prefix → "Unknown") which is sufficient for every caller. Zero callers in the codebase (grep -r get_memory_provenance apps/ packages/returns no hits). Closes White W1 review finding from PR #55. Migration must be applied to prod viasupabase db push --linkedafter merge.
2026-04-29 — v0.3.1: behavioral drift (Jensen-Shannon divergence on tool-call distributions)
Behavioral drift algorithm: replaced the v1
insufficient_datastub inbehavioral-drift.tswith a real Jensen-Shannon divergence implementation. Per-agent temporal drift: for each agent active in both windows with ≥5 calls per window, compute JSD(baseline_distribution, current_distribution) over the union tool vocabulary with Laplace smoothing, normalize to [0,1] by dividing by ln(2), and average across eligible agents (max 20). ReturnsBehavioralDriftReportwithscore,status,note,jsd(raw),agentCount,agentDistributions(top-3 tools per agent), andwindowA/windowBboundary metadata.Type layer: new
BehavioralDriftReport,AgentToolDistribution,BehavioralWindowinterfaces intypes.ts.DriftReport.behavioralretyped fromMetricStubtoBehavioralDriftReport(superset — no breaking change for callers readingscore/status/note).DriftInputextended withbaselineSince?andcurrentSince?.Weight rebalance:
WEIGHTSincompute.tsshifted from{semantic: 1.0, coordination: 0, behavioral: 0}to{semantic: 0.7, coordination: 0, behavioral: 0.3}. For projects without tool_call_log volume, behavioral returnsinsufficient_dataand contributes 0 — overall drift score drops ~30% relative to the v1 number for projects with sufficient behavioral data. Next target{0.5, 0.25, 0.25}when coordination ships.CLI: new
--baseline-since <window>and--current-since <window>flags ontages drift. Both must appear together; baseline-since must be earlier than current-since; both use the existingNd/Nh/ISO grammar.renderHumanextended with behavioral score, raw JSD, agent count, window boundaries, threshold guidance, and per-agent top-3 tool breakdown.Tests: 18 new tests — 12 in
behavioral-drift.test.ts(windowing, JSD math, multi-agent averaging, top-tools reporting), 5 incompute.test.ts(weight exposure, weight contribution paths, edge cases), 1 in CLIdrift.test.ts(flag parsing). 843 passing total (was 826 on main).
2026-04-29 — CI test-mock fix
CI test-mock fix —
supabase.authinterface added tocommands-smokemock:mockSupabasenow includes amockAuthobject with stubbedsetSession,getSession, andrefreshSession(all returning a valid session shape). Without this, any test that callswriteAuthConfigand runs withoutTAGES_SERVICE_KEYset would crash withcannot read 'setSession' of undefined— becausecreateAuthenticatedClientreadsauth.jsonand callssupabase.auth.setSession(...)on the auth-path branch. The bug was latent until CI was widened frompnpm --filter @tages/server testtopnpm -r testin c9a27f1;resetMockSupabase()updated to clear the three new mock functions alongsidefrom/rpc.
2026-04-29 — PR #55 White second-review fix bundle (W2 limit-semantics, W2-AOT codex regex, Q1 Windows guard)
W2 limit-semantics —
drift.ts--limitregression reverted:--limitnow controls display top-K only (original semantics, default 10). A newMAX_DB_ROWS = 10000constant applied to both Supabase queries provides defensive OOM protection without conflating "show top N keys" with "fetch N rows from the chronological window".W2-AOT codex regex —
stripTagesBlockTOML array-of-tables support:targetHeaderandanyHeaderregexes now match[[mcp_servers.tages]]in addition to[mcp_servers.tages]. Hand-edited configs using double-bracket syntax are now cleaned correctly by--force. JSDoc updated to document the comment-swallowing limitation.Q1 Windows guard —
pathToFileURLentrypoint fix (codex, cursor, gemini plugins): Replacedimport.meta.url === \file://${process.argv[1]}`withimport.meta.url === pathToFileURL(process.argv[1]).hrefin all three plugins. The original was broken on Windows (import.meta.urlusesfile:///C:/...;process.argv[1]usesC:...— never equal), silently no-oppingmain()undernpx`.Regression test:
packages/codex-plugin/src/__tests__/index.test.tsadds one test covering[[mcp_servers.tages]]strip (10 tests total, was 9). 826 passing overall.Deferred: W1 (comment swallowing before next non-tages header) — documented in JSDoc; S2 (friendly ENOENT for
computeOracleSha) — punt, raw error acceptable for eval harness.
2026-04-29 — PR #55 White-review fix bundle
B1 —
drift.tscrash on bad--since:resolveSince()is now wrapped in try/catch; invalid input prints a clean error message and exits with code 1 instead of throwing uncaught.B2 —
codex-pluginduplicate TOML block:--forceno longer appends a second[mcp_servers.tages]header. A new exportedstripTagesBlock()helper removes any existing tages tables in-place before the new block is written. USAGE text updated to reflect the replace-in-place behaviour.W2 —
--limitapplied to queries:tages drift --limitnow passes.limit()to both Supabase queries (field_changesandtool_call_log) in addition to display truncation; the value is validated as a positive integer.W3 —
agents-mdfederation header: whenagents-md-owners.jsonis configured butmemories.team_iddoesn't exist, the generated AGENTS.md opens with a machine-readable<!-- TAGES_FEDERATION_NOTE: ... -->HTML comment so agents and reviewers see the limitation immediately.Q1 — dynamic oracle SHA:
eval/longmemeval/src/dataset.tsnow exportscomputeOracleSha()that hashes the on-disk oracle file at run time;run.tsreports the actual SHA instead of a hardcoded constant.CI widening:
.github/workflows/ci.ymlandpublish.ymlexpanded from--filter @tages/serverto-r(all packages).publish.ymlalso adds@tages/codex-pluginand@tages/gemini-pluginto the npm publish matrix.New tests (15 total): regression suite for
codex-plugin(9 tests, including round-trip strip+append guard), plus entry-point tests forcursor-plugin(3) andgemini-plugin(3). Pluginmain()calls guarded withimport.meta.urlcheck so packages are importable in tests.
2026-04-20
README hygiene: Stripped unreproducible benchmark claim from
## Benchmarkssection; corrected MCP tool, CLI command, test, and migration counts to match current codebase.Bet A — Memory Governance foundation: New
/governancemarketing page (draft,noindex). Migration0057_provenance_fields.sqladdssession_id,source_context, andtool_namecolumns tomemorieswith a GIN index and aget_memory_provenanceRPC.MemoryTypeScript type extended withsessionId,toolName, andsourceContext. Formal spec atdocs/provenance-model.md.Bet B — AGENTS.md native tooling: New
tages agents-md writeandtages agents-md auditCLI subcommands.writegenerates a canonical 6-section AGENTS.md from project memory.auditflags vagueness, missing sections, missing runnable commands, and absence of the three-tier Always/Ask/Never boundary pattern.Bet D — Cross-tool distribution: New
@tages/cursor-pluginpackage. Runningnpx @tages/cursor-plugininstalls Tages in Cursor by writing.cursor/mcp.json. Setup guide atdocs/cursor-setup.md.CI: New
.github/workflows/publish.ymltriggers onv*tag push to publish packages to npm (requiresNPM_TOKENrepo secret).Strategy documents:
analysis/directory lands with competitive analysis, trend scan, positioning brief, deep research execution doc, Monte Carlo pricing model, and research notes.PLAN.mdandREMAINING.mdadded at repo root.
Review fixes (post-White review)
supabase/migrations/0057_provenance_fields.sql: pinnedsearch_path = public, extensionsand added anis_project_member(auth.uid(), m.project_id)guard inside theget_memory_provenanceSECURITY DEFINER function so it cannot leak provenance across projects. (B1, Q1).github/workflows/publish.yml: publish@tages/cursor-pluginalongside@tages/shared,@tages/server,@tages/clionv*tag push. (W1)packages/cli/src/commands/agents-md.ts: replaced the unsupported\Zanchor inextractSectionwith a JS-correct end-of-string lookahead so last-section audit rules (missing-commands, missing-tech-versions) fire correctly. Regression test added. (W2)apps/dashboard/src/components/marketing/governance-page.tsx: correctedsession_idfield type in the Provenance model table fromtexttouuid. (S1)W3 (test count targets) was declined — counts reflect a verified test-run output, not a goal. S2 (control-flow warning) was deferred — no runtime impact.
2026-04-20 — Bet A governance foundation (Sprint A + B + C)
Pre-launch hygiene (Phase 0):
HOOK.mdand.semgrep-results/added to.gitignore. Closed migration 0042 git gap — file had been applied to prod on Apr 10 but never committed;supabase migration list --linkedconfirmed prod/local match.Sprint A — Differentiation foundation (Phase 3.1 + 3.2): New
@tages/codex-pluginpackage (TOML writer targeting~/.codex/config.toml,--dry-run, block detection). New@tages/gemini-pluginpackage (JSON merge into~/.gemini/settings.jsonwith preserved top-level keys). Newtages agents-md diffandtages agents-md federateCLI subcommands extending the Bet B foundation. White review fixes: gemini env-var placeholders, codex regex false-positive on[mcp_servers.tages.env]alone, diff negation-word boundary.Sprint B — LongMemEval harness scaffold (Phase 1.1): New
eval/longmemeval/directory with standalone TypeScript harness (not in pnpm workspace). RetainDB-pattern methodology documented with comparability caveat (Supermemory/RetainDB baselines on deprecated dataset). Pluggable memory backend:in-memorylexical floor +tages-clireal integration. Dry-run verified; real runs pendingOPENAI_API_KEY+ sandboxTAGES_EVAL_PROJECT.Sprint C —
tages driftv1 (Phase 3.3): Newtages driftCLI command (experimental). Semantic drift uses a real instability metric (1 − 1/distinct_values over field_changes); 10 unit tests cover zero/two/three-value instability, session+agent reporting, whitespace normalization, topK. Coordination drift stubbed (not_implemented) — blocked onmemories.team_idcolumn. Behavioral drift stubbed (insufficient_data/not_implemented) — tool_call_log has raw data, v2 calibration pending design partners.--json,--since,--agent,--limitflags.Chore: Plugin packages (cursor, codex, gemini) now use
--passWithNoTestssopnpm -r testdoes not fail at the root.
2026-04-19
Fixed GitHub OAuth login redirecting to the marketing homepage instead of
/app/projectsafter callback. Root cause:SameSite=Strictcookies are withheld by the browser on cross-site top-level navigations (i.e. GitHub's redirect back to/auth/callback?code=...), so the PKCE verifier and refreshed session cookies never reached the server. Reverted to Supabase's defaultSameSite=Lax, which is the correct setting for SSR auth cookies.HttpOnly + Secure + Laxstill blocks CSRF on state-changing requests.Invite-flow overhaul (Team plan): Team invites now send a one-time Supabase magic-link email rather than inserting directly into
team_members. Pending invites expire after 30 days; expired rows are skipped byaccept_pending_invites. Owners and admins can revoke a pending invite before it is accepted (new DELETE RLS policy). The invite role dropdown enforces RBAC at both the UI and server layers — owners can invite admin or member, admins can only invite member. Dashboard sign-ins now callaccept_pending_invitesvia the OAuth callback, so web-only sign-ups resolve dangling invites automatically (previously only the MCP server did this on startup). Requires migrations0055_invite_expiry.sqland0056_invite_delete_policy.sqlapplied to your Supabase project.
2026-04-17
Split literal Stripe-style test fixtures in
safety.test.tsandobserve.test.tsvia string concatenation so GitHub secret scanning no longer flags them. The fixtures are intentional test inputs, not real credentials.
License
Named After
Tages — the Etruscan divine child who appeared from a furrow in the earth and dictated sacred knowledge to scribes before vanishing. The knowledge persisted long after the source was gone.
This server cannot be installed
Maintenance
Resources
Unclaimed servers have limited discoverability.
Looking for Admin?
If you are the server author, to access and configure the admin panel.
Related MCP Servers
- AlicenseNot gradedqualityCmaintenanceA local, persistent memory system for AI coding assistants that stores decisions, patterns, and session context via MCP tools. It enables cross-session memory management using SQLite and optional vector search without external dependencies or cloud storage.3763MIT
- AlicenseAqualityAmaintenanceMCP-native, local-first memory for coding agents that turns real sessions into reusable decisions, gotchas, and domain knowledge.176MIT
- AlicenseNot gradedqualityCmaintenancePersistent memory for AI coding agents. Enables agents to save and recall decisions, patterns, bugs, and context across sessions via an MCP server with local SQLite storage.452MIT
- AlicenseNot gradedqualityAmaintenancePersistent, local memory for AI coding agents that learns how you work, not just what you said. Supports Claude Code, Codex CLI, Cursor, and any MCP client.63MIT
Related MCP Connectors
Persistent memory and cross-session learning for AI coding assistants (hosted remote MCP).
Shared, governed long-term memory for AI agents across tools and sessions via MCP and REST.
Your memory, everywhere AI goes. Build knowledge once, access it via MCP anywhere.
Latest Blog Posts
- Who's Calling? MCP Hosts Are an Identity Blind Spot (And the Spec Knows It)By Om-Shree-0709 on .mcpAgent IdentityOAuth 2.1
- Your AI Chatbot Just Exposed Your CEO's Salary to an InternBy Om-Shree-0709 on .Agent IdentityMCP SecurityOAuth Delegation
- Why MCP Servers Need Execution Sandboxing (And Why Your Current Stack Isn't Enough)By Om-Shree-0709 on .Agentic AiPrompt InjectionWebAssembly
MCP directory API
We provide all the information about MCP servers via our MCP API.
curl -X GET 'https://glama.ai/api/mcp/v1/servers/ryantlee25-droid/tages'
If you have feedback or need assistance with the MCP directory API, please join our Discord server