Compared, with dates
How Tugra compares
Stars, commits and capability columns all read 12 September 2026 · re-measurable with the commands at the end.
Including the places where the other projects are ahead of us. Every cell says whether it comes from the project's own documents or from a function we found in its code, because those are two different kinds of evidence, and a comparison written by a vendor is worth exactly as much as its verifiability.
Read this first
Tugra is the smallest project on this page: 0 GitHub stars against claude-mem's 93,727 and Mem0's 65,183. It has no embeddings, no graph and no server; it is Markdown files in a directory behind four MCP tools. If you want a memory that fills itself and finds things by meaning, pick one of the others. Keep reading only if what you need is a memory where every record names its source, carries a verification date and a shelf life, and refuses to invent on a topic it has no evidence for.
How to read a cell. Docs is what the project's documentation says. Code names the function we found producing that behaviour at the pinned commit, or says not found when we did not find one in the files we read. Not found is not proof of absence. Nothing was executed; this is a static reading of open code and public documents. Tugra's own row is held to the same notation.
The table
Star counts, licenses and last-commit dates were read from the GitHub API on 12 September 2026. The last-commit date is the committer date of the newest commit on the default branch. Capability columns were read the same day from each project's documentation and from the source files linked under the table. If we got something wrong about your project, write to us and we will correct the page and note the correction.
| Project | Stars | Last commit | License | Source required on every record | Verification date and shelf life | Stale records out of the default result | Boundary record that forbids invention | Secret pattern refused before write | Per-agent permission profile | Runs with no network | Human-readable record file |
|---|---|---|---|---|---|---|---|---|---|---|---|
| Tugra | 0 | 2026-09-12 | Apache-2.0 | YesCode: dogrula.ts requires the source list, and each entry needs a pointer and a YYYY-MM-DD date. A fact, decision or boundary without its own evidence fails gate K1 and is demoted to an observation. |
YesFrontmatter fields verified and shelf_life; a malformed shelf life is refused (sema.ts). Search ranks fresh above stale (arama.ts). |
PartlyRetired and rotten records are out of the default set. A stale record stays in, ranked below fresh ones and labelled stale with its age (arama.ts). |
YesBoundary is a record type (sinir, sema.ts). A proposed boundary is always quarantined until a human accepts it, whatever the proposing agent's trust (arama.ts). |
YesCode: olguOner() runs sirDeseniBul() over title, body and sources before anything is written; on a match nothing is written, not even the permission log, and only the pattern name is returned, never the match (arama.ts, sir.ts). |
Yes, when the store existsCode: yetki.ts keeps a record per agent with axes for touch, outbound, autonomy, persistence and spend ceiling, levels A0 to A5. Without a permission directory the vault runs in single-user mode and everyone can search. |
Yes, by constructionStdio transport (serveStdio, mcp.ts); the published modules import no http, net, dns or fetch; dependencies are the MCP SDK, yaml and zod. event_report appends a local line. Not measured with packet capture. |
YesOne Markdown file per record with YAML frontmatter (dosya.ts). |
| Mem0 OSS | 65,183 | 2026-09-11 | Apache-2.0 | NoDocs: metadata and scopes. Code: _create_memory() accepts a payload with no source (memory/main.py). |
Expiry onlyCode: expiration_date through _normalize_expiration_date(); no verification date. |
Yes, for expiredCode: search() and get_all() take show_expired=False; _payload_is_expired() filters. |
Nocustom_instructions exists; no dedicated boundary record. |
No_is_sensitive_field() is for telemetry and config; no pattern refusal on memory writes found. |
Scopesuser, agent and run scopes via _build_filters_and_metadata(); a scope, not an authorisation profile. |
NoModel and embedding providers, telemetry on by default (telemetry.py). |
NoVector store plus SQLite history. |
| Letta Code the older letta-ai/letta has 24,714 stars, last commit 2026-09-10, and its README points here | 3,314 | 2026-09-12 | Apache-2.0 | NoCode: validateMemoryFileFrontmatter() v2 requires name and description; no source field (memory-frontmatter.ts). |
NoNot found in the frontmatter validation. | Not found | Noread_only in the legacy format; no topic-ban record. |
Not foundDocs: secret values are hidden from context; no write-time pattern refusal found. | PartlyCode: createMemoryConfinementLauncherWithAvailability() confines the memory root of memory sub-agents; permission modes in docs. |
PartlyLocal file paths exist; zero network across the whole harness is not stated. | YesGit-tracked Markdown memory (validateMemoryTreeConstraints() processes .md). |
| Graphiti Zep's open layer; the hosted engine is separate | 30,831 | 2026-09-11 | Apache-2.0 | PartlyCode: EpisodicNode.source_description is required; EntityEdge.episodes=[] is allowed, so not every record (nodes.py, edges.py). |
Bi-temporalvalid_at, invalid_at, expired_at; not a verification date or a shelf life. |
Not by defaultSearchFilters time filters default to None; current-only results are not automatic. |
NoCustom ontology; no topic-ban semantics. | Not found | Scopesgroup_ids is a query scope, not an authenticated agent ACL. |
NoGraphiti.__init__() calls providers and telemetry; local LLM endpoints are supported. |
NoGraph database (Node.save()). |
| LangMem | 1,660 | 2026-09-09 | MIT | Nocreate_manage_memory_tool() works with default content; the schema is customisable (knowledge/tools.py). |
Backend's jobNo TTL on store.put() in LangMem itself. |
NoSearch delegates to the store; no staleness filter of its own. | NoCustom schema is possible; nothing built in. | Not foundNot in the manage_memory() write path. |
Per toolNamespaces and actions_permitted (create, update, delete) per tool; not an identity-based ACL. |
Backend-dependentExamples use remote models. | NoIn-memory or database store (BaseStore.put()). |
| Cognee | 30,656 | 2026-09-09 | Apache-2.0 | PartlyProvenanceEntry.source_document defaults to an empty string (ProvenanceEntry.py). |
Optionalvalid_from and valid_until are optional; no mandatory verification date. |
Not verifiedA link from valid_until to default search exclusion was not found. |
No | Nosanitize_value() truncates and normalises; no pattern refusal. |
Yes, dataset ACLcheck_permission_on_dataset(); depends on identity and permission configuration. |
NoDefault provider is OpenAI; local Ollama is possible; zero network not shown. | NoGraph, vector and relational databases. |
| Official MCP memory server numbers are the whole monorepo | 90,273 | 2026-09-03 | MIT in its README; the repository root says Apache-2.0 with remaining MIT contributions | NoEntitySchema is name, type and observations only (src/memory/index.ts). |
No | NosearchNodes() matches text and ignores age. |
NoFree-form observations. | NocreateEntities() and addObservations() go to saveGraph() without a filter. |
NoOne graph manager. | Yes, in its own pathStdioServerTransport and fs; no network call seen in the main path. Install and dependencies not tested. |
Yesmemory.jsonl. |
| claude-mem | 93,727 | 2026-09-12 | Apache-2.0 | Session onlyObservations are bound to a session (SessionStore.initializeSchema()); no independent source field. |
Nocreated_at only. |
NoOptional date filter in SessionSearch.searchObservations(); no default exclusion. |
No | Opt-in tag<private> tags are removed by stripTags(); you mark it, the tool does not detect it. |
FiltersPlatform and project filters; per-agent authorisation not stated. | PartlyLocal worker and SQLite; hosted options in some integrations; zero network not stated. | NoSQLite (bun:sqlite). |
Files and documents read, 12 September 2026
- Tugra at 4c20ce9:
src/dogrula.ts,src/sema.ts,src/arama.ts,src/sir.ts,src/yetki.ts,src/mcp.ts,src/dosya.ts,package.json. - Mem0 at c7ee362: memory/main.py, telemetry.py, README.
- Letta Code at 9a40d27: memory-frontmatter.ts, memory-constraints.ts, memory-confinement-launcher.ts, README.
- Graphiti at c035afb: nodes.py, edges.py, graphiti.py, search_filters.py, README.
- LangMem at 9d033b4: knowledge/tools.py, README.
- Cognee at c0d18c8: ProvenanceEntry.py, sanitization.py, check_permission_on_dataset.py; docs ACL.
- Official MCP memory server at d73f99e: src/memory/index.ts, memory README, root LICENSE.
- claude-mem at b11034b: SessionStore.ts, SessionSearch.ts, tag-stripping.ts; docs private tags.
Where they are ahead of us
- Retrieval. Mem0 combines semantic, BM25 and entity search; Graphiti walks a temporal graph with three retrieval modes; Cognee offers graph, vector and relational storage. Tugra's search is a token score, then freshness, then confidence. No embeddings, no graph.
- Automatic extraction. Mem0, claude-mem and Letta write memory for you as you work. Tugra accepts only a proposal that names its source. That is friction, and it is deliberate, and it is still friction.
- Adoption. 0 stars against 93,727, 65,183 and 30,831. No external contributors and no external production deployment we know of.
- Scale and multi-user. Tugra is files in a directory. There is no index server and no multi-user service; Cognee's dataset ACL model is broader than our per-agent profile.
- Bi-temporal modelling. Graphiti records when a fact was true and when the system learned it. Tugra records when a fact was last verified and how long that holds, which is a narrower question.
- No merge, no delete, no conflict detection. Two writers, two files. Retirement exists; erasure does not. Contradictory facts can sit side by side until a human says otherwise (README).
Where we are different
- Source is a gate, not a field. Gate K1 demotes an unsourced fact to an observation. Elsewhere on this page a source is optional metadata or a session id.
- Verification date and shelf life on disk.
verifiedandshelf_lifeare frontmatter fields you can read with any editor. Mem0's expiry is the nearest thing; Graphiti's bi-temporal fields answer a different question. - A boundary record. A topic with no evidence gets a record that says so, and that record forbids invention. We did not find an equivalent record type in the other seven.
- Secret refused before write. The only project on this page where we found a write-time pattern refusal. claude-mem's
<private>tag is the nearest, and you have to remember to use it. - No network by construction. Stdio, no HTTP import, three dependencies. The official MCP memory server shares this property.
- Human-readable. One Markdown file per record; Letta's Markdown memory and the official server's JSONL share this too.
Not in the table, and why
- Microsoft "Memoria". We found no Microsoft product or repository of that name; a Copilot Studio page with that title is a localisation of its Memory feature.
- Zep Cloud. Graphiti's README separates Zep's proprietary engine from the open code. We score only what we can read.
- The older letta-ai/letta. Its README routes current code to letta-code. Its 24,714 stars were not added to letta-code's.
Verify this page yourself
Do not take the numbers on trust. They take about a minute to gather.
# Stars, license and newest commit on the default branch
for r in dogrucanemek-alt/tugra mem0ai/mem0 letta-ai/letta-code getzep/graphiti \
langchain-ai/langmem topoteretes/cognee modelcontextprotocol/servers thedotmack/claude-mem; do
gh api "repos/$r" --jq '"\(.full_name) stars \(.stargazers_count) \(.license.spdx_id)"'
gh api "repos/$r/commits?per_page=1" --jq '.[0].commit.committer.date'
done
# Read the same file we read, at the same commit
gh api -H "Accept: application/vnd.github.raw+json" \
"repos/mem0ai/mem0/contents/mem0/memory/main.py?ref=c7ee362" | grep -n "show_expired"
# Try the refusal yourself: propose a fact with no source, then one with a key in it
npx tugra init
npx tugra
The second command, piped, is the MCP server. Point any MCP client at it and call fact_propose without a source: the answer names gate K1. Put a private key in the body: the answer names the pattern and writes nothing.