# xmemory Full LLM Context > Adaptive, schema-grounded memory for any AI-agent workflow, from flexible EAV-like fact capture to normalized, validated state. This file is generated from the public route registry, rendered marketing pages, documentation source, and YAML API/SDK references. Use `/llms.txt` for compact routing; use this file when a larger context window, offline index, or retrieval pipeline can benefit from fuller content. ## Product summary xmemory is a general-purpose memory engine for agentic AI systems, designed to support any workflow from day one. Agents keep a natural-language read/write interface while xmemory handles extraction, validation, deduplication, state updates, relations, provenance, and schema evolution. A memory instance can begin with a loose, EAV-like schema that captures facts flexibly, then move toward typed, normalized structures as the workflow becomes better understood and requires stronger guarantees. ## Why xmemory works across workflows - xmemory is designed to be the memory layer for any agent workflow from day one: personalisation, conversational memory, working memory, research, coding, operations, multi-agent coordination, and governed product state. - Schema is a control dial, not an adoption barrier. Start with a loose, EAV-like (entity-attribute-value) schema for broad and flexible fact capture while the domain is still emerging; move toward more typed, relational, and normalized structures when more precision, consistency, governance, or query power is valuable. - Agents and users do not need to hand-design a mature schema before getting value. xmemory can generate a useful starting schema from a workflow description, existing systems or sample data, and its evolution engine can adapt that schema as real read and write patterns reveal new requirements. - Text, vector, and Markdown memory can support thematic recall and fast prototypes. xmemory can start with comparable flexibility through a loose schema while also providing a direct path to deduplicated facts, current state, relations, aggregations, explicit unknowns, and stronger guarantees without replacing the memory layer later. - Graph memory connects entities, but a graph alone does not define which fact is current, validate writes, enforce required fields and uniqueness, mediate conflicts, or provide the full memory lifecycle. - MCP over a database exposes storage operations. It does not by itself extract facts from language or supply validation, deduplication, stateful updates, provenance, retries, schema evolution, and observable decisions; those responsibilities otherwise remain in prompts, wrappers, jobs, and application code. ## Evidence and qualifications - On xmemory's four-domain end-to-end memory benchmark, which includes updates, deletions, relation changes, aggregation, and negative/exclusion cases, xmemory reports 99.15% precision, 95.12% recall, and 97.10% F1; compared systems report 80.16%-87.24% F1. Treat these as results on that published benchmark, not a universal guarantee. - On the separate Splitwise-style application benchmark, xmemory reports 95.2% accuracy. The website's '2x+ fewer tokens' statement is a modeled read-heavy scenario using the assumptions shown on the Why page, not a general measured reduction. ## Operational guidance - Use `https://api.xmemory.ai` for REST and `https://mcp.xmemory.ai/` for MCP. - API access requires `Authorization: Bearer ` on every REST request; never expose API keys in public prompts, generated files, or client-side code. - A memory instance is scoped to one adaptable schema inside a cluster. Start with a loose generated schema or a stricter domain model, create the instance, write natural-language facts, then read with natural-language questions. - Prefer `write_async` plus `write_status` for latency-sensitive workflows. Use synchronous `write` only when the next step must read the committed data immediately. - Never read immediately after an async write unless `write_status` is `completed`; use the synchronous write path for strict read-after-write consistency. - Choose read mode deliberately: `single-answer` for natural-language answers, `xresponse` for structured objects and relations, and `raw-tables` for raw SQL result sets. - Use `extract` to preview schema-aware extraction without persisting data, and `describe` to get agent-facing tool descriptions enriched with the instance's current schema. - For schema evolution, use dry-run migration previews or the suggestion flow: review, decide, then apply. Confirm destructive changes only when data loss is intended. - Choose deployment based on control requirements: Pure SaaS runs fully in the xmemory cloud; Zero-retention SaaS processes requests in xmemory's cloud while stored data remains in a customer-controlled RDS, Azure, or GCP database; On-premise runs the full stack from the Docker Compose package with customer-managed LLM keys. - Treat the API, SDK, CLI, and MCP references as authoritative for agent-callable operations. Product pages also describe broader console and platform capabilities such as access control, TTL policy, import/export, and manual editing; do not invent public endpoints for those capabilities when the references do not document one. ## REST API surface Base URL: `https://api.xmemory.ai` - **Writing:** `POST /instances/{instance_id}/write`, `POST /instances/{instance_id}/write_async`, `POST /instances/{instance_id}/write_status` - **Reading:** `POST /instances/{instance_id}/read` - **Extracting:** `POST /instances/{instance_id}/extract` - **Describing:** `GET /instances/{instance_id}/describe` - **Clusters:** `GET /clusters`, `GET /clusters/{cluster_id}` - **Schema generation:** `POST /clusters/{cluster_id}/instances/generate_schema` - **Instances:** `POST /clusters/{cluster_id}/instances`, `GET /instances`, `GET /instances/{instance_id}`, `GET /instances/{instance_id}/schema`, `PUT /instances/{instance_id}/schema`, `PUT /instances/{instance_id}`, `PATCH /instances/{instance_id}`, `DELETE /instances/{instance_id}` - **Schema evolution:** `POST /instances/{instance_id}/migrations/dry_run`, `GET /instances/{instance_id}/migrations`, `GET /instances/{instance_id}/migrations/{migration_id}` - **Suggestion engine:** `POST /instances/{instance_id}/suggestions/review`, `POST /instances/{instance_id}/suggestions/decide`, `POST /instances/{instance_id}/suggestions/apply`, `POST /instances/{instance_id}/pending-feedback/discard` - **Health check:** `GET /healthz` ## Public route index - [xmemory](https://xmemory.ai/): Schema-grounded, agent-first memory engine for reliable facts, relationships, and workflow state. Agent-readable source: https://xmemory.ai/index.html.md. - [xmemory product overview](https://xmemory.ai/product-overview/): Product capabilities, memory-harness responsibilities, control, portability, integrations, and console observability. Agent-readable source: https://xmemory.ai/product-overview/index.html.md. - [xmemory pricing and deployment](https://xmemory.ai/pricing-deployment/): Subscription plans, usage quotas, deployment options, security, and contact information. Agent-readable source: https://xmemory.ai/pricing-deployment/index.html.md. - [Why xmemory](https://xmemory.ai/why/): How schema-grounded memory differs from text and Markdown memory, graph memory, and MCP over a database, with benchmark evidence. Agent-readable source: https://xmemory.ai/why/index.html.md. - [About xmemory](https://xmemory.ai/about/): xmemory's mission, team, company background, offices, and contact information. Agent-readable source: https://xmemory.ai/about/index.html.md. - [Live chat demo](https://xmemory.ai/chat-demo/): Interactive demo of the natural-language write/read memory loop. Agent-readable source: https://xmemory.ai/chat-demo/index.html.md. - [Privacy policy](https://xmemory.ai/privacy-policy.html): How xmemory collects, uses, stores, and protects personal data. - [Terms and conditions](https://xmemory.ai/terms-and-conditions.html): Terms governing access to and use of xmemory services. - [xmemory Blog](https://xmemory.ai/blog/): Posts on schema-grounded memory, agent reliability, benchmarks, and schema evolution. Agent-readable source: https://xmemory.ai/blog/index.html.md. - [Schema Evolution: Engineer Your Memory, Don't Just Store It](https://xmemory.ai/schema-evolution/): Schema is the programming language for memory - an explicit, enforceable contract for exactly what to remember. xmemory infers a strong schema from your data or workflow for value from day one, then keeps it perfectly fit through a schema evolution engine, so your memory gets better the more you use it. Agent-readable source: https://xmemory.ai/schema-evolution/index.html.md. - [Chasing AI memory SOTA: Beating the Benchmark, Missing the Point](https://xmemory.ai/chasing-sota-in-ai-memory/): Why agentic memory benchmark numbers can be noisy, and what we should measure instead. Agent-readable source: https://xmemory.ai/chasing-sota-in-ai-memory/index.html.md. - [Should Agents Adapt to Systems - or Should Systems Adapt to Agents?](https://xmemory.ai/should-agents-adapt-to-systems-or-should-systems-adapt-to-agents/): As business logic moves into prompts, the boundary between agent reasoning and system-owned semantics needs to become much cleaner. Agent-readable source: https://xmemory.ai/should-agents-adapt-to-systems-or-should-systems-adapt-to-agents/index.html.md. - [Schema as the Core of Reliability in AI Memory](https://xmemory.ai/schema-as-the-core-of-reliability/): The core idea behind xmemory and why text-only memory misses many complex memory request types. Agent-readable source: https://xmemory.ai/schema-as-the-core-of-reliability/index.html.md. - [Install xmemory in your agent](https://xmemory.ai/install/): Install the xmemory plugin or connector in Claude Code, Codex, Claude Desktop, claude.ai, or ChatGPT, bind instances to a project, and see what each step asks of you. Agent-readable source: https://xmemory.ai/install/index.html.md. - [How xmemory works](https://xmemory.ai/integration-overview/): How xmemory works, how to define schema-based memory, and where to start integrating it into agentic workflows. Agent-readable source: https://xmemory.ai/integration-overview/index.html.md. - [MCP](https://xmemory.ai/mcp/): MCP integration guide. Agent-readable source: https://xmemory.ai/mcp/index.html.md. - [CLI](https://xmemory.ai/cli/): xmemcli command-line client and the agent skill. Agent-readable source: https://xmemory.ai/cli/index.html.md. - [API](https://xmemory.ai/api/): REST API reference. Agent-readable source: https://xmemory.ai/api/index.html.md. - [Python](https://xmemory.ai/python/): Python SDK reference. Agent-readable source: https://xmemory.ai/python/index.html.md. - [TypeScript](https://xmemory.ai/typescript/): TypeScript SDK reference. Agent-readable source: https://xmemory.ai/typescript/index.html.md. - [Pydantic AI](https://xmemory.ai/pydantic/): Pydantic AI integration example. Agent-readable source: https://xmemory.ai/pydantic/index.html.md. - [Google ADK](https://xmemory.ai/google-adk/): Google ADK integration example. Agent-readable source: https://xmemory.ai/google-adk/index.html.md. - [Mastra AI](https://xmemory.ai/mastra-ai/): Mastra AI integration example. Agent-readable source: https://xmemory.ai/mastra-ai/index.html.md. - [n8n](https://xmemory.ai/n8n/): n8n integration example. Agent-readable source: https://xmemory.ai/n8n/index.html.md. - [LangChain](https://xmemory.ai/langchain/): LangChain integration example. Agent-readable source: https://xmemory.ai/langchain/index.html.md. - [Temporal](https://xmemory.ai/temporal/): The xmemory Temporal plugin — durable agent memory as replay-safe Temporal Activities, in Python and TypeScript. Agent-readable source: https://xmemory.ai/temporal/index.html.md. - [OpenAI Agents SDK](https://xmemory.ai/openai-agents/): OpenAI Agents SDK integration example. Agent-readable source: https://xmemory.ai/openai-agents/index.html.md. - [Claude Agent SDK](https://xmemory.ai/claude-agent-sdk/): Claude Agent SDK and Messages API integration example. Agent-readable source: https://xmemory.ai/claude-agent-sdk/index.html.md. - [Codex](https://xmemory.ai/codex/): OpenAI Codex CLI integration example. Agent-readable source: https://xmemory.ai/codex/index.html.md. - [Claude](https://xmemory.ai/claude/): Connect xmemory to Claude via MCP. Agent-readable source: https://xmemory.ai/claude/index.html.md. - [ChatGPT](https://xmemory.ai/chatgpt/): Connect xmemory to ChatGPT and Codex via MCP. Agent-readable source: https://xmemory.ai/chatgpt/index.html.md. ## Source documents ### xmemory Canonical URL: https://xmemory.ai/ Markdown URL: https://xmemory.ai/index.html.md #### Schema-grounded agent-first memory engine Let agents read and write in natural language while xmemory turns facts, relationships, and workflow state into validated, queryable memory. [Get started](#try-it-out) #### Why xmemory is different ##### The problem Text-based memory, vector RAG, and Graph RAG can retrieve context, but knowledge is still inferred from text and correctness is undefined. ##### The idea Schema is the programming language that turns messy recall into reliable memory, giving agents an explicit, short feedback loop at write time. ##### The solution xmemory gives agents a simple text interface while isolating the hard work of correctness: explicit records, conflicts, and duplicates. [Read more](https://xmemory.ai/why/) #### Key use cases Three places where agent memory stops being a prompt trick and starts becoming product infrastructure. [ ##### User retention and personalisation Build long-lasting agent relationships with compact user profiles that capture specific facts over time. xmemory keeps recall precise and fast. See demo](https://xmemory.ai/chat-demo/) ##### Working memory for agents Give agents reliable state for execution plans, task progress, long research findings, and accumulated tool knowledge across multi-agent and long-running workflows. ##### Governed agentic workflows Bring observability and governance to regulated environments with schema-backed RBAC, lineage, debugging, and testing for every memory read and write. #### Measurements ##### Measured against real memory failure modes We evaluate whether systems can store, update, deduplicate, and retrieve facts and relationships reliably, not just whether they can recall similar text.\* xmemory97.10% Mem0 (no graph) 87.24% Cognee86.18% Mem0 (graph) 86.07% Supermemory80.49% Zep80.16% \* Read more about measurement methodology and open benchmarks in our [white paper](https://arxiv.org/abs/2604.27906). ##### Token consumption Lower token use than text-based memory by optimising reads. 2x+ fewer tokens Assuming 10 reads per write, 10 write tokens per 5 read tokens for xmemory, and 5 write tokens per 12 read tokens for typical text-based storage architecture. ##### Integrate into your stack [](https://xmemory.ai/n8n/) [](https://xmemory.ai/langchain/) [](https://xmemory.ai/google-adk/) [](https://xmemory.ai/pydantic/) [](https://xmemory.ai/mastra-ai/) [](https://xmemory.ai/openai-agents/) [](https://xmemory.ai/claude-agent-sdk/) [](https://xmemory.ai/codex/) #### Quickstart Dear , please read the [integration documentation](https://xmemory.ai/integration-overview/) and integrate xmemory into my project. I want to use xmemory whenever they need to store information related to their context, execution steps, or tool usage. They should create memory schemas dynamically when needed for a task, or use schemas that I will explicitly define. #### Our blog June 26, 2026 ##### [Schema Evolution: Engineer Your Memory, Don't Just Store It](https://xmemory.ai/schema-evolution/) Schema is the programming language for memory - easy to create, and kept perfectly fit by a schema evolution engine. May 7, 2026 ##### [Chasing AI memory SOTA: Beating the Benchmark, Missing the Point](https://xmemory.ai/chasing-sota-in-ai-memory/) Why benchmark leadership can be misleading for AI memory, and what reliability metrics should measure instead. March 27, 2026 ##### [Should Agents Adapt to Systems - or Should Systems Adapt to Agents?](https://xmemory.ai/should-agents-adapt-to-systems-or-should-systems-adapt-to-agents/) As business logic moves into prompts, the boundary between agent reasoning and system-owned semantics needs to become much cleaner. March 18, 2026 ##### [Schema as the Core of Reliability in AI Memory](https://xmemory.ai/schema-as-the-core-of-reliability/) The core idea behind xmemory and why text-only memory misses many complex memory request types. [View all posts →](https://xmemory.ai/schema-evolution/) #### Resources > Image: Preview of the xmemory white paper xmemory white paper ##### From Unstructured Recall to Schema-Grounded Memory [Read](https://arxiv.org/abs/2604.27906) Featuring xmemory > Image: Preview of the Bessemer Venture Partners article AI Infrastructure Roadmap: Five frontiers for 2026 Bessemer Venture Partners ##### AI Infrastructure Roadmap: Five frontiers for 2026 [Read](https://www.bvp.com/atlas/ai-infrastructure-roadmap-five-frontiers-for-2026) Featuring xmemory > Image: Preview of the Andreessen Horowitz article Why We Need Continual Learning Andreessen Horowitz ##### Why We Need Continual Learning [Read](https://a16z.com/why-we-need-continual-learning/) #### FAQ How is xmemory different from MCP over Postgres? xmemory is not just a database exposed through MCP. It is a schema-based memory layer that lets agents read and write in natural language while xmemory owns the state-of-the-art harness that is otherwise fragile and spread across prompts, wrappers, and workflow code: - Schema extraction and mapping - Validation and type enforcement - Deduplication and stateful updates - Relations and queryable state - Provenance and observability - Schema creation and evolution - Async write queues to unlock agentic workflows while keeping latency low MCP can expose tools. xmemory is meant to make the memory behavior itself agent-native and reliable. For the deeper architectural argument, see [Should Agents Adapt to Systems - or Should Systems Adapt to Agents?](https://xmemory.ai/should-agents-adapt-to-systems-or-should-systems-adapt-to-agents/). What if I don’t have a schema yet? You do not need to hand-design a perfect schema first. Schema is a control dial, not a gate: - You can start with loose schemas, then move toward normalized, structured data as you learn more about the use case. - xmemory can infer a useful starting schema from historic data or a simple workflow description, so it can deliver value from day one. - The system can analyze reads and suggest schema evolution, helping memory get better and better over time. See [How xmemory works](https://xmemory.ai/integration-overview/) for the full flow. Why not just use text memory or vector memory? Text and vector memory are useful for recall, but reliable AI systems need short, explicit feedback loops. Agents need a programming language that tells them whether an update is correct, which data is present, and how it is represented without relying on adjacent context. That language is schema. It enforces correctness, reduces knowledge inference at read time, and prevents bloating, drift, and corruption, especially in long-running workflows or personalisation over longer periods. For the fuller argument, read [Schema as the Core of Reliability in AI Memory](https://xmemory.ai/schema-as-the-core-of-reliability/). What is the place for xmemory in my stack? xmemory is external storage that agents use like a database or file store to save and retrieve context. Agents can create memory instances themselves, define schemas for a task on the fly, or use fixed instances for longer-lived context. It can reliably replace an agentic system of record, or run as a sidecar validation engine with periodic sync to the system of record, so people can review updates before they merge. The simple rule is: whenever agents need to save and retrieve text information, that layer can be xmemory. You can jump straight to the [integration guides](https://xmemory.ai/integration-overview/#integration-guides) in [How xmemory works](https://xmemory.ai/integration-overview/). #### Try it out Tell us about your workflow and we’ll help you choose the right integration path. ### xmemory product overview Canonical URL: https://xmemory.ai/product-overview/ Markdown URL: https://xmemory.ai/product-overview/index.html.md #### Isolating your agents from memory complexity. Agents write and read in plain text through MCP, CLI, APIs, and pre-built integrations. xmemory keeps the underlying data clean, structured, and queryable, while giving agents a short, explicit feedback loop when something needs correction. > Image: Diagram showing agents using a natural language read and write interface through MCP, API, CLI, and libraries into the xmemory engine and schema-grounded memory #### Focused memory starts with schema xmemory is a memory engine for saving and retrieving the facts that matter to your workflow. We do not believe useful memory means saving everything. We believe memory should be focused on your product, vertical, and agent task. ##### Not a transcript dump Agents see too much context to remember all of it. xmemory is for the durable facts your product needs later, not every intermediate message. ##### Schema defines focus Schema is a programming language for memory. It tells the engine which objects, facts, and relationships matter for your workflow. ##### Vertical by design Support, sales, coding, research, and operations need different memory. Schema makes that domain model explicit instead of hidden in prompts. #### The memory harness xmemory covers Every memory-related workflow needs these capabilities somewhere. Sometimes they are explicit services, sometimes they hide inside prompts, cleanup jobs, and side workflows. xmemory takes this complexity away so developers can focus on product logic and business value. 01 ##### Data extraction & Schema mapping Turn natural-language writes into facts that match your memory model. 02 ##### Validation & Type enforcement Keep stored memory aligned with expected fields, types, and constraints. 03 ##### Deduplication & Stateful updates Update the current state instead of accumulating conflicting notes. 04 ##### Relations & Queryable state Store facts as connected state that can answer direct product questions. 05 ##### Provenance & Observability See where memory came from and how reads and writes changed it. 06 ##### Governance Control access, review sensitive memory, and keep humans in the loop. 07 ##### Schema creation & Evolution Create schemas from workflows and adapt them as the product surface changes. 08 ##### Async write queue Let agents keep moving while memory writes are processed reliably. #### Control without the maintenance burden In-house memory feels like control until every stale fact, schema change, conflict rule, and migration becomes yours to maintain. xmemory handles the infrastructure while product-critical choices stay explicit. 01 ##### Schema defines the boundary Choose which objects, fields, and relations can be stored. Field and object descriptions give extraction your product semantics instead of leaving them hidden in prompts. 02 ##### Structured and natural-language APIs Write from messy context, explicit fields, or both. Scope reads and writes when your product knows the shape, while keeping flexible memory for everything less structured. 03 ##### Freshness and conflict policy Configure what counts as non-conflicting state, how facts should update, and when memories expire with TTL, so stale or contradictory memory does not become manual maintenance. 04 ##### Import, export, and leave cleanly Bring existing memory into xmemory, inspect what is stored, and export it when needed. Your memory stays a product asset, not a vendor trap. #### Agent-native connectivity and integrations Connect xmemory where your agents already work: through MCP tools, CLI flows, direct APIs, language SDKs, or pre-built integrations for common agent frameworks and automation stacks. [MCP](https://xmemory.ai/mcp/) [CLI](https://xmemory.ai/cli/) [API](https://xmemory.ai/api/) [Python](https://xmemory.ai/python/) [TypeScript](https://xmemory.ai/typescript/) [Pydantic AI](https://xmemory.ai/pydantic/) [Google ADK](https://xmemory.ai/google-adk/) [Mastra AI](https://xmemory.ai/mastra-ai/) [n8n](https://xmemory.ai/n8n/) [LangChain](https://xmemory.ai/langchain/) [OpenAI Agents SDK](https://xmemory.ai/openai-agents/) [Claude Agent SDK](https://xmemory.ai/claude-agent-sdk/) [Claude](https://xmemory.ai/mcp/) [ChatGPT](https://xmemory.ai/mcp/) [Codex](https://xmemory.ai/codex/) [Cursor](https://xmemory.ai/mcp/) [Windsurf](https://xmemory.ai/mcp/) #### Console to see inside memory > Image: xmemory Console home dashboard with operation charts, instance table, and API controls > Image: xmemory Console inspect view showing filtered memory operations > Image: xmemory Console operation detail showing extracted objects, relations, and input text ##### Operation history See and query every memory operation for testing, debugging, and incident resolution. ##### Table view Open the current data in tables for full transparency on what AI knows now. ##### Manual control Edit data manually when you need explicit control over the truth and policies agents rely on. ##### Access control Control API keys and permissions to protect data from unauthorised agentic corruption. ### xmemory pricing and deployment Canonical URL: https://xmemory.ai/pricing-deployment/ Markdown URL: https://xmemory.ai/pricing-deployment/index.html.md xmemory: Pricing & Deployment #### Clear pricing for projects of any size. | Plan details | | --- | | Monthly subscription | | Monthly quota, k tokensUsage is in xmemory tokens and varies by schema and request complexity. Typical reads use 20–60 tokens; writes use 40–120+, depending on data submitted. | | Daily quota (burst limit), k tokens | | AI Support | | Observability console | | Schema evolution | | Logs retention | | Memory instancesAn instance is a separate database with its own schema. It can hold multiple objects, but focused schemas for one use case and workflow improve quality and cost control. | | Human support | | Object-level RBAC Coming soon | | SSO Coming soon | | Plan details | [Free](/#try-it-out) | [Developer](/#try-it-out) | [Team](/#try-it-out) | [Business](/#try-it-out) | | --- | --- | --- | --- | --- | | Monthly subscription | $0 | $49 | $499 | Custom | | Monthly quota, k tokensUsage is in xmemory tokens and varies by schema and request complexity. Typical reads use 20–60 tokens; writes use 40–120+, depending on data submitted. | 70 | 200 | 1,000 | 3,000+ | | Daily quota (burst limit), k tokens | 35 | 70 | 200 | 1,000+ | | AI Support | ✓ | ✓ | ✓ | ✓ | | Observability console | ✓ | ✓ | ✓ | ✓ | | Schema evolution | – | – | ✓ | ✓ | | Logs retention | 1 day | 7 days | 30 days | up to 1 year | | Memory instancesAn instance is a separate database with its own schema. It can hold multiple objects, but focused schemas for one use case and workflow improve quality and cost control. | 5 | 50 | 100 | 1000+ | | Human support | – | – | Slack | Enterprise | | Object-level RBACComing soon | – | – | ✓ | ✓ | | SSOComing soon | – | – | – | ✓ | #### Deployment Start fast in our cloud, keep data in your database, or run the full stack under your control. Fastest path01 ##### Pure SaaS xmemory runs fully in our cloud. Best for POCs, independent developers, and teams that want quick adoption with high reliability and low support effort. CloudTier + Usage Pricing Data stays yours02 ##### Zero-retention SaaS Connect your RDS, Azure, or GCP database. xmemory processes requests in our cloud, while your stored data remains inside your controlled database environment. Private DBTier + Usage Pricing Full control03 ##### On-premise Run xmemory from our Docker Compose package in your own environment, using your own LLM keys and enterprise contract terms. Docker ComposeContact Sales #### Security We take security seriously and are moving quickly toward the certifications, compliance controls, and trust infrastructure our customers expect. SOC 2Type I complete · Type II pending GDPRUK GDPR ready Zero retentionOptions available ISO 27001In progress #### Contacts Please get in touch with any questions or enquiries. [General — info@xmemory.ai](mailto:info@xmemory.ai) [Sales — sales@xmemory.ai](mailto:sales@xmemory.ai) [Legal — legal@xmemory.ai](mailto:legal@xmemory.ai) ### Why xmemory Canonical URL: https://xmemory.ai/why/ Markdown URL: https://xmemory.ai/why/index.html.md #### Agent memory should not be your product’s weakest link. xmemory turns messy agent interactions into structured, validated, observable memory, taking care of the reliability work that usually gets scattered across prompts, tools, cleanup jobs, and application code. > Image: Radar chart comparing xmemory with knowledge graph, pure vector, and file-based memory across latency, cost efficiency, accuracy, consistency, observability, inspectability, governance, scalability, maintenance, and cross-agent use #### Why xmemory is different Without structure, ambiguity is not an edge case — it is the default. With structure, facts become addressable, updatable, queryable, and auditable. ##### Text-based memory Stores notes, summaries, and recalled context. ##### xmemory Stores facts, normalised and clean for complex requests Thematic recall — "What did we discuss?" Every read is another act of interpretation State is reconstructed from prose each time No deduplication — facts accumulate and conflict No provenance — changes are untraceable Ambiguity is the default, not the edge case Exact lookup — "What timeout did we set?" Latest state — "What is the current status?" Relational queries — "Which users affected after release X?" Explicit unknowns — "What facts are missing?" Aggregation — "How many incidents since Monday?" Constraints reject ambiguity instead of guessing #### Why not markdown files? Markdown files are excellent for prototyping, but they make long-term memory depend on prose, prompts, and luck. ##### Structure is local The model creates file structure in the moment. It can follow today’s prompt, but it is not a durable contract across runs, agents, or versions. ##### Reads get wider A simple question can require reading current notes, old summaries, related files, and compacted history before the agent can answer. ##### Updates rewrite prose Changing one fact means finding the right sentence, preserving context, avoiding duplication, and hoping nearby meaning stays intact. ##### Entities hide in text The same account, person, incident, or task can appear across many files. Unless the agent reads everything, connections disappear. File memory over time Read more files, infer more meaning, spend more tokens. Targeted memory call What timeout did we set for Acme? customer: Acmesetting: timeoutsource: incident #184 timeout = 30s Read the smallest reliable slice of state. #### Why not graph memory? Graphs are powerful for connecting knowledge, but agent memory also needs reliable writes, current state, and explicit semantics. ##### Edges are not truth A graph can connect related entities, but the workflow still needs to know which fact is current, validated, and safe to use. ##### Writes need contracts Memory writes need required fields, allowed values, uniqueness rules, and conflict handling, not only extracted nodes and relations. ##### Updates are ambiguous “Amy is no longer Kevin’s manager” should update current state, preserve history, and avoid leaving stale edges as live facts. ##### Retrieval is not lifecycle Real workflows need deletion, provenance, permissions, schema evolution, and observability around every memory operation. Graph memory Related edges are retrieved, but current truth is still inferred. Validated state update Amy is no longer Kevin’s manager. Priya is. UPDATE employment\_relation SET valid\_to \= now() WHERE employee \= 'Kevin' AND manager \= 'Amy';INSERT INTO employment\_relation (employee, manager, valid\_from, source) VALUES ('Kevin', 'Priya', now(), trace\_id);SELECT manager FROM current\_employment WHERE employee \= 'Kevin'; manager = Priya Relations become explicit state with history and constraints. #### Why not MCP over Database? MCP is a great way to expose tools and data, but it does not turn messy agent memory into reliable state by itself. ##### Tools are not semantics MCP can expose database operations, but the agent still has to decide what the user meant and which state should change. ##### Schema is not mapping Tables define storage. They do not extract facts from language, map them safely, or explain missing and ambiguous fields. ##### Writes need mediation Real memory writes need validation, deduplication, conflict handling, provenance, retries, and observable decisions. ##### Reliability gets scattered Without a memory layer, reliability work spreads across prompts, wrappers, app code, cleanup jobs, and eval scripts. MCP over Database The database is reachable, but the memory harness still has to be designed and maintained inside your custom tooling. xmemory over Database xmemory is the engine between natural-language agents and reliable structured storage. #### Measurements Structure improves memory quality. xmemory extracts structured data better than one-shot structured output. ##### Measured against real memory failure modes We evaluate whether systems can store, update, deduplicate, and retrieve facts and relationships reliably, not just whether they can recall similar text.\* xmemory97.10% Mem0 (no graph) 87.24% Cognee86.18% Mem0 (graph) 86.07% Supermemory80.49% Zep80.16% ##### Why not just ask a frontier model for JSON? Main markers show extraction quality for single-object schemas. Range spans complex relational schemas to single-field F1 — and xmemory stays ahead of one-shot frontier model APIs.\* xmemory62.67%90.42%97.53% Gemini 3.1 Pro preview61.67%89.24%97.17% OpenAI GPT-5.5 with high reasoning effort44.00%83.98%96.39% Anthropic Sonnet 4.642.00%83.56%96.40% Anthropic Opus 4.742.67%83.31%96.56% OpenAI GPT-5.538.00%81.02%96.01% \* Read more about measurement methodology and open benchmarks in our [white paper](https://arxiv.org/abs/2604.27906). ##### Token consumption Lower token use than text-based memory by optimising reads. 2x+ fewer tokens Assuming 10 reads per write, 10 write tokens per 5 read tokens for xmemory, and 5 write tokens per 12 read tokens for typical text-based storage architecture. ### Live chat demo Canonical URL: https://xmemory.ai/chat-demo/ Markdown URL: https://xmemory.ai/chat-demo/index.html.md Chat Memory Demo #### Chat memory demo True personalisation requires strong factual memory. Scroll through 1 year of chats. Click saved, retrieved, or updated memory tags to trace them in the timeline. Select any memory on the timeline to inspect its usage on the right. ### xmemory Blog Canonical URL: https://xmemory.ai/blog/ Markdown URL: https://xmemory.ai/blog/index.html.md Browse the latest posts: - [Schema Evolution: Engineer Your Memory, Don't Just Store It](https://xmemory.ai/schema-evolution/) - [Chasing AI memory SOTA: Beating the Benchmark, Missing the Point](https://xmemory.ai/chasing-sota-in-ai-memory/) - [Should Agents Adapt to Systems - or Should Systems Adapt to Agents?](https://xmemory.ai/should-agents-adapt-to-systems-or-should-systems-adapt-to-agents/) - [Schema as the Core of Reliability in AI Memory](https://xmemory.ai/schema-as-the-core-of-reliability/) ### Schema Evolution: Engineer Your Memory, Don't Just Store It Canonical URL: https://xmemory.ai/schema-evolution/ Markdown URL: https://xmemory.ai/schema-evolution/index.html.md xmemory is built on schemas. It is not a drop-in, remember-everything box - and that is exactly where its power comes from. [Structure is what makes memory reliable](https://xmemory.ai/schema-as-the-core-of-reliability/): a schema is an explicit, enforceable contract for what your memory must remember, what it may ignore, and which values it must never invent. That is what turns memory into a system of record rather than a guess - and it is why xmemory reaches [97.10% end-to-end F1 at 99.15% precision, against general-purpose baselines that land between 80 and 87%](https://arxiv.org/abs/2604.27906). The schema is the best part of xmemory. This post is about the two things that make it effortless: how easy it is to start one, and how xmemory keeps it perfectly fit to your work - automatically - through schema evolution. #### Schema is your control dial A schema isn't overhead you carry; it's a dial you turn. At one end sits a loose, almost schema-less shape - an entity-attribute-value (EAV) store that captures facts flexibly while you are still learning a domain. At the other sits a fully normalized, third-normal-form (3NF) design: typed objects, fields, and relations, where every fact is precise, deduplicated, and directly queryable. You choose where on that dial to sit, and you can move along it whenever you like - because more structure buys more reliability. xmemory's results show memory quality rising steadily [as structure increases](https://arxiv.org/abs/2604.27906). > Image: The schema control dial: a loose EAV CustomerAttribute store on the left, a single denormalized CustomerMemory object in the middle, and a fully normalized 3NF design on the right with Customer, Company, CustomerObjection, CustomerNextStep, and CustomerPreference objects joined by relations - from loose schemas (EAV) to strict schemas (3NF) and everything in between. And you don't hand-write any of it. Describe your use case in a guided chat and xmemory drafts the schema for you - objects, fields, and relations you can review and adjust on the spot - or let your own agent build it straight from the CLI. Either way it is inferred from what you already have: a narrow projection of your systems of record (CRM, ERP, ticketing, a warehouse), a sample of your historical data, or a plain description of the questions you want answered. Every path produces a working contract that delivers value from day one. Best of all, you don't maintain it. Keeping a schema in step with a changing business is ongoing work, so xmemory does it for you - not the other way around. That is what the schema evolution engine is for. #### How schema evolution works Evolution keeps your schema fit by learning from how the memory is actually used. **Signals from the read and write paths.** The read path is the richest source of truth about what your schema should be. Every question your agents ask is direct evidence of what they need, and when a question reaches for something the schema doesn't yet hold, that gap is precise signal - captured for free, without ever slowing the read down. The write path adds its own: recurring ambiguity, or values that don't quite fit, point to where the contract can be sharpened. Together they give xmemory a continuous, low-cost picture of exactly how your schema should grow. > Image: The schema evolution loop: the write path extracts raw data into the schema and the read path serves retrieval, while read patterns and replayed raw data feed an evolution engine that updates the schema. This is the loop at the heart of evolution - the same one we lay out in [the xmemory whitepaper](https://arxiv.org/abs/2604.27906): usage flows into a schema engine, which turns observed demand into concrete schema changes on the write path, closing the loop between how memory is used and how it is structured. **A clear loop, with you in control.** Recurring signals are consolidated into a few concrete, deduplicated proposals - a field to add, an object or relation the workload now needs - each carrying the evidence behind it: the real queries that asked for it, and how often. > Image: xmemory's review screen: reads that came up short, and the changes proposed from them - a new Task object, a due_date field, a TaskOwner relation to Person - each shown with the exact queries that needed it. You review and decide - accept, refine, or defer, one change at a time. You can adjust a proposal before it lands, and rejecting one teaches the engine to stop suggesting it - and every proposal can be checked against the very questions it is meant to answer, so what lands is verified, not guessed. Each accepted change lands as a versioned migration: atomic, data-preserving (a rename is carried out as a rename, so every value is kept), and reversible. In the after-update diff, only what changed lights up green - the newly added object standing out against the existing schema it joins: > Image: An applied migration in xmemory's console, YAML view: the newly added Task object highlighted green line by line, while the existing schema above it stays plain - the change shown as a diff against the prior version. **Data replay.** Because interpretation happens once, on the write path, a migration can reach backward as well as forward. When the schema gains a field, object, or relation, xmemory replays your history through the same verified extraction and backfills the new structure for data you already hold - so a value that was always present becomes queryable everywhere, not just for new writes. And the replay is precise: rather than re-importing everything, it revisits only the history the change actually touches, so the cost of completing the past scales with the size of the change, not the size of your memory. Where a value genuinely was never there, it is recorded as an explicit unknown rather than invented - your memory stays honest about what it does and doesn't know. **Autonomous evolution.** Steering doesn't have to be hands-on. Grant an agent the right permissions and it can run the loop itself - weighing the evidence behind each proposal and applying the changes that hold up - a genuine path to self-evolving memory, with you in oversight at all times. **Lineage and governance, for free.** Every change is a versioned migration, so your schema carries its whole history: you can see exactly what changed, when, who - or which agent - approved it, and the evidence that justified it, and reconstruct or roll back to any earlier version on demand. Because evolution moves through concrete, testable migrations rather than quiet prompt edits, there is no prompt drift - every improvement stays explicit and accountable. The audit trail isn't extra work you take on; it is a by-product of how evolution already works. > Image: xmemory's activity log: reads tagged schema gap, then the evolution's review, decide, and update events - each attributed to a key and timestamped, tracing a single schema change end to end. #### Why this is the future of memory **Schema is the programming language for memory.** Prose tells a model what happened; a schema tells memory what matters - precisely, declaratively, and once. It is a [declaration of intent](https://xmemory.ai/schema-as-the-core-of-reliability/) and an enforceable contract, not a soft instruction to be re-interpreted on every read. That is the difference between memory that sounds right and memory that is right. **It gives agents a short, explicit feedback loop.** Structured reads and writes answer an agent immediately and unambiguously: this field exists, this value is required, this query has an answer - or an explicit unknown. A tight, explicit loop is what makes agents dependable, and it is exactly what [systems should give agents instead of fuzzy glue](https://xmemory.ai/should-agents-adapt-to-systems-or-should-systems-adapt-to-agents/). **It compounds.** Because evolution is driven by what you actually read, your schema bends toward your use case and your vertical - it gets better the more you use it, tuned by real demand rather than guesswork. It only ever adds what evidence justifies and you approve, so it stays the minimal contract for your workload: your agents make the schema sharper without ever corrupting or bloating it. They stay on a leash; the schema stays clean. This is what it means to engineer your memory rather than just store it. You start in minutes, you get a system of record from day one, and it only improves - the same discipline that produces [95.2% accuracy on real application workloads](https://xmemory.ai/chasing-sota-in-ai-memory/). And it is memory you can answer for: you always know what it remembers, how that has changed, and why - data governance as a side effect of doing memory well. The schema is the core of xmemory, and with evolution it is a core that keeps getting stronger the more you use it. ### Chasing AI memory SOTA: Beating the Benchmark, Missing the Point Canonical URL: https://xmemory.ai/chasing-sota-in-ai-memory/ Markdown URL: https://xmemory.ai/chasing-sota-in-ai-memory/index.html.md 66.88%, 80.1%, 85%, 90.79%, 93%, 91.69% and [even 100%](https://x.com/bensig/status/2041236952998171118) — what do all these numbers have in common? They're all state-of-the-art **(SOTA)** scores on various agentic memory benchmarks published at different times. The truth is that these numbers are not directly comparable, and they don't necessarily reflect real-world performance. In this post, we'll explore why chasing SOTA in memory benchmarks can be misleading, and what we should actually care about when evaluating memory systems. #### The Measurement Problem: Recall Is Not Memory Most memory benchmarks are, at their core, **thematic recall** tests. A long conversation is stored. Later, a question probes whether the system can surface a relevant passage. Score well, and you are declared a capable memory agent. This framing is useful when the downstream task is *"did the user mention their dog's name?"* It is the wrong framing for the class of memory operations that actually break production systems: - **Single fact lookup** — **`What timeout did we set?`** - **Aggregation** — **`What's the average budget mentioned by our customers over the last month?`** - **State tracking** — **`What's the current status of the release?`** - **Relational queries** — **`Which users reported errors after version X?`** - **Negative / Exclusion queries** — **`Which courses are not starting next month?`** You can read more on the topic in our [white paper](https://arxiv.org/pdf/2604.27906) and [blogpost](https://xmemory.ai/schema-as-the-core-of-reliability/). These are not exotic edge cases. They are the bread and butter of any production-ready memory system. And virtually none of the standard benchmarks test them directly. The deeper issue is architectural. When memory is implemented as embedded text chunks and similarity search, thematic recall is the *natural* capability. The system retrieves passages that are semantically near the query. It does not retrieve the *current* value of a field. It does not answer "has this ever been explicitly set to null?" reliably. It drifts toward plausible completion rather than evidence-grounded recall. Benchmarks that score on thematic proximity reward exactly this architecture — and hide its failure modes. The chart below maps — qualitatively — how well popular benchmarks cover the memory functions that actually matter: what each one tests, what it quietly ignores, and why those blind spots pushed us to build our own datasets. We get into what those datasets look like later in the article. > Image: Radar chart showing approximate coverage of six memory functions — thematic recall, single fact lookup, aggregation, state tracking, relational queries, negative/exclusion — by LoCoMo, LongMemEval, and the xmemory end-to-end benchmark. > **Note:** The benchmarks are not wrong. They measure what they set out to measure. The gap is between what they measure and what production memory systems are asked to do. #### Existing benchmarks — a quick tour and overview of the gaps We will discuss two of the most popular benchmarks in the space: **LoCoMo** and **LongMemEval**. Both are important steps forward, but both also have significant gaps when it comes to predicting real-world performance. ##### LoCoMo **LoCoMo (Long-term Conversational Memory)** is probably the most cited benchmark in the memory space right now. It tests agents on long multi-session dialogues spanning up to 35 chat sessions, covering question types from single-hop retrieval to adversarial questions where the answer simply doesn't exist in the data. Sounds rigorous. And in some ways it is. The entire dataset is **synthetically generated** — virtual agents talking to each other, seeded with LLM-crafted personas and causal event graphs. Real conversations are messy, inconsistent, and full of ambiguity. LoCoMo conversations are… not. They're clean, coherent, and structurally organized in ways that real human speech rarely is. That's great for controlled evaluation. It's terrible for predicting whether your system will survive contact with actual users. Researchers at Letta [demonstrated](https://www.letta.com/blog/benchmarking-ai-agent-memory) that you can hit **74% accuracy on LoCoMo by literally dumping the conversation history into a flat file** — no sophisticated memory architecture, no vector database, no retrieval magic. Just a file. Which means that LoCoMo isn't quite testing what we think it's testing. And the problems go deeper than synthetic data. In the appendix of our white paper, we call out a few concrete LoCoMo examples. In `conv-30`, the following questions illustrate how fuzzy the category system is: ```json { "question": "What do Jon and Gina both have in common?", "answer": "They lost their jobs and decided to start their own businesses." }, { "question": "What is Gina's favorite style of dance?", "answer": "Contemporary" }, { "question": "What is Jon's favorite style of dance?", "answer": "Contemporary" } ``` The issue is apparent here: the label set is not comprehensive enough to capture all relevant dimensions of the task. They also show practical ambiguity, since equally plausible interpretations can map the same question to different label categories. Other items are worse because the label is not just ambiguous but incomplete. One question asks *"What might John's financial status be?"* but the evidence provided does not support a definitive factual answer — it only suggests an inference: ```json { "question": "What might John's financial status be?", "answer": "Middle-class or wealthy", "evidence": ["D5:5"] }, { "speaker": "John", "dia_id": "D5:5", "text": "It's definitely isn't, Maria. My kids have so much and others don't. We really need to do something about it." } ``` Similarly, the benchmark asks whether Caroline would likely own Dr. Seuss books based on a comment about collecting children's classics—again, inference rather than fact retrieval: ```json { "question": "Would Caroline likely have Dr. Seuss books on her bookshelf?", "answer": "Yes, since she collects classic children's books", "evidence": ["D6:9"] }, { "speaker": "Caroline", "dia_id": "D6:9", "text": "I've got lots of kids' books- classics, stories from different cultures, educational books, all of that." } ``` In both cases the benchmark is scoring a probabilistic inference, not retrieval of a stored fact. That is a reasoning task wearing a memory benchmark's name tag. Add the reported [label-error rate of about 6.4%](https://github.com/snap-research/locomo/issues/27#issuecomment-3921992262) (many of these errors we identified ourselves before deciding not to measure on LoCoMo), and you get a leaderboard where dataset ambiguity can matter almost as much as memory quality. The most charitable interpretation: LoCoMo measures retrieval from synthetic data. The least charitable: it measures who has the most patient prompt engineer. ##### LongMemEval LongMemEval (Wu et al., 2024, ICLR 2025) extends the evaluation surface meaningfully. Its 500 manually constructed questions cover five core abilities: information extraction, multi-session reasoning, temporal reasoning, **knowledge updates**, and abstention. The knowledge-update category is a genuine addition — it asks whether a system correctly supersedes stale information when a user revises a preference or corrects a prior statement. The abstention category is also notable. A system that always produces an answer will score poorly on questions about information that was never provided. This tests a failure mode that matters in production: hallucination of a plausible but ungrounded answer. LongMemEval's scope is still conversational and QA-oriented. Scores are measured against free-text answers judged by an LLM evaluator. This means the benchmark is well-suited for assessing whether the right *content* is retrieved and surfaced, but it does not directly evaluate whether a memory system can answer structured queries, enforce consistency across fields, aggregate across records, or handle relational constraints. A RAG pipeline and a schema-grounded system can achieve similar QA scores through very different mechanisms — and only one of those mechanisms will behave reliably as state complexity grows. There are label-related issues in LongMemEval datasets, too. There is a [public issues log](https://docs.google.com/spreadsheets/d/16cHPu2B4XhgC-VvolIoWNs8wwm0Zkbpgu8H9x-qhxWg/edit?gid=0#gid=0) — kudos to the team for transparency here. ##### Benchmark setup, system configuration and reproducibility When Emergence AI published their impressive LongMemEval results, a sharp-eyed developer noticed a [hardcoded retrieval limit](https://medium.com/asymptotic-spaghetti-integration/emergence-ai-broke-the-agent-memory-benchmark-i-tried-to-break-their-code-23b9751ded97) of `k=42` — 42 chunks returned per query, no matter what. Tweaking that single parameter to a dynamic value shifted results noticeably. Cost went up 42%. The number on the leaderboard moved. One hyperparameter. One number buried in a config file. Huge effect on the headline score. Now consider that every team running these benchmarks makes dozens of such choices: which LLM backbone, which embedding model, which chunking strategy, what temperature, what prompt template, how data gets ingested. None of this is standardized. The result is that comparing two systems on the same benchmark is, to use a technical term, *vibes-based*. LLM judges are part of this story too. In practice they are often unavoidable: once a benchmark asks open-ended questions over natural language, somebody has to decide whether an answer is correct, partially correct, or just plausible nonsense. But that doesn't make vague labels acceptable. If the gold answer itself requires commonsense inference, or if a question could reasonably belong to several categories, then the judge is no longer just checking correctness — it is compensating for holes in the benchmark design. LongMemEval runs into related issues here as well: it mixes retrieval with reasoning, and even its GitHub repo has reports of label problems similar to LoCoMo's. At that point, your score is partly a measure of memory quality and partly a measure of which judge model you used, how forgiving it was, and how much ambiguity it was asked to smooth over. #### So, what does X% SOTA on these benchmarks actually mean? Let's be honest about what the number represents. It means: *on this dataset, with these hyperparameters, using this LLM backbone, graded by this judge, implemented in a way that may or may not be reproducible, we got this score.* That's not nothing! It's a useful data point in a controlled experiment. What it is **not** is a claim that the memory problem is solved, or that users will have a better experience, or that your system will perform better than a competitor's in production. Moreover, results are often non-actionable from a product perspective, as it's not clear what memory functions in your system could be implemented better. The same number can mean wildly different things depending on: - Whether the team [optimized their prompts specifically for this dataset](https://github.com/mem0ai/mem0/blob/397f3414ee7c3bf5eadec20ef4a3dca0f1c1c567/evaluation/src/memzero/add.py#L16) - Whether they used a more powerful (and more expensive) backbone model - Whether the benchmark's synthetic nature flatters their architecture - Whether the evaluation methodology can actually be reproduced. > Image: Flow diagram showing how Dataset, Benchmark Setup, System, and LLM judge choices propagate into a SOTA number. **Chasing AI memory SOTA** is useful when it sharpens the product. It becomes noise when it is used as a substitute for product reliability. #### Can we do better? At xmemory, we care about benchmark performance, but we care more about what those numbers mean inside a product. A retrieval score on a narrow dataset is not the same thing as reliable memory in a multi-step workflow, in a customer-facing agent, or in a system that writes back into business logic. That is why we look at memory quality through two lenses: 1. Controlled evaluation, where we can compare systems fairly. 2. Product metrics, where we measure whether memory actually improves outcomes for users and operators. The second category matters more. Better memory should reduce task failure, improve personalisation, lower repetition, and increase confidence that the system is acting on the right facts. In our white paper, we propose a few concrete targeted measurements to test these outcomes. Our datasets target both write and read paths in a memory system — correctness of the answer depends not only on retrieval capabilities but also on the ability to store and update information accurately. We published all the datasets [here](https://github.com/xmemory-ai/datasets). ##### Extraction benchmark: where quality actually drops One useful example is our extraction benchmark on the modified insurance-claims dataset from the [Cleanlab structured-output benchmark](https://cleanlab.ai/blog/structured-output-benchmark/). We report three levels at once: field-level F1, object-level accuracy (all fields in an object must be correct), and output-level accuracy (the entire claim output must be correct). The key pattern is consistent across systems: field-level scores are high, object-level is lower, and output-level is lower still. In other words, small per-field errors compound quickly as task complexity increases. > Image: Structured output benchmark chart showing quality drop from field-level F1 to object accuracy to output accuracy across systems. This is exactly why leaderboard headlines based only on retrieval or per-field quality can be misleading: **product reliability depends on end-to-end correctness, not just local accuracy**. ##### End-to-end memory functions benchmark: where architecture differences become visible Our end-to-end benchmark in the white paper is designed to test memory functions directly across four independent domain datasets — corporate, education, medical, and finance — with writes that include updates, deletions, renames, and relation changes, followed by read queries covering single-fact lookup, state tracking, relational joins, aggregation, and negative-exclusion cases. Evaluation is micro-averaged at the fact level across all domains. On this setup, **xmemory** reaches **99.15% precision**, **95.12% recall**, and **97.10% F1**, while third-party systems land between **80.16% and 87.24% F1** (Zep 80.16%, Supermemory 80.49%, Mem0 graph 86.07%, Cognee 86.18%, Mem0 no graph 87.24%). The point is not that one stack wins one benchmark; it is that when you evaluate explicit state transitions and absence handling end-to-end, architecture differences become much harder to hide behind retrieval-only scores. Results obtained on this benchmark suggest missing or underdeveloped capabilities in a memory system, hence they are directly actionable for product development. > Image: Overall memory quality chart comparing systems on end-to-end memory benchmark performance. ##### Real-world dataset: application-level memory under workflow pressure To get closer to production behavior, the white paper also includes a real-world style **Splitwise** benchmark where each write is a natural-language expense event (who paid, how much, when, for whom) and reads require both retrieval and computation (balances, thresholds, participant-level rollups). On this task, **xmemory** reaches **95.2%** accuracy, compared with **73.75%** (Supermemory), **68.0%** (Cognee), **59.1%** (Mem0 graph), **54.9%** (Mem0 no graph), and **25.7%** (Zep). We also report that code-generated Markdown harnesses reached **12%** and **40%**, while customer-facing frontier-model application harnesses were around **92%**. The takeaway is practical: once memory quality is measured inside an actual workflow, stable structured state and mutation handling matter more than evaluation in an artificial environment. #### Conclusion: the number is real, the meaning is negotiable SOTA benchmark numbers in agentic memory are not lies. They're just... non-deterministic summaries of a specific experimental configuration, evaluated against noisy datasets, using LLM judges that introduce their own variance, on tasks that may not resemble what real users actually need. Which is a long way of saying: take them seriously as one data point, and not at all seriously as a ground truth. We're in a field that is measuring itself against itself. The real question isn't *"are we beating last week's leaderboard?"* — it's *"are we building something that makes people's work meaningfully better?"* That's harder to measure. It's also the only thing that matters. ### Should Agents Adapt to Systems - or Should Systems Adapt to Agents? Canonical URL: https://xmemory.ai/should-agents-adapt-to-systems-or-should-systems-adapt-to-agents/ Markdown URL: https://xmemory.ai/should-agents-adapt-to-systems-or-should-systems-adapt-to-agents/index.html.md A lot of **business logic** is moving out of code and into prompts. Things that used to be implemented as **explicit rules, branching logic, and application code** are now increasingly expressed in **instructions, examples, and model reasoning**. Routing, classification, extraction, prioritization, exception handling - more and more of it is being handed over to **LLMs**. At the other end of the stack, very little has changed. **Databases** still store state. **APIs** still expose rigid interfaces. **Infrastructure** still expects exact inputs. **Systems of record** still need consistency, predictability, and maintainability. #### Software is being pulled apart > Image: A diagram showing language-native agent logic on one side and structured systems on the other, with a transitional middle layer between them. On one side, logic is becoming **softer, more dynamic, and language-native**. On the other, the systems that matter most remain **structured, explicit, and unforgiving**. Between those two worlds, a large transitional layer is emerging: **MCP wrappers, vibe-coded APIs, agentic .md files, skills, plugins, and all kinds of custom glue** that help models operate real systems. This layer exists because the fit is still awkward. Models are not yet fully self-sufficient tool users, and most systems are not yet designed to be used by agents. Our guess is that much of this middle layer is **temporary**. Over time, it will be absorbed from both sides. One possibility is that models become good enough at **reasoning, tool use, and code generation** that they no longer need much custom guidance. They inspect interfaces, infer usage patterns, generate the missing wrappers for themselves, and operate systems with little more than credentials and permissions. The other possibility is that systems themselves become more **agent-native**. Instead of exposing only low-level primitives, they start taking responsibility for more of the context needed to use them correctly. #### MCP is only the beginning This is where **MCP** matters. MCP is pushing the ecosystem toward **standardization** in how models discover and call tools. That is an important step. But tool selection and invocation are only the beginning. What actually makes system interaction work is all the **system-specific context** that still sits outside the call itself. Storage is a good example. A model may know that it should call a database tool, but using that tool correctly requires much more than invocation. It requires knowledge of **schema, relationships, constraints, duplication rules, update semantics, and the existing state of the data**. Today, that context is often pushed into prompts or custom scaffolding around the database interface. Developers end up recreating storage behavior outside the storage system itself. > Image: A diagram showing agent communication flowing through schema knowledge and constraints into structured storage. #### System knowledge should belong to the system But this is not really **domain knowledge**. It is **system knowledge**. And system knowledge should be **owned by the system**. An agent should know what it wants to say and what actions it is allowed to take. The storage layer should handle the rest - **mapping intent onto structured writes, deciding whether something is an insert or an update, resolving duplicates and relations, using existing context, and dealing with ambiguity in a reliable way**. That is the deeper adaptation many systems still need to go through. Right now, a lot of **agent infrastructure** is really just a stopgap. We wrap systems in instructions, examples, helper functions, retrieval steps, and defensive glue so that agents can use them without breaking things. Some of that works. But it also spreads **system-specific behavior** across prompts and application code, making the whole stack harder to reason about, test, and maintain. A more durable approach is to move that intelligence **closer to the system itself**. If we want **agentic software** to be reliable, predictable, and maintainable, systems cannot remain passive endpoints behind thin wrappers. They need to expose **higher-level semantics** around what can be read, written, inferred, merged, and rejected. Not by becoming fuzzy, but by taking responsibility for the parts of interaction that are inherently system-specific. #### The next abstraction layer This would not be a new pattern in software. Storage interfaces have been evolving in this direction for decades. At the lowest level, developers had to think in terms of **raw hardware and specific memory locations**. Filesystems raised the abstraction, so the user no longer managed physical layout directly, but worked with **named files and directories**. Databases raised it further, letting applications work with **structured records, relations, indexes, and query languages** instead of file management. At each step, the interface improved by **absorbing complexity that previously sat with the caller**. The shift now happening with agents feels like the next step in that same progression. > Image: A diagram showing a higher-level interaction layer between agents and underlying storage, hardware, and services. #### A cleaner boundary This is the direction we are pursuing with **xmemory**. We are building a way for agents to interact with **structured, schema-based storage in natural language**, while the storage layer takes responsibility for **schema-aware extraction, relation resolution, deduplication, ambiguity handling, and the use of existing context**. The goal is not to make storage less structured. It is to make **natural language interaction with storage disciplined**. As more business logic moves into reasoning, systems will have to absorb more of the **operational burden** that agent developers currently carry in prompts and glue code. The long-term outcome is probably not an endless pile of wrappers between models and systems. It is a **cleaner boundary**. Models should reason. Systems should own their semantics. And much of the awkward middle layer should eventually disappear. ### Schema as the Core of Reliability in AI Memory Canonical URL: https://xmemory.ai/schema-as-the-core-of-reliability/ Markdown URL: https://xmemory.ai/schema-as-the-core-of-reliability/index.html.md Most AI memory systems are still built around one core assumption: memory is mainly *a retrieval problem*. Sometimes that takes the form of classic vector RAG - store text, embed it, retrieve similar chunks later. Sometimes it shows up in agentic systems that "remember" by endlessly appending observations, tool outputs, prior decisions, and working context into prompts, then compacting or summarizing that history when the context window fills up. And sometimes it appears in more advanced Graph RAG systems that add structure to retrieval by making relationships explicit. These are different approaches, and some are meaningfully better than others. But they still share the same underlying pattern: memory is stored largely as *unstructured or semi-structured context*, and **correctness is reconstructed later**. That works well for one class of memory requests: *thematic recall*. What were we discussing about this project? What direction were we leaning? What was the general context around this decision? For those questions, approximate retrieval is often enough. But operational memory is not just thematic recall. Once memory becomes an input to decisions, workflows, or automation, the requests change. Now the system is being asked: What timeout did we set? Which vendor did we choose? What is the latest status? Which users reported an issue after release X? Which entities are related to this incident? Which expected facts are missing? How many times has this happened in the last month? These are not all the same kind of query. Some are exact lookups. Some are state queries. Some are aggregations. Some are relational. Some depend on exclusions, constraints, or explicit unknowns. And that is the real dividing line: **different memory request types demand more than semantic similarity**. > Image: Examples of different memory query types that go beyond approximate retrieval. #### Search can recover context. Memory must support facts. The problem is not that retrieval is useless. It is that **retrieval alone is not enough**. If memory is stored as text, summaries, or compacted prompts, **every read becomes a fresh act of interpretation**. The model has to parse prose, infer what matters, resolve ambiguity, and reconstruct the facts from narrative. The same history can produce slightly different answers depending on what was retrieved, how it was compressed, or how the model interprets it in that moment. That is the *core weakness* of both vector-style memory and prompt-compacted memory. Agentic systems often appear different because they do not always rely on semantic search. Instead, they carry memory forward by repeatedly appending context and compressing it over time. But the underlying issue is the same. Memory is still living as text. Low-salience but high-importance details can be merged, blurred, or dropped during summarization. Then later, the model is expected to recover exact facts from an approximate narrative trace. Reliable memory cannot depend on *repeated reinterpretation*. It has to be able to retrieve facts as facts. #### Graph RAG is a real step forward - but not the endpoint It is also important to be fair about what already exists. The world is not just simple vector databases and chunk retrieval anymore. Graph RAG and related approaches add meaningful structure. They make relationships more explicit, improve multi-hop reasoning, reduce some ambiguity, and often outperform flat retrieval when questions depend on connected entities or linked context. That matters. But Graph RAG still usually improves how the system navigates memory more than how memory itself is governed. **In many graph-based systems, the nodes or leaves still resolve to text fragments, summaries, or embeddings**. The graph helps the system find related information, but it does not necessarily define what must be remembered, what counts as a valid fact, whether two records should be merged, whether a missing value is truly unknown, or whether a retrieved answer violates a constraint. In other words, it brings more structure to retrieval, but not enough structure to guarantee reliable factual memory. That is the gap schema fills. > Image: A schema example showing typed objects, relations, and structured memory design. #### Facts need shape If memory is going to answer operational questions reliably, **facts cannot remain buried inside text**. They need *structure*. A memory system has to know when two things are the same thing. It needs deduplication so the same customer, decision, incident, or requirement is not stored in slightly different forms across time. It needs types so a date is understood as a date, a state as a state, and an identifier as an identifier. It needs relations so facts connect to the entities and events they belong to. And it needs *constraints* so the system can reject ambiguity instead of smoothing over it with a plausible guess. This is what turns memory from stored language into usable knowledge. Without structure, ambiguity is not an edge case. It is the default. The same entity gets duplicated. The same fact gets phrased three ways. A stale value and a current value sit side by side in narrative history. The model has to guess which one matters now. **With structure, memory becomes much more precise. Facts become addressable. State can be updated explicitly. Queries can filter, count, compare, and join over remembered information instead of re-deriving it every time from prose.** That is why different memory request types matter so much. The moment you need *exactness, statefulness, aggregation, relations, or explicit absence,* memory needs more than retrieval. It needs shape. > Image: An incident-style example illustrating observability, provenance, and traceable memory state. #### True observability and control require structure This is also where observability becomes real. If memory is mainly chunks, summaries, or prompt history, you can inspect pieces of the process, but not the contract itself. You may see what was retrieved. You may see what the model answered. But you still *cannot clearly inspect what was stored, why it was stored, what changed, what failed validation, or where a specific answer actually came from.* **Structured memory changes that.** Once memory is schema-grounded, records can be typed, versioned, validated, diffed, and linked to provenance. Unknowns can be explicit. Conflicts can be surfaced. Invalid writes can fail loudly. State changes can be tracked as state changes instead of silently blending into narrative history. That is the *difference between black-box recall and inspectable infrastructure.* And it is not just about debugging. It is also about control. A reliable memory layer has to **make clear what should be remembered, what should be ignored, what requires confirmation, and what must never be guessed.** That is very difficult to do with unstructured memory alone. It becomes much more tractable when the system is operating against a schema. #### The best way to tell memory what matters is schema This leads to the practical question underneath all of it: how do you tell memory what and how to remember? One answer is *prompting.* And prompting does matter. In fact, a large part of today's agentic memory systems depends on prompts - prompts to summarize, prompts to compact context, prompts to decide what to retain, prompts to extract salient details, prompts to rehydrate compressed memory later. But prompts are soft instructions. They are situational. They depend on wording, context, and the model's interpretation in the moment. They are useful, but they are not strong enough to serve as the core contract for reliable memory. A schema is. **A schema is the strongest prompt memory can have** - because it does not merely suggest what matters, it defines it. It says which entities exist, which fields matter, which relations are valid, which values are allowed, what counts as missing, and where the system must abstain instead of guessing. It survives paraphrase. It can be tested. It can be versioned. It can be observed. It can be enforced. **That is why schema-based memory is so powerful. It turns intent into an explicit, durable interface between the world and the memory layer.** Prompts still play an important role, but their role becomes clearer inside a schema-grounded system. They help detect objects, extract candidate fields, resolve links, and retry failed writes. But they operate inside a governed structure rather than replacing it. The result is a memory system that does not simply sound coherent. **It behaves predictably.** #### Reliability starts at the memory layer The deeper point is simple. If AI systems are going to do more than recover context - if they are going to make decisions, trigger workflows, maintain state, and operate over long horizons - then memory cannot remain a fuzzy layer of retrieved or compacted text. It has to become a *governed system of facts.* That means going beyond semantic search alone. It means going beyond endlessly appended and summarized prompt memory. It means acknowledging the progress of Graph RAG while also recognizing its limits. And it means embracing deduplication, types, relations, constraints, provenance, and schema as first-class parts of the memory system. Because at the moment memory has to answer not just what feels related, but what is true, structure stops being optional. Schema is not decoration. **Schema is the core of reliability.** ### Install xmemory in your agent Canonical URL: https://xmemory.ai/install/ Markdown URL: https://xmemory.ai/install/index.html.md This page is generic: it contains no instance ids, names, or data. To connect a specific instance you need its id, which comes from the [xmemory console](https://console.xmemory.ai) or from the response that created it. > xmemory is a first-party memory store: it holds the data you explicitly save to your xmemory > instance, in xmemory's own backend. It does NOT read the assistant's built-in memory, your past > chat history, or your files, email, or cloud drives — it only stores and returns what is written > to this instance. Pick your surface: | Surface | What installs | Session-start context | |---|---|---| | [Claude Code](#claude-code) | Plugin (MCP servers, skills, hooks) | Yes, for instances bound `autoload` | | [Codex](#codex) | Plugin (MCP servers, skills, hooks) | Yes, once hooks are enabled and trusted | | [Claude Desktop and claude.ai](#claude-desktop-and-claudeai) | Custom connector | No | | [ChatGPT](#chatgpt) | Custom MCP server (a "plugin") | No | Every surface reaches the same remote MCP server at `https://mcp.xmemory.ai`. #### What you need - An xmemory account and at least one instance. The [console](https://console.xmemory.ai) creates both; so does the [agent onboarding prompt](https://xmemory.ai/agent-onboarding-prompt.txt), which walks an agent through installing the CLI, signing in by email, and creating a first instance. This page starts one step later, from an instance that already exists. - The instance id (a UUID) of whatever you want to connect. - For Claude Code: the `claude` CLI. For Codex: the `codex` CLI. None of the paths below asks you to paste an API key into a client configuration file — each connection signs in through a browser. On the two connector surfaces that browser form is xmemory's own, and it asks for an xmemory API key from the console's **API Keys** page; the key stays with xmemory rather than being written into the client. The optional CLI in [step 4](#4-preload-bound-instances-at-session-start) keeps its own credential on disk; see [security posture](#security-posture). --- #### Claude Code ##### 1. Install the plugin ```bash claude plugin marketplace add xmemory-ai/claude-code-plugin claude plugin install xmemory@xmemory-ai ``` The equivalent inside a session is `/plugin marketplace add xmemory-ai/claude-code-plugin` followed by `/plugin install xmemory@xmemory-ai`. Restart Claude Code, or run `/reload-plugins`. The plugin registers two MCP servers and three skills: | Component | What it does | |---|---| | `xmemory` MCP server | Reads and writes one instance's data | | `xmemory-admin` MCP server | Creates, lists and manages instances and schemas | | `/xmemory:connect` | Discovers your instances and writes the project binding | | `/xmemory:doctor` | Reports which parts of the setup work, and what to do about the rest | | `/xmemory:xmemory-memory` | Tells Claude when to reach for the memory tools | It also installs a `SessionStart` hook and a `PreCompact` hook. Both are POSIX `sh` with no dependencies, and neither can fail a session — see [what the install asks of you](#what-the-install-asks-of-you). ##### 2. Authorize the connections Run `/mcp` and authorize `xmemory`. A browser opens; nothing is pasted anywhere. The `xmemory` and `xmemory-admin` servers authorize **separately** — one working says nothing about the other. The bundled `xmemory` entry points at the root URL, so it holds one connection bound to one instance, which you choose in the sign-in screen. To keep several instances live at the same time, add one named server per instance. Registering two instances under one name silently replaces the first, so **name each entry `xmemory-` followed by the first eight characters of its instance id** — the same name `xmemcli instance setup` prints for that instance. Following this page and following the per-instance instructions then produce one entry, not two under different names. Which of the two forms below applies depends on the local CLI, so ask it first. This reports `version` and `authenticated` together, contacts nothing, and exits 0 either way — read the fields rather than the exit code, and treat "command not found" as no CLI: ```bash xmemcli --json status ``` **With `xmemcli` installed and signed in** (0.0.7 or newer), register through it. The credential is read from the CLI's own configuration on every connection, so an entry written today still works in a session opened next week on a machine whose environment carries nothing: ```bash claude mcp add xmemory- -- xmemcli mcp ``` `mcp` arrived in **0.0.7**. An older client accepts the command and then cannot run it, leaving a server that never starts, so upgrade first with `uv tool install --upgrade xmemcli` if `version` is lower. **Without the CLI**, register against the endpoint directly and sign in through a browser: ```bash claude mcp add --transport http xmemory- https://mcp.xmemory.ai/instance/ ``` The direct form is also what a hand-written `.mcp.json` holds: ```json { "mcpServers": { "xmemory-work": { "type": "http", "url": "https://mcp.xmemory.ai/instance/" }, "xmemory-personal": { "type": "http", "url": "https://mcp.xmemory.ai/instance/" } } } ``` Each entry authorizes on its own. ##### 3. Bind instances to a project A **binding** records which instances an agent working in a directory should know about, and how eagerly to engage each one. Ask Claude to "connect xmemory to this project" and the `connect` skill discovers your instances and writes `.xmemory.json`. | Tier | Meaning | |---|---| | `autoload` | Pull this instance's context at the start of every session in this directory | | `available` | Default. Do not preload; engage it when the work matches | | `off` | Bound but dormant — silences an instance inherited from a wider scope | Two files are merged like git config, with the nearer one winning field by field: `.xmemory.json` at the project root, and `~/.xmemory.json` for instances personal to you. Binding is local bookkeeping. It does not grant access, move data, or change anything on the server — and binding three instances does not make all three readable over MCP at once, which is what the per-instance server entries above are for. ##### 4. Preload bound instances at session start Instances bound `autoload` are pulled into the session by the `SessionStart` hook, which runs in a separate process and so cannot reach the MCP sign-in. It needs its own credential, which is what [`xmemcli`](https://xmemory.ai/cli/) provides: ```bash uv tool install xmemcli xmemcli auth login ``` If an older client is already on the machine, upgrade it with `uv tool install --upgrade xmemcli`. The hook reads bindings and fetches context through `xmemcli binding` and `xmemcli context`, and the per-instance MCP transport is `xmemcli mcp` — all three are in **0.0.7**, so an install left at an earlier version preloads nothing. `/xmemory:doctor` reports the version it finds. Everything else — binding, and the instance context that arrives with the MCP connection — works without it. A binding written today starts preloading the moment the CLI is installed. ##### 5. Check the setup ``` /xmemory:doctor ``` Five things have to line up and they fail independently: the MCP server registered, the connection authorized, a binding present, `xmemcli` installed and signed in, and no hand-wired hooks duplicating the plugin's. The doctor reports all five and names the remaining step. If you already wired xmemory into Claude Code by hand, installing the plugin does not replace your hooks — both run, and context is injected twice. Set `XMEMORY_DISABLE_HOOKS=1` to stand the plugin's hooks down and keep your own; the skills and MCP servers are unaffected. --- #### Codex Codex installs the same package as Claude Code. One repository carries two entry points, so the skills and the session-start hook are the ones described above, not a separate build: ```bash codex plugin marketplace add xmemory-ai/claude-code-plugin codex plugin add xmemory@xmemory-ai ``` Start a new session after installing. ##### Hooks are gated on this surface Codex lifecycle hooks require `[features] hooks = true`, and their definitions must also be reviewed and trusted through `/hooks`. Both are deliberate, and both are yours to grant. Until they are, nothing runs at session start and the plugin's managed block in the global `AGENTS.md` carries the instructions instead — a file Codex always loads. The `doctor` skill checks the two states separately and names whichever is missing. ##### Connect an instance Installing the package and pointing it at an instance are separate acts. Codex takes stdio servers, so the same two forms apply as on Claude Code. Check the local CLI first, as above — `xmemcli --json status` reports `version` and `authenticated` and contacts nothing. **With `xmemcli` installed and signed in** (0.0.7 or newer): ```bash codex mcp add xmemory- -- xmemcli mcp ``` **Without the CLI**, register directly and sign in through a browser: ```bash codex mcp add xmemory- --url https://mcp.xmemory.ai/instance/ codex mcp login xmemory- ``` `codex mcp login` opens a browser; no key is written into `config.toml`. Restart Codex and run `/mcp` to confirm the server is connected and its tools are listed. The naming rule is the one above: `xmemory-` plus the first eight characters of the instance id, matching what `xmemcli instance setup` prints. Two instances sharing a name means one entry, and the second silently replaces the first. For a headless setup that authenticates with an API key rather than a browser, see the [Codex guide](https://xmemory.ai/codex/). --- #### Claude Desktop and claude.ai Open **Customize → Connectors**, open the add menu beside the **Connectors** heading — a **+** on claude.ai, an **Add** menu in Claude Desktop — choose **Add custom connector**, and give it this URL: ``` https://mcp.xmemory.ai/instance/ ``` The instance path binds the connector to that instance up front. To pick the instance during sign-in instead, use the root URL `https://mcp.xmemory.ai/`. Click **Connect** and complete the browser sign-in. That form asks for an **xmemory API key**, which you create on the **API Keys** page of the console; it has one field and no other way through. Enable the connector for a conversation to use its tools. The connector then appears on the mobile apps too. There are no plugins or hooks on these surfaces, so the connection itself is what binds the instance. The [Claude guide](https://xmemory.ai/claude/) has screenshots of the dialog and of the per-tool permissions screen; the menu path above is the current one. --- #### ChatGPT On **chatgpt.com**, turn on Developer mode under **Settings → Security and login** first: until it is on, the **Plugins** page has nothing on it to add a custom server with. Then open **Plugins** (a top-level item in the left sidebar), choose **+**, give it a name, keep **Connection** on **Server URL** and paste the same instance URL, leave **Authentication** on **OAuth**, and confirm the risk notice to create it. The **ChatGPT desktop app** takes a different route — **Settings → Plugins → MCPs → + Add server**, where **Type** has to be set to **Streamable HTTP**, because the form opens on STDIO and its next field is a shell command. Complete the browser sign-in when the connector asks for it; it asks for an **xmemory API key** from the console's **API Keys** page. Then mention it by the name you gave it to use its tools in a chat. The [ChatGPT guide](https://xmemory.ai/chatgpt/) covers the Codex CLI and the admin and status connection types; for the connector itself, the routes above are the current ones. --- #### What the install asks of you Stated up front rather than discovered halfway through: - **Approving commands.** When an agent runs the install for you, it asks before each command. - **A browser sign-in per connection.** Data and admin planes are separate sign-ins. On Claude Desktop, claude.ai and ChatGPT that form asks for an xmemory API key from the console. - **Choosing tool groups.** The sign-in screen lets you pick which tools to authorize. Core memory and schema-evolution suggestions are granted by default; **schema management** — which can replace a schema — starts unchecked. - **Hooks, on Claude Code and Codex.** Installing the plugin enables them on Claude Code; on Codex they additionally wait on `[features] hooks = true` and your `/hooks` approval. A directory with no `.xmemory.json` gets silence: the `SessionStart` hook exits without a network call. Where something is bound `autoload`, it fetches that instance's context. The `PreCompact` hook is a reminder to the agent to persist durable facts before context is summarized away — it has no model and sends nothing itself. A missing CLI, an expired credential, or an unreachable API costs context, never the session. Both hooks exit successfully and say in one line what went wrong, so an agent can tell "nothing saved" from "could not reach it". #### Security posture - **No static tokens in client configuration.** Every connection above authorizes with OAuth 2.1 and PKCE through a browser. - **The two planes authorize separately.** Reading and writing one instance's data is a different consent from creating, managing, or deleting instances. - **An instance id is an identifier, not a credential.** It appears in URLs and committed files. Access is decided by the credential presented, never by knowledge of the id. - **`.xmemory.json` holds no secrets** and is meant to be committed and shared with your team. **`.xmemrc.json` is not** — it sits in the same directory and holds a plaintext API key. `xmemcli context` refuses to fetch with a credential found inside a checkout, so committing one both exposes the key and disables preloading. - **A committed binding shares a list, not data.** When a teammate opens a session there, those instances are fetched with *their* credential, so the server returns only what their own access already reaches. An instance they never listed for themselves is flagged in their session as having come from the project. - **Per-instance setup and context reads are authenticated and audit-logged.** This page is static and carries none of that content. #### Machine-readable copies of this page - Markdown: [`https://xmemory.ai/install/index.html.md`](https://xmemory.ai/install/index.html.md) - Site index for agents: [`llms.txt`](https://xmemory.ai/llms.txt), [`llms-full.txt`](https://xmemory.ai/llms-full.txt) - Install coordinates as JSON: [`/.well-known/xmemory.json`](https://xmemory.ai/.well-known/xmemory.json) - Bootstrap prompt for a reader with no account yet: [`/agent-onboarding-prompt.txt`](https://xmemory.ai/agent-onboarding-prompt.txt) ### How xmemory works Canonical URL: https://xmemory.ai/integration-overview/ Markdown URL: https://xmemory.ai/integration-overview/index.html.md xmemory is a memory interface with natural-language inputs and outputs, backed by schema under the hood. That means your agents can **write memory in plain text** and **read memory in plain text**, while xmemory still stores information in a structured, governed form. Instead of treating memory as loose context alone, xmemory lets you define what should be remembered, how it should be represented, and what kinds of questions the system needs to answer reliably later. #### The schema question To use xmemory, you first create a memory instance and define the schema for it. That schema can come from: - an existing project or system of record - a manual definition you write yourself - the xmemory schema generation tool The schema generation tool is useful when you know the kind of information you want to store, but you do not want to design the schema from scratch. In that case, you describe in detail: - what information the agent will store - what objects, events, or entities matter - what typical questions you expect to ask memory afterwards From that, xmemory can generate a schema designed for the workflow you are building. **Example schema generation request** ```text Generate a very simple and short xmemory schema for tracking shared office expenses between colleagues. It should let us store colleagues, expense records, and debts/obligations so we can see who owes whom, how much, currency, and what the expense was for. Include short text fields for names and descriptions, an amount field, a currency field, a date field for when the expense happened, and relations linking payer, debtor, and optional related expense. ``` **Example schema in YAML** ```yaml xmd_version: v1 title: Office Expense Tracker description: Schema for tracking shared office expenses, who paid, and who owes whom. objects: colleague: description: A person participating in shared office expenses. fields: name: type: str required: true description: Full name of the colleague. enum: null default: null primary_key: - name debt: description: A debt or financial obligation between two colleagues. fields: amount: type: float required: false description: Amount owed. enum: null default: null currency: type: str required: false description: Currency of the debt. enum: null default: null note: type: str required: false description: Optional note or reason for the debt. enum: null default: null primary_key: [] expense: description: A shared office expense record. fields: amount: type: float required: false description: Total monetary amount of the expense. enum: null default: null currency: type: str required: false description: Currency code for the expense (e.g. USD, EUR). enum: null default: null date: type: str required: false description: Date the expense occurred (YYYY-MM-DD). enum: null default: null description: type: str required: false description: Optional details about what the expense was for. enum: null default: null title: type: str required: true description: Short label identifying the expense. enum: null default: null primary_key: - title relations: debt_record: description: Links a debt to the colleague who paid (payer), the colleague who owes (debtor), and optionally the related expense. objects: debt: type: debt on_delete: cascade description: null debtor: type: colleague on_delete: cascade description: null expense: type: expense on_delete: nullify description: null payer: type: colleague on_delete: cascade description: null keys: primary: - debt expense_payer: description: Links an expense to the colleague who paid for it. objects: expense: type: expense on_delete: cascade description: null payer: type: colleague on_delete: cascade description: null keys: primary: - expense ``` #### Create the memory instance Once you have the schema, you can use it to create a memory instance. That instance becomes the memory interface your agent or workflow talks to. From there, the interaction model is simple: - **write** memory using free-form text requests - **read** memory by asking questions or describing the data you want to retrieve In practice, this gives you a memory layer that feels conversational from the outside, but stays structured and queryable underneath. #### Sync and async writes xmemory supports both **synchronous** and **asynchronous** writes. For most production use cases, we strongly recommend **asynchronous writes** unless you need to read the written data back immediately. In many workflows, especially conversational ones, the system does not need to pause and verify exactly what was saved before continuing. A chat assistant can often keep the conversation moving while memory is written in the background. That is why async writes are usually the better default: - lower latency in the user-facing flow - less blocking inside agent loops - easier integration when memory capture does not need to affect the next response immediately A good example is **chat memory**. In most chat scenarios, you do not need to inspect the saved memory right away. The system can continue the conversation without waiting to confirm exactly what was stored. The main exception is **form-filling chats** or other guided flows where the agent must know what information is still missing before asking the next question. In those cases, **synchronous writes** are often the better choice because the system benefits from having the updated memory state immediately available. #### Typical usage flow The usual xmemory flow looks like this: 1. Define or generate a schema. 2. Create a memory instance from that schema. 3. Send natural-language writes into xmemory. 4. Ask natural-language read queries when the agent needs to recover facts, state, or context. This makes it easy to plug xmemory into agent loops, workflow engines, or multi-agent systems without forcing every integration to work directly with raw structured storage. #### Good examples ##### Chat memory One common pattern is chat memory. Important user messages can be sent to xmemory for memorization inside a **customer-profile schema you define** — for example, one that captures preferences, facts, identity, and goals. That gives the system a durable, structured representation of the information that should survive beyond a single conversation. ##### Agentic working memory Another strong fit is agentic working memory. Agents can create schemas and memory instances on the fly to save: - execution steps - task artefacts - state - tool knowledge - intermediate facts - shared knowledge across collaborating agents This is especially useful in multi-agent systems where several agents need to coordinate around shared state and durable task knowledge. #### Integration guides To integrate xmemory into your workflows, start with one of the guides below. ##### Manual integrations - [MCP](https://xmemory.ai/mcp/) - Expose xmemory to agents as tools through the Model Context Protocol. - [CLI](https://xmemory.ai/cli/) - Onboard, read, write, and manage schemas from the terminal with `xmemcli` (plus the agent skill). - [API](https://xmemory.ai/api/) - Connect xmemory to your workflows, apps, or UIs over HTTP. - [Python](https://xmemory.ai/python/) - Use xmemory directly inside Python codebases and backend workflows. - [TypeScript](https://xmemory.ai/typescript/) - Use xmemory inside TypeScript and Node.js applications. ##### Example integrations - [Pydantic AI](https://xmemory.ai/pydantic/) - Use xmemory as a structured memory layer in pydantic-ai agents. - [Google ADK](https://xmemory.ai/google-adk/) - Add structured memory to agents built with Google's Agent Development Kit. - [Mastra AI](https://xmemory.ai/mastra-ai/) - Integrate xmemory into Mastra-based agent workflows. - [n8n](https://xmemory.ai/n8n/) - Connect xmemory to automations and workflow pipelines in n8n. - [LangChain](https://xmemory.ai/langchain/) - Use xmemory with LangChain agents through MCP or direct API access. - [Temporal](https://xmemory.ai/temporal/) - Combine durable Temporal Workflows with retry-safe xmemory Activities and structured memory across executions. - [OpenAI Agents SDK](https://xmemory.ai/openai-agents/) - Give an OpenAI Agents SDK agent persistent memory through MCP or custom function tools. - [Claude Agent SDK](https://xmemory.ai/claude-agent-sdk/) - Connect the Claude Agent SDK or the Messages API MCP connector to xmemory. - [Codex](https://xmemory.ai/codex/) - Add xmemory to the OpenAI Codex CLI as an MCP server. - [Claude](https://xmemory.ai/claude/) - Connect xmemory to claude.ai, Claude Desktop, and Claude Code. - [ChatGPT](https://xmemory.ai/chatgpt/) - Connect xmemory to ChatGPT and the Codex CLI. If you are deciding where to begin, start with the interface that matches how your system already works: - choose **MCP** if your agent framework is tool-driven - choose **API** if you want direct application-level control - choose **Python** or **TypeScript** if you want the cleanest language-native integration path ### MCP Canonical URL: https://xmemory.ai/mcp/ Markdown URL: https://xmemory.ai/mcp/index.html.md xmemory exposes a [Model Context Protocol](https://modelcontextprotocol.io/) server over Streamable HTTP. Any MCP-compatible client — Claude Desktop, Cursor, Windsurf, pydantic-ai, LangChain, Mastra, or a plain SDK call — can connect and get access to xmemory's read and write tools with no custom code. The MCP server supports two authentication paths: - **OAuth2 flow** for interactive browser-based connectors. - **Direct fixed API-key flow** for headless agents, CI, evals, and scripts. > **Connecting a specific client?** See the step-by-step guides for [Claude](https://xmemory.ai/claude/) (claude.ai, Desktop, and Claude Code), [ChatGPT](https://xmemory.ai/chatgpt/), [Codex](https://xmemory.ai/codex/), and [n8n](https://xmemory.ai/n8n/). Any other MCP client (Cursor, Windsurf, VS Code, and other popular tools) connects with the same URL and Bearer token — see [Authentication](#authentication) and [Connection](#connection) below. --- #### Adding xmemory as a connector xmemory connects to any MCP-compatible client as a remote (Streamable HTTP) server at `https://mcp.xmemory.ai/` — no local install or custom code. Most clients add it through their built-in connector or MCP-server settings. 1. In your client, add a new custom connector / MCP server with the URL `https://mcp.xmemory.ai/`. 2. Authenticate — either **interactively (OAuth2)**, where you pick a **connection type** (**instance** or **admin**) on the xmemory connect page, or **headless**, by pointing at a qualified shortcut URL and sending your `xmem_...` API key as a Bearer token. See [Authentication](#authentication) below for exact tokens and headers. 3. Approve the connection — xmemory's read and write tools then appear in the client. --- #### Authentication **API key:** To use xmemory APIs or integrations (including MCP), you need an API key. Get one from the [xmemory console](https://console.xmemory.ai). Copy and securely store the key. Never share your API key publicly. ##### Option 1: OAuth2 (interactive) Point your client to `https://mcp.xmemory.ai/` and complete the OAuth2 login flow. The client receives an opaque MCP token (format: `xmem_mcp_...`) that it sends as the `Authorization: Bearer` token on every request. For OAuth-issued sessions, binding metadata (instance/admin/status) is stored server-side. ##### Option 2: Direct fixed API key (headless) Headless clients can skip OAuth and send an account API key directly as the Bearer token on MCP shortcut paths: ``` Authorization: Bearer ``` The session type is determined by the URL path, so a direct API-key request must target a **qualified shortcut path** (the bare root `https://mcp.xmemory.ai/` is for OAuth only): - `/instance/` — instance tools bound to that instance. - `/admin` — global admin (fleet) tools. Admin is global-only; `/admin/` is not a recognized shortcut. - `/status` — status-only tool. The API key is revalidated on every request. An example of adding an xmemory instance to Claude Code using the direct API key method: ```sh claude mcp add --transport http xmemory https://mcp.xmemory.ai/instance/ --header "Authorization: Bearer " ``` --- #### Connection Clients that use a config file — e.g. `.mcp.json` — expect an entry like: ```json { "mcpServers": { "xmemory": { "url": "https://mcp.xmemory.ai/instance/", "headers": { "Authorization": "Bearer " } } } } ``` The `url` and token depend on which authentication path you use: | Auth path | `url` | Bearer token | |---|---|---| | OAuth2 (interactive) | `https://mcp.xmemory.ai/` | `` | | Direct API key (instance) | `https://mcp.xmemory.ai/instance/` | `` | | Direct API key (admin) | `https://mcp.xmemory.ai/admin` | `` | For framework-specific setup, see the **Example integrations** guides in the sidebar — [Pydantic AI](https://xmemory.ai/pydantic/), [LangChain](https://xmemory.ai/langchain/), [Google ADK](https://xmemory.ai/google-adk/), and more. For chat apps and CLIs, see the [Claude](https://xmemory.ai/claude/) and [ChatGPT](https://xmemory.ai/chatgpt/) guides. --- #### Tools The xmemory MCP server exposes 9 default tools to instance connections, all operating on the instance bound at login, plus an opt-in **Schema management** group (5 more) the instance owner can grant on the connect page. For direct API-key usage, `/instance/` creates the same instance-scoped tool surface, while `/admin` switches to the global admin (fleet) tools. Admin is **global-only** — it owns no per-instance state, so `/admin/` is **not** a recognized shortcut; a specific instance's schema is managed from that instance's own connection. Tool descriptions are **dynamic** — on each `list_tools()` call, the server fetches your instance's schema and appends a summary of its object types and relations to each tool description. This means the LLM sees tool descriptions tailored to your specific instance, making it more likely to use the tools correctly. **Tool responses.** There are two response families. **Instance (bound) tools** — `read`, `write`, the schema tools, etc. — return a **flat JSON object** with a top-level **`status`** (`"ok"` or `"error"`) and the payload keys inline (no wrapper); `get_instance_id` is the one exception and returns a bare ID string. **Admin tools** instead return a JSON **envelope** — the parts that matter are **`items`** (the result; a single record is `items[0]`) and **`errors`** (populated on failure). Both families also carry a **`console_url`** linking to that operation in the console. Each tool below documents its own payload. **`get_instance_id`** — Return the instance ID bound to the current session Returns: A bare instance-ID string (32-char UUID hex, e.g. `a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6`) — not a JSON envelope. Details: Useful for display, logging, or confirming which instance the agent is operating on. **`get_instance_schema`** — Return the bound instance's schema (object types, fields, relations, deduplication keys) Returns: A flat object `{ status, data_schema }` — `data_schema` holds the instance schema. Details: The LLM can call this to understand what kinds of data the instance stores, which helps it formulate better `write` and `read` calls. **`get_setup_instructions`** — Return how to connect this instance on an agent surface, ordered for where it is likely to be used Parameters: - `format` (`string`) — `agent` (default) for the steps to run now; `project` to also return the shared setup files a team commits once — each teammate still approves and signs in themselves. Returns: A JSON object of per-surface steps and commands. With `format=project` it also carries committable file fragments, each stating how it merges into whatever the repository already has. Details: For adding the same memory somewhere else — "how do I also connect this on my desktop?" — rather than for reaching it from here, which is already done by the time the tool is callable. Commands only. The sign-in step hands over credentials out of band, so nothing returned here is a secret, and an instance id is an identifier rather than a credential. Computed from the instance's current settings, so editing them changes the next response. **`write`** — Extract structured entities from free-form text and persist them (synchronous — blocks until fully committed) Parameters: - `text` (`string`, required) — Free-form text containing facts to extract and remember. - `session_id` (`string | null`) — Session ID for tracing (e.g. `claude-qwxhjkmrtz`). Returns: A flat object `{ status, write_id, cleaned_objects: { objects, relations }, changes: { created, updated, deleted }, trace_id, console_url }`. `changes` is the per-object breakdown the diff engine applied; `console_url` links to the run in the console. Example response: ```json { "status": "ok", "write_id": "", "cleaned_objects": { "objects": [], "relations": [] }, "changes": { "created": {}, "updated": {}, "deleted": {} }, "trace_id": "...", "console_url": "https://console.xmemory.ai/..." } ``` Details: Internally, the server runs a two-phase pipeline: an LLM extracts structured objects according to your instance's schema, then a diff engine compares them against existing data and applies inserts, updates, and deletes. Because `write` blocks until committed, you can call `read` immediately after and get consistent results. **`write_async`** — Same as `write`, but enqueues the operation and returns immediately with a `write_id` Parameters: - `text` (`string`, required) — Free-form text containing facts to extract and remember. - `session_id` (`string | null`) — Session ID for tracing. Returns: A flat object `{ status, write_id, trace_id, console_url }`. Poll completion with `write_status`. Details: **Important:** do not call `read` immediately after `write_async` — the data may not be committed yet. Use `write_status` to poll, or use `write` (synchronous) when you need to read right after. **`write_status`** — Check the status of an async write previously submitted via `write_async` Parameters: - `write_id` (`string`, required) — The write ID returned by `write_async`. Returns: A flat object `{ write_id, write_status, error_detail, error, completed_at, trace_id, console_url, status }`. The lifecycle state is in `write_status` (see below); `status` is the call's own `ok`/`error`. Details: | `write_status` | Meaning | |---|---| | `queued` | Waiting to be picked up | | `processing` | Currently being extracted and applied | | `extracting` · `extracted` · `applying` | Intermediate states of the two-phase pipeline | | `completed` | Successfully committed — safe to `read` | | `failed` | Extraction or persistence failed; see `error_detail` | | `not_found` | No write with this ID exists | Treat anything other than `completed` / `failed` / `not_found` as still in flight. **`read`** — Query the instance and return a natural-language answer Parameters: - `query` (`string`, required) — A natural-language question about the stored data. - `session_id` (`string | null`) — Session ID for tracing. Returns: A flat object `{ status, reader_result, sql, pending_suggestions, trace_id, console_url }`. The natural-language answer is at `reader_result.answer`. Example response: ```json { "status": "ok", "reader_result": { "answer": "Bob Lee joined last Monday." }, "sql": null, "pending_suggestions": 0, "trace_id": "...", "console_url": "https://console.xmemory.ai/..." } ``` Details: Internally, the server translates the question into SQL against the instance's knowledge graph, executes it with automatic retry and empty-result verification, and formats the result into a plain-text answer. ##### Schema evolution (suggestion engine) xmemory learns from reads that couldn't be fully answered and, on demand, surfaces a single rolling proposal of schema improvements for the bound instance. The flow is three tools — **review → decide → apply** — and a change is only applied when you call `apply_pending_decisions`. **`review_suggestions`** — Return the consolidated proposal and a `proposal_version` token Parameters: - `session_id` (`string | null`) — Session ID for tracing. Returns: A flat object `{ status, instance_id, proposal, retry_after_seconds }`, where `proposal` = `{ proposal_version, schema_version, items }`. When a migration is already running, `status` is `evolution_in_progress` and `retry_after_seconds` is set. **`decide_suggestions`** — Record an `accept` / `reject` / `defer` per item, in bulk Parameters: - `proposal_version` (`string`, required) — Proposal version token from `review_suggestions`. - `decisions` (`array`, required) — Per-item decisions, each `{ "item_fingerprint": "...", "decision": "accept" | "reject" | "defer", "edits"?: {...} }`. - `session_id` (`string | null`) — Session ID for tracing. Returns: A flat object `{ status, decisions_recorded, warnings, next_proposal_version }`. Pass `next_proposal_version` to `apply_pending_decisions`. **`apply_pending_decisions`** — Commit accepted decisions as one migration Parameters: - `proposal_version` (`string`, required) — Proposal version token (the `next_proposal_version` from `decide_suggestions`). - `session_id` (`string | null`) — Session ID for tracing. Returns: A flat object `{ status, migration_id, prior_version, new_version, applied_items, summary }`. `status` is `nothing_to_apply` when no accepted items remained. Always confirm with the user before deciding or applying. Rejecting an item suppresses that exact suggestion in future proposals. ##### Schema management (opt-in) A separate, **opt-in** permission group lets an instance connection own its schema lifecycle directly — distinct from the suggestion engine above. These tools default **off** and are exposed only when the **Schema management** permission is granted on the OAuth connect page, so headless direct-key bindings can't reach them. Schema dry-runs and migration history now live here: admin connections are global-only and no longer edit a specific instance's schema. A bound instance already has a schema, so it **evolves** that schema with `enhance_schema` — there is no per-instance `generate_schema` (generating a fresh schema from scratch is a new-instance concern that lives only on the admin connection, as `admin_generate_schema`). **`enhance_schema`** — LLM: improve the bound instance's schema — returns the new YAML and a migration plan; does not apply it Parameters: - `schema_description` (`string`, required) — Free-form description of what should change in the schema. - `schema_to_improve` (`string`, required) — The current YAML schema to improve (from `get_instance_schema`). - `session_id` (`string | null`) — Session ID for tracing. Returns: A flat object `{ status, generated_schema, migration_plan, summary, warnings, repair_log }` — the improved YAML **and** a structured migration plan for non-additive changes. Apply it with `update_instance_schema`. **`update_instance_schema`** — Replace the bound instance's schema, applied as a migration Parameters: - `schema_yml` (`string`) — New schema as YAML (one of `schema_yml` / `schema_json`). - `schema_json` (`string`) — New schema as JSON (one of `schema_yml` / `schema_json`). - `migration_plan` (`object`) — Serialized migration plan (from `enhance_schema`) for non-additive changes. - `confirm_destructive` (`boolean`) — Set `true` to acknowledge ops that drop data. Defaults to `false`. - `session_id` (`string | null`) — Session ID for tracing. Returns: A flat object `{ status, migration_id, prior_version, new_version, statements_executed }` when a migration ran. **`dry_run_schema_migration`** — Preview the DDL `update_instance_schema` would run, without applying it Parameters: - `schema_yml` (`string`) — Candidate schema as YAML (one of `schema_yml` / `schema_json`). - `schema_json` (`string`) — Candidate schema as JSON (one of `schema_yml` / `schema_json`). - `migration_plan` (`object`) — Serialized migration plan, same shape as for `update_instance_schema`. - `confirm_destructive` (`boolean`) — Set `true` to preview destructive ops without the gate rejecting. Defaults to `false`. - `session_id` (`string | null`) — Session ID for tracing. Returns: A flat object `{ status, current_version, statements, plan_summary, warnings }` (`statements` are the planned DDL). **`list_schema_migrations`** — List the bound instance's applied migrations, newest first Parameters: - `limit` (`integer`) — Max records to return (1–200). Defaults to `50`. - `before_id` (`string`) — Pagination cursor: return migrations applied before this ID. - `include_yaml` (`boolean`) — Include the before/after YAML snapshots for each record (can be large). Defaults to `false`. - `session_id` (`string | null`) — Session ID for tracing. Returns: A flat object `{ status, items, next_before_id, has_more }`; each record in `items` = `{ id, applied_at, source, prior_version, new_version, ops, ops_summary }`. **`get_schema_migration`** — Get one applied-migration record of the bound instance by ID Parameters: - `migration_id` (`string`, required) — The migration record ID (from `list_schema_migrations`). - `include_yaml` (`boolean`) — Include the before/after YAML snapshots. Defaults to `false`. - `session_id` (`string | null`) — Session ID for tracing. Returns: A flat object `{ status, record }`, the migration record `{ id, applied_at, source, prior_version, new_version, ops, ops_summary, yaml_before?, yaml_after? }`. > **Important:** never hand-write YAML schemas. Use `enhance_schema` to evolve the instance's schema, preview with `dry_run_schema_migration`, then apply with `update_instance_schema`. ##### Admin tools Selecting the *admin* connection type at login switches the tool surface to the global **fleet** management tools, all prefixed `admin_`. Admin operates across your whole fleet rather than a single instance, so it has no bound-instance state. To edit a specific instance's schema, use that instance's own connection and its **Schema management** group (above); admin's by-id schema tool (`admin_get_instance_schema_by_id`) is read-only. > **Important:** never hand-write YAML schemas. Always use `admin_generate_schema` or `admin_enhance_schema` to produce a valid schema, then pass the result to `admin_create_instance`. **Stateless schema helpers** **`admin_generate_schema`** — Generate a YAML schema from a free-form description Parameters: - `schema_description` (`string`, required) — Free-form description of the schema to generate. - `cluster_id` (`string`) — Target cluster; required when authenticating with an API key. - `session_id` (`string | null`) — Session ID for tracing. Returns: `items[0]` = `{ data_schema }` (the generated schema). **`admin_enhance_schema`** — Improve an existing YAML schema Parameters: - `schema_description` (`string`, required) — Free-form description of the desired improvements. - `schema_to_improve` (`string`, required) — The existing YAML schema to improve. - `cluster_id` (`string`) — Target cluster; required when authenticating with an API key. - `session_id` (`string | null`) — Session ID for tracing. Returns: `items[0]` = `{ data_schema, migration_plan, summary, warnings, repair_log }` — the enhanced schema **and** a structured migration plan for applying non-additive changes safely. **Instance lifecycle** **`admin_create_instance`** — Create a new empty instance from a YAML schema Parameters: - `schema_yaml` (`string`, required) — YAML data schema (from `admin_generate_schema` / `admin_enhance_schema`). - `cluster_id` (`string`) — Target cluster; required when authenticating with an API key. - `name` (`string`) — Instance name; required when `cluster_id` is provided. - `description` (`string`) — Instance description. - `session_id` (`string | null`) — Session ID for tracing. Returns: `items[0]` = `{ instance_id }` (the new instance). Bind to it via a fresh `/instance/` connection. **Cluster-scoped management** **`admin_list_clusters`** — List clusters accessible to the API key Returns: `items` = clusters, each `{ id, org_id, name, description }`. **`admin_get_cluster`** — Get a single cluster by ID Parameters: - `cluster_id` (`string`, required) — The cluster ID to fetch. Returns: `items[0]` = a cluster `{ id, org_id, name, description }`. **`admin_list_instances`** — List instances in a cluster Parameters: - `cluster_id` (`string`, required) — The cluster ID to list instances from. - `verbose` (`boolean`) — When `false`, return instance IDs only. Defaults to `true`. Returns: `items` = instances in the cluster. **`admin_get_instance`** — Get instance metadata within a cluster Parameters: - `cluster_id` (`string`, required) — The cluster the instance belongs to. - `instance_id` (`string`, required) — The instance ID to fetch. Returns: `items[0]` = the instance `{ id, cluster_id, name, description, data_schema, ... }`. **`admin_delete_instance`** — Delete an instance within a cluster Parameters: - `cluster_id` (`string`, required) — The cluster the instance belongs to. - `instance_id` (`string`, required) — The instance ID to delete. Returns: `ids` = the deleted instance ID. **`admin_update_instance_metadata`** — Replace an instance's name/description within a cluster Parameters: - `cluster_id` (`string`, required) — The cluster the instance belongs to. - `instance_id` (`string`, required) — The instance ID to update. - `name` (`string`, required) — New instance name. - `description` (`string | null`) — New instance description (omit or `null` to clear). Returns: `items[0]` = the updated instance. **`admin_patch_instance_metadata`** — Partially update an instance's metadata within a cluster Parameters: - `cluster_id` (`string`, required) — The cluster the instance belongs to. - `instance_id` (`string`, required) — The instance ID to patch. - `name` (`string`) — New instance name. - `description` (`string`) — New instance description. Returns: `items[0]` = the updated instance. **Instance-scoped management** (no `cluster_id` needed) **`admin_list_own_instances`** — List all instances across linked clusters Parameters: - `verbose` (`boolean`) — When `false`, return instance IDs only. Defaults to `true`. Returns: `items` = instances across every cluster the API key can reach. **`admin_get_instance_by_id`** — Get instance metadata by instance ID Parameters: - `instance_id` (`string`, required) — The instance ID to fetch. Returns: `items[0]` = the instance. **`admin_delete_instance_by_id`** — Delete an instance by instance ID Parameters: - `instance_id` (`string`, required) — The instance ID to delete. Returns: `ids` = the deleted instance ID. **`admin_get_instance_schema_by_id`** — Get an instance's schema by instance ID Parameters: - `instance_id` (`string`, required) — The instance ID whose schema to fetch. Returns: `items[0]` = the instance schema. **`admin_update_instance_metadata_by_id`** — Replace an instance's name/description by instance ID Parameters: - `instance_id` (`string`, required) — The instance ID to update. - `name` (`string`, required) — New instance name. - `description` (`string`) — New instance description. Returns: `items[0]` = the updated instance. **`admin_patch_instance_metadata_by_id`** — Partially update an instance's metadata by instance ID Parameters: - `instance_id` (`string`, required) — The instance ID to patch. - `name` (`string`) — New instance name. - `description` (`string`) — New instance description. Returns: `items[0]` = the updated instance. --- #### Sync vs async writes Use **`write`** when you need to read the data back immediately — it blocks until committed, guaranteeing consistency. Use **`write_async`** + **`write_status`** when throughput matters more than immediate consistency — the client isn't blocked, and you can poll for completion later. --- #### Error handling Tools never raise — on failure they return a parseable JSON error instead, in one of three shapes: `{"error": ""}` (a guard failure, e.g. no instance bound to the session), `{"status": "error", "error_message": ""}` (the flat instance-tool response on failure), or the admin envelope's `errors` array — with schema-evolution failures carrying a structured `error_type` (`stale_proposal_version`, `dependency_closure_failed`, `destructive_confirmation_required`, …). Common messages: | Error | Cause | |---|---| | `"no instance bound to this session"` | Token is invalid or not linked to an instance | | `"text size (N bytes) exceeds maximum (M bytes)"` | Write payload too large (limit: 1 MB) | | `"write queue not ready"` | Background processor hasn't started | | `"write failed: "` | Extraction or persistence failure | ### CLI Canonical URL: https://xmemory.ai/cli/ Markdown URL: https://xmemory.ai/cli/index.html.md `xmemcli` is the xmemory command-line client — the fastest way to **onboard** (authenticate, design a schema, create an instance) and to read, write, and evolve memory from a terminal or a script. It talks to the HTTP API at `https://api.xmemory.ai`. For agents that call xmemory *as tools during a task*, use the [MCP](https://xmemory.ai/mcp/) server. `xmemcli` is the **onboarding / control plane**; MCP is the **runtime data plane** — they complement each other. **API key:** To use xmemory APIs or integrations, you need an API key. Get one from the [xmemory console](https://console.xmemory.ai), or run `xmemcli auth login` (below). Never share your API key publicly. --- #### Install Install it as a persistent tool (recommended for regular use — the examples below call `xmemcli` directly): ```bash uv tool install xmemcli # or: pip install xmemcli xmemcli help ``` Or run it with no install — handy for a quick try, CI, or letting an agent invoke it — via [uv](https://docs.astral.sh/uv/)'s `uvx` (prefix any command with `uvx`): ```bash uvx xmemcli help ``` --- #### Add the agent skill (optional) `xmemcli` ships an **agent skill** that teaches AI coding agents to use xmemory. Install it into your agents (Claude Code, Cursor, Codex, Gemini CLI, and more) with the [`skills`](https://github.com/vercel-labs/skills) installer: ```bash npx skills add xmemory-ai/xmemory-skill ``` The agent then uses xmemory on its own — listing available instances, checking what's already stored before redoing work, and writing down what it learns for next time. --- #### Authenticate The easiest path is the browser login (PKCE) — best run **while you're onboarding xmemory in the Console**, so the handoff lands in `.xmemrc.json` with almost no extra steps: ```bash xmemcli auth login # opens the browser; --no-browser for headless xmemcli auth status ``` Alternatively, create an API key in the [Console](https://console.xmemory.ai). Prefer keeping it in a `.xmemrc.json` (which `auth login` writes for you) over the environment — a `$XMEM_API_KEY` env var also works and is handy for CI. `xmemcli` resolves credentials `--api-key` → `$XMEM_API_KEY` → the nearest `.xmemrc.json` (walking up from the current directory). Add `.xmemrc.json` to `.gitignore` — it holds secrets. --- #### Onboard: create an instance from a description xmemory stores data in typed **instances** governed by a schema. Describe the domain, let `xmemcli` synthesize the schema, then create the instance: ```bash xmemcli xmd generate "Track contacts with name, email, company, and notes." -o schema.yml xmemcli xmd validate schema.yml xmemcli instance create --name contacts --description "People we talk to" --schema-file schema.yml # → save the returned UUID: export XMEM_INSTANCE_ID="" ``` List the memory stores available to you any time: ```bash xmemcli org list instances ``` --- #### Connect an agent to an instance Creating an instance does not connect anything to it. `instance setup` prints how to connect this one, ordered by where it is likely to be used: ```bash xmemcli instance setup "$XMEM_INSTANCE_ID" ``` The instance id is optional — it falls back to `--instance-id` or `XMEM_INSTANCE_ID`. The steps are computed from the instance's current settings, so they keep up as it changes, and they carry **no credential**: each connection signs in separately. `instance create` already returns these instructions alongside the new id, so the common path needs no second call. For a whole team, `--format project` additionally prints the shared setup files to commit once. Each teammate still approves the install and signs in themselves: ```bash xmemcli instance setup "$XMEM_INSTANCE_ID" --format project ``` ##### Serve one instance to an MCP client `xmemcli mcp` is a stdio transport, and it **arrived in 0.0.7**. An MCP client starts it, and it forwards each frame to that instance with the credential read from `.xmemrc.json` **per connection**. Nothing is captured when the entry is registered, so a client configured today still works in a session opened next week on a machine whose environment carries nothing: ```bash claude mcp add xmemory- -- xmemcli mcp codex mcp add xmemory- -- xmemcli mcp ``` Name each entry `xmemory-` plus the first eight characters of the instance id — what `instance setup` prints for it. Two instances registered under one name leave one entry, the second having quietly replaced the first. Check the client before registering, because an older one accepts the command and then cannot run it, leaving a server that fails to start: ```bash xmemcli --json status # reports `version` and `authenticated`, contacts nothing uv tool install --upgrade xmemcli # if version is below 0.0.7 ``` Signed out, the client is told which command fixes it rather than being left with a server that failed to start for no stated reason. --- #### Bind instances to a project A **binding** records which instances an agent working in this directory should know about, and how eagerly to engage each one. It is local bookkeeping — no network, no credential — and it grants no access by itself. ```bash xmemcli binding add "$XMEM_INSTANCE_ID" --tier autoload --purpose "Team decisions" xmemcli binding list xmemcli binding remove "$XMEM_INSTANCE_ID" ``` Three tiers: `autoload` pulls that instance's context into every session, `available` (the default) engages on demand, and `off` keeps the binding dormant. Bindings live in `.xmemory.json`, found by walking up from the working directory. Scopes **merge**, git-config style: `~/.xmemory.json` supplies your personal instances, a repository's committed `.xmemory.json` adds the team's, and the nearer file wins field by field. `--scope user` writes the personal file; the default writes the committable one. **`.xmemory.json` holds no secrets and is meant to be committed** — an instance id is an identifier, not a credential. A teammate who opens a session there fetches those instances with *their* own credential, so they see only what their access already reaches. `.xmemrc.json` sits in the same directory and holds a plaintext API key; that one is never committed. To see what a session-start hook would inject for the `autoload` bindings here: ```bash xmemcli context --text ``` This is the only binding command that needs a credential. Where nothing is tiered `autoload`, it makes no request at all. --- #### Read and write `write` and `read` act on the **active instance** — the `XMEM_INSTANCE_ID` you exported above (they take no instance argument, unlike control-plane commands such as `schema`/`instance`, which name the instance explicitly). ```bash xmemcli write "Alice Johnson works at Acme Corp. Her email is alice@acme.com." xmemcli read "What is Alice's email?" # bulk / non-blocking: xmemcli write --no-wait "..." # returns a write_id xmemcli write-status ``` Writes take plain language — xmemory maps the text onto the instance's schema. --- #### Evolve the schema There are two paths. Both end in a versioned migration — see the [schema evolution](https://xmemory.ai/schema-evolution/) post for the product story. ##### Suggestion engine from read traffic When agents ask questions the schema cannot answer, those misses become signal. xmemory consolidates recurring gaps into a proposal: concrete ops (add a field, introduce an object, wire a relation), each with a fingerprint, evidence queries, and a `proposal_version` token. Until recently that loop lived in the Console and MCP; from **xmemcli 0.0.6** it is also a CLI path: `schema suggestions review` → `decide` → `apply`. **When to use it.** Prefer this path when `read` responses show a non-zero `pending_suggestions` count, or when you know agents keep asking for fields the schema does not hold. In pretty mode that count also prints as a one-line hint on single-answer reads (for example `(2 pending schema suggestion(s) — xmemcli schema suggestions review)`); structured / raw / xresponse reads still carry the count in JSON only. Prefer hand-driven redesign (below) when you want to describe a change yourself in plain language. **Review** is read-only — it fetches the current consolidated proposal without changing the schema: ```bash xmemcli schema suggestions review "$XMEM_INSTANCE_ID" # optional: --session-id (forwarded on review, decide, and apply for tracing) ``` Curated output includes each item's fingerprint, a short `op_summary` (for example `add_field Task due_date`), frequency, rationale, and sample queries. Copy the `proposal_version` from this response and pass it to decide. After decide, apply with `next_proposal_version` from the decide response (not the review token). If the engine is busy, review returns `status: evolution_in_progress` with exit 0 (and often `retry_after_seconds`); wait and review again — that is a status, not an error stage. **Decide** records accept / reject / defer for items in that proposal. Always pass `--proposal-version` from the review you just ran. There is no silent version swap: if the server has moved on, you get a clear stale-version error (`stage: stale_proposal`) and re-review. ```bash xmemcli schema suggestions decide "$XMEM_INSTANCE_ID" \ --proposal-version "" \ --accept "" \ --reject "" ``` Pass exactly one input mode per invocation: the repeatable `--accept` / `--reject` / `--defer` flags, **or** a JSON decisions file via `--file` (`[{item_fingerprint, decision}, ...]` or `{decisions: [...]}`). Do not combine `--file` with the fingerprint flags. Conflicting decisions for the same fingerprint are a usage error. Other suggestion-specific failure stages to watch for: `invalid_decision` (bad fingerprint or decision), `dependency` (accept the dangling dependencies first), `not_initialised` (write once to initialize the instance), and `apply` (apply-time failure). **Apply** commits the accepted items as a single versioned migration. After decide, pass `next_proposal_version` from the decide response (not the review token). You can apply with the review `proposal_version` only when you skip decide and the proposal is already decided. `--confirm-destructive` is required (preview removals in the Console first if the proposal may drop data): ```bash xmemcli schema suggestions apply "$XMEM_INSTANCE_ID" \ --proposal-version "" \ --confirm-destructive ``` End-to-end sketch: ```bash # 1. Notice pending work (optional — also on read responses) xmemcli read "What is the due date for task X?" # 2. Review the proposal xmemcli schema suggestions review "$XMEM_INSTANCE_ID" # → note proposal_version and fingerprints # 3. Decide with a human xmemcli schema suggestions decide "$XMEM_INSTANCE_ID" \ --proposal-version "" \ --accept "" # 4. Apply xmemcli schema suggestions apply "$XMEM_INSTANCE_ID" \ --proposal-version "" \ --confirm-destructive ``` **Boundaries.** Before/after YAML **preview** for a proposal remains Console-only (API-key callers cannot use `/suggestions/preview`). Hand-driven schema work stays on the commands below. Full help: `xmemcli help`, `xmemcli help schema`, and `xmemcli help schema suggestions`. ##### Hand-driven redesign When you describe a change yourself (not traffic-driven gaps): ```bash xmemcli schema get "$XMEM_INSTANCE_ID" -o schema.yml xmemcli xmd enhance schema.yml "Add a Contact.phone field." -o schema-v2.yml xmemcli xmd validate schema-v2.yml xmemcli schema dry-run "$XMEM_INSTANCE_ID" --schema-file schema-v2.yml # preview the migration xmemcli schema update "$XMEM_INSTANCE_ID" --schema-file schema-v2.yml # apply it ``` `xmemcli help` and `xmemcli help ` document every command. ### API Canonical URL: https://xmemory.ai/api/ Markdown URL: https://xmemory.ai/api/index.html.md The xmemory REST API is what the [Python](https://xmemory.ai/python/) and [TypeScript](https://xmemory.ai/typescript/) SDKs call under the hood. You can also call it directly with `curl`, `fetch`, or any HTTP client. For MCP-based access (no HTTP calls needed), see the [MCP](https://xmemory.ai/mcp/) guide. All endpoints live under `https://api.xmemory.ai`, accept JSON, and return JSON. Every request except `/healthz` must send your xmemory API key as a Bearer token: `Authorization: Bearer `. Get your API key from the [xmemory console](https://console.xmemory.ai). Copy and securely store it — never share your API key publicly. --- #### Response envelope Every endpoint wraps its result in a standard envelope: ```json { "ids": [""], "items": [ { "…": "the actual payload" } ], "errors": [], "console_url": "https://console.xmemory.ai/…" } ``` On a **successful** response, the per-endpoint shapes documented below (`write_id`, `reader_result`, `data_schema`, …) are the object inside **`items[0]`** — a raw HTTP caller must read them there, not at the top level. `ids` collects created resource IDs, `errors` any non-fatal errors, and `console_url` links to the relevant view in the [console](https://console.xmemory.ai). (The [Python](https://xmemory.ai/python/) and [TypeScript](https://xmemory.ai/typescript/) SDKs unwrap this for you.) Schema-evolution endpoints report failures differently: they return a structured error body at the top level rather than in this envelope (see [Structured errors](https://xmemory.ai/api/#structured-errors)). --- #### Try it Write something, then read it back — two `curl` calls: ```bash # Write curl -X POST https://api.xmemory.ai/instances/$XMEM_INSTANCE_ID/write \ -H "Authorization: Bearer $XMEM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "text": "Alice Johnson works at Acme Corp. Her email is alice@acme.com.", "extraction_logic": "deep" }' # Read curl -X POST https://api.xmemory.ai/instances/$XMEM_INSTANCE_ID/read \ -H "Authorization: Bearer $XMEM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "What is Alice'\''s email?" }' # → {"ids": [], "items": [{"reader_result": {"answer": "alice@acme.com"}}], "errors": [], "console_url": "…"} ``` The rest of this page covers every endpoint in detail. The data-plane endpoints (write, read, extract, describe) come first; if you don't have an instance yet, jump to [Clusters](#clusters) and [Instances](#instances) to create one. --- #### Writing Send raw text; xmemory extracts typed objects from it and persists them. `write` blocks until committed; `write_async` returns immediately and you poll `write_status` for completion. Both endpoints alternatively accept `structured_mutations` for deterministic, extraction-free edits — see [Structured writes](#structured-writes). **`POST /instances/{instance_id}/write`** — Persist data — extract structured objects from text, or apply explicit structured mutations (blocks until committed) Parameters: - `text` (`string`) — Free-form text to extract from. Exactly one of `text` / `structured_mutations` must be provided. - `structured_mutations` (`array`) — Ordered list of deterministic, LLM-free create/update/delete mutations of objects and relations — see [Structured writes](#structured-writes). Mutually exclusive with `text`. - `extraction_logic` (`string`) — `"fast"` (default) or `"deep"`. Text writes only. Defaults to `"fast"`. - `trace_id` (`string`) — Caller-supplied ID echoed back on the response for log correlation. - `session_id` (`string`) — Session ID for grouping related calls in tracing. - `use_diff_engine` (`boolean`) — Override the instance's diff-engine setting for this write. Text writes only. Returns: - `write_id` (`string`) — ID of the committed write. - `cleaned_objects` (`object`) — The stored objects, as `{ objects: [...], relations: [...] }`. - `changes` (`object`) — What the write did, grouped into `created` / `updated` / `deleted` (each `{ objects: [...], relations: [...] }`; updated entries carry per-field old/new values). - `trace_id` (`string`) — Echoed trace id for log correlation. Example response: ```json { "write_id": "...", "cleaned_objects": { "objects": [], "relations": [] }, "changes": { "created": { "objects": [], "relations": [] }, "updated": { "objects": [], "relations": [] }, "deleted": { "objects": [], "relations": [] } }, "trace_id": "..." } ``` **`POST /instances/{instance_id}/write_async`** — Same write pipeline as write, but returns immediately instead of blocking Parameters: - `text` (`string`) — Free-form text to extract from. Exactly one of `text` / `structured_mutations` must be provided. - `structured_mutations` (`array`) — Ordered list of deterministic, LLM-free create/update/delete mutations — see [Structured writes](#structured-writes). Mutually exclusive with `text`. - `extraction_logic` (`string`) — `"fast"` (default) or `"deep"`. Text writes only. Defaults to `"fast"`. - `trace_id` (`string`) — Caller-supplied ID echoed back for log correlation. - `session_id` (`string`) — Session ID for grouping related calls in tracing. - `use_diff_engine` (`boolean`) — Override the instance's diff-engine setting for this write. Text writes only. Returns: - `write_id` (`string`) — ID to poll with `writeStatus`. Do not read immediately after an async write — the data may not be committed yet. Example response: ```json { "write_id": "..." } ``` **`POST /instances/{instance_id}/write_status`** — Check whether an async write has finished Parameters: - `write_id` (`string`, required) — ID returned by `writeAsync`. Returns: - `write_status` (`string`) — One of `queued`, `processing`, `completed`, `failed`, `not_found` — plus the intermediate two-phase-pipeline states `extracting`, `extracted`, `applying`. Treat anything other than `completed` / `failed` / `not_found` as still in flight. - `error_detail` (`string`) — Human-readable failure detail when `write_status` is `failed`; otherwise null. - `error` (`object`) — Structured error (`code`, `message`) when the write failed; otherwise null. - `completed_at` (`string`) — ISO-8601 timestamp when the write finished; null until then. Example response: ```json { "write_id": "...", "write_status": "completed", "error_detail": null, "error": null, "completed_at": "2025-03-16T12:34:56Z" } ``` ##### Structured writes When you already know exactly what to store, skip extraction entirely: pass `structured_mutations` instead of `text` (exactly one of the two — the request is rejected if both or neither are present). Each mutation is applied deterministically, with no LLM involved, making structured writes fast and exactly repeatable. A mutation is tagged `object_mutation` or `relation_mutation` and carries exactly one operation — `create`, `update`, or `delete`: ```bash curl -X POST https://api.xmemory.ai/instances/$XMEM_INSTANCE_ID/write \ -H "Authorization: Bearer $XMEM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "structured_mutations": [ { "object_mutation": { "object_type": "person", "create": { "key": { "email": "alice@acme.com" }, "values": { "name": "Alice Johnson" } } } }, { "relation_mutation": { "relation_type": "works_at", "create": { "endpoints": [ { "object_name": "person", "key": { "email": "alice@acme.com" } }, { "object_name": "company", "key": { "name": "Acme Corp" } } ] } } } ] }' ``` **Addressing objects.** `key` identifies an object by its user-defined primary-key fields (as above) — or, for `update` / `delete`, by `{ "xuid": "..." }` (the server-generated unique ID returned in `changes`). `create` never takes an `xuid`; it is generated server-side. **Object operations.** - `create` — `key` (primary-key fields) plus `values` (the remaining fields). - `update` — `key` plus `values`; a `null` value **clears** that field. - `delete` — `key` only. **Relation operations.** - `create` — `endpoints` (one per relation role: `object_name` + that object's `key`) plus optional own-field `values`. - `update` — address by `endpoints`, or by `key: { "xuid": ... }` when endpoints are ambiguous; `values` as for objects. - `delete` — match by `endpoints` (a subset is allowed) or `key`; deleting more than one matched row requires `"allow_bulk_delete": true`. **Ordering and batches.** Mutations apply in list order, and later mutations may reference objects created earlier in the same batch (e.g. create a `person`, then a relation pointing at it). Multiple mutations of the *same* object or relation in one batch follow a strict contract: compatible sequences compose (create then update applies the update to the created row; update then delete nets out to a delete; duplicate deletes are no-ops), while contradictory ones — create then delete, or anything after a delete — are rejected with a "split into separate writes" error rather than silently reordered. **Response.** The same shape as a text write; `changes` reports exactly what was `created` / `updated` / `deleted`, with per-field old/new values on updates. `write_async` accepts `structured_mutations` identically — poll `writeStatus` and read `changes` from the completed status. **Availability.** Structured writes can be disabled per deployment; when off, the request fails with `"Structured writes are not enabled on this deployment."` --- #### Reading Ask a question in natural language — xmemory searches the instance's stored objects and returns a synthesized answer. **`POST /instances/{instance_id}/read`** — Query the instance in natural language Parameters: - `query` (`string`, required) — Natural-language question. - `mode` (`string`) — `"single-answer"` (default), `"xresponse"`, or `"raw-tables"`. Defaults to `"single-answer"`. - `scope` (`object`) — Restrict the read to specific objects: `{ "objects": [...], "relations_scope": "no_relations" | "all_relations" }`. Defaults to `no_relations` (objects only). See [Scoped reads](https://xmemory.ai/api/#scoped-reads). - `return_sql` (`boolean`) — Include the executed SQL in the response (`sql`). Defaults to `false`. - `skip_suggestion_capture` (`boolean`) — Skip recording read-gap signals for the schema-suggestion engine. Defaults to `false`. - `trace_id` (`string`) — Caller-supplied ID echoed back for log correlation. - `session_id` (`string`) — Session ID for grouping related calls in tracing. Returns: The `reader_result` shape depends on the mode: | Mode | `reader_result` | Best for | |---|---|---| | `single-answer` | `{"answer": "Bob Lee joined last Monday."}` | Natural-language answers | | `xresponse` | `{"objects": [...], "relations": [...]}` | Structured data | | `raw-tables` | `{"tables": [...]}` | Raw SQL results | Also returned: `sql` (the executed SQL, when `return_sql: true`), `pending_suggestions` (count of pending schema improvements), and `trace_id`. --- #### Scoped reads Restrict a query to a specific set of objects in your instance, limiting data access to only what you specify. Useful for multi-tenant systems, privacy controls, and compliance requirements. ##### Request format Add a `scope` field to the `/read` endpoint: ```json { "query": "What is their email?", "scope": { "objects": [ { "type": "Person", "key": {"email": "alice@example.com"} } ], "relations_scope": "no_relations" } } ``` ##### Scope structure ###### `objects` (required) List of one or more objects to query. Each object has: - `type` (string): The object type (e.g., `"Person"`, `"Order"`). - `key` (object): Identify the object by its primary key fields (e.g., `{"email": "alice@example.com"}`). ###### `relations_scope` (optional, default: `"no_relations"`) Control whether to include relations that these objects are part of: - `"no_relations"`: Only the specified objects. - `"all_relations"`: Include relations these objects are part of. ##### Examples ###### Query a single object ```bash curl -X POST https://api.xmemory.ai/instances/your-instance-id/read \ -H "Authorization: Bearer $XMEM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "What is their email and phone number?", "scope": { "objects": [ {"type": "Person", "key": {"name": "Alice Johnson"}} ] } }' ``` ###### Query multiple objects with relations ```bash curl -X POST https://api.xmemory.ai/instances/your-instance-id/read \ -H "Authorization: Bearer $XMEM_API_KEY" \ -H "Content-Type: application/json" \ -d '{ "query": "Which projects do these people work on together?", "scope": { "objects": [ {"type": "Person", "key": {"email": "alice@example.com"}}, {"type": "Person", "key": {"email": "bob@example.com"}} ], "relations_scope": "all_relations" } }' ``` **Semantics:** The scope is limited to the two specified Person objects. With `"relations_scope": "all_relations"`, relations these Person objects are part of become visible. ##### Error handling If the scope is invalid, the API returns HTTP 400: ```json { "status": "error", "error_message": "Unknown scope object type 'Foo'; valid types are Person, Project" } ``` --- #### Extracting Preview the objects a `write` would produce — same extraction pipeline, but nothing is stored. **`POST /instances/{instance_id}/extract`** — Extract structured objects from text without storing them — preview what a write would produce Parameters: - `text` (`string`, required) — Free-form text to extract from. - `extraction_logic` (`string`) — `"fast"` (default) or `"deep"`. Same parameters as write. Defaults to `"fast"`. - `trace_id` (`string`) — Caller-supplied ID echoed back for log correlation. - `session_id` (`string`) — Session ID for grouping related calls in tracing. Returns: - `objects_extracted` (`object`) — The extracted (but not stored) objects, as `{ objects: [...], relations: [...] }`. - `trace_id` (`string`) — Echoed trace id for log correlation. Example response: ```json { "objects_extracted": { "objects": [], "relations": [] }, "trace_id": "..." } ``` --- #### Describing Return the agent-facing tool descriptions for an instance, enriched with its live schema. **`GET /instances/{instance_id}/describe`** — Return agent-facing tool descriptions enriched with the instance's actual schema Returns: - `instance_id` (`string`) — The instance the tools operate on. - `instance_name` (`string`) — Human-readable instance name. - `schema_summary` (`string`) — Human-readable description of the instance's schema, suitable for including in an LLM system prompt. - `tools` (`array`) — Tool descriptions; only includes tools the caller has permission to use. - `purpose` (`string`) — What this memory is for — the instance description, under the name the agent-facing surfaces give it. `null` when the instance has none. - `owner_instructions` (`string`) — The standing preference set for how agents should use this instance, rendered verbatim. Free text set by anyone holding edit permission on the instance, so label it with its provenance rather than presenting it as your own words when you put it in a prompt. - `usage_brief` (`string`) — Generated from the instance's schema. `null` until it has been generated, and cleared again by a schema change, so treat a null here as ordinary rather than as an error. Example response: ```json { "instance_id": "...", "instance_name": "contacts", "schema_summary": "This instance tracks contacts with name, email, ...", "purpose": "who we have talked to and how to reach them", "owner_instructions": "Prefer updating an existing record over creating a near-duplicate.", "usage_brief": null, "tools": [ { "name": "write", "description": "Extract structured data from text and persist it.", "when_to_use": "When you need to store new facts ...", "parameters": [ { "name": "text", "type": "string", "description": "...", "required": true }, { "name": "extraction_logic", "type": "string", "description": "...", "required": false, "enum": ["fast", "deep"], "default": "fast" } ], "http_method": "POST", "http_path": "/instances/.../write" } ] } ``` --- #### Clusters Clusters are the top-level container that holds instances. List the clusters you can access, or fetch one by ID. **`GET /clusters`** — List all clusters accessible to the authenticated user Returns: An array of clusters, each `{ id, org_id, name, description }`. Example request: ```bash curl https://api.xmemory.ai/clusters \ -H "Authorization: Bearer $XMEM_API_KEY" ``` **`GET /clusters/{cluster_id}`** — Get a single cluster by ID Returns: A single cluster `{ id, org_id, name, description }`. Example request: ```bash curl https://api.xmemory.ai/clusters/$XMEM_CLUSTER_ID \ -H "Authorization: Bearer $XMEM_API_KEY" ``` --- #### Schema generation Turn a plain-language description into a typed xmemory schema — the first step before creating an instance. **`POST /clusters/{cluster_id}/instances/generate_schema`** — Generate an xmemory schema from a plain-language description Parameters: - `schema_description` (`string`, required) — Plain-language description of the data to track. - `current_yml_schema` (`string`) — Existing schema to refine; runs the *enhance* path and returns a structured migration plan so the change can be applied safely (renames preserve data, etc.). Returns: - `data_schema` (`object`) — The generated schema. When `current_yml_schema` is omitted (create-from-scratch), only `data_schema` is returned. - `migration_plan` (`object`) — Structured plan to apply the change; pass it to the dry-run and update endpoints. See [Schema evolution](#schema-evolution). - `summary` (`string`) — Human-readable summary of the change. - `warnings` (`array`) — Non-fatal warnings raised while generating or enhancing the schema. - `repair_log` (`array`) — Automatic corrections applied to make the generated schema valid. Example response: ```json { "data_schema": { "...": "..." }, "migration_plan": { "ops": [ { "op_type": "rename_field", "object_name": "person", "old_name": "mail", "new_name": "email" } ] }, "summary": "Rename person.mail to person.email.", "warnings": [], "repair_log": [] } ``` --- #### Instances An instance is a memory store with a typed schema, living inside a cluster — create one before you write or read. These endpoints manage its whole lifecycle: create, list, fetch, update its schema or metadata, and delete. **`POST /clusters/{cluster_id}/instances`** — Create an instance from a generated schema Parameters: - `name` (`string`, required) — Instance name. - `description` (`string`) — Description. - `instance_schema` (`object`, required) — Use `"yml": { "value": "..." }` for YAML schemas, or `"json_schema": { "value": "..." }` for JSON. - `schema_description` (`string`) — Plain-language description stored with the instance. - `generate_trace_id` (`string`) — Trace id linking this instance to the `generateSchema` call that produced its schema. Returns: - `id` (`string`) — The new instance ID. Save it — you'll pass it with every subsequent call. - `cluster_id` (`string`) — The cluster the instance was created in. - `name` (`string`) — Instance name. - `description` (`string`) — Instance description, if set. - `data_schema` (`object`) — The instance's active schema. Example response: ```json { "id": "...", "cluster_id": "...", "name": "contacts", "description": null, "data_schema": { "...": "..." } } ``` **`GET /instances`** — List all instances accessible to the authenticated user Returns: An array of instances (see `getInstance` for the per-item shape). Example request: ```bash curl https://api.xmemory.ai/instances \ -H "Authorization: Bearer $XMEM_API_KEY" ``` **`GET /instances/{instance_id}`** — Get a single instance by ID Returns: - `id` (`string`) — Instance ID. - `cluster_id` (`string`) — The cluster the instance belongs to. - `name` (`string`) — Instance name. - `description` (`string`) — Instance description, if set. - `created_on` (`string`) — ISO-8601 creation timestamp. - `created_by_email` (`string`) — Email of the account that created the instance. - `data_schema` (`object`) — The instance's active schema. **`GET /instances/{instance_id}/schema`** — Get the current schema of an instance Returns: - `data_schema` (`object`) — The instance's active schema (objects, fields, relations). **`PUT /instances/{instance_id}/schema`** — Update the schema of an existing instance Parameters: - `instance_schema` (`object`, required) — The new schema. A purely additive change (new objects/fields/relations) needs only this field. - `migration_plan` (`object`) — Required for a non-additive change (rename, remove, type change); pass the plan from `generateSchema`. - `confirm_destructive` (`boolean`) — Set `true` for any op that drops data. Defaults to `false`. Returns: - `migration_id` (`string`) — Present when a migration ran (a non-additive change). - `prior_version` (`integer`) — Schema version before this update. - `new_version` (`integer`) — Schema version after this update. - `migration_warnings` (`array`) — Non-fatal warnings emitted by the migration. Errors: A non-additive change sent without a plan is rejected with `non_additive_change_requires_plan`. An op that drops data without `confirm_destructive: true` returns `destructive_confirmation_required`. See [Structured errors](#structured-errors). **`PUT /instances/{instance_id}`** — Update instance metadata (name and description) Parameters: - `name` (`string`) — New instance name. - `description` (`string`) — Updated description. - `agent_owner_instructions` (`string`) — Standing preference for how agents should use this instance, rendered verbatim wherever it is shown. Max 2000 characters. Omit the key to leave the stored value alone; send `null` to clear it. - `expected_owner_instructions_epoch` (`integer`) — The `agent_owner_instructions_epoch` the edit was composed from. Sent alongside an instructions edit, it causes a save that raced another writer to be refused rather than applied. - `agent_surfaces` (`array`) — Agent surfaces this instance is expected to be used from: `claude_code`, `codex`, `claude_desktop`, `chatgpt`. Advisory — it orders the connect steps an instance is offered and grants no access. Carried here as well as on `PATCH` so a console saving the whole edit form does it in one call. - `agent_default_binding_tier` (`string`) — Default prominence for a binding: `autoload` or `available`. Advisory. - `agent_engagement_hints` (`array`) — Short routing phrases describing when an agent should engage this instance. At most 16, each at most 200 characters. Returns: - `id` (`string`) — Instance ID. - `name` (`string`) — Updated instance name. - `description` (`string`) — Updated description. Errors: When `expected_owner_instructions_epoch` is sent and the stored instructions changed after it was read, the save is refused with `409` and `INVALID_INPUT` rather than discarding the other edit. See [Structured errors](#structured-errors). **`PATCH /instances/{instance_id}`** — Update some instance metadata fields, leaving the rest alone Parameters: - `name` (`string`) — New instance name. - `description` (`string`) — Updated description (send `null` to clear). - `agent_surfaces` (`array`) — Agent surfaces this instance is expected to be used from: `claude_code`, `codex`, `claude_desktop`, `chatgpt`. Advisory — it orders the connect steps an instance is offered and grants no access. - `agent_default_binding_tier` (`string`) — Default prominence for a binding: `autoload` or `available`. Advisory. - `agent_engagement_hints` (`array`) — Short routing phrases describing when an agent should engage this instance. At most 16, each at most 200 characters. - `agent_owner_instructions` (`string`) — Standing preference for how agents should use this instance. Max 2000 characters. Prefer `PUT` for this field — it is the only route that accepts `expected_owner_instructions_epoch`, so an edit sent here cannot be guarded against a concurrent one. Returns: - `id` (`string`) — Instance ID. - `name` (`string`) — Instance name. - `description` (`string`) — Instance description. Errors: `name` may be updated but not cleared — sending `"name": null` is rejected. See [Structured errors](#structured-errors). **`DELETE /instances/{instance_id}`** — Delete an instance Returns: A list of deleted instance IDs. --- #### Schema evolution xmemory can change a live schema without losing data. There are two paths, both ending in an atomic migration with an audit record: a **direct migration** you drive yourself, and a **suggestion engine** that proposes improvements from real read traffic. Additive-only callers are unaffected — the endpoints above keep working as before. **`POST /instances/{instance_id}/migrations/dry_run`** — Preview a migration without applying it Parameters: - `instance_schema` (`object`, required) — The proposed new schema (same body shape as the schema update). - `migration_plan` (`object`) — Plan for a non-additive change; pass the plan from `generateSchema`. - `confirm_destructive` (`boolean`) — Set `true` for any op that drops data. Defaults to `false`. Returns: - `statements` (`array`) — The planned DDL statements. - `current_version` (`integer`) — The instance's current schema version (the migration would advance it). - `plan_summary` (`object`) — Counts of ops by type: `{ count_by_op_type, total }`. - `requires_metadata_sync` (`boolean`) — Whether applying would also resync derived metadata. Example response: ```json { "status": "ok", "instance_id": "...", "current_version": 4, "statements": ["ALTER TABLE person RENAME COLUMN mail TO email"], "warnings": [], "plan_summary": { "count_by_op_type": { "rename_field": 1 }, "total": 1 }, "requires_metadata_sync": true } ``` Errors: A non-additive change without a plan returns `non_additive_change_requires_plan`; a data-dropping op without confirmation returns `destructive_confirmation_required`. See [Structured errors](#structured-errors). **`GET /instances/{instance_id}/migrations`** — List applied migrations, newest first Parameters: - `limit` (`integer`) — Range 1–200. Defaults to `50`. - `before_id` (`string`) — UUID pagination cursor. - `include_yaml` (`boolean`) — Adds before/after schema snapshots. Defaults to `false`. Returns: - `items` (`array`) — Migration records (see `getMigration` for the per-item shape). `source` is `direct` or `suggestion_engine`. - `next_before_id` (`string`) — Pass as `before_id` to fetch the next (older) page; null on the last page. - `has_more` (`boolean`) — Whether more records exist beyond this page. Example response: ```json { "status": "ok", "items": [ { "id": "...", "applied_at": "...", "source": "suggestion_engine", "decided_by": "...", "prior_version": 3, "new_version": 4, "ops": [ ], "ops_summary": { } } ], "next_before_id": null, "has_more": false } ``` **`GET /instances/{instance_id}/migrations/{migration_id}`** — Get a single migration record Parameters: - `include_yaml` (`boolean`) — Adds the before/after schema snapshots (`yaml_before` / `yaml_after`). Defaults to `false`. Returns: - `id` (`string`) — Migration record ID. - `applied_at` (`string`) — ISO-8601 timestamp the migration was applied. - `source` (`string`) — `direct` or `suggestion_engine`. - `prior_version` (`integer`) — Schema version before the migration. - `new_version` (`integer`) — Schema version after the migration. - `ops` (`array`) — The migration operations that were applied. - `ops_summary` (`object`) — Counts of ops by type. - `yaml_before` (`string`) — Schema YAML before the migration (only when `include_yaml: true`). - `yaml_after` (`string`) — Schema YAML after the migration (only when `include_yaml: true`). ##### Suggestion engine — review → decide → apply The engine surfaces a single rolling proposal per instance, derived on demand from accumulated read-gap signals. The minimum flow is three calls. **`POST /instances/{instance_id}/suggestions/review`** — Return the consolidated proposal and a proposal_version concurrency token Parameters: - `session_id` (`string`) — Body may be `{}` or `{ "session_id": "..." }`. Returns: - `proposal` (`object`) — Holds `proposal_version` (concurrency token), `schema_version`, and `items`. Example response: ```json { "status": "ok", "instance_id": "...", "proposal": { "proposal_version": "ab12...", "schema_version": 4, "items": [ { "item_fingerprint": "fp1", "op": { "op_type": "add_field", "object_name": "person", "field_name": "phone", "field_type": "str" }, "rationale": "queried but missing", "frequency": 3, "depends_on": [], "evidence_query_samples": ["what is bob's phone"] } ] } } ``` Errors: When a migration is already running, the response is `{ "status": "evolution_in_progress", "retry_after_seconds": 5 }` — back off and retry. **`POST /instances/{instance_id}/suggestions/decide`** — Record an accept / reject / defer per item in one batch Parameters: - `proposal_version` (`string`, required) — The `proposal_version` from review. - `decisions` (`array`, required) — One `{ "item_fingerprint": "...", "decision": "accept" | "reject" | "defer" }` per item. - `session_id` (`string`) — Session ID for tracing. Returns: The recorded decisions, advisory dependency `warnings`, and a `next_proposal_version` you can pass straight to apply. Example response: ```json { "proposal_version": "ab12...", "decisions": [ { "item_fingerprint": "fp1", "decision": "accept" } ] } ``` Errors: A `proposal_version` that no longer matches the current proposal returns `stale_proposal_version`; malformed decisions return `invalid_decision_input`. See [Structured errors](#structured-errors). **`POST /instances/{instance_id}/suggestions/apply`** — Commit accepted decisions as one migration Parameters: - `proposal_version` (`string`, required) — The `next_proposal_version` from decide. - `session_id` (`string`) — Session ID for tracing. Returns: On success, `{ "status": "ok", "migration_id": "...", "prior_version": 4, "new_version": 5, "applied_items": ["fp1"], "summary": "..." }`, or `{ "status": "nothing_to_apply" }` if no accepted items remained. Example response: ```json { "status": "ok", "migration_id": "...", "prior_version": 4, "new_version": 5, "applied_items": ["fp1"], "summary": "..." } ``` Errors: A stale token returns `stale_proposal_version`; an unmet dependency returns `dependency_closure_failed`; a failed commit returns `apply_failed`. See [Structured errors](#structured-errors). **`POST /instances/{instance_id}/pending-feedback/discard`** — Clear the pending schema-improvement backlog for this instance Returns: - `rows_discarded` (`integer`) — Number of pending feedback signals that were discarded. - `instance_id` (`string`) — The instance whose backlog was cleared. Example response: ```json { "status": "ok", "instance_id": "...", "rows_discarded": 12 } ``` Details: Bulk-supersedes the pending schema-improvement backlog. Idempotent — discarded signals never resurface, and calling it with nothing pending is a safe no-op. ##### Structured errors Schema-evolution endpoints return a structured error body with a 4xx/5xx status: ```json { "status": "error", "error_type": "stale_proposal_version", "error_message": "...", "details": { ... } } ``` `error_type` is one of: `non_additive_change_requires_plan`, `destructive_confirmation_required`, `stale_schema_version`, `migration_precondition_failed`, `migration_execution_failed`, `migration_not_found`, `stale_proposal_version`, `dependency_closure_failed`, `invalid_decision_input`, `apply_failed`, `instance_not_initialised`. The SDKs surface `error_type` as a structured error code. ##### Limits No backfill of historical data into new fields; no rollback of a committed migration (atomic abort on failure is the safety story); identity tightening (`change_object.new_primary_key`, `change_relation.new_keys`) is relax-only. --- #### Health check **`GET /healthz`** — Returns 200 if the API is reachable (no authentication required) Example request: ```bash curl https://api.xmemory.ai/healthz ``` --- #### Errors All endpoints return an error response on failure with the appropriate HTTP status code (`400`, `401`, `402`, `404`, `429`, `500`). Error bodies carry a structured `code` in `errors[0]`. **Branch on the `code`, not on the bare HTTP status** — the status alone does not say whether retrying is worthwhile. ##### Quota and rate limits | HTTP | `code` | Meaning | Retryable? | |---|---|---|---| | `402` | `QUOTA_EXCEEDED` | The tenant exhausted its plan's usage allowance (daily or monthly token quota). | No — only after the quota window resets. | | `429` | `RATE_LIMITED` | A genuine velocity/rate limit was hit (too many requests in a short window). | Yes — back off and retry, honoring `Retry-After`. | ###### `402` — `QUOTA_EXCEEDED` The tenant has used up its allotted tokens for the current window. The error body adds a `details` object: ```json { "errors": [ { "code": "QUOTA_EXCEEDED", "message": "Daily token quota exceeded.", "details": { "kind": "daily_quota_exceeded", "retry_after_seconds": 3600 } } ] } ``` - `details.kind` is `"daily_quota_exceeded"` or `"monthly_quota_exceeded"`. - `details.retry_after_seconds` is the number of seconds until the window resets, or `null` if the window is not resettable. - When the window is resettable, the server also sends an HTTP `Retry-After: ` header. This is **not** retryable in the short term. Surface a "usage limit reached — upgrade your plan" message to the user. Do not auto-retry; you may retry once the quota window has reset. ###### `429` — `RATE_LIMITED` A real rate limit — the client is sending requests too fast. This **is** retryable: back off and retry, honoring the HTTP `Retry-After: ` header when present. The official SDKs parse that header into a convenience attribute — `.retry_after` (Python, `int` seconds or `None`) / `.retryAfter` (TypeScript, `number` seconds or `undefined`) — on the raised error, for both this case and a resettable `402 QUOTA_EXCEEDED`. ### Python Canonical URL: https://xmemory.ai/python/ Markdown URL: https://xmemory.ai/python/index.html.md The `xmemory-ai` package gives your Python code persistent, structured memory. Write free-form text, have it automatically extracted into typed objects, and query it back in natural language. For MCP-based integration (no SDK needed), see the [MCP](https://xmemory.ai/mcp/) guide. **API key:** To use xmemory APIs or integrations, you need an API key. Get one from the [xmemory console](https://console.xmemory.ai). Copy and securely store the key. Never share your API key publicly. The full method, data-type, enum, and exception listing is in the [Reference](#reference) at the bottom of this page. --- #### Installation ```bash pip install xmemory-ai ``` Requires Python 3.10+ and `pydantic>=2.0`. --- #### Quick start This is the full flow — from schema to stored knowledge to answers — in a single script: ```python import yaml from xmemory import XmemoryClient, SchemaType # Connect (reads XMEM_API_KEY from env if api_key is not passed) client = XmemoryClient(api_key="your-api-key") # List available clusters clusters = client.admin.list_clusters() cluster_id = clusters[0].id # Describe what you want to remember schema = client.admin.generate_schema( cluster_id, "Track contacts with name, email, company, and notes.", ) # Create a memory instance from that schema inst = client.admin.create_instance( cluster_id=cluster_id, name="contacts", schema_text=yaml.dump(schema.data_schema), schema_type=SchemaType.YML, ) # inst is an InstanceAPI handle — all subsequent calls go through it. # Write some information inst.write("Alice Johnson works at Acme Corp. Her email is alice@acme.com.") inst.write("Bob Lee is a designer at Globex. He joined last Monday.") # Read it back result = inst.read("What is Alice's email?") print(result.reader_result) # {"answer": "alice@acme.com"} result = inst.read("Who joined recently?") print(result.reader_result) # {"answer": "Bob Lee joined last Monday."} client.close() ``` Once you have an instance ID, skip the schema step on subsequent runs: ```python client = XmemoryClient(api_key="your-api-key") inst = client.instance("your-saved-instance-id") ``` --- #### Configuration | Parameter | Env var | Default | Description | |---|---|---|---| | `api_key` | `XMEM_API_KEY` | `None` | API key for authentication | | `url` | `XMEM_API_URL` | `https://api.xmemory.ai` | API base URL | | `timeout` | — | `60` | Default request timeout in seconds | | `http_client` | — | `None` | External `httpx.Client` (you manage its lifecycle) | All parameters are keyword-only. The API key and URL fall back to their environment variables when not passed. ##### Context manager ```python with XmemoryClient(api_key="your-api-key") as client: inst = client.instance("your-instance-id") result = inst.read("What is Alice's email?") ``` --- #### Async client `AsyncXmemoryClient` exposes the **same methods with the same signatures** as the sync client — three things differ, and that's the whole story: - **`await` every call** — `await inst.read(...)`, `await client.admin.create_instance(...)`. - **`aclose()` instead of `close()`** — or just use the context manager. - **`async with` instead of `with`.** ```python from xmemory import AsyncXmemoryClient async def main(): async with AsyncXmemoryClient(api_key="your-api-key") as client: inst = client.instance("your-instance-id") result = await inst.read("What is Alice's email?") print(result.reader_result) # exiting `async with` calls aclose() for you (or: await client.aclose()) import asyncio asyncio.run(main()) ``` --- #### Writing Send free-form text — xmemory extracts structured objects according to your schema and merges them into the knowledge graph. ```python inst = client.instance("your-instance-id") resp = inst.write("Carol is a senior engineer at Initech. Her email is carol@initech.com.") print(resp.changes) # what the write created / updated / deleted print(resp.trace_id) # request trace id when available ``` `WriteResult`, `ReadResult`, and `ExtractResult` include `trace_id`, which is useful when you want to correlate SDK calls with API logs. ##### Extraction logic Control the speed/accuracy tradeoff — pass `extraction_logic=ExtractionLogic.DEEP` to override the `FAST` default: | Value | When to use | |---|---| | `FAST` | High-volume, low-stakes writes (default) | | `DEEP` | Important or complex information | ##### Async writes For latency-sensitive code, enqueue a write and return immediately: ```python resp = inst.write_async("Dave manages the London office.") print(resp.write_id) # use this to poll for completion ``` Then check the status: ```python status = inst.write_status(resp.write_id) print(status.write_status) # → WriteQueueStatus.QUEUED | PROCESSING | EXTRACTING | EXTRACTED | APPLYING | COMPLETED | FAILED | NOT_FOUND ``` Do not call `read` immediately after `write_async` — the data may not be committed yet. Poll with `write_status` until `COMPLETED`, or use `write` (synchronous) when you need to read right after. ##### Structured writes When you already know exactly what to store, skip extraction: pass `structured_mutations` instead of `text` (exactly one of the two — the client raises `ValueError` otherwise). Each mutation is a deterministic, LLM-free create / update / delete of an object or relation, applied in list order — later mutations may reference objects created earlier in the same batch. ```python from xmemory import ObjectCreate, ObjectMutation, ObjectUpdate, RelationMutation resp = inst.write(structured_mutations=[ ObjectMutation( object_type="person", create=ObjectCreate(key={"email": "carol@initech.com"}, values={"name": "Carol"}), ), RelationMutation( relation_type="works_at", create={"endpoints": [ {"object_name": "person", "key": {"email": "carol@initech.com"}}, {"object_name": "company", "key": {"name": "Initech"}}, ]}, ), ]) print(resp.changes) # exactly what was created / updated / deleted ``` Each mutation carries exactly one of `create` / `update` / `delete`. `key` identifies an object by its user-defined primary-key fields, or (for update/delete) by `{"xuid": ...}`. A `None` value in `values` **clears** that field: ```python inst.write(structured_mutations=[ ObjectMutation( object_type="person", update=ObjectUpdate(key={"email": "carol@initech.com"}, values={"role": None}), ), ]) ``` Plain dicts in the API wire form work anywhere a model does — handy for payloads produced by an LLM: ```python inst.write(structured_mutations=[ {"object_mutation": {"object_type": "person", "delete": {"key": {"email": "carol@initech.com"}}}}, ]) ``` `write_async` accepts `structured_mutations` identically — poll with `write_status` as usual. See the [API reference](https://xmemory.ai/api/#structured-writes) for the full wire format, relation addressing (`endpoints`, `allow_bulk_delete`), and in-batch ordering rules. --- #### Reading Ask questions in natural language. xmemory translates them into SQL against the knowledge graph and returns a formatted answer. ```python resp = inst.read("Who works at Acme Corp?") print(resp.reader_result) print(resp.trace_id) ``` ##### Read modes ```python from xmemory import ReadMode # Plain-text answer (default) resp = inst.read("What is Alice's email?", read_mode=ReadMode.SINGLE_ANSWER) # → {"answer": "alice@acme.com"} # Structured objects and relations resp = inst.read("Show all contacts", read_mode=ReadMode.XRESPONSE) # → {"objects": [...], "relations": [...]} # Raw SQL result sets resp = inst.read("List all contacts", read_mode=ReadMode.RAW_TABLES) # → {"tables": [...]} ``` ##### Scoped reads Restrict a query to a specific set of objects. Useful for multi-tenant systems, privacy controls, and compliance requirements. ```python from xmemory import ReadScope, ScopeObjectRef, RelationsScope scope = ReadScope( objects=[ ScopeObjectRef(type="Person", key={"name": "Alice Johnson"}) ], relations_scope=RelationsScope.NO_RELATIONS, ) resp = inst.read("What is their email?", scope=scope) print(resp.reader_result) ``` ###### Multiple objects with relations ```python scope = ReadScope( objects=[ ScopeObjectRef(type="Person", key={"email": "alice@example.com"}), ScopeObjectRef(type="Person", key={"email": "bob@example.com"}), ], relations_scope=RelationsScope.ALL_RELATIONS, ) resp = inst.read("Which projects do these people work on together?", scope=scope) ``` ###### Scope options - `NO_RELATIONS` (default): Only the specified objects. - `ALL_RELATIONS`: Include junction tables between objects. ###### Error handling ```python from xmemory import XmemoryAPIError try: resp = inst.read("...", scope=scope) except XmemoryAPIError as e: if e.status == 400: print(f"Invalid scope: {e.message}") else: raise ``` --- #### Extracting (without writing) Preview what xmemory would extract from a piece of text, without storing anything: ```python resp = inst.extract("Dave manages the London office.") print(resp.objects_extracted) print(resp.trace_id) ``` Accepts the same `extraction_logic` parameter as `write`. --- #### Describing (agent tool discovery) The `describe()` method returns agent-facing tool descriptions enriched with the instance's actual schema. Use it to tell an LLM what tools are available and how to call them. ```python desc = inst.describe() # Plain text — inject into a system prompt print(desc.as_text()) # Anthropic tool-use format tools = desc.as_anthropic_tools() # OpenAI function-calling format tools = desc.as_openai_tools() ``` Results are cached locally for 5 minutes. To force a refresh (e.g. after updating the schema): ```python inst.clear_describe_cache() desc = inst.describe() ``` `as_text()` shows tools as method signatures by default. Pass `include_http=True` to also show HTTP method and path for raw REST callers. `describe()` also carries what the instance is *for*: ```python desc.purpose # what the memory is for (the instance description) desc.owner_instructions # the standing preference set for it, verbatim desc.usage_brief # generated from the schema; None until generated ``` `as_text()` includes the first two — the purpose under the instance line, the standing preference above the schema summary. `usage_brief` is left out of it, since it restates the schema summary already there; read the attribute if you want it. Both are free text set by anyone holding edit permission on the instance, so `as_text()` labels each with where it came from rather than presenting it as the library's own words. Those labels state provenance; they are not a security boundary. If you inject this into a system prompt you are still handling text you do not control. --- #### Cluster and instance management These methods live on `client.admin` and cover the control plane: discovering clusters, generating schemas, creating instances, and evolving them safely. ##### Clusters ```python clusters = client.admin.list_clusters() for c in clusters: print(f"{c.id}: {c.name}") ``` ##### Schema generation Describe what you want to track in plain language: ```python schema = client.admin.generate_schema( cluster_id, "Track user preferences, open tasks with priorities, and conversation history.", ) print(schema.data_schema) ``` Object names use **CamelCase** (`UserPreferences`, `OpenTask`). The generation endpoint handles naming conventions automatically. To evolve an existing schema (rename / remove / type change) rather than create one, use `enhance_schema` — it returns a structured migration plan; see [Schema evolution](#schema-evolution). ##### Instances Create an instance from a generated schema: ```python import yaml from xmemory import SchemaType inst = client.admin.create_instance( cluster_id=cluster_id, name="my-memory", schema_text=yaml.dump(schema.data_schema), schema_type=SchemaType.YML, description="Optional description", ) # inst is a bound InstanceAPI — use inst.write(), inst.read(), etc. ``` For a purely additive change (new objects/fields/relations), update the schema directly — existing data is preserved: ```python new_schema = client.admin.generate_schema( cluster_id, "Add an assignee field to tasks.", current_yml_schema=current_schema, ) client.admin.update_instance_schema( instance_id, yaml.dump(new_schema.data_schema), SchemaType.YML ) ``` List and delete instances: ```python instances = client.admin.list_instances() info = client.admin.get_instance(instance_id) client.admin.delete_instance(instance_id) ``` ##### Agent-facing instance metadata An instance can carry metadata that shapes how agents connect to it and what they do with it. Use `patch_instance_metadata` to set the advisory hints: every argument is independent — **omitting one leaves the stored value untouched**, and passing `None` clears it. ```python from xmemory import AgentSurface, BindingTier client.admin.patch_instance_metadata( instance_id, # Advisory hints — they seed what a connect flow proposes, and grant nothing. agent_surfaces=[AgentSurface.CLAUDE_CODE, AgentSurface.CODEX], agent_default_binding_tier=BindingTier.AUTOLOAD, agent_engagement_hints=["a convention is learned or corrected"], ) ``` Concurrent edits to these three are last-writer-wins by design: they only seed what a connect flow proposes, so the loser of a race re-applies a suggestion. `agent_owner_instructions` is not like that. Read it back from `InstanceInfo`. The hints come back as plain strings, so a value your server knows and your library version does not is returned rather than rejected: ```python info = client.admin.get_instance(instance_id) info.agent_owner_instructions info.agent_surfaces # e.g. ["claude_code", "codex"] info.agent_default_binding_tier # e.g. "autoload" ``` Set `agent_owner_instructions` through `update_instance_metadata`, not `patch_instance_metadata`. It is rendered to agents verbatim and a second writer edits it from the same screen, so a silently lost edit is a rule that stops being enforced — and `update_instance_metadata` is the only call that carries `expected_owner_instructions_epoch`. Pass the epoch you read the value at and the losing save is refused instead of applied: ```python info = client.admin.get_instance(instance_id) client.admin.update_instance_metadata( instance_id, info.name, info.description, agent_owner_instructions=(info.agent_owner_instructions or "") + "\nAlso: never paraphrase a rule.", expected_owner_instructions_epoch=info.agent_owner_instructions_epoch, ) ``` ##### Schema evolution Non-additive changes (rename / remove / type change) preserve data via a structured **migration plan**. Use `enhance_schema` to get the plan, `dry_run_migration` to preview the DDL, then `update_instance_schema` to apply it. `confirm_destructive=True` is required for ops that drop data. ```python current = client.admin.get_instance_schema(instance_id).data_schema # 1. Enhance — new schema + an executor-ready migration plan. enhanced = client.admin.enhance_schema( cluster_id, "Rename Person.mail to Person.email.", yaml.dump(current), ) print(enhanced.summary) for op in enhanced.migration_plan.ops: print(op) new_yaml = yaml.dump(enhanced.data_schema) # 2. Dry-run — preview the DDL, apply nothing. preview = client.admin.dry_run_migration( instance_id, new_yaml, SchemaType.YML, migration_plan=enhanced.migration_plan, ) print(preview.statements) # 3. Update — apply. A rename is non-destructive, so confirm_destructive stays False. info = client.admin.update_instance_schema( instance_id, new_yaml, SchemaType.YML, migration_plan=enhanced.migration_plan, confirm_destructive=False, ) print(info.migration_id, info.prior_version, "->", info.new_version) ``` Review applied migrations: ```python page = client.admin.list_migrations(instance_id, limit=20) for r in page.items: print(r.id, r.source, r.prior_version, "->", r.new_version) detail = client.admin.get_migration(instance_id, page.items[0].id, include_yaml=True) print(detail.yaml_before, detail.yaml_after) ``` Migration ops are exported as typed models (`MigrationPlan`, `AddField`, `RenameField`, `RemoveObject`, …). `ProposalItem.op` and `MigrationRecord.ops` stay as raw dicts for forward compatibility — use `parse_migration_op(...)` to validate them. ##### Suggestion engine (review → decide → apply) xmemory watches read traffic and, on demand, surfaces a single rolling proposal of schema improvements. The flow is three calls — never wrap them; the checkpoints are the point. ```python from xmemory import DecisionInput inst = client.instance(instance_id) # 1. Review — proposal + optimistic-concurrency token. review = inst.review_suggestions() if review.status == "evolution_in_progress": print(f"Migration in flight; retry in {review.retry_after_seconds}s") else: proposal = review.proposal for item in proposal.items: print(item.item_fingerprint, item.rationale, item.op) # 2. Decide — accept / reject / defer per item, in bulk. decided = inst.decide_suggestions( proposal.proposal_version, [DecisionInput(item_fingerprint=i.item_fingerprint, decision="accept") for i in proposal.items], ) # 3. Apply — commit accepted decisions as one migration. applied = inst.apply_pending_decisions(decided.next_proposal_version) print(applied.status, applied.summary) ``` --- #### Error handling All errors raise `XmemoryAPIError`. The exception carries an optional `.status` (HTTP status code), `.code` (structured error code, when the server returned one), `.details`, and `.retry_after` (int seconds parsed from the `Retry-After` header, `>= 0`, or `None`). ```python from xmemory import XmemoryAPIError, XmemoryHealthCheckError # Check connectivity try: client.check_health() except XmemoryHealthCheckError as e: print(f"API unreachable: {e}") # Handle operation errors try: inst.write("...") except XmemoryAPIError as e: print(f"Error (HTTP {e.status}): {e}") ``` `XmemoryHealthCheckError` is a subclass of `XmemoryAPIError`, so catching `XmemoryAPIError` covers both. The schema-evolution methods set `.code` to a structured error type you can match on — e.g. `stale_proposal_version`, `destructive_confirmation_required`, `non_additive_change_requires_plan`: ```python try: inst.apply_pending_decisions(token) except XmemoryAPIError as e: if e.code == "stale_proposal_version": review = inst.review_suggestions() # re-review and retry ``` The same `.code` branching separates a spent allowance from a genuine rate limit — match on `.code`, not on the status: ```python try: inst.write("...") except XmemoryAPIError as e: if e.code == "QUOTA_EXCEEDED": # Plan/usage allowance exhausted; e.details["kind"] is # "daily_quota_exceeded" or "monthly_quota_exceeded". # Not retryable now — surface "limit reached / upgrade your plan". # You may retry after e.details["retry_after_seconds"] (if not None). ... elif e.code == "RATE_LIMITED": # Genuine rate limit — back off and retry, honoring Retry-After. ... else: raise ``` For both `RATE_LIMITED` (429) and a resettable `QUOTA_EXCEEDED` (402), use `e.retry_after` — the seconds parsed from the `Retry-After` header (an `int >= 0`, or `None` when the header was absent or in HTTP-date form) — as your backoff delay. --- #### Reference The complete method, data-type, enum, and exception listing. Methods are grouped by where they live; expand a row for its parameters, returns, and details. ##### Client Constructors and client-level handles. **`XmemoryClient(url=None, *, timeout=60, api_key=None, http_client=None)`** — Create a sync client Parameters: - `url` (`str`) — API base URL; falls back to `XMEM_API_URL`. The only positional arg. Defaults to `None`. - `timeout` (`float`) — Default request timeout in seconds. Defaults to `60`. - `api_key` (`str`) — API key for authentication; falls back to `XMEM_API_KEY`. Defaults to `None`. - `http_client` (`httpx.Client`) — External HTTP client (you manage its lifecycle). Defaults to `None`. **`AsyncXmemoryClient(url=None, *, timeout=60, api_key=None, http_client=None)`** — Create an async client (same signature; methods are awaitable) Parameters: - `url` (`str`) — API base URL; falls back to `XMEM_API_URL`. The only positional arg. Defaults to `None`. - `timeout` (`float`) — Default request timeout in seconds. Defaults to `60`. - `api_key` (`str`) — API key for authentication; falls back to `XMEM_API_KEY`. Defaults to `None`. - `http_client` (`httpx.AsyncClient`) — External HTTP client (you manage its lifecycle). Defaults to `None`. **`client.admin`** — Control-plane accessor — clusters, schema generation, instances, schema evolution Returns: `client.admin` (`AsyncAdminAPI` on the async client) — the receiver for the admin / control-plane methods (clusters, schema generation, instances, schema evolution). **`client.instance(instance_id)`** — Get a handle for data operations on an instance Parameters: - `instance_id` (`str`, required) — The instance ID to operate on. Returns: A bound `InstanceAPI` handle (`AsyncInstanceAPI` on the async client) — call `write` / `read` / `extract` / … on it. **`client.check_health()`** — Raise XmemoryHealthCheckError if the API is unreachable Returns: `None` on success; raises `XmemoryHealthCheckError` otherwise. **`client.close()`** — Close the underlying HTTP client (no-op if you supplied your own) Details: **Async client:** use `await client.aclose()` instead — or prefer the context manager (`with` / `async with`). **`await client.aclose()`** — Async counterpart of close() (AsyncXmemoryClient) ##### Admin methods Control-plane methods on `client.admin` — clusters, schema generation, instances, and schema evolution. **`list_clusters(*, ids=None, timeout=None)`** — List clusters Parameters: - `ids` (`list[str]`) — Filter to specific cluster IDs. - `timeout` (`float`) — Per-call timeout in seconds. Returns: `list[ClusterInfo]` **`get_cluster(cluster_id, *, timeout=None)`** — Get a cluster by ID Parameters: - `cluster_id` (`str`, required) — The cluster ID to fetch. - `timeout` (`float`) — Per-call timeout in seconds. Returns: `ClusterInfo` **`generate_schema(cluster_id, schema_description, *, current_yml_schema=None, timeout=None)`** — Generate a schema from a plain-language description Parameters: - `cluster_id` (`str`, required) — The cluster to generate the schema for. - `schema_description` (`str`, required) — Plain-language description of what to track. - `current_yml_schema` (`str`) — Existing schema to extend additively. - `timeout` (`float`) — Per-call timeout in seconds. Returns: `GenerateSchemaResult` **`enhance_schema(cluster_id, schema_description, current_yml_schema, *, timeout=None)`** — Evolve a schema → new schema + migration plan Parameters: - `cluster_id` (`str`, required) — The cluster the schema belongs to. - `schema_description` (`str`, required) — Plain-language description of the change. - `current_yml_schema` (`str`, required) — The current schema to evolve. - `timeout` (`float`) — Per-call timeout in seconds. Returns: Pass the returned `migration_plan` to `dry_run_migration` / `update_instance_schema`. **`create_instance(cluster_id, name, schema_text, schema_type, *, description=None, schema_description=None, timeout=None)`** — Create a new instance; returns a bound handle Parameters: - `cluster_id` (`str`, required) — The cluster to create the instance in. - `name` (`str`, required) — Name for the instance. - `schema_text` (`str`, required) — The schema document text. - `schema_type` (`SchemaType`, required) — Format of the schema (`YML` or `JSON`). - `description` (`str`) — Description. - `schema_description` (`str`) — Plain-language schema description. - `timeout` (`float`) — Per-call timeout in seconds. Returns: A bound `InstanceAPI` handle for the new instance — call `inst.id` to get its ID. **`list_instances(*, ids=None, timeout=None)`** — List instances Parameters: - `ids` (`list[str]`) — Filter to specific instance IDs. - `timeout` (`float`) — Per-call timeout in seconds. Returns: `list[InstanceInfo]` **`get_instance(instance_id, *, timeout=None)`** — Get an instance by ID Parameters: - `instance_id` (`str`, required) — The instance ID to fetch. - `timeout` (`float`) — Per-call timeout in seconds. Returns: `InstanceInfo` **`get_instance_schema(instance_id, *, timeout=None)`** — Get an instance's schema Parameters: - `instance_id` (`str`, required) — The instance whose schema to fetch. - `timeout` (`float`) — Per-call timeout in seconds. Returns: `InstanceSchemaInfo` **`update_instance_schema(instance_id, schema_text, schema_type, *, migration_plan=None, confirm_destructive=False, timeout=None)`** — Update a schema; pass migration_plan for non-additive changes Parameters: - `instance_id` (`str`, required) — The instance to update. - `schema_text` (`str`, required) — The new schema document text. - `schema_type` (`SchemaType`, required) — Format of the schema (`YML` or `JSON`). - `migration_plan` (`MigrationPlan | dict`) — Plan for non-additive changes (from `enhance_schema`). - `confirm_destructive` (`bool`) — Required for ops that drop data. Defaults to `False`. - `timeout` (`float`) — Per-call timeout in seconds. Returns: The `migration_id` / `prior_version` / `new_version` / `migration_warnings` fields are populated when a migration ran. Errors: Raises `XmemoryAPIError` with `.code` `non_additive_change_requires_plan` (no plan) or `destructive_confirmation_required` (drops data without `confirm_destructive=True`). **`update_instance_metadata(instance_id, name, description, *, agent_owner_instructions=UNSET, expected_owner_instructions_epoch=UNSET, timeout=None)`** — Update instance name and description Parameters: - `instance_id` (`str`, required) — The instance to update. - `name` (`str`, required) — New name for the instance. - `description` (`str | None`, required) — New description (pass `None` to clear). - `agent_owner_instructions` (`str | None`) — Standing preference for how agents should use this instance. Omit to leave it as it is; pass `None` to clear it. Max 2000 characters. Defaults to `UNSET`. - `expected_owner_instructions_epoch` (`int`) — The `agent_owner_instructions_epoch` your edit was composed from. Defaults to `UNSET`. - `timeout` (`float`) — Per-call timeout in seconds. Returns: Omitting an argument sends no such key at all, so a rename never disturbs the standing preference. Errors: When `expected_owner_instructions_epoch` is sent and the stored instructions changed after you read them, the save is refused rather than applied: `XmemoryAPIError` with `.status` `409` and `.code` `INVALID_INPUT`. Re-read the instance, reapply your change, and save again. **`patch_instance_metadata(instance_id, *, name=UNSET, description=UNSET, agent_surfaces=UNSET, agent_default_binding_tier=UNSET, agent_engagement_hints=UNSET, agent_owner_instructions=UNSET, timeout=None)`** — Change some metadata fields, leaving the rest alone Parameters: - `instance_id` (`str`, required) — The instance to update. - `name` (`str`) — New name for the instance. Defaults to `UNSET`. - `description` (`str | None`) — New description (pass `None` to clear). Defaults to `UNSET`. - `agent_surfaces` (`Sequence[str] | None`) — Agent surfaces this instance is expected to be used from — see `AgentSurface`. Advisory: it orders the connect steps and grants nothing. Defaults to `UNSET`. - `agent_default_binding_tier` (`str | None`) — Default prominence for a binding — see `BindingTier`. Advisory. Defaults to `UNSET`. - `agent_engagement_hints` (`Sequence[str] | None`) — Short routing phrases, e.g. `"a convention is learned or corrected"`. At most 16, each at most 200 characters. Defaults to `UNSET`. - `agent_owner_instructions` (`str | None`) — Standing preference for how agents should use this instance. Max 2000 characters. Prefer `update_instance_metadata` for this field — it is the only call that accepts `expected_owner_instructions_epoch`, so an edit sent here cannot be guarded against a concurrent one. Defaults to `UNSET`. - `timeout` (`float`) — Per-call timeout in seconds. Returns: Every argument is independent: omit one and the stored value is untouched, pass `None` to clear it. This is the only call that accepts the three `agent_*` hints; `update_instance_metadata` is the only one that accepts the epoch guard. **`delete_instance(instance_id, *, timeout=None)`** — Delete an instance Parameters: - `instance_id` (`str`, required) — The instance ID to delete. - `timeout` (`float`) — Per-call timeout in seconds. Returns: The deleted instance IDs. **`dry_run_migration(instance_id, schema_text, schema_type, *, migration_plan=None, confirm_destructive=False, timeout=None)`** — Preview a migration's DDL Parameters: - `instance_id` (`str`, required) — The instance to preview against. - `schema_text` (`str`, required) — The new schema document text. - `schema_type` (`SchemaType`, required) — Format of the schema (`YML` or `JSON`). - `migration_plan` (`MigrationPlan | dict`) — Plan for non-additive changes. - `confirm_destructive` (`bool`) — Required for ops that drop data. Defaults to `False`. - `timeout` (`float`) — Per-call timeout in seconds. Returns: `statements` are the planned DDL; nothing is applied. **`list_migrations(instance_id, *, limit=50, before_id=None, include_yaml=False, timeout=None)`** — List applied migrations (newest first) Parameters: - `instance_id` (`str`, required) — The instance to list migrations for. - `limit` (`int`) — Maximum number of records to return. Defaults to `50`. - `before_id` (`str`) — Page cursor: pass the previous page's `next_before_id`. - `include_yaml` (`bool`) — Include the before/after YAML in each record. Defaults to `False`. - `timeout` (`float`) — Per-call timeout in seconds. Returns: Page with `next_before_id` — pass it as `before_id` to fetch the next page. **`get_migration(instance_id, migration_id, *, include_yaml=False, timeout=None)`** — Get a single migration record Parameters: - `instance_id` (`str`, required) — The instance the migration belongs to. - `migration_id` (`str`, required) — The migration record ID. - `include_yaml` (`bool`) — Include the before/after YAML snapshots. Defaults to `False`. - `timeout` (`float`) — Per-call timeout in seconds. Returns: `yaml_before` / `yaml_after` are populated only when `include_yaml=True`. ##### Instance methods Data-plane methods on a bound instance (`client.instance(id)`) — writing, reading, extracting, describing, and the suggestion engine. **`write(text="", *, structured_mutations=None, extraction_logic=ExtractionLogic.FAST, diff_engine=None, timeout=None)`** — Persist data — extract objects from text, or apply structured mutations Parameters: - `text` (`str`) — Free-form text to extract from. Exactly one of `text` / `structured_mutations` must be provided (enforced with `ValueError`). - `structured_mutations` (`list`) — Ordered list of `ObjectMutation` / `RelationMutation` models (or equivalent wire-form dicts): deterministic, LLM-free create/update/delete mutations — see [Structured writes](https://xmemory.ai/python/#structured-writes). Mutually exclusive with `text`. - `extraction_logic` (`ExtractionLogic`) — Speed/accuracy tradeoff (`FAST` default, or `DEEP`). Text writes only. Defaults to `ExtractionLogic.FAST`. - `diff_engine` (`bool`) — Override the server's diff-engine setting for this write. Text writes only. - `timeout` (`float`) — Per-call timeout in seconds. Returns: `WriteResult` **`write_async(text="", *, structured_mutations=None, extraction_logic=ExtractionLogic.FAST, diff_engine=None, timeout=None)`** — Enqueue a write; returns write_id immediately Parameters: - `text` (`str`) — Free-form text to extract from. Exactly one of `text` / `structured_mutations` must be provided. - `structured_mutations` (`list`) — Same structured-mutation input as `write`. Mutually exclusive with `text`. - `extraction_logic` (`ExtractionLogic`) — Speed/accuracy tradeoff (`FAST` default, or `DEEP`). Text writes only. Defaults to `ExtractionLogic.FAST`. - `diff_engine` (`bool`) — Override the server's diff-engine setting for this write. Text writes only. - `timeout` (`float`) — Per-call timeout in seconds. Returns: Poll for completion with `write_status` using the returned `write_id`. **`write_status(write_id, *, timeout=None)`** — Poll the status of an async write Parameters: - `write_id` (`str`, required) — The write ID returned by `write_async`. - `timeout` (`float`) — Per-call timeout in seconds. Returns: `WriteStatusResult` **`read(query, *, read_mode=ReadMode.SINGLE_ANSWER, read_id=None, timeout=None)`** — Query the instance in natural language Parameters: - `query` (`str`, required) — Natural-language question. - `read_mode` (`ReadMode`) — Result shape (`SINGLE_ANSWER` default, `XRESPONSE`, or `RAW_TABLES`). Defaults to `ReadMode.SINGLE_ANSWER`. - `read_id` (`str`) — Correlation ID for the read. - `timeout` (`float`) — Per-call timeout in seconds. Returns: `reader_result` is shaped by `read_mode` (e.g. `{"answer": ...}`). **`extract(text, *, extraction_logic=ExtractionLogic.FAST, timeout=None)`** — Extract objects without writing them Parameters: - `text` (`str`, required) — Free-form text to extract from. - `extraction_logic` (`ExtractionLogic`) — Speed/accuracy tradeoff (`FAST` default, or `DEEP`). Defaults to `ExtractionLogic.FAST`. - `timeout` (`float`) — Per-call timeout in seconds. Returns: `ExtractResult` **`describe(*, timeout=None)`** — Get agent-facing tool descriptions (cached 5 min) Parameters: - `timeout` (`float`) — Per-call timeout in seconds. Returns: `DescribeResult` **`clear_describe_cache()`** — Force next describe() to fetch fresh data Returns: `None` **`get_schema(*, timeout=None)`** — Get this instance's schema Parameters: - `timeout` (`float`) — Per-call timeout in seconds. Returns: `InstanceSchemaInfo` **`review_suggestions(*, session_id=None, timeout=None)`** — Get the rolling schema-improvement proposal Parameters: - `session_id` (`str`) — Session correlation ID. - `timeout` (`float`) — Per-call timeout in seconds. Returns: On `status` `evolution_in_progress`, back off for `retry_after_seconds` and retry — it's a status, not an error. **`decide_suggestions(proposal_version, decisions, *, session_id=None, timeout=None)`** — Record accept/reject/defer decisions in bulk Parameters: - `proposal_version` (`str`, required) — Optimistic-concurrency token from the review. - `decisions` (`list[DecisionInput]`, required) — Per-item decisions — `DecisionInput(item_fingerprint, decision, edits=None)`. - `session_id` (`str`) — Session correlation ID. - `timeout` (`float`) — Per-call timeout in seconds. Returns: Pass `next_proposal_version` straight to `apply_pending_decisions` without re-reviewing. Errors: Raises `XmemoryAPIError` with `.code` `stale_proposal_version` (token out of date) or `invalid_decision_input`. **`apply_pending_decisions(proposal_version, *, session_id=None, timeout=None)`** — Apply accepted decisions as one migration Parameters: - `proposal_version` (`str`, required) — The `next_proposal_version` from the decision step. - `session_id` (`str`) — Session correlation ID. - `timeout` (`float`) — Per-call timeout in seconds. Returns: `status` `nothing_to_apply` means no accepted items were left to apply. Errors: Raises `XmemoryAPIError` with `.code` `stale_proposal_version`, `dependency_closure_failed`, or `apply_failed`. ##### Types **Data types** **`ClusterInfo`** — A cluster. - `id` (`str`) — Cluster ID. - `org_id` (`str`) — Owning organization ID. - `name` (`str`) — Cluster name. - `description` (`str | None`) — Cluster description. **`InstanceInfo`** — An instance's metadata. - `id` (`str`) — Instance ID. - `cluster_id` (`str`) — The cluster this instance belongs to. - `name` (`str`) — Instance name. - `description` (`str | None`) — Instance description. - `data_schema` (`dict | None`) — The instance's data schema. - `agent_surfaces` (`list[str] | None`) — Agent surfaces this instance is expected to be used from. Advisory; `None` means no opinion. Read as plain strings, so a value newer than your library version is returned rather than rejected. - `agent_default_binding_tier` (`str | None`) — Default prominence for a binding (`autoload` / `available`). Advisory. - `agent_engagement_hints` (`list[str] | None`) — Short routing phrases seeding when an agent should engage this instance. - `agent_owner_instructions` (`str | None`) — The standing preference set for how agents should use this instance, rendered verbatim wherever it is shown. - `agent_owner_instructions_epoch` (`int`) — Which edit of the instructions above this response describes. Pass it back as `expected_owner_instructions_epoch` to make an edit conflict-safe. - `migration_id` (`str | None`) — Set only by `update_instance_schema` when a migration ran. - `prior_version` (`int | None`) — Schema version before the migration (migration responses only). - `new_version` (`int | None`) — Schema version after the migration (migration responses only). - `migration_warnings` (`list[str] | None`) — Non-fatal migration warnings (migration responses only). **`InstanceSchemaInfo`** — An instance's schema. - `data_schema` (`dict`) — The instance's data schema. **`WriteResult`** — Result of a synchronous write. - `write_id` (`str`) — ID of the committed write. - `changes` (`Any`) — What the write did, grouped into `created` / `updated` / `deleted` (null on responses from an older server). - `trace_id` (`str | None`) — Correlate the call with API logs. **`AsyncWriteResult`** — Acknowledgement of an enqueued write. - `write_id` (`str`) — Poll completion with `write_status`. **`WriteStatusResult`** — Status of an async write. - `write_id` (`str`) — The write being polled. - `write_status` (`WriteQueueStatus`) — Current queue status. - `error_detail` (`str | None`) — Failure detail when `write_status` is `FAILED`. - `completed_at` (`datetime | None`) — When the write completed. **`ReadResult`** — Result of a read. - `reader_result` (`Any`) — The formatted answer, shaped by `read_mode` (e.g. `{"answer": ...}`). - `trace_id` (`str | None`) — Correlate the call with API logs. **`ExtractResult`** — Result of an extract (nothing persisted). - `objects_extracted` (`Any`) — The objects that would be stored (`objects` / `relations`). - `trace_id` (`str | None`) — Correlate the call with API logs. **`DescribeResult`** — Agent-facing tool descriptions, with `as_text(include_http=False)`, `as_anthropic_tools()`, and `as_openai_tools()` format helpers. - `instance_id` (`str`) — The described instance. - `instance_name` (`str`) — Instance name. - `schema_summary` (`str`) — Human-readable schema summary. - `tools` (`list[ToolDescription]`) — One entry per agent-callable tool. - `purpose` (`str | None`) — What this memory is for — the instance description, under the name the agent-facing surfaces give it. Included in `as_text()`. - `owner_instructions` (`str | None`) — The standing preference set for this instance, rendered verbatim. Included in `as_text()`, labelled with where it came from rather than as the library's own words. - `usage_brief` (`str | None`) — Generated from the schema; `None` until generated, and cleared again by a schema change. Left out of `as_text()`, since it restates `schema_summary`. **`GenerateSchemaResult`** — A generated schema. - `data_schema` (`dict`) — The produced schema as a dict. **`EnhanceSchemaResult`** — An evolved schema plus an executor-ready migration plan. - `data_schema` (`dict`) — The produced schema as a dict. - `migration_plan` (`MigrationPlan | None`) — Reconciled plan; pass to `update_instance_schema` / `dry_run_migration`. - `summary` (`str | None`) — Human-readable summary of the change. - `warnings` (`list[dict]`) — Non-fatal reconciler observations. - `repair_log` (`list[dict]`) — Auto-repairs applied to the LLM output. **`DryRunResult`** — Planned migration DDL, with nothing applied. - `status` (`"ok"`) — Always `"ok"` for a successful dry run. - `instance_id` (`str`) — The instance previewed against. - `current_version` (`int`) — Current schema version. - `statements` (`list[str]`) — The planned DDL statements. - `warnings` (`list[str]`) — Non-fatal warnings. - `plan_summary` (`PlanSummary`) — Per-op-type breakdown. - `requires_metadata_sync` (`bool`) — Whether a metadata sync is needed after applying. **`MigrationRecord`** — One applied-migration history row. - `id` (`str`) — Migration record ID. - `applied_at` (`datetime`) — When the migration was applied. - `source` (`str`) — `direct` or `suggestion_engine`. - `decided_by` (`str | None`) — Who decided the migration, if recorded. - `prior_version` (`int`) — Schema version before. - `new_version` (`int`) — Schema version after. - `ops` (`list[dict]`) — The raw ops applied (kept as dicts for forward-compat). - `ops_summary` (`PlanSummary`) — Per-op-type breakdown. - `notes` (`str | None`) — Free-text notes. - `yaml_before` (`str | None`) — Schema YAML before (only when `include_yaml=True`). - `yaml_after` (`str | None`) — Schema YAML after (only when `include_yaml=True`). **`ListMigrationsResult`** — A page of migration records, newest first. - `status` (`"ok"`) — Always `"ok"`. - `instance_id` (`str`) — The instance listed. - `items` (`list[MigrationRecord]`) — The records on this page. - `next_before_id` (`str | None`) — Pagination cursor; `None` at the tail. - `has_more` (`bool`) — Whether more pages follow. **`ReviewSuggestionsResult`** — The rolling schema-improvement proposal (or an in-progress status). - `status` (`"ok" | "evolution_in_progress"`) — `evolution_in_progress` means a migration is in flight. - `instance_id` (`str`) — The instance reviewed. - `proposal` (`ConsolidatedProposal | None`) — The proposal when `status` is `"ok"`. - `retry_after_seconds` (`int | None`) — Back-off hint when `evolution_in_progress`. **`DecideSuggestionsResult`** — Result of recording a batch of decisions. - `status` (`"ok"`) — Always `"ok"`. - `instance_id` (`str`) — The instance decided on. - `decisions_recorded` (`list[RecordedDecision]`) — One per recorded decision. - `warnings` (`list[DependencyWarning]`) — Advisory dependency warnings. - `next_proposal_version` (`str`) — Token to pass to `apply_pending_decisions`. **`ApplyPendingDecisionsResult`** — Result of applying accepted decisions as one migration. - `status` (`"ok" | "nothing_to_apply"`) — `nothing_to_apply` when no accepted items remained. - `instance_id` (`str`) — The instance applied to. - `migration_id` (`str | None`) — The committed migration's ID (when one ran). - `prior_version` (`int`) — Schema version before. - `new_version` (`int`) — Schema version after. - `applied_items` (`list[str]`) — Item fingerprints that were applied. - `summary` (`str`) — Human-readable summary. - `warnings` (`list[str]`) — Non-fatal warnings. - `notes` (`list[str]`) — Additional notes. **`MigrationPlan`** — An ordered sequence of migration ops — the wire format emitted by `enhance_schema` and consumed by `update_instance_schema` / `dry_run_migration`. - `ops` (`list[MigrationOp]`) — The ordered ops. **`MigrationOp`** — One migration operation — a discriminated union keyed on `op_type`. - `op_type` (`str`) — Discriminator: one of `add_object`, `remove_object`, `rename_object`, `change_object`, `add_field`, `remove_field`, `rename_field`, `change_field`, `add_relation`, `remove_relation`, `rename_relation`, `change_relation` — each variant carries its own fields. **`PlanSummary`** — Per-op-type breakdown of a migration plan. - `count_by_op_type` (`dict[str, int]`) — Op count keyed by `op_type`. - `total` (`int`) — Total number of ops. **`ConsolidatedProposal`** — The single rolling improvement proposal for an instance. - `instance_id` (`str`) — The instance the proposal is for. - `proposal_version` (`str`) — Optimistic-concurrency token for `decide_suggestions` / `apply_pending_decisions`. - `schema_version` (`int`) — Schema version the proposal targets. - `items` (`list[ProposalItem]`) — The accept/reject/defer-able items. - `generated_at` (`datetime`) — When the proposal was generated. - `notes` (`list[str]`) — Proposal-level notes. **`ProposalItem`** — One decide-able item inside a proposal. - `item_fingerprint` (`str`) — Stable ID; pass in a `DecisionInput`. - `op` (`dict`) — The proposed op (raw dict; parse with `parse_migration_op`). - `evidence_feedback_ids` (`list[str]`) — Feedback IDs that motivated the item. - `evidence_query_samples` (`list[str]`) — Sample queries that motivated the item. - `frequency` (`int`) — How often the supporting signal occurred. - `depends_on` (`list[str]`) — Fingerprints this item depends on. - `current_decision` (`str | None`) — Existing decision, if any. - `rationale` (`str`) — Why the item was proposed. **`DecisionInput`** — One decision in a `decide_suggestions` batch. - `item_fingerprint` (`str`) — The `ProposalItem` to decide on. - `decision` (`str`) — One of `accept`, `reject`, `defer`. - `edits` (`dict | None`) — Optional op override (must keep the same `op_type`). **`RecordedDecision`** — A persisted decision. - `item_fingerprint` (`str`) — The decided item. - `decision_id` (`str`) — ID of the recorded decision. **`DependencyWarning`** — An advisory (non-blocking) dependency warning from `decide_suggestions`. - `kind` (`str`) — Warning kind. - `item_fingerprint` (`str`) — The item the warning concerns. - `related_fingerprints` (`list[str]`) — Related item fingerprints. - `related_summaries` (`list[str]`) — Human-readable summaries of the related items. - `guidance` (`str`) — What to do about it. **`ToolDescription`** — One agent-callable tool in a DescribeResult. - `name` (`str`) — Tool name. - `description` (`str`) — What the tool does. - `when_to_use` (`str`) — Guidance on when to call it. - `parameters` (`list[ToolParameterDescription]`) — The tool's parameters. - `http_method` (`str`) — Underlying HTTP method. - `http_path` (`str`) — Underlying HTTP path. **`ToolParameterDescription`** — One parameter of a ToolDescription. - `name` (`str`) — Parameter name. - `type` (`str`) — Parameter type. - `description` (`str`) — What the parameter is. - `required` (`bool`) — Whether it must be supplied. - `enum` (`list[str] | None`) — Allowed values, if constrained. - `default` (`str | None`) — Default value, if any. **Enums** | Enum | Values | | --- | --- | | `SchemaType` | `YML`, `JSON` | | `ExtractionLogic` | `FAST`, `DEEP` | | `ReadMode` | `SINGLE_ANSWER`, `RAW_TABLES`, `XRESPONSE` | | `WriteQueueStatus` | `QUEUED`, `PROCESSING`, `EXTRACTING`, `EXTRACTED`, `APPLYING`, `COMPLETED`, `FAILED`, `NOT_FOUND` | **Exceptions** | Exception | Parent | Attributes | | --- | --- | --- | | `XmemoryAPIError` | `Exception` | `.status`, `.code`, `.details`, `.retry_after` (each `None` when absent) | | `XmemoryHealthCheckError` | `XmemoryAPIError` | `.status` (HTTP status code or `None`) | ### TypeScript Canonical URL: https://xmemory.ai/typescript/ Markdown URL: https://xmemory.ai/typescript/index.html.md The `xmemory` npm package is a lightweight TypeScript client for the xmemory API. Zero dependencies — it uses native `fetch` only. For MCP-based integration, see the [MCP](https://xmemory.ai/mcp/) guide. For framework-specific setup, see [Mastra AI](https://xmemory.ai/mastra-ai/). **API key:** To use xmemory APIs or integrations, you need an API key. Get one from the [xmemory console](https://console.xmemory.ai). Copy and securely store the key. Never share your API key publicly. The full method, data-type, enum, and exception listing is in the [Reference](#reference) at the bottom of this page. --- #### Installation ```bash npm install xmemory ``` --- #### Quick start ```typescript // Connect (reads XMEM_API_URL and XMEM_API_KEY from env) const xm = new XmemoryClient({ apiKey: "your-api-key" }); // Get a handle for an existing instance const inst = xm.instance("your-instance-id"); // Write some information await inst.write("Alice Johnson works at Acme Corp. Her email is alice@acme.com."); await inst.write("Bob Lee is a designer at Globex. He joined last Monday."); // Read it back const result = await inst.read("What is Alice's email?"); console.log(result.reader_result); // → {"answer": "alice@acme.com"} ``` --- #### Creating a client Three options, depending on whether you want a health check: ```typescript // Factory function — runs a health check automatically const xm = await xmemoryInstance({ url: "https://api.xmemory.ai", apiKey: "..." }); // Static method — same behavior const xm = await XmemoryClient.create({ url: "https://api.xmemory.ai", apiKey: "..." }); // Constructor — no health check const xm = new XmemoryClient({ url: "https://api.xmemory.ai", apiKey: "..." }); ``` | Option | Env var | Default | Description | |---|---|---|---| | `apiKey` | `XMEM_API_KEY` | `undefined` | API key for authentication | | `url` | `XMEM_API_URL` | `https://api.xmemory.ai` | API base URL | | `timeoutMs` | — | `60000` | Default request timeout in milliseconds | All three forms resolve `apiKey` and `url` from environment variables when not passed. Only `xmemoryInstance()` and `XmemoryClient.create()` perform a health check on construction. Every method also accepts an `options.timeoutMs` (a per-call timeout in milliseconds that overrides the client default); it's omitted from the per-method options in the [Reference](#reference) to keep them focused on the method-specific options. ##### Instance handle Use `xm.instance(id)` to get a scoped handle for data operations: ```typescript const inst = xm.instance("your-instance-id"); // All data operations go through the handle await inst.write("Carol is a senior engineer at Initech."); const result = await inst.read("Who works at Initech?"); console.log(result.reader_result); ``` --- #### Writing Send free-form text — xmemory extracts structured objects according to your schema and merges them into the knowledge graph. ```typescript const resp = await inst.write("Carol is a senior engineer at Initech."); console.log(resp.write_id); console.log(resp.changes); // what the write created / updated / deleted console.log(resp.trace_id); // request trace id when available ``` ##### Extraction logic Control the speed/accuracy tradeoff: ```typescript await inst.write("...", { extractionLogic: "deep" }); ``` | Value | When to use | |---|---| | `"fast"` | High-volume, low-stakes writes (default) | | `"deep"` | Important or complex information | ##### Async writes For latency-sensitive code, enqueue a write and return immediately: ```typescript const { write_id } = await inst.writeAsync("Dave manages the London office."); console.log(write_id); // use this to poll for completion ``` `write()` and `read()` responses include `trace_id`, which is useful for correlating client calls with API logs. Then check the status: ```typescript const status = await inst.writeStatus(write_id); console.log(status.write_status); // → "queued" | "processing" | "extracting" | "extracted" | "applying" | "completed" | "failed" | "not_found" ``` Do not call `read` immediately after `writeAsync` — the data may not be committed yet. Poll with `writeStatus` until `"completed"`, or use `write` (synchronous) when you need to read right after. ##### Structured writes When you already know exactly what to store, skip extraction: pass a `WriteMutation[]` instead of text (an empty array throws). Each mutation is a deterministic, LLM-free create / update / delete of an object or relation, applied in array order — later mutations may reference objects created earlier in the same batch. ```typescript const resp = await inst.write([ { object_mutation: { object_type: "person", create: { key: { email: "carol@initech.com" }, values: { name: "Carol" } }, }, }, { relation_mutation: { relation_type: "works_at", create: { endpoints: [ { object_name: "person", key: { email: "carol@initech.com" } }, { object_name: "company", key: { name: "Initech" } }, ], }, }, }, ]); console.log(resp.changes); // exactly what was created / updated / deleted ``` Each mutation is tagged `object_mutation` / `relation_mutation` and carries exactly one of `create` / `update` / `delete` (enforced at compile time). `key` identifies an object by its user-defined primary-key fields, or (for update/delete) by `{ xuid: ... }`. A `null` value in `values` **clears** that field: ```typescript await inst.write([ { object_mutation: { object_type: "person", update: { key: { email: "carol@initech.com" }, values: { role: null } }, }, }, ]); ``` `writeAsync` accepts a `WriteMutation[]` identically — poll with `writeStatus` as usual. See the [API reference](https://xmemory.ai/api/#structured-writes) for the full wire format, relation addressing (`endpoints`, `allow_bulk_delete`), and in-batch ordering rules. --- #### Reading Ask questions in natural language: ```typescript const resp = await inst.read("Who works at Acme Corp?"); console.log(resp.reader_result); console.log(resp.trace_id); ``` ##### Read modes ```typescript // Plain-text answer (default) const resp = await inst.read("What is Alice's email?", { readMode: "single-answer" }); // Structured objects and relations const resp = await inst.read("Show all contacts", { readMode: "xresponse" }); // Raw SQL result sets const resp = await inst.read("List all contacts", { readMode: "raw-tables" }); ``` ##### Scoped reads Restrict a query to a specific set of objects. Useful for multi-tenant systems, privacy controls, and compliance requirements. ```typescript const scope: ReadScope = { objects: [ { type: "Person", key: { name: "Alice Johnson" }, }, ], relations_scope: "no_relations", }; const resp = await inst.read("What is their email?", { scope }); console.log(resp.reader_result); ``` ###### Multiple objects with relations ```typescript const scope: ReadScope = { objects: [ { type: "Person", key: { email: "alice@example.com" } }, { type: "Person", key: { email: "bob@example.com" } }, ], relations_scope: "all_relations", }; const resp = await inst.read( "Which projects do these people work on together?", { scope } ); ``` ###### Scope options - `"no_relations"` (default): Only the specified objects. - `"all_relations"`: Include junction tables between objects. ###### Error handling ```typescript try { const resp = await inst.read("...", { scope }); } catch (e) { if (e instanceof XmemoryAPIError && e.status === 400) { console.error(`Invalid scope: ${e.message}`); } else { throw e; } } ``` --- #### Async writes For latency-sensitive code, enqueue a write and return immediately: ```typescript const { write_id } = await inst.writeAsync("Dave manages the London office."); console.log(write_id); // use this to poll for completion ``` `write()` and `read()` responses include `trace_id`, which is useful for correlating client calls with API logs. Then check the status: ```typescript const status = await inst.writeStatus(write_id); console.log(status.write_status); // → "queued" | "processing" | "completed" | "failed" | "not_found" ``` Do not call `read` immediately after `writeAsync` — the data may not be committed yet. Poll with `writeStatus` until `"completed"`, or use `write` (synchronous) when you need to read right after. --- #### Extracting (without writing) Preview what xmemory would extract from a piece of text, without storing anything: ```typescript const resp = await inst.extract("Dave manages the London office."); console.log(resp.objects_extracted); console.log(resp.trace_id); ``` Accepts the same `extractionLogic` option as `write`. --- #### Describing (agent tool discovery) The `describe()` method returns agent-facing tool descriptions enriched with the instance's actual schema. Use it to tell an LLM what tools are available and how to call them. ```typescript const desc = await inst.describe(); // Plain text — inject into a system prompt console.log(desc.asText()); // Anthropic tool-use format const tools = desc.asAnthropicTools(); // OpenAI function-calling format const tools = desc.asOpenaiTools(); ``` Results are cached locally for 5 minutes. To force a refresh (e.g. after updating the schema): ```typescript inst.clearDescribeCache(); const desc = await inst.describe(); ``` `asText()` shows tools as method signatures by default. Pass `{ includeHttp: true }` to also show HTTP method and path for raw REST callers. `describe()` also carries what the instance is *for*: ```typescript desc.purpose; // what the memory is for (the instance description) desc.ownerInstructions; // the standing preference set for it, verbatim desc.usageBrief; // generated from the schema; null until generated ``` `asText()` includes the first two — the purpose under the instance line, the standing preference above the schema summary. `usageBrief` is left out of it, since it restates the schema summary already there; read the property if you want it. Both are free text set by anyone holding edit permission on the instance, so `asText()` labels each with where it came from rather than presenting it as the library's own words. Those labels state provenance; they are not a security boundary. If you inject this into a system prompt you are still handling text you do not control. --- #### Cluster and instance management All management operations live on `xm.admin`: discovering clusters, generating schemas, creating instances, and evolving them safely. ##### Clusters ```typescript const clusters = await xm.admin.listClusters(); const clusterId = clusters[0].id; ``` ##### Schema generation ```typescript const schema = await xm.admin.generateSchema( clusterId, "Track contacts with name, email, company, and notes.", ); console.log(schema.data_schema); ``` To evolve an existing schema (rename / remove / type change) rather than create one, use `enhanceSchema` — it returns a structured migration plan; see [Schema evolution](#schema-evolution). ##### Instances Create an instance from a generated schema: ```typescript const inst = await xm.admin.createInstance( clusterId, "contacts", schemaYml, SchemaType.YML, { description: "User contacts" }, ); // inst is a bound InstanceHandle — use inst.write(), inst.read(), etc. await inst.write("Alice joined the team."); ``` For a purely additive change (new objects/fields/relations), update the schema directly — existing data is preserved: ```typescript await xm.admin.updateInstanceSchema(instanceId, newSchemaYml, SchemaType.YML); ``` List and delete instances: ```typescript const instances = await xm.admin.listInstances(); const info = await xm.admin.getInstance(instanceId); await xm.admin.deleteInstance(instanceId); ``` ##### Agent-facing instance metadata An instance can carry metadata that shapes how agents connect to it and what they do with it. Use `patchInstanceMetadata` to set the advisory hints: every option is independent — **omitting one leaves the stored value untouched**, and passing `null` clears it. ```typescript await xm.admin.patchInstanceMetadata(instanceId, { // Advisory hints — they seed what a connect flow proposes, and grant nothing. agentSurfaces: [AgentSurface.CLAUDE_CODE, AgentSurface.CODEX], agentDefaultBindingTier: BindingTier.AUTOLOAD, agentEngagementHints: ["a convention is learned or corrected"], }); ``` Concurrent edits to these three are last-writer-wins by design: they only seed what a connect flow proposes, so the loser of a race re-applies a suggestion. `agent_owner_instructions` is not like that. Read it back from `InstanceInfo`. The hints come back as plain strings, so a value your server knows and your library version does not is returned rather than making the instance unreadable: ```typescript const info = await xm.admin.getInstance(instanceId); info.agent_owner_instructions; info.agent_surfaces; // e.g. ["claude_code", "codex"] info.agent_default_binding_tier; // e.g. "autoload" ``` Set `agentOwnerInstructions` through `updateInstanceMetadata`, not `patchInstanceMetadata`. It is rendered to agents verbatim and a second writer edits it from the same screen, so a silently lost edit is a rule that stops being enforced — and `updateInstanceMetadata` is the only call that carries `expectedOwnerInstructionsEpoch`. Pass the epoch you read the value at and the losing save is refused instead of applied: ```typescript const info = await xm.admin.getInstance(instanceId); await xm.admin.updateInstanceMetadata(instanceId, info.name, info.description ?? "", { agentOwnerInstructions: `${info.agent_owner_instructions ?? ""}\nAlso: never paraphrase a rule.`, expectedOwnerInstructionsEpoch: info.agent_owner_instructions_epoch, }); ``` ##### Schema evolution Non-additive changes (rename / remove / type change) preserve data via a structured **migration plan**. Use `enhanceSchema` to get the plan, `dryRunMigration` to preview the DDL, then `updateInstanceSchema` to apply it. `confirmDestructive: true` is required for ops that drop data. ```typescript const current = (await xm.admin.getInstanceSchema(instanceId)).data_schema; // 1. Enhance — new schema + an executor-ready migration plan. const enhanced = await xm.admin.enhanceSchema( clusterId, "Rename Person.mail to Person.email.", yaml.dump(current), ); console.log(enhanced.summary, enhanced.migration_plan?.ops); const newYaml = yaml.dump(enhanced.data_schema); // 2. Dry-run — preview the DDL, apply nothing. const preview = await xm.admin.dryRunMigration(instanceId, newYaml, SchemaType.YML, { migrationPlan: enhanced.migration_plan ?? undefined, }); console.log(preview.statements); // 3. Update — apply. A rename is non-destructive, so confirmDestructive stays false. const info = await xm.admin.updateInstanceSchema(instanceId, newYaml, SchemaType.YML, { migrationPlan: enhanced.migration_plan ?? undefined, confirmDestructive: false, }); console.log(info.migration_id, info.prior_version, "->", info.new_version); ``` Review applied migrations: ```typescript const page = await xm.admin.listMigrations(instanceId, { limit: 20 }); for (const r of page.items) console.log(r.id, r.source, r.prior_version, "->", r.new_version); const detail = await xm.admin.getMigration(instanceId, page.items[0].id, { includeYaml: true }); console.log(detail.yaml_before, detail.yaml_after); ``` Migration ops are a discriminated union on `op_type` (`MigrationPlan`, `AddField`, `RenameField`, `RemoveObject`, …). `ProposalItem.op` and `MigrationRecord.ops` stay raw for forward compatibility — narrow them to `MigrationOp` when needed. ##### Suggestion engine (review → decide → apply) xmemory watches read traffic and, on demand, surfaces a single rolling proposal of schema improvements. The flow is three calls — never wrap them; the checkpoints are the point. ```typescript const inst = xm.instance(instanceId); // 1. Review — proposal + optimistic-concurrency token. const review = await inst.reviewSuggestions(); if (review.status === "evolution_in_progress") { console.log(`Migration in flight; retry in ${review.retry_after_seconds}s`); } else if (review.proposal) { for (const item of review.proposal.items) { console.log(item.item_fingerprint, item.rationale, item.op); } // 2. Decide — accept / reject / defer per item, in bulk. const decisions: DecisionInput[] = review.proposal.items.map((i) => ({ item_fingerprint: i.item_fingerprint, decision: "accept", })); const decided = await inst.decideSuggestions(review.proposal.proposal_version, decisions); // 3. Apply — commit accepted decisions as one migration. const applied = await inst.applyPendingDecisions(decided.next_proposal_version); console.log(applied.status, applied.summary); } ``` --- #### Error handling All errors throw `XmemoryAPIError` with optional `.status` (HTTP status), `.code` (structured error code, when the server returned one), `.details`, and `.retryAfter` (number of seconds parsed from the `Retry-After` header, or `undefined`). ```typescript try { const xm = await XmemoryClient.create({ apiKey: "..." }); } catch (e) { if (e instanceof XmemoryHealthCheckError) { console.error("Server unreachable:", e.message); } } try { await inst.write("..."); } catch (e) { if (e instanceof XmemoryAPIError) { console.error(`Error (HTTP ${e.status}): ${e.message}`); } } ``` The schema-evolution methods set `.code` to a structured error type you can match on — e.g. `stale_proposal_version`, `destructive_confirmation_required`, `non_additive_change_requires_plan`: ```typescript try { await inst.applyPendingDecisions(token); } catch (e) { if (e instanceof XmemoryAPIError && e.code === "stale_proposal_version") { const review = await inst.reviewSuggestions(); // re-review and retry } } ``` The same `.code` branching separates a spent allowance from a genuine rate limit — match on `.code`, not on the status: ```typescript try { await inst.write("..."); } catch (e) { if (e instanceof XmemoryAPIError) { if (e.code === "QUOTA_EXCEEDED") { // Plan/usage allowance exhausted; e.details.kind is // "daily_quota_exceeded" or "monthly_quota_exceeded". // Not retryable now — surface "limit reached / upgrade your plan". // You may retry after e.details.retry_after_seconds (if not null). } else if (e.code === "RATE_LIMITED") { // Genuine rate limit — back off and retry, honoring Retry-After. } } } ``` For both `RATE_LIMITED` (429) and a resettable `QUOTA_EXCEEDED` (402), use `e.retryAfter` — the seconds parsed from the `Retry-After` header (a `number`, or `undefined` when the header was absent) — as your backoff delay. --- #### Reference The complete method, data-type, enum, and exception listing. Methods are grouped by where they live; expand a row for its parameters, returns, and details. ##### Client Constructors and client-level accessors. **`new XmemoryClient(options?)`** — Create a client (no health check) Parameters: - `options.apiKey` (`string`) — API key for authentication. Falls back to `XMEM_API_KEY`. - `options.url` (`string`) — API base URL. Falls back to `XMEM_API_URL`. Defaults to `https://api.xmemory.ai`. - `options.timeoutMs` (`number`) — Default request timeout in milliseconds. Defaults to `60000`. Returns: `XmemoryClient` **`XmemoryClient.create(options?)`** — Create a client with health check Parameters: - `options.apiKey` (`string`) — API key for authentication. Falls back to `XMEM_API_KEY`. - `options.url` (`string`) — API base URL. Falls back to `XMEM_API_URL`. Defaults to `https://api.xmemory.ai`. - `options.timeoutMs` (`number`) — Default request timeout in milliseconds. Defaults to `60000`. Returns: `Promise` **`xmemoryInstance(options?)`** — Factory with health check (same as .create()) Parameters: - `options.apiKey` (`string`) — API key for authentication. Falls back to `XMEM_API_KEY`. - `options.url` (`string`) — API base URL. Falls back to `XMEM_API_URL`. Defaults to `https://api.xmemory.ai`. - `options.timeoutMs` (`number`) — Default request timeout in milliseconds. Defaults to `60000`. Returns: `Promise` **`xm.admin`** — Control-plane accessor — clusters, schema generation, instances, schema evolution Returns: `xm.admin` — the receiver for the admin / control-plane methods (clusters, schema generation, instances, schema evolution). **`xm.instance(instanceId)`** — Get a handle for data operations on an instance (synchronous) Parameters: - `instanceId` (`string`, required) — The instance to scope data operations to. Returns: A bound `InstanceHandle` — call `write` / `read` / `extract` / … on it. (Synchronous; not a Promise.) **`xm.checkHealth()`** — Throws XmemoryHealthCheckError if the API is unreachable Returns: `Promise` ##### Admin methods Control-plane methods on `xm.admin` — clusters, schema generation, instances, and schema evolution. **`listClusters(options?)`** — List clusters Parameters: - `options.ids` (`string[]`) — Filter to specific cluster IDs. Returns: Each item is a [`ClusterInfo`](#ClusterInfo). **`getCluster(clusterId, options?)`** — Get a cluster by ID Parameters: - `clusterId` (`string`, required) — The cluster to fetch. Returns: A [`ClusterInfo`](#ClusterInfo). **`generateSchema(clusterId, schemaDescription, options?)`** — Generate a schema from a description Parameters: - `clusterId` (`string`, required) — The cluster to generate the schema for. - `schemaDescription` (`string`, required) — Natural-language description of what to track. - `options.currentYmlSchema` (`string`) — Existing schema to extend additively. Returns: `GenerateSchemaResult` — `{ data_schema }`. **`enhanceSchema(clusterId, schemaDescription, currentYmlSchema, options?)`** — Evolve a schema → new schema + migration plan Parameters: - `clusterId` (`string`, required) — The cluster the instance belongs to. - `schemaDescription` (`string`, required) — Natural-language description of the change. - `currentYmlSchema` (`string`, required) — The current schema as YAML. Returns: `EnhanceSchemaResult` — `{ data_schema, migration_plan: MigrationPlan | null, summary, warnings, repair_log }`. Pass `migration_plan` to `dryRunMigration` / `updateInstanceSchema`. **`createInstance(clusterId, name, schemaText, schemaType, options?)`** — Create an instance; resolves to a bound handle Parameters: - `clusterId` (`string`, required) — The cluster to create the instance in. - `name` (`string`, required) — Instance name. - `schemaText` (`string`, required) — The schema source. - `schemaType` (`SchemaType`, required) — Schema format (`SchemaType.YML` or `SchemaType.JSON`). - `options.description` (`string`) — Instance description. - `options.schemaDescription` (`string`) — Plain-language schema description. Returns: A bound `InstanceHandle` for the new instance — read `inst.id` for its ID. **`listInstances(options?)`** — List instances Parameters: - `options.ids` (`string[]`) — Filter to specific instance IDs. Returns: Each item is an [`InstanceInfo`](#InstanceInfo). **`getInstance(instanceId, options?)`** — Get an instance by ID Parameters: - `instanceId` (`string`, required) — The instance to fetch. Returns: An [`InstanceInfo`](#InstanceInfo). **`getInstanceSchema(instanceId, options?)`** — Get an instance's schema Parameters: - `instanceId` (`string`, required) — The instance to fetch the schema for. Returns: An [`InstanceSchemaInfo`](#InstanceSchemaInfo). **`updateInstanceSchema(instanceId, schemaText, schemaType, options?)`** — Update a schema; pass migrationPlan for non-additive changes Parameters: - `instanceId` (`string`, required) — The instance to update. - `schemaText` (`string`, required) — The new schema source. - `schemaType` (`SchemaType`, required) — Schema format (`SchemaType.YML` or `SchemaType.JSON`). - `options.migrationPlan` (`MigrationPlan`) — Executor-ready plan for a non-additive change. - `options.confirmDestructive` (`boolean`) — Required for ops that drop data. Defaults to `false`. Returns: An [`InstanceInfo`](#InstanceInfo) — also carries `migration_id`, `prior_version`, `new_version`, `migration_warnings` when a migration ran. Errors: Throws `XmemoryAPIError` with `.code` `non_additive_change_requires_plan` (no plan) or `destructive_confirmation_required` (drops data without `confirmDestructive`). **`updateInstanceMetadata(instanceId, name, description, options?)`** — Update instance name and description Parameters: - `instanceId` (`string`, required) — The instance to update. - `name` (`string`, required) — New instance name. - `description` (`string`, required) — New instance description. - `options.agentOwnerInstructions` (`string | null`) — Standing preference for how agents should use this instance. Omit to leave it as it is; pass `null` to clear it. Max 2000 characters. - `options.expectedOwnerInstructionsEpoch` (`number`) — The `agent_owner_instructions_epoch` your edit was composed from; a save that raced someone else's edit is then refused rather than overwriting it. Returns: The updated [`InstanceInfo`](#InstanceInfo). Omitting an option sends no such key at all, so a rename never disturbs the standing preference. Errors: When `expectedOwnerInstructionsEpoch` is sent and the stored instructions changed after you read them, the save is refused rather than applied: `XmemoryAPIError` with `.status` `409` and `.code` `INVALID_INPUT`. Re-read the instance, reapply your change, and save again. **`patchInstanceMetadata(instanceId, options?)`** — Change some metadata fields, leaving the rest alone Parameters: - `instanceId` (`string`, required) — The instance to update. - `options.name` (`string`) — New instance name. - `options.description` (`string | null`) — New instance description (pass `null` to clear). - `options.agentSurfaces` (`readonly string[] | null`) — Agent surfaces this instance is expected to be used from — see `AgentSurface`. Advisory: it orders the connect steps and grants nothing. - `options.agentDefaultBindingTier` (`string | null`) — Default prominence for a binding — see `BindingTier`. Advisory. - `options.agentEngagementHints` (`readonly string[] | null`) — Short routing phrases, e.g. `"a convention is learned or corrected"`. At most 16, each at most 200 characters. - `options.agentOwnerInstructions` (`string | null`) — Standing preference for how agents should use this instance. Max 2000 characters. Prefer `updateInstanceMetadata` for this field — it is the only call that accepts `expectedOwnerInstructionsEpoch`, so an edit sent here cannot be guarded against a concurrent one. Returns: The updated [`InstanceInfo`](#InstanceInfo). Every option is independent: omit one and the stored value is untouched, pass `null` to clear it. This is the only call that accepts the three `agent*` hints; `updateInstanceMetadata` is the only one that accepts the epoch guard. **`deleteInstance(instanceId, options?)`** — Delete an instance Parameters: - `instanceId` (`string`, required) — The instance to delete. Returns: `string[]` — the deleted instance IDs. **`dryRunMigration(instanceId, schemaText, schemaType, options?)`** — Preview a migration's DDL Parameters: - `instanceId` (`string`, required) — The instance to preview against. - `schemaText` (`string`, required) — The proposed schema source. - `schemaType` (`SchemaType`, required) — Schema format (`SchemaType.YML` or `SchemaType.JSON`). - `options.migrationPlan` (`MigrationPlan`) — Executor-ready plan for a non-additive change. - `options.confirmDestructive` (`boolean`) — Required for ops that drop data. Defaults to `false`. Returns: `DryRunResult` — `{ status, instance_id, current_version, statements: string[] (planned DDL), warnings, plan_summary: PlanSummary, requires_metadata_sync }`. **`listMigrations(instanceId, options?)`** — List applied migrations (newest first) Parameters: - `instanceId` (`string`, required) — The instance to list migrations for. - `options.limit` (`number`) — Maximum number of records to return. Defaults to `50`. - `options.beforeId` (`string`) — Page cursor: pass the previous page's `next_before_id`. - `options.includeYaml` (`boolean`) — Include before/after YAML in each record. Returns: `ListMigrationsResult` — `{ items: MigrationRecord[], next_before_id: string | null, has_more }`. **`getMigration(instanceId, migrationId, options?)`** — Get a single migration record Parameters: - `instanceId` (`string`, required) — The instance the migration belongs to. - `migrationId` (`string`, required) — The migration record to fetch. - `options.includeYaml` (`boolean`) — Include before/after YAML in the record. Returns: `MigrationRecord` — `{ id, applied_at, source, prior_version, new_version, ops, ops_summary, yaml_before, yaml_after }`. ##### Instance methods Data-plane methods on a bound instance handle (`xm.instance(id)`) — writing, reading, extracting, describing, and the suggestion engine. **`write(text | mutations, options?)`** — Persist data — extract objects from text, or apply structured mutations Parameters: - `text` (`string`) — Free-form text to extract from. Pass either this or `mutations`. - `mutations` (`WriteMutation[]`) — Ordered list of deterministic, LLM-free create/update/delete mutations of objects and relations — see [Structured writes](https://xmemory.ai/typescript/#structured-writes). An empty array throws. - `options.extractionLogic` (`"fast" | "deep"`) — Speed/accuracy tradeoff. Text form only. Defaults to `"fast"`. - `options.diffEngine` (`boolean`) — Override the server's diff-engine setting for this write. Text form only. Returns: - `write_id` (`string`) — ID of the committed write. - `changes` (`unknown`) — What the write did, grouped into `created` / `updated` / `deleted`. - `trace_id` (`string | null`) — Correlate the call with API logs. **`writeAsync(text | mutations, options?)`** — Enqueue a write; resolves with write_id immediately Parameters: - `text` (`string`) — Free-form text to extract from. Pass either this or `mutations`. - `mutations` (`WriteMutation[]`) — Same structured-mutation input as `write`. - `options.extractionLogic` (`"fast" | "deep"`) — Speed/accuracy tradeoff. Text form only. Defaults to `"fast"`. - `options.diffEngine` (`boolean`) — Override the server's diff-engine setting for this write. Text form only. Returns: - `write_id` (`string`) — Use this to poll for completion with `writeStatus`. **`writeStatus(writeId, options?)`** — Poll the status of an async write Parameters: - `writeId` (`string`, required) — The write to poll, from `writeAsync`. Returns: - `write_id` (`string`) — Echoes the polled write ID. - `write_status` (`WriteQueueStatus`) — Current queue status of the write. - `error_detail` (`string | null`) — Failure detail when the write failed. - `completed_at` (`string | null`) — When the write finished, when available. **`read(query, options?)`** — Query the instance in natural language Parameters: - `query` (`string`, required) — Natural-language question. - `options.readMode` (`"single-answer" | "raw-tables" | "xresponse"`) — Response shape. Defaults to `"single-answer"`. - `options.traceId` (`string`) — Correlate the call with API logs. Returns: - `reader_result` (`unknown`) — The answer, shaped by `readMode` (e.g. `{ answer }` for single-answer). - `trace_id` (`string | null`) — Correlate the call with API logs. **`extract(text, options?)`** — Extract objects without writing them Parameters: - `text` (`string`, required) — Free-form text to extract from. - `options.extractionLogic` (`"fast" | "deep"`) — Speed/accuracy tradeoff. Defaults to `"fast"`. Returns: - `objects_extracted` (`unknown`) — The objects that would be stored (`objects` / `relations`). - `trace_id` (`string | null`) — Correlate the call with API logs. **`describe(options?)`** — Get agent-facing tool descriptions (cached 5 min) Parameters: - `options.includeHttp` (`boolean`) — Also show HTTP method and path for raw REST callers. Returns: `DescribeResult` — properties `instanceId`, `instanceName`, `schemaSummary`, `tools`, plus `purpose` (what this memory is for), `ownerInstructions` (the standing preference set for it, verbatim) and `usageBrief` (generated from the schema; `null` until generated); methods `asText({ includeHttp? })`, `asAnthropicTools()`, `asOpenaiTools()`. `asText()` includes the first two, each labelled with where it came from rather than as the library's own words, and leaves `usageBrief` out since it restates `schemaSummary`. (Class with camelCase props, unlike the snake_case wire results.) **`clearDescribeCache()`** — Force next describe() to fetch fresh data (synchronous) Returns: `void` **`getSchema(options?)`** — Get this instance's schema Returns: An [`InstanceSchemaInfo`](#InstanceSchemaInfo). **`reviewSuggestions(options?)`** — Get the rolling schema-improvement proposal Parameters: - `options.sessionId` (`string`) — Session ID for tracing. Returns: `ReviewSuggestionsResult` — `{ status, proposal: ConsolidatedProposal | null, retry_after_seconds }`. When `status` is `"evolution_in_progress"`, back off for `retry_after_seconds` — it's a status, not a thrown error. **`decideSuggestions(proposalVersion, decisions, options?)`** — Record accept/reject/defer decisions in bulk Parameters: - `proposalVersion` (`string`, required) — Optimistic-concurrency token from `reviewSuggestions`. - `decisions` (`DecisionInput[]`, required) — Per-item decisions — `{ item_fingerprint, decision, edits? }`. - `options.sessionId` (`string`) — Session ID for tracing. Returns: `DecideSuggestionsResult` — `{ decisions_recorded, warnings, next_proposal_version }`. Pass `next_proposal_version` to `applyPendingDecisions`. Errors: Throws `XmemoryAPIError` with `.code` `stale_proposal_version` (token out of date) or `invalid_decision_input`. **`applyPendingDecisions(proposalVersion, options?)`** — Apply accepted decisions as one migration Parameters: - `proposalVersion` (`string`, required) — The `next_proposal_version` from `decideSuggestions`. - `options.sessionId` (`string`) — Session ID for tracing. Returns: `ApplyPendingDecisionsResult` — `{ status ("ok" | "nothing_to_apply"), migration_id, prior_version, new_version, applied_items, summary, warnings, notes }`. Errors: Throws `XmemoryAPIError` with `.code` `stale_proposal_version`, `dependency_closure_failed`, or `apply_failed`. ##### Types **Data types** **`ClusterInfo`** — A cluster. - `id` (`string`) — Cluster ID. - `org_id` (`string`) — Owning organization ID. - `name` (`string`) — Cluster name. - `description` (`string | null`) — Cluster description. **`InstanceInfo`** — An instance's metadata. - `id` (`string`) — Instance ID. - `cluster_id` (`string`) — The cluster this instance belongs to. - `name` (`string`) — Instance name. - `description` (`string | null`) — Instance description. - `data_schema` (`object | null`) — The instance's data schema. - `agent_surfaces` (`string[] | null`) — Agent surfaces this instance is expected to be used from. Advisory; `null` means no opinion. Typed as plain strings, so a value newer than your library version is returned rather than making the instance unreadable. - `agent_default_binding_tier` (`string | null`) — Default prominence for a binding (`autoload` / `available`). Advisory. - `agent_engagement_hints` (`string[] | null`) — Short routing phrases seeding when an agent should engage this instance. - `agent_owner_instructions` (`string | null`) — The standing preference set for how agents should use this instance, rendered verbatim wherever it is shown. - `agent_owner_instructions_epoch` (`number`) — Which edit of the instructions above this response describes. Pass it back as `expectedOwnerInstructionsEpoch` to make an edit conflict-safe. **`InstanceSchemaInfo`** — An instance's schema. - `data_schema` (`object`) — The instance's data schema. **`GenerateSchemaResult`** — A generated schema. - `data_schema` (`object`) — The produced schema. **`EnhanceSchemaResult`** — An evolved schema plus an executor-ready migration plan. - `data_schema` (`object`) — The produced schema. - `migration_plan` (`MigrationPlan | null`) — Reconciled plan; pass to `updateInstanceSchema` / `dryRunMigration`. - `summary` (`string | null`) — Human-readable summary of the change. - `warnings` (`object[]`) — Non-fatal reconciler observations. - `repair_log` (`object[]`) — Auto-repairs applied to the LLM output. **`DryRunResult`** — Planned migration DDL, with nothing applied. - `status` (`"ok"`) — Always `"ok"` for a successful dry run. - `instance_id` (`string`) — The instance previewed against. - `current_version` (`number`) — Current schema version. - `statements` (`string[]`) — The planned DDL statements. - `warnings` (`string[]`) — Non-fatal warnings. - `plan_summary` (`PlanSummary`) — Per-op-type breakdown. - `requires_metadata_sync` (`boolean`) — Whether a metadata sync is needed after applying. **`MigrationRecord`** — One applied-migration history row. - `id` (`string`) — Migration record ID. - `applied_at` (`string`) — When the migration was applied (ISO-8601). - `source` (`string`) — `direct` or `suggestion_engine`. - `decided_by` (`string | null`) — Who decided the migration, if recorded. - `prior_version` (`number`) — Schema version before. - `new_version` (`number`) — Schema version after. - `ops` (`object[]`) — The raw ops applied (kept raw for forward-compat). - `ops_summary` (`PlanSummary`) — Per-op-type breakdown. - `notes` (`string | null`) — Free-text notes. - `yaml_before` (`string | null`) — Schema YAML before (only when `includeYaml: true`). - `yaml_after` (`string | null`) — Schema YAML after (only when `includeYaml: true`). **`ListMigrationsResult`** — A page of migration records, newest first. - `status` (`"ok"`) — Always `"ok"`. - `instance_id` (`string`) — The instance listed. - `items` (`MigrationRecord[]`) — The records on this page. - `next_before_id` (`string | null`) — Pagination cursor; `null` at the tail. - `has_more` (`boolean`) — Whether more pages follow. **`ReviewSuggestionsResult`** — The rolling schema-improvement proposal (or an in-progress status). - `status` (`"ok" | "evolution_in_progress"`) — `evolution_in_progress` means a migration is in flight. - `instance_id` (`string`) — The instance reviewed. - `proposal` (`ConsolidatedProposal | null`) — The proposal when `status` is `"ok"`. - `retry_after_seconds` (`number | null`) — Back-off hint when `evolution_in_progress`. **`DecideSuggestionsResult`** — Result of recording a batch of decisions. - `status` (`"ok"`) — Always `"ok"`. - `instance_id` (`string`) — The instance decided on. - `decisions_recorded` (`RecordedDecision[]`) — One per recorded decision. - `warnings` (`DependencyWarning[]`) — Advisory dependency warnings. - `next_proposal_version` (`string`) — Token to pass to `applyPendingDecisions`. **`ApplyPendingDecisionsResult`** — Result of applying accepted decisions as one migration. - `status` (`"ok" | "nothing_to_apply"`) — `nothing_to_apply` when no accepted items remained. - `instance_id` (`string`) — The instance applied to. - `migration_id` (`string | null`) — The committed migration's ID (when one ran). - `prior_version` (`number`) — Schema version before. - `new_version` (`number`) — Schema version after. - `applied_items` (`string[]`) — Item fingerprints that were applied. - `summary` (`string`) — Human-readable summary. - `warnings` (`string[]`) — Non-fatal warnings. - `notes` (`string[]`) — Additional notes. **`MigrationPlan`** — An ordered sequence of migration ops — the wire format emitted by `enhanceSchema` and consumed by `updateInstanceSchema` / `dryRunMigration`. - `ops` (`MigrationOp[]`) — The ordered ops. **`MigrationOp`** — One migration operation — a discriminated union keyed on `op_type`. - `op_type` (`string`) — Discriminator: one of `add_object`, `remove_object`, `rename_object`, `change_object`, `add_field`, `remove_field`, `rename_field`, `change_field`, `add_relation`, `remove_relation`, `rename_relation`, `change_relation` — each variant carries its own fields. **`PlanSummary`** — Per-op-type breakdown of a migration plan. - `count_by_op_type` (`Record`) — Op count keyed by `op_type`. - `total` (`number`) — Total number of ops. **`ConsolidatedProposal`** — The single rolling improvement proposal for an instance. - `instance_id` (`string`) — The instance the proposal is for. - `proposal_version` (`string`) — Optimistic-concurrency token for `decideSuggestions` / `applyPendingDecisions`. - `schema_version` (`number`) — Schema version the proposal targets. - `items` (`ProposalItem[]`) — The accept/reject/defer-able items. - `generated_at` (`string`) — When the proposal was generated (ISO-8601). - `notes` (`string[]`) — Proposal-level notes. **`ProposalItem`** — One decide-able item inside a proposal. - `item_fingerprint` (`string`) — Stable ID; pass in a `DecisionInput`. - `op` (`object`) — The proposed op (raw; cast to `MigrationOp` when needed). - `evidence_feedback_ids` (`string[]`) — Feedback IDs that motivated the item. - `evidence_query_samples` (`string[]`) — Sample queries that motivated the item. - `frequency` (`number`) — How often the supporting signal occurred. - `depends_on` (`string[]`) — Fingerprints this item depends on. - `current_decision` (`string | null`) — Existing decision, if any. - `rationale` (`string`) — Why the item was proposed. **`DecisionInput`** — One decision in a `decideSuggestions` batch. - `item_fingerprint` (`string`) — The `ProposalItem` to decide on. - `decision` (`"accept" | "reject" | "defer"`) — The decision for this item. - `edits` (`object | null`) — Optional op override (must keep the same `op_type`). **`RecordedDecision`** — A persisted decision. - `item_fingerprint` (`string`) — The decided item. - `decision_id` (`string`) — ID of the recorded decision. **`DependencyWarning`** — An advisory (non-blocking) dependency warning from `decideSuggestions`. - `kind` (`string`) — Warning kind. - `item_fingerprint` (`string`) — The item the warning concerns. - `related_fingerprints` (`string[]`) — Related item fingerprints. - `related_summaries` (`string[]`) — Human-readable summaries of the related items. - `guidance` (`string`) — What to do about it. **`ToolDescription`** — One agent-callable tool in a DescribeResult. - `name` (`string`) — Tool name. - `description` (`string`) — What the tool does. - `when_to_use` (`string`) — Guidance on when to call it. - `parameters` (`ToolParameterDescription[]`) — The tool's parameters. - `http_method` (`string`) — Underlying HTTP method. - `http_path` (`string`) — Underlying HTTP path. **`ToolParameterDescription`** — One parameter of a ToolDescription. - `name` (`string`) — Parameter name. - `type` (`string`) — Parameter type. - `description` (`string`) — What the parameter is. - `required` (`boolean`) — Whether it must be supplied. - `enum` (`string[] | undefined`) — Allowed values, if constrained. - `default` (`string | undefined`) — Default value, if any. **Enums** ```typescript const SchemaType = { YML: 0, JSON: 1 } as const; ``` | Enum | Values | | --- | --- | | `SchemaType` | `SchemaType.YML`, `SchemaType.JSON` | | `ExtractionLogic` | `"fast"`, `"deep"` | | `ReadMode` | `"single-answer"`, `"raw-tables"`, `"xresponse"` | | `WriteQueueStatus` | `"queued"`, `"processing"`, `"extracting"`, `"extracted"`, `"applying"`, `"completed"`, `"failed"`, `"not_found"` | **Exceptions** | Exception | Parent | Attributes | | --- | --- | --- | | `XmemoryAPIError` | `Error` | `.status`, `.code`, `.details`, `.retryAfter` (each `undefined` when absent) | | `XmemoryHealthCheckError` | `XmemoryAPIError` | `.status` (HTTP status code or `undefined`) | ### Pydantic AI Canonical URL: https://xmemory.ai/pydantic/ Markdown URL: https://xmemory.ai/pydantic/index.html.md This guide shows how to use xmemory as a persistent memory layer for a [pydantic-ai](https://ai.pydantic.dev/) agent. Two approaches are covered: - **MCP** — point pydantic-ai at the xmemory MCP server; the agent gets `write` and `read` tools automatically. - **HTTP API** — call the REST API directly from custom pydantic-ai tools; useful when you want full control. --- #### Prerequisites ```bash pip install "pydantic-ai>=0.0.14" xmemory-ai pyyaml ``` You need an xmemory API key. Get one from the [xmemory console](https://console.xmemory.ai). --- #### Part 1 — Create an instance from a Pydantic schema xmemory stores data in typed *instances*. Each instance has a schema that describes the objects and relations you want to track. You normally describe what to remember in plain language and xmemory generates the schema for you — but since you're already working with Pydantic, you can feed your model's JSON schema straight to the generation endpoint. xmemory converts it into its own typed schema and returns it ready for instance creation. This is a one-time setup script. Save the returned instance ID (e.g. in an environment variable) and reuse it in your agent. ```python import os import yaml from pydantic import BaseModel from xmemory import XmemoryClient, SchemaType API_KEY = os.environ["XMEM_API_KEY"] # 1. Define your domain model with Pydantic class Contact(BaseModel): name: str email: str | None = None company: str | None = None notes: str | None = None # 2. Connect and pick a cluster client = XmemoryClient(api_key=API_KEY) clusters = client.admin.list_clusters() cluster_id = clusters[0].id # 3. Convert the Pydantic schema to an xmemory schema schema_response = client.admin.generate_schema( cluster_id, schema_description=f"for following json_schema: {Contact.model_json_schema()}", ) # 4. Create the instance inst = client.admin.create_instance( cluster_id=cluster_id, name="contacts", schema_text=yaml.dump(schema_response.data_schema, allow_unicode=True), schema_type=SchemaType.YML, ) print(f"Created instance: {inst.id}") # → store this in INSTANCE_ID and reuse it on subsequent runs ``` The instance ID is a UUID string. Keep it — you'll use it to get an instance handle on subsequent runs. --- #### Part 2 — Agent with custom xmemory tools (HTTP API) Wrap `write` and `read` as pydantic-ai tools so the agent can store and recall information during a conversation. ```python import os from pydantic import BaseModel, ConfigDict from pydantic_ai import Agent, RunContext from xmemory import XmemoryClient API_KEY = os.environ["XMEM_API_KEY"] INSTANCE_ID = os.environ["XMEM_INSTANCE_ID"] # from Part 1 class Deps(BaseModel): model_config = ConfigDict(arbitrary_types_allowed=True) client: XmemoryClient agent = Agent( "anthropic:claude-sonnet-4-6", deps_type=Deps, system_prompt=( "You are a helpful assistant with access to a persistent memory store. " "Use `remember` to save new information and `recall` to look things up." ), ) @agent.tool def remember(ctx: RunContext[Deps], text: str) -> str: """Store information in long-term memory.""" inst = ctx.deps.client.instance(INSTANCE_ID) result = inst.write(text) return f"Stored (write_id={result.write_id})." @agent.tool def recall(ctx: RunContext[Deps], query: str) -> str: """Retrieve information from long-term memory.""" inst = ctx.deps.client.instance(INSTANCE_ID) result = inst.read(query) return result.reader_result.get("answer", str(result.reader_result)) # Run it deps = Deps(client=XmemoryClient(api_key=API_KEY)) # Store something result = agent.run_sync( "Remember that Alice Johnson works at Acme Corp, her email is alice@acme.com.", deps=deps, ) print(result.output) # Recall it later result = agent.run_sync( "What do you know about Alice?", deps=deps, ) print(result.output) ``` ##### Read modes The `read` method supports three modes via `read_mode`: | `read_mode` | `reader_result` shape | When to use | |---|---|---| | `"single-answer"` | `{"answer": "..."}` | Natural-language question → plain text answer | | `"xresponse"` | `{"objects": [...], "relations": [...]}` | Get structured objects back | | `"raw-tables"` | `{"tables": [...]}` | Raw SQL result sets | --- #### Part 3 — MCP approach (fewer lines of code) pydantic-ai supports MCP servers natively. The xmemory MCP server exposes `write` and `read` (and more) as ready-made tools — no boilerplate needed. ##### Authentication This headless flow sends your account **API key** directly as the Bearer token on the `/instance/` shortcut path — no OAuth exchange needed. The instance is bound by the **URL path**, so you don't pass `instance_id` explicitly in tool calls. See the [MCP guide](https://xmemory.ai/mcp/#authentication) for the other connection options. ```python import os from pydantic_ai import Agent from pydantic_ai.mcp import MCPServerStreamableHTTP API_KEY = os.environ["XMEM_API_KEY"] INSTANCE_ID = os.environ["XMEM_INSTANCE_ID"] # from Part 1 mcp_server = MCPServerStreamableHTTP( url=f"https://mcp.xmemory.ai/instance/{INSTANCE_ID}", headers={"Authorization": f"Bearer {API_KEY}"}, ) agent = Agent( "anthropic:claude-sonnet-4-6", mcp_servers=[mcp_server], system_prompt=( "You have access to a persistent memory store via the xmemory tools. " "Use `write` to remember things and `read` to look them up." ), ) async def main(): async with agent.run_mcp_servers(): result = await agent.run( "Remember that Bob Smith is a senior engineer at Globex. " "Then tell me what you know about Bob." ) print(result.output) if __name__ == "__main__": import asyncio asyncio.run(main()) ``` ###### Available MCP tools (instance connection type) The instance connection exposes 9 default tools — 6 bound (`get_instance_id`, `get_instance_schema`, `write`, `write_async`, `write_status`, `read`) and the 3-tool schema-evolution suggestion engine (`review_suggestions`, `decide_suggestions`, `apply_pending_decisions`) — plus an opt-in **Schema management** group (5 more). See the [MCP — Tools](https://xmemory.ai/mcp/#tools) reference for full parameter and return-shape details. ### Google ADK Canonical URL: https://xmemory.ai/google-adk/ Markdown URL: https://xmemory.ai/google-adk/index.html.md #### xmemory Integration Guide for Google ADK This guide explains how to integrate **xmemory-ai** into agents built with the [Google Agent Development Kit (ADK)](https://google.github.io/adk-docs/). xmemory gives your agent persistent, structured memory: write free-form text, have it extracted into a typed schema, and query it back in natural language at any time. --- #### Installation ```bash pip install google-adk xmemory-ai pyyaml ``` --- #### Authentication & Configuration The client reads credentials from environment variables by default. | Variable | Description | |---|---| | `XMEM_API_KEY` | API key for the xmemory API | | `XMEM_API_URL` | API base URL (defaults to `https://api.xmemory.ai`) | Set them before running your agent: ```bash export XMEM_API_KEY="your-api-key" ``` Or pass them explicitly when constructing the client (see below). --- #### Core Concepts | Concept | Description | |---|---| | **Cluster** | A group of instances. List clusters with `client.admin.list_clusters()`. | | **Instance** | A named memory store with a typed schema inside a cluster. Identified by a UUID string. | | **Schema** | A YAML or JSON definition of the data structure xmemory extracts and stores. Generate schemas using `client.admin.generate_schema()`. | | **Write** | Send free-form text; xmemory extracts structured objects and merges them into the instance. | | **Read** | Query the instance in natural language; xmemory answers from stored structured data. | --- #### Quick Start ```python import yaml from xmemory import XmemoryClient, SchemaType client = XmemoryClient(api_key="your-api-key") # Step 1: pick a cluster clusters = client.admin.list_clusters() cluster_id = clusters[0].id # Step 2: generate a schema from a plain-language description schema_resp = client.admin.generate_schema( cluster_id, "Track contacts with their name, email address, and notes.", ) # Step 3: create a memory instance using the generated schema inst = client.admin.create_instance( cluster_id=cluster_id, name="contacts", schema_text=yaml.dump(schema_resp.data_schema), schema_type=SchemaType.YML, ) # inst is a bound handle — save inst.id for subsequent runs # Step 4: write information into memory inst.write("Alice Johnson joined the team today. Her email is alice@example.com.") # Step 5: read it back result = inst.read("What is Alice's email address?") print(result.reader_result) ``` --- #### Schemas **Use `client.admin.generate_schema()` to create or update schemas.** The generation endpoint understands xmemory's schema format and naming conventions, and produces schemas that extract data correctly. ```python # Generate a schema from a plain-language description resp = client.admin.generate_schema( cluster_id, "Track user preferences, past conversations, and open tasks with priorities and due dates.", ) schema_yml = resp.data_schema # pass this to create_instance ``` Object names in schemas use **CamelCase** (e.g. `UserPreferences`, `OpenTask`, `ConversationEntry`). The generation endpoint handles this automatically. ##### Updating a schema When your memory needs change, describe the updated structure and regenerate: ```python resp = client.admin.generate_schema( cluster_id, "Track user preferences, past conversations, and open tasks with priorities, due dates, and assignees.", current_yml_schema=current_schema_yml, # pass the existing schema so changes are incremental ) client.admin.update_instance_schema(instance_id, yaml.dump(resp.data_schema), SchemaType.YML) ``` Passing `current_yml_schema` preserves existing objects and fields — only the described changes are applied. --- #### Defining Tools for Google ADK The recommended pattern is to wrap xmemory operations as ADK `FunctionTool` instances and register them on your agent. The agent decides when to call them. ```python import os from google.adk.agents import Agent from google.adk.tools import FunctionTool from xmemory import XmemoryClient, ReadMode, XmemoryAPIError INSTANCE_ID = os.environ["XMEM_INSTANCE_ID"] client = XmemoryClient(api_key=os.environ["XMEM_API_KEY"]) inst = client.instance(INSTANCE_ID) def remember(text: str) -> str: """ Store information in long-term memory. Call this whenever the user shares facts, preferences, or context worth remembering. Args: text: Free-form text describing what to remember. Returns: Confirmation string. """ try: resp = inst.write(text) return f"Stored in memory (write_id={resp.write_id})." except XmemoryAPIError as e: return f"Memory write failed: {e}" def recall(query: str) -> str: """ Retrieve information from long-term memory. Call this when you need to recall facts, preferences, or context stored in previous sessions. Args: query: Natural language question about stored information. Returns: Answer derived from stored memory. """ try: resp = inst.read(query, read_mode=ReadMode.SINGLE_ANSWER) return str(resp.reader_result) if resp.reader_result is not None else "Nothing found." except XmemoryAPIError as e: return f"Memory read failed: {e}" agent = Agent( name="my_agent", model="gemini-2.5-flash", instruction="You are a helpful assistant with long-term memory. Use your memory tools to remember and recall information.", tools=[ FunctionTool(remember), FunctionTool(recall), ], ) ``` --- #### Async Writes For latency-sensitive agents, use `write_async` to enqueue a write and continue without blocking, then poll for completion with `write_status`. ```python from xmemory import WriteQueueStatus, XmemoryAPIError def remember_async(text: str) -> str: """ Enqueue a memory write without blocking. Returns a write_id to check later. Args: text: Free-form text to store. Returns: The write_id assigned to this operation. """ try: resp = inst.write_async(text) return f"Write enqueued. write_id={resp.write_id}" except XmemoryAPIError as e: return f"Memory write failed: {e}" def check_write_status(write_id: str) -> str: """ Check the status of a previously enqueued memory write. Args: write_id: The write_id returned by remember_async. Returns: Current status: queued, processing, completed, failed, or not_found. """ try: resp = inst.write_status(write_id) status = resp.write_status if status == WriteQueueStatus.FAILED: return f"Write failed: {resp.error_detail}" if status == WriteQueueStatus.COMPLETED: return f"Write completed at {resp.completed_at}." return f"Write status: {status}." except XmemoryAPIError as e: return f"Status check failed: {e}" ``` --- #### Managing Instances ##### Create a new instance Generate the schema first, then create the instance: ```python import yaml from xmemory import XmemoryClient, SchemaType client = XmemoryClient(api_key="your-api-key") clusters = client.admin.list_clusters() cluster_id = clusters[0].id schema_resp = client.admin.generate_schema( cluster_id, "Track user preferences and open tasks with priorities and due dates.", ) inst = client.admin.create_instance( cluster_id=cluster_id, name="my-memory", schema_text=yaml.dump(schema_resp.data_schema), schema_type=SchemaType.YML, ) print(inst.id) # persist this value ``` ##### Reuse an existing instance ```python client = XmemoryClient(api_key="your-api-key") inst = client.instance("your-saved-instance-id") ``` ##### Update the schema of an existing instance Generate the updated schema incrementally, then apply it: ```python schema_resp = client.admin.generate_schema( cluster_id, "Track user preferences and open tasks with priorities, due dates, and assignees.", current_yml_schema=current_schema_yml, ) client.admin.update_instance_schema( instance_id, yaml.dump(schema_resp.data_schema), SchemaType.YML ) ``` --- #### Read Modes The `read` method supports three modes via `ReadMode`: | Mode | Description | |---|---| | `ReadMode.SINGLE_ANSWER` | Natural language answer to the query (default). Best for most agent use cases. | | `ReadMode.RAW_TABLES` | Returns raw structured data tables without summarisation. | | `ReadMode.XRESPONSE` | Full structured response envelope with metadata. | ```python from xmemory import ReadMode # Natural language answer resp = inst.read("What tasks are overdue?", read_mode=ReadMode.SINGLE_ANSWER) # Raw structured data resp = inst.read("List all tasks", read_mode=ReadMode.RAW_TABLES) ``` --- #### Extraction Logic The `write` and `write_async` methods accept an `extraction_logic` parameter — pass `ExtractionLogic.DEEP` to override the `FAST` default: | Value | Description | |---|---| | `ExtractionLogic.FAST` | Fastest; lower accuracy (default). Use for high-volume, low-stakes writes. | | `ExtractionLogic.DEEP` | Most thorough extraction. Best for important or complex information. | --- #### Error Handling All xmemory operations raise `XmemoryAPIError` on failure. The exception carries an optional `.status` attribute with the HTTP status code. ```python from xmemory import XmemoryAPIError, XmemoryHealthCheckError try: client.check_health() except XmemoryHealthCheckError as e: print(f"API unreachable: {e}") try: resp = inst.write("...") except XmemoryAPIError as e: print(f"Error {e.status}: {e}") ``` --- #### Complete Agent Example ```python import os import yaml from google.adk.agents import Agent from google.adk.tools import FunctionTool from xmemory import ( XmemoryClient, ReadMode, WriteQueueStatus, XmemoryAPIError, ) client = XmemoryClient(api_key=os.environ["XMEM_API_KEY"]) inst = client.instance(os.environ["XMEM_INSTANCE_ID"]) def remember(text: str) -> str: """Store information in long-term memory. Call whenever the user shares facts worth preserving.""" try: resp = inst.write(text) return "Remembered." except XmemoryAPIError as e: return f"Could not store memory: {e}" def recall(query: str) -> str: """Recall information from long-term memory. Call when you need context from past sessions.""" try: resp = inst.read(query, read_mode=ReadMode.SINGLE_ANSWER) return str(resp.reader_result) if resp.reader_result is not None else "Nothing found." except XmemoryAPIError as e: return f"Could not recall memory: {e}" def remember_in_background(text: str) -> str: """Enqueue a memory write without waiting. Use when you don't need to confirm storage immediately.""" try: resp = inst.write_async(text) return f"Enqueued. write_id={resp.write_id}" except XmemoryAPIError as e: return f"Could not enqueue: {e}" def check_memory_write(write_id: str) -> str: """Check if a background memory write has completed.""" try: resp = inst.write_status(write_id) if resp.write_status == WriteQueueStatus.COMPLETED: return "Memory saved successfully." if resp.write_status == WriteQueueStatus.FAILED: return f"Memory write failed: {resp.error_detail}" return f"Status: {resp.write_status}." except XmemoryAPIError as e: return f"Status check failed: {e}" agent = Agent( name="persistent_memory_agent", model="gemini-2.5-flash", instruction=( "You are a helpful assistant with persistent long-term memory across sessions. " "Use `remember` to store important facts the user shares. " "Use `recall` before answering questions that may depend on past context. " "Use `remember_in_background` for non-critical information to avoid latency." ), tools=[ FunctionTool(remember), FunctionTool(recall), FunctionTool(remember_in_background), FunctionTool(check_memory_write), ], ) ``` --- #### Reference The Google ADK integration uses the Python SDK, so every client, admin, and instance method — with full parameters, return types, and errors — is documented in the [Python SDK reference](https://xmemory.ai/python/#reference): - [Client methods](https://xmemory.ai/python/#client) - [Admin methods](https://xmemory.ai/python/#admin-methods) - [Instance methods](https://xmemory.ai/python/#instance-methods) ### Mastra AI Canonical URL: https://xmemory.ai/mastra-ai/ Markdown URL: https://xmemory.ai/mastra-ai/index.html.md [Mastra](https://mastra.ai/) supports MCP servers natively, which makes adding xmemory straightforward — paste a prompt into your AI-assisted IDE and the integration is done. For the full tool reference, see the [MCP](https://xmemory.ai/mcp/) guide. For direct HTTP API usage, see the [TypeScript](https://xmemory.ai/typescript/) guide. --- #### Setup **API key:** To use xmemory APIs or integrations, you need an API key. Get one from the [xmemory console](https://console.xmemory.ai). Copy and securely store the key. Never share your API key publicly. Scaffold a Mastra project and add MCP support: ```bash npm create mastra@latest my-xmemory-agent cd my-xmemory-agent npm install @mastra/mcp ``` Add your keys to `.env`: ```bash XMEM_API_KEY=your-api-key-here XMEM_INSTANCE_ID=your-instance-id ANTHROPIC_API_KEY=your-anthropic-key ``` --- #### Add xmemory From your Mastra project directory, give this prompt to an AI-assisted IDE (Cursor, Windsurf, Claude Code, etc.): ```text Integrate the `xmemory` MCP server with this Mastra instance. Add `@mastra/mcp` to package.json if not already present. Create an MCP client that: - Reads XMEM_API_KEY and XMEM_INSTANCE_ID from the environment (throw if missing). - Connects to https://mcp.xmemory.ai/instance/ with the API key as the Bearer auth header. Load the MCP tools via listTools() and export them. Create an `xmemory` Agent wired to those tools with these instructions: > You are the xmemory assistant. You help users manage and query their > xmemory instance: > - Create and configure new instances, generate or enhance schemas, > connect and disconnect from instances. > - Use the xmemory_admin_* tools to perform administrative and schema > operations as requested. > - Be concise and confirm what you did after each action. Register the agent in the Mastra instance alongside any existing agents. ``` That's it. The exact set of tools the agent sees depends on the **connection type** chosen at MCP login. With the *instance* connection type, the agent gets 9 default tools — 6 bound (`get_instance_id`, `get_instance_schema`, `write`, `write_async`, `write_status`, `read`) and the 3-tool schema-evolution suggestion engine (`review_suggestions`, `decide_suggestions`, `apply_pending_decisions`) — plus an opt-in **Schema management** group (5 more). With the *admin* connection type it instead gets the schema and instance-management tools (`admin_*`). See the [MCP — Tools](https://xmemory.ai/mcp/#tools) reference for full details. > **Note on auth.** This headless setup sends your account **API key** directly as the Bearer token on the `/instance/` shortcut path — no OAuth exchange needed. The instance is bound by the URL path, so tool calls don't pass `instance_id`. See the [MCP guide](https://xmemory.ai/mcp/#authentication) for the other connection options. --- #### Reference The prompt above produces files like the ones below. Use these to verify the output or to set things up manually. **MCP client** — `src/mastra/mcp.ts` ```typescript if (!process.env.XMEM_API_KEY || !process.env.XMEM_INSTANCE_ID) { throw new Error('XMEM_API_KEY and XMEM_INSTANCE_ID environment variables are required'); } export const xmemoryMcp = new MCPClient({ id: 'xmemory', servers: { xmemory: { url: new URL(`https://mcp.xmemory.ai/instance/${process.env.XMEM_INSTANCE_ID}`), requestInit: { headers: { Authorization: `Bearer ${process.env.XMEM_API_KEY}`, }, }, }, }, }); ``` **Tool loader** — `src/mastra/tools/xmemory.ts` ```typescript let xmemoryTools: Record = {}; try { xmemoryTools = await xmemoryMcp.listTools(); } catch (err) { console.error('Failed to load xmemory MCP tools:', err); } export { xmemoryTools }; ``` **Agent** — `src/mastra/agents/xmemory.ts` ```typescript export const xmemoryAgent = new Agent({ name: 'xmemory-agent', model: anthropic('claude-sonnet-4-6'), instructions: 'You are the xmemory assistant. You help users manage and query their xmemory instance: ' + 'create and configure new instances, generate or enhance schemas, connect and disconnect from instances. ' + 'Use the xmemory_admin_* tools to perform administrative and schema operations as requested. ' + 'Be concise and confirm what you did after each action.', tools: { ...xmemoryTools }, }); ``` **Registration** — `src/mastra/index.ts` ```typescript export const mastra = new Mastra({ agents: { xmemoryAgent }, }); ``` ### n8n Canonical URL: https://xmemory.ai/n8n/ Markdown URL: https://xmemory.ai/n8n/index.html.md #### xmemory integration with n8n Visit [https://xmemory.ai/integration-overview/](https://xmemory.ai/integration-overview/) for **comprehensive guides, API references, and integration examples**. **API key:** To use xmemory APIs or integrations (including n8n), you need an API key. Get one from the [xmemory console](https://console.xmemory.ai). Copy and securely store the key. Never share your API key publicly. #### Integration with `n8n-nodes-xmemory` custom node This chapter walks through the full integration flow using the `n8n-nodes-xmemory` custom node: 1. Add **xmemory node** 2. **Create instance from YAML** 3. **Authorize using API key** 4. **Write** memory 5. **Read** memory 6. Run **full execution** Source code of the node could be found in the [n8n-nodes-xmemory repository](https://github.com/xmemory-ai/n8n-nodes-xmemory/). ##### 0) Install xmemory node - Go to Settings -> Community Nodes in n8n. - Click "Install a community node". - Enter `n8n-nodes-xmemory` and install the package. > Image: Install n8n xmemory community node ##### 1) xmemory node Add the xmemory custom node in your workflow. > Image: n8n xmemory custom node ##### 2) Create instance from yml Use the node operation that creates a new xmemory instance from a YAML schema. The **Create Instance** operation requires: - **Cluster ID** — the cluster to create the instance in, either by entering the ID manually or picking a cluster from the list - **Instance Name** — a name for the new instance - **Schema Format** and **Schema Text** — the schema content (JSON or YAML) > Image: Create xmemory instance from yaml in n8n Here the following xmemory schema was used: ```yaml xmd_version: v1 title: Website Contact Form description: Schema for capturing people interested in xmemory who submitted their contact information through the website form. objects: Contact: description: Represents a person interested in xmemory who submitted their contact information through the website form. Captures personal identification details and contact information for follow-up communication. fields: uuid: type: str required: true description: Unique identifier for the person. Used for internal tracking and reference. Must be a non-empty string. enum: null default: null name: type: str required: false description: Name of the person. Required for personalized communication. enum: null default: null email: type: str required: false description: Email address of the person. Primary contact method for follow-up communication. Must be a valid email format. enum: null default: null use_case: type: str required: false description: Optional field to capture the specific use case or interest of the person regarding xmemory. This information can help tailor follow-up communication and provide relevant resources. enum: null default: null primary_key: - uuid relations: {} ``` It represents a simple schema for a chatbot collecting contact information from website visitors interested in xmemory. Normally, you don't need to create instances on-the-fly. You'll need your `cluster_id` to create an instance — find it in the xmemory dashboard. Once created, store the `instance_id` and reuse it in your workflow for all subsequent `write` and `read` operations. ##### 3) Authorize using API key Configure your xmemory API key in node credentials so the workflow can access your instance. > Image: Authorize n8n xmemory node with API key ##### 4) Write Send content to xmemory with the **write** operation. > Image: Write data to xmemory from n8n ##### 5) Read Query stored memory with the **read** operation. > Image: Read data from xmemory in n8n ##### 6) Full execution Execute the workflow end-to-end to validate create, write, and read in one run. > Image: Full n8n workflow execution with xmemory node #### Integration with xmemory MCP You can also connect n8n to xmemory via MCP. For full MCP documentation, see [https://xmemory.ai/mcp](https://xmemory.ai/mcp). ##### 1) MCP credentials and auth type Set the MCP server address to `https://mcp.xmemory.ai/` and use **OAuth2** authentication. > Image: n8n MCP credentials with xmemory server address and OAuth2 auth ##### 2) OAuth2 server address Configure the OAuth2 authentication via `https://oauth2.xmemory.ai/` xmemory server. > Image: n8n OAuth2 credential showing xmemory server address ##### 3) Login and instance selection Sign in on the xmemory login page (use the API key) and select the instance you want the MCP session to use. > Image: xmemory MCP login page and instance selection ##### 4) Available xmemory MCP tools After successful auth, n8n lists all available xmemory MCP tools. > Image: n8n MCP tools list for xmemory ### LangChain Canonical URL: https://xmemory.ai/langchain/ Markdown URL: https://xmemory.ai/langchain/index.html.md This guide shows how to use xmemory as a persistent memory layer for a [LangChain](https://python.langchain.com/) agent. Two approaches are covered: - **MCP** — connect LangChain to the xmemory MCP server via `langchain-mcp-adapters`; the agent gets `write` and `read` tools automatically. - **HTTP API** — call the REST API directly from custom LangChain tools; useful when you want full control. **API key:** To use xmemory APIs or integrations, you need an API key. Get one from the [xmemory console](https://console.xmemory.ai). Copy and securely store the key. Never share your API key publicly. --- #### Prerequisites ```bash pip install langchain langchain-anthropic xmemory-ai pyyaml ``` For the MCP approach (Part 3), also install: ```bash pip install langchain-mcp-adapters ``` --- #### Part 1 — Create an instance xmemory stores data in typed *instances*. Each instance has a schema that describes the objects and relations you want to track. Describe what you want to remember in plain language — xmemory generates a typed schema from that free-text description and returns it ready for instance creation. This is a one-time setup script. Save the returned instance ID (e.g. in an environment variable) and reuse it in your agent. ```python import os import yaml from xmemory import XmemoryClient, SchemaType API_KEY = os.environ["XMEM_API_KEY"] # 1. Connect and pick a cluster client = XmemoryClient(api_key=API_KEY) clusters = client.admin.list_clusters() cluster_id = clusters[0].id # 2. Generate a schema from a plain-language description schema_response = client.admin.generate_schema( cluster_id, "Track contacts with name, email, company, and notes.", ) # 3. Create the instance inst = client.admin.create_instance( cluster_id=cluster_id, name="contacts", schema_text=yaml.dump(schema_response.data_schema, allow_unicode=True), schema_type=SchemaType.YML, ) print(f"Created instance: {inst.id}") # → store this in INSTANCE_ID and reuse it on subsequent runs ``` The instance ID is a UUID string. Keep it — you'll use it to get an instance handle on subsequent runs. --- #### Part 2 — Agent with custom xmemory tools (HTTP API) Wrap `write` and `read` as LangChain tools so the agent can store and recall information during a conversation. ```python import asyncio import os from langchain.agents import create_agent from langchain_core.tools import tool from xmemory import XmemoryClient API_KEY = os.environ["XMEM_API_KEY"] INSTANCE_ID = os.environ["XMEM_INSTANCE_ID"] # from Part 1 client = XmemoryClient(api_key=API_KEY) inst = client.instance(INSTANCE_ID) @tool def remember(text: str) -> str: """Store information in long-term memory.""" # Sync write so a follow-up `recall` sees it immediately. In production, # prefer inst.write_async(text) for fire-and-forget writes you won't read # back right away, then poll inst.write_status(write_id) if you need to. result = inst.write(text) return f"Stored (write_id={result.write_id})." @tool def recall(query: str) -> str: """Retrieve information from long-term memory.""" result = inst.read(query) return result.reader_result.get("answer", str(result.reader_result)) agent = create_agent( "anthropic:claude-sonnet-4-6", tools=[remember, recall], system_prompt=( "You are a helpful assistant with access to a persistent memory store. " "Use `remember` to save new information and `recall` to look things up." ), ) async def main(): # Store something result = await agent.ainvoke( {"messages": "Remember that Alice Johnson works at Acme Corp, her email is alice@acme.com."} ) print(result["messages"][-1].content) # Recall it later result = await agent.ainvoke( {"messages": "What do you know about Alice?"} ) print(result["messages"][-1].content) if __name__ == "__main__": asyncio.run(main()) ``` ##### Read modes The `read` method supports three modes via `read_mode`: | `read_mode` | `reader_result` shape | When to use | |---|---|---| | `"single-answer"` | `{"answer": "..."}` | Natural-language question → plain text answer | | `"xresponse"` | `{"objects": [...], "relations": [...]}` | Get structured objects back | | `"raw-tables"` | `{"tables": [...]}` | Raw SQL result sets | --- #### Part 3 — MCP approach (fewer lines of code) LangChain supports MCP servers via the [`langchain-mcp-adapters`](https://github.com/langchain-ai/langchain-mcp-adapters) package. The xmemory MCP server exposes `write` and `read` (and more) as ready-made tools — no boilerplate needed. ##### Authentication This headless flow sends your account **API key** directly as the Bearer token on the `/instance/` shortcut path — no OAuth exchange needed. The instance is bound by the **URL path**, so you don't pass `instance_id` explicitly in tool calls. See the [MCP guide](https://xmemory.ai/mcp/#authentication) for the other connection options. ```python import asyncio import os from langchain.agents import create_agent from langchain_mcp_adapters.client import MultiServerMCPClient API_KEY = os.environ["XMEM_API_KEY"] INSTANCE_ID = os.environ["XMEM_INSTANCE_ID"] # from Part 1 client = MultiServerMCPClient( { "xmemory": { "transport": "http", "url": f"https://mcp.xmemory.ai/instance/{INSTANCE_ID}", "headers": { "Authorization": f"Bearer {API_KEY}", }, }, } ) async def main(): tools = await client.get_tools() agent = create_agent( "anthropic:claude-sonnet-4-6", tools=tools, system_prompt=( "You have access to a persistent memory store via the xmemory tools. " "Use `write` to remember things and `read` to look them up." ), ) result = await agent.ainvoke( {"messages": "Remember that Bob Smith is a senior engineer at Globex. " "Then tell me what you know about Bob."} ) print(result["messages"][-1].content) if __name__ == "__main__": asyncio.run(main()) ``` ###### Available MCP tools (instance connection type) The instance connection exposes 9 default tools — 6 bound (`get_instance_id`, `get_instance_schema`, `write`, `write_async`, `write_status`, `read`) and the 3-tool schema-evolution suggestion engine (`review_suggestions`, `decide_suggestions`, `apply_pending_decisions`) — plus an opt-in **Schema management** group (5 more). See the [MCP — Tools](https://xmemory.ai/mcp/#tools) reference for full parameter and return-shape details. ### Temporal Canonical URL: https://xmemory.ai/temporal/ Markdown URL: https://xmemory.ai/temporal/index.html.md [Temporal](https://docs.temporal.io/) runs long-lived, crash-proof workflows; xmemory stores durable, schema-grounded memory your agents can recall across runs, users, and services. The **xmemory Temporal plugin** wires the two together so a memory read or write becomes a replay-safe Temporal Activity — added to your Worker with a single line. It ships in two languages that mirror each other: - **Python** — [`xmemory-temporal`](https://github.com/xmemory-ai/xmemory-temporal) — publishing to PyPI - **TypeScript** — [`@xmemory/temporal`](https://github.com/xmemory-ai/xmemory-temporal-ts) — publishing to npm :::note[Preview — not yet on PyPI / npm] The plugin is currently under review for [Temporal](https://temporal.io/)'s AI Partner Ecosystem. The packages will be published to PyPI (`xmemory-temporal`) and npm (`@xmemory/temporal`) once the review completes. Until then, work from the source repositories — [xmemory-temporal](https://github.com/xmemory-ai/xmemory-temporal) (Python) and [xmemory-temporal-ts](https://github.com/xmemory-ai/xmemory-temporal-ts) (TypeScript) — and install from GitHub as shown below. ::: > An agent's memory is exactly the state you don't want to lose when a worker crashes mid-turn. Putting xmemory behind Temporal makes a memory write a durable step: it survives process death, redeploys, and rolling upgrades, and Temporal — not your code — owns its retries and timeouts. #### What you get - **Memory as Activities.** `read`, `write`, `write_async` + `write_status` run as Activities, so all network I/O stays out of Workflow code and Workflows replay deterministically. - **A durable deep write.** `write_durable(text)` enqueues a write and polls it to completion from the Workflow, so a multi-minute extraction survives worker restarts — the poll state lives in Workflow history, not a worker process. - **A near-zero-diff migration.** The Workflow-side handle mirrors the plain xmemory client, so agent code that already calls `inst.read(...)` / `inst.write(...)` keeps working — it just dispatches to an Activity. - **Temporal-owned retries.** xmemory errors map to typed `ApplicationFailure`s with retryable / non-retryable verdicts (see below), so you tune `RetryPolicy` against stable error-type strings. - **Opt-in auto-capture** of Activity results into memory, via an Activity interceptor that never touches the replay path. #### Prerequisites - Python 3.10+ or Node.js 20+ - A local Temporal dev server or a Temporal Cloud namespace - An xmemory API key and cluster Start a local Temporal server with the [Temporal CLI](https://docs.temporal.io/cli): ```bash temporal server start-dev ``` Set your xmemory API key: ```bash export XMEM_API_KEY="your-xmemory-api-key" ``` Install the plugin. **Until the PyPI / npm release**, install straight from the GitHub repositories: ```bash # Python — from source (until the PyPI release) pip install "git+https://github.com/xmemory-ai/xmemory-temporal.git" # TypeScript — from source (until the npm release) npm install github:xmemory-ai/xmemory-temporal-ts @temporalio/worker @temporalio/client ``` Once the packages are published this becomes `pip install xmemory-temporal` / `npm install @xmemory/temporal`. #### Create an xmemory instance xmemory stores information in typed *instances*. Generate a schema, then create an instance to hold your agent's memory. Choose primary keys the agent will naturally restate in text — a person's full name, or a literal `customer_id` / `interaction_id` you supply — so the diff engine can resolve repeated writes to the same record. ```python import json import os from xmemory import SchemaType, XmemoryClient with XmemoryClient(api_key=os.environ["XMEM_API_KEY"]) as client: cluster_id = client.admin.list_clusters()[0].id schema = client.admin.generate_schema( cluster_id, ( "Track customer-support memory. Customers are identified by customer_id. " "Support interactions are identified by interaction_id and record the " "message, recalled context, response, and outcome. Make customer_id and " "interaction_id primary keys so repeated writes update the same records." ), ) instance = client.admin.create_instance( cluster_id=cluster_id, name="temporal-support-memory", schema_text=json.dumps(schema.data_schema), schema_type=SchemaType.JSON, ) print(instance.id) ``` Run it once and save the printed instance id: ```bash export XMEM_INSTANCE_ID="the-returned-instance-id" ``` You can also create and manage instances in the [xmemory console](https://console.xmemory.ai/). See the [Python SDK guide](https://xmemory.ai/python/) for other schema and instance-management options. #### Quickstart — Python Register the plugin on the **Client**; the Worker inherits it automatically. ```python from temporalio.client import Client from temporalio.worker import Worker from xmemory_temporal import XmemoryConfig, XmemoryPlugin from workflows import CustomerSupportWorkflow config = XmemoryConfig(instance_id="") # reads XMEM_API_KEY from the env plugin = XmemoryPlugin(config) client = await Client.connect("localhost:7233", plugins=[plugin]) # The Worker inherits the client's plugins automatically — do NOT pass it again here. worker = Worker(client, task_queue="xmemory-support", workflows=[CustomerSupportWorkflow]) await worker.run() ``` Inside the Workflow, `xmemory_for_workflow()` returns a handle whose methods dispatch to Activities: ```python from temporalio import workflow with workflow.unsafe.imports_passed_through(): from xmemory_temporal import xmemory_for_workflow @workflow.defn class CustomerSupportWorkflow: @workflow.run async def run(self, customer_id: str, interaction_id: str, message: str) -> str: mem = xmemory_for_workflow() # A memory store has no ambient "current user" — name whom the fact is # about, then recall by that name (or pass scope= to bind to a record). context = await mem.read(f"What should support know about {customer_id}?") # ... handle the request (put any LLM call in its own Activity) ... response = f"Interaction {interaction_id} recorded for {customer_id}." # A durable write: enqueue + poll to completion, surviving worker restarts. await mem.write_durable( f"interaction_id: {interaction_id}. customer_id: {customer_id}. " f"message: {message}. response: {response}. status: completed." ) return str(context.reader_result) # reader_result is Any ``` #### Quickstart — TypeScript Register the plugin on the **Worker** (the TS plugin is a `WorkerPlugin`; the client does not carry it). ```ts const plugin = new XmemoryPlugin({ instanceId: '' }); // reads XMEM_API_KEY const connection = await NativeConnection.connect({ address: 'localhost:7233' }); const worker = await Worker.create({ connection, taskQueue: 'xmemory-support', workflowsPath: require.resolve('./workflows'), plugins: [plugin], }); await worker.run(); ``` ```ts // workflows.ts export async function customerSupportWorkflow( customerId: string, interactionId: string, message: string, ): Promise { const mem = xmemoryForWorkflow(); const context = await mem.read(`What should support know about ${customerId}?`); const response = `Interaction ${interactionId} recorded for ${customerId}.`; await mem.writeDurable( `interaction_id: ${interactionId}. customer_id: ${customerId}. ` + `message: ${message}. response: ${response}. status: completed.`, ); return context.readerResult; } ``` Importing `xmemoryForWorkflow` from the package root inside Workflow code is safe: the package is marked side-effect-free, so Temporal's Workflow bundler tree-shakes the plugin and the xmemory client (non-Workflow-safe modules) out of the sandbox bundle. > **Register the plugin once, never twice.** In Python, put it on the Client (the Worker inherits it) — passing it to both registers the Activities twice and fails with *"More than one activity named xmemory_read"*. In TypeScript, put it on the Worker. Use one plugin instance per Worker. #### Durable writes A deep xmemory extraction can take minutes. `write_durable` enqueues the write and then polls its status **from the Workflow**, so the wait is a Temporal timer in server-side history rather than a blocked activity slot: ```python status = await mem.write_durable(text, max_wait=timedelta(minutes=15)) ``` ```ts const status = await mem.writeDurable(text, { maxWaitMs: 15 * 60_000 }); ``` Because the poll loop lives in Workflow history, you can redeploy the worker fleet mid-write and nothing is lost — the loop resumes on the new worker and completes. Under the time-skipping test environment a 15-minute write runs in milliseconds, because Temporal fast-forwards the timers. For the fire-and-forget pattern (kick off several writes, keep talking, join before the turn ends), `write_async_start()` and `write_status()` are public too. #### Timeouts **The Workflow owns every activity budget.** `xmemory_for_workflow()` sets each call's `start_to_close_timeout`, and the Activity derives its xmemory client timeout from the deadline Temporal actually assigned it — always a margin below, so the client gives up first and you get an attributable xmemory error rather than an opaque Temporal activity timeout. ```python mem = xmemory_for_workflow( read_timeout=timedelta(seconds=60), # a deep read on a large instance write_timeout=timedelta(minutes=5), ) ``` ```ts const mem = xmemoryForWorkflow({ readTimeout: '60s', writeTimeout: '5m' }); ``` Because the client timeout is *derived* rather than configured separately, the two can never disagree: lowering a Workflow's budget lowers the client's with it. The defaults are 120s for reads, 180s for writes, and 30s for the enqueue and each status poll; the gap between the activity deadline and the client timeout is the one knob that stays worker-side (`client_margin_seconds` / `clientMarginMs`). #### Replay safety and idempotency Two things keep memory operations correct under retries and replay: - **Replay never re-issues an operation.** All I/O is in Activities; Workflow code only schedules Activities and sleeps. Temporal replays Workflow code but never re-runs a completed Activity, so a replay never repeats a memory read or write. The test suite proves this with a forced-replay (`max_cached_workflows=0`) side-effects test. - **Writes default to at-most-once.** It is tempting to lean on xmemory's primary-key dedup to make retries safe — a re-write of the same fact should update the same record. But **primary-key extraction is non-deterministic**: xmemory authors keys with a model that can normalize the same value differently across runs (e.g. `Dr. Robert Kim` vs `Robert Kim`), and a disagreement forks the entity into a **new** row. So a lost-response retry can duplicate. Rather than risk that silently, write Activities default to `maximum_attempts=1`: a failed write surfaces to your Workflow, which decides to retry, compensate, or fail. Reads and status-polls (idempotent) retry generously. **Structured writes are the reliable way to make a write retryable.** Pass explicit mutations instead of free text and the primary key is one you supply, so nothing is extracted and re-applying the write is deterministic: ```python mem = xmemory_for_workflow(write_retry_policy=RetryPolicy(maximum_attempts=3)) await mem.write(structured_mutations=[ {"object_mutation": {"object_type": "Customer", "update": {"key": {"customer_id": "c-1"}, "values": {"tier": "gold"}}}} ]) ``` ```ts const mem = xmemoryForWorkflow({ writeRetryPolicy: { maximumAttempts: 3 } }); await mem.write('', { structuredMutations: [ { object_mutation: { object_type: 'Customer', update: { key: { customerId: 'c-1' }, values: { tier: 'gold' } } } }, ], }); ``` A mutation is a `create`, `update`, or `delete` on one object or relation, and it carries the key explicitly, so a retry addresses the same row instead of forking a new one. An `update` in particular re-applies identically. For text writes, opt into retries only when your primary keys are literal identifiers that appear verbatim in the text, such as a `customer_id` you supply, so the extractor has no room to normalise them differently on a second pass. That is a convention you have to keep, not something the API enforces. **Scoped writes**, which xmemory is adding in the near future, will bind a text write to a known record and guarantee a stable primary key, closing the gap for text writes too. This is why the support example above builds its write text from literal `customer_id` / `interaction_id` values — with those as schema primary keys, opting into retries is safe. #### Error handling xmemory errors become `ApplicationFailure`s with stable `type` strings you can match in a `RetryPolicy` (`non_retryable_error_types=[...]` / `nonRetryableErrorTypes`). The mapping is derived from the server's own error codes: | xmemory condition | `type` | Retryable? | |---|---|---| | transport error / timeout / HTTP ≥ 500 / 408 | `XmemoryServerError` / `XmemoryUnavailable` | yes | | `RATE_LIMITED` (429) | `XmemoryRateLimited` | yes — honors `Retry-After` | | `QUOTA_EXCEEDED` + daily | `XmemoryDailyQuotaExceeded` | yes (long backoff) | | `QUOTA_EXCEEDED` + monthly | `XmemoryMonthlyQuotaExceeded` | no | | `QUOTA_EXCEEDED` (kind unknown) | `XmemoryQuotaExceeded` | no | | `UNAUTHORIZED` / `FORBIDDEN` | `XmemoryAuthFailed` | no | | `NOT_FOUND` | `XmemoryNotFound` | no | | validation / conflict / schema-evolution rejections | `XmemoryBadRequest` / `XmemorySchemaRejected` | no | | Activities registered without the plugin | `XmemoryNotBound` | no | | an unrecognized code | `XmemoryUnknown` | yes (never fatal) | Plus three raised by the durable-write loop from a polled `write_status`, all non-retryable: `XmemoryWriteFailed` (the queued write reported `failed`), `XmemoryWriteNotFound` (the write id was not found), and `XmemoryWriteTimeout` (polling exceeded `max_wait`). An unrecognized error code stays retryable and never raises — a stricter client that crashed on a newer server's code would break during rolling deploys. #### Credentials and privacy The config holds the **name** of the environment variable that supplies the API key (`XMEM_API_KEY` by default), never the key itself — so nothing secret is serialized into Activity arguments, which Temporal persists in the clear. Pass the key in-process instead with `XmemoryPlugin(config, api_key=...)` / `new XmemoryPlugin(config, { apiKey })` if you prefer. **Your memory text and queries, however, *are* in history.** The query you `read` and the text you `write` are Activity inputs, and the error mapping keeps raw transport strings out of failure *messages* — but the inputs themselves, and the `reader_result`, are persisted to cleartext Temporal history and shown in the Web UI. If your memory text is sensitive, install a Temporal **Payload Codec** to encrypt payloads at the edge — the plugin deliberately does not impose one, since a codec applies namespace-wide to every payload, not just xmemory's. #### Auto-capture (opt-in) Instead of writing memory explicitly, you can capture the results of your own Activities into memory automatically: ```python from xmemory_temporal import AutoCaptureConfig, XmemoryPlugin plugin = XmemoryPlugin( config, auto_capture=AutoCaptureConfig( project=lambda activity_name, result: summarize(result), # return None to skip sample_rate=0.25, ), ) ``` Off by default. It runs as an **Activity** interceptor (outside the replay path), requires a `project` function that decides what — if anything — to remember, samples to bound fan-out, and never fails the wrapped Activity if a capture write errors. Capture is an enqueue (`write_async`) bounded by a short timeout, so it can never slow the wrapped Activity past its `start_to_close`. It skips any Activity whose name starts with `xmemory_`, and never captures Queries. #### Production checklist - Register the plugin once (Python: on the Client; TypeScript: on the Worker), one instance per Worker. - Prefer `write_durable` when a later step needs the write to land; use `read` for recall. - Keep write retries off unless your primary keys are literal identifiers present verbatim in the text. - Match `RetryPolicy(non_retryable_error_types=...)` to the typed error strings above. - Set activity budgets on `xmemory_for_workflow()` — raise them for deep reads or large writes rather than leaving a call to time out. - Return only the minimum recalled context — Activity results are recorded in Workflow history. - Install a Payload Codec if memory text is sensitive. - Keep Temporal and xmemory credentials in a secret manager, never in Workflow arguments. The plugin source lives on GitHub — [xmemory-temporal](https://github.com/xmemory-ai/xmemory-temporal) (Python) and [xmemory-temporal-ts](https://github.com/xmemory-ai/xmemory-temporal-ts) (TypeScript). For the underlying client APIs, see the [Python](https://xmemory.ai/python/) and [TypeScript](https://xmemory.ai/typescript/) SDK guides. For the raw tool surface, see the [MCP](https://xmemory.ai/mcp/) guide. ### OpenAI Agents SDK Canonical URL: https://xmemory.ai/openai-agents/ Markdown URL: https://xmemory.ai/openai-agents/index.html.md This guide shows how to use xmemory as a persistent memory layer for an [OpenAI Agents SDK](https://openai.github.io/openai-agents-python/) agent. Two approaches are covered: - **MCP** — connect the Agents SDK to the xmemory MCP server; the agent gets `write` and `read` tools automatically. - **HTTP API** — call the REST API directly from custom function tools; useful when you want full control. **API key:** To use xmemory APIs or integrations, you need an API key. Get one from the [xmemory console](https://console.xmemory.ai). Copy and securely store the key. Never share your API key publicly. --- #### Prerequisites ```bash pip install openai-agents xmemory-ai pyyaml ``` Set `XMEM_API_KEY` (your xmemory key — create one in the [console](https://console.xmemory.ai)) in your environment; every example below reads it. The Agents SDK uses OpenAI models, so set `OPENAI_API_KEY` too. --- #### Part 1 — Create an instance xmemory stores data in typed *instances*. Describe what you want to remember in plain language and xmemory designs the schema for you — natural-language generation is the primary path (you can also hand it an existing schema, e.g. a Pydantic model's JSON schema). This is a one-time setup script. Save the returned instance ID (e.g. in an environment variable) and reuse it in your agent. > **Fastest path:** the [CLI](https://xmemory.ai/cli/) creates an instance in three commands > (`xmemcli xmd generate → validate → instance create`). The Python script below is the equivalent > if you'd rather stay in code. ```python import os import yaml from xmemory import XmemoryClient, SchemaType API_KEY = os.environ["XMEM_API_KEY"] # Connect and pick a cluster client = XmemoryClient(api_key=API_KEY) cluster_id = client.admin.list_clusters()[0].id # Describe what to remember in plain language — xmemory designs the schema schema = client.admin.generate_schema( cluster_id, "Track contacts with name, email, company, and notes.", ) # Create the instance from that schema inst = client.admin.create_instance( cluster_id=cluster_id, name="contacts", schema_text=yaml.dump(schema.data_schema, allow_unicode=True), schema_type=SchemaType.YML, ) print(f"Created instance: {inst.id}") # → store this in XMEM_INSTANCE_ID and reuse it on subsequent runs ``` The instance ID is a UUID string. Keep it — you'll use it to get an instance handle on subsequent runs. --- #### Part 2 — Agent with custom xmemory tools (HTTP API) Wrap `write` and `read` as Agents SDK function tools so the agent can store and recall information during a conversation. ```python import asyncio import os from agents import Agent, Runner, function_tool from xmemory import XmemoryClient API_KEY = os.environ["XMEM_API_KEY"] INSTANCE_ID = os.environ["XMEM_INSTANCE_ID"] # from Part 1 client = XmemoryClient(api_key=API_KEY) inst = client.instance(INSTANCE_ID) @function_tool def remember(text: str) -> str: """Store information in long-term memory.""" result = inst.write_async(text) return f"Stored (write_id={result.write_id})." @function_tool def recall(query: str) -> str: """Retrieve information from long-term memory.""" result = inst.read(query) return result.reader_result.get("answer", str(result.reader_result)) agent = Agent( name="Assistant", instructions=( "You are a helpful assistant with access to a persistent memory store. " "Use `remember` to save new information and `recall` to look things up." ), tools=[remember, recall], ) async def main(): result = await Runner.run( agent, "Remember that Alice Johnson works at Acme Corp, her email is alice@acme.com.", ) print(result.final_output) if __name__ == "__main__": asyncio.run(main()) ``` > `remember` uses `write_async`, which returns immediately — recall it in a **later** `Runner.run` (a separate turn); avoid reading it back in the same turn as the async write. ##### Read modes The `read` method supports three modes via `read_mode`: | `read_mode` | `reader_result` shape | When to use | |---|---|---| | `single-answer` | `{"answer": …}` | Plain-text answer | | `xresponse` | `{"objects": …, "relations": …}` | Structured objects back | | `raw-tables` | `{"tables": …}` | Raw SQL result sets | --- #### Part 3 — MCP approach (fewer lines of code) The Agents SDK connects to MCP servers natively via `MCPServerStreamableHttp`. The xmemory MCP server exposes `write` and `read` (and more) as ready-made tools — no boilerplate needed. ##### Authentication This headless flow sends your account **API key** directly as the Bearer token on the `/instance/` shortcut path — no OAuth exchange needed. The instance is bound by the **URL path**. See the [MCP guide](https://xmemory.ai/mcp/#authentication) for the other connection options. ```python import asyncio import os from agents import Agent, Runner from agents.mcp import MCPServerStreamableHttp API_KEY = os.environ["XMEM_API_KEY"] INSTANCE_ID = os.environ["XMEM_INSTANCE_ID"] # from Part 1 async def main(): async with MCPServerStreamableHttp( name="xmemory", params={ "url": f"https://mcp.xmemory.ai/instance/{INSTANCE_ID}", "headers": {"Authorization": f"Bearer {API_KEY}"}, }, cache_tools_list=True, ) as server: agent = Agent( name="Assistant", instructions=( "You have access to a persistent memory store via the xmemory tools. " "Use `write_async` to remember things and `read` to look them up." ), mcp_servers=[server], ) result = await Runner.run( agent, "Remember that Bob Smith is a senior engineer at Globex.", ) print(result.final_output) if __name__ == "__main__": asyncio.run(main()) ``` ##### Hosted alternative If you would rather have OpenAI connect to the MCP server server-side (through the Responses API) instead of from your own process, use `HostedMCPTool` and pass the token in `headers` (the `authorization` field is for OAuth access tokens, not a static header): ```python import asyncio import os from agents import Agent, Runner, HostedMCPTool API_KEY = os.environ["XMEM_API_KEY"] INSTANCE_ID = os.environ["XMEM_INSTANCE_ID"] # from Part 1 agent = Agent( name="Assistant", instructions="Use the xmemory tools to remember and recall information.", tools=[ HostedMCPTool( tool_config={ "type": "mcp", "server_label": "xmemory", "server_url": f"https://mcp.xmemory.ai/instance/{INSTANCE_ID}", "headers": {"Authorization": f"Bearer {API_KEY}"}, "require_approval": "never", } ) ], ) async def main(): result = await Runner.run(agent, "What do you know about Alice?") print(result.final_output) if __name__ == "__main__": asyncio.run(main()) ``` #### Available MCP tools (instance connection type) The instance connection exposes 9 default tools — 6 bound (`get_instance_id`, `get_instance_schema`, `write`, `write_async`, `write_status`, `read`) and the 3-tool schema-evolution suggestion engine (`review_suggestions`, `decide_suggestions`, `apply_pending_decisions`) — plus an opt-in **Schema management** group (5 more). See the [MCP — Tools](https://xmemory.ai/mcp/#tools) reference for full parameter and return-shape details. ### Claude Agent SDK Canonical URL: https://xmemory.ai/claude-agent-sdk/ Markdown URL: https://xmemory.ai/claude-agent-sdk/index.html.md This guide shows how to give Claude persistent memory by connecting it to the xmemory MCP server. Two approaches are covered: - **Agent SDK** — the [Claude Agent SDK](https://code.claude.com/docs/en/agent-sdk) connects to the xmemory MCP server; the agent gets `write` and `read` (and more) as ready-made tools. - **Messages API connector** — the [Claude Messages API](https://platform.claude.com/docs/en/agents-and-tools/mcp-connector) connects to the MCP server directly, with no SDK. **API key:** To use xmemory APIs or integrations, you need an API key. Get one from the [xmemory console](https://console.xmemory.ai). Copy and securely store the key. Never share your API key publicly. --- #### Prerequisites ```bash pip install claude-agent-sdk anthropic xmemory-ai pyyaml ``` Set `XMEM_API_KEY` (your xmemory key — create one in the [console](https://console.xmemory.ai)) in your environment; every example below reads it. Both approaches call Claude, so set `ANTHROPIC_API_KEY` too. (For TypeScript, the Agent SDK package is `@anthropic-ai/claude-agent-sdk`.) --- #### Part 1 — Create an instance xmemory stores data in typed *instances*. Describe what you want to remember in plain language and xmemory designs the schema for you — natural-language generation is the primary path (you can also hand it an existing schema, e.g. a Pydantic model's JSON schema). This is a one-time setup script. Save the returned instance ID (e.g. in an environment variable) and reuse it in your agent. > **Fastest path:** the [CLI](https://xmemory.ai/cli/) creates an instance in three commands > (`xmemcli xmd generate → validate → instance create`). The Python script below is the equivalent > if you'd rather stay in code. ```python import os import yaml from xmemory import XmemoryClient, SchemaType API_KEY = os.environ["XMEM_API_KEY"] # Connect and pick a cluster client = XmemoryClient(api_key=API_KEY) cluster_id = client.admin.list_clusters()[0].id # Describe what to remember in plain language — xmemory designs the schema schema = client.admin.generate_schema( cluster_id, "Track contacts with name, email, company, and notes.", ) # Create the instance from that schema inst = client.admin.create_instance( cluster_id=cluster_id, name="contacts", schema_text=yaml.dump(schema.data_schema, allow_unicode=True), schema_type=SchemaType.YML, ) print(f"Created instance: {inst.id}") # → store this in XMEM_INSTANCE_ID and reuse it on subsequent runs ``` The instance ID is a UUID string. Keep it — you'll use it to get an instance handle on subsequent runs. --- #### Part 2 — Claude Agent SDK with MCP ##### Authentication This headless flow sends your account **API key** directly as the Bearer token on the `/instance/` shortcut path — no OAuth exchange needed. The instance is bound by the **URL path**. See the [MCP guide](https://xmemory.ai/mcp/#authentication) for the other connection options. Register the xmemory MCP server under `mcp_servers` (keyed by server name) with `type: "http"`. MCP tools must be allow-listed via `allowed_tools` using the `mcp____` pattern, or Claude can see them but not call them. ```python import asyncio import os from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage API_KEY = os.environ["XMEM_API_KEY"] INSTANCE_ID = os.environ["XMEM_INSTANCE_ID"] # from Part 1 async def main(): options = ClaudeAgentOptions( mcp_servers={ "xmemory": { "type": "http", "url": f"https://mcp.xmemory.ai/instance/{INSTANCE_ID}", "headers": {"Authorization": f"Bearer {API_KEY}"}, } }, allowed_tools=["mcp__xmemory__write_async", "mcp__xmemory__read"], ) async for message in query( prompt="Remember that Alice Johnson works at Acme Corp, her email is alice@acme.com.", options=options, ): if isinstance(message, ResultMessage) and message.subtype == "success": print(message.result) if __name__ == "__main__": asyncio.run(main()) ``` The TypeScript SDK takes the same shape under `mcpServers` / `allowedTools`: ```typescript const INSTANCE_ID = process.env.XMEM_INSTANCE_ID!; const API_KEY = process.env.XMEM_API_KEY!; for await (const message of query({ prompt: "Remember that Alice Johnson works at Acme Corp, her email is alice@acme.com.", options: { mcpServers: { xmemory: { type: "http", url: `https://mcp.xmemory.ai/instance/${INSTANCE_ID}`, headers: { Authorization: `Bearer ${API_KEY}` }, }, }, allowedTools: ["mcp__xmemory__write_async", "mcp__xmemory__read"], }, })) { if (message.type === "result" && message.subtype === "success") { console.log(message.result); } } ``` > `write_async` returns immediately — recall it in a **later** turn (a subsequent `query` reflects it). Avoid `read` in the same turn as an async write. --- #### Part 3 — Messages API MCP connector The Messages API can connect to a remote MCP server itself — Claude calls the tools server-side. Pass the server under `mcp_servers` with `type: "url"`, set `authorization_token` to your **raw** xmemory API key (the connector adds the `Bearer ` prefix), and reference the server from `tools` with an `mcp_toolset` entry. The connector is in beta — pass the `mcp-client-2025-11-20` beta flag. ```python import os import anthropic API_KEY = os.environ["XMEM_API_KEY"] INSTANCE_ID = os.environ["XMEM_INSTANCE_ID"] # from Part 1 client = anthropic.Anthropic() # reads ANTHROPIC_API_KEY response = client.beta.messages.create( model="claude-sonnet-4-6", max_tokens=1024, betas=["mcp-client-2025-11-20"], mcp_servers=[ { "type": "url", "url": f"https://mcp.xmemory.ai/instance/{INSTANCE_ID}", "name": "xmemory", "authorization_token": API_KEY, } ], tools=[{"type": "mcp_toolset", "mcp_server_name": "xmemory"}], messages=[{"role": "user", "content": "What do you know about Alice?"}], ) print(response) ``` --- #### Available MCP tools (instance connection type) The instance connection exposes 9 default tools — 6 bound (`get_instance_id`, `get_instance_schema`, `write`, `write_async`, `write_status`, `read`) and the 3-tool schema-evolution suggestion engine (`review_suggestions`, `decide_suggestions`, `apply_pending_decisions`) — plus an opt-in **Schema management** group (5 more). See the [MCP — Tools](https://xmemory.ai/mcp/#tools) reference for full parameter and return-shape details. ### Codex Canonical URL: https://xmemory.ai/codex/ Markdown URL: https://xmemory.ai/codex/index.html.md This guide shows how to connect the [OpenAI Codex CLI](https://developers.openai.com/codex/) to xmemory via MCP. Codex talks to the xmemory MCP server over Streamable HTTP and gets `write` and `read` (and more) as ready-made tools — no custom code. **API key:** To use xmemory APIs or integrations, you need an API key. Get one from the [xmemory console](https://console.xmemory.ai). Copy and securely store the key. Never share your API key publicly. :::note[There is also a Codex plugin] Codex can install the same xmemory plugin package as Claude Code, which brings the skills and the session-start hook rather than the MCP server alone — see [Install xmemory in your agent](https://xmemory.ai/install/#codex). This page covers the headless path, where Codex authenticates with an API key instead of a browser sign-in. ::: --- #### Prerequisites Install the Codex CLI (remote MCP servers with custom headers need Codex **0.46+**; use a current build): ```bash npm install -g @openai/codex codex --version ``` You also need an xmemory **instance ID**. The fastest way to create one is the [CLI](https://xmemory.ai/cli/) (`xmemcli xmd generate → validate → instance create`); the [Python](https://xmemory.ai/python/) guide works too. Keep the returned UUID — you'll put it in the server URL below. --- #### Configure the MCP server Codex reads MCP servers from `~/.codex/config.toml`. Add an `[mcp_servers.]` table. Because the server is remote, set `url`; the transport is detected automatically (Streamable HTTP). Name the table `xmemory-` plus the **first eight characters of the instance id** — the same name `xmemcli instance setup` prints for it. One table is one connection to one instance, so a second instance under a shared name replaces the first, and the plugin already ships a server called `xmemory` pointing at the shared host. This headless flow sends your account **API key** directly as the Bearer token on the `/instance/` shortcut path — no OAuth exchange needed. The instance is bound by the **URL path**. See the [MCP guide](https://xmemory.ai/mcp/#authentication) for the other connection options. ```toml [mcp_servers.xmemory-] url = "https://mcp.xmemory.ai/instance/" http_headers = { Authorization = "Bearer " } ``` `http_headers` values are sent verbatim on every request — include the `Bearer ` prefix yourself. ##### Keep the token out of the file To avoid storing the key in `config.toml`, use `env_http_headers`, which reads each header value from an environment variable (the variable holds the **full** header value, including `Bearer `): ```toml [mcp_servers.xmemory-] url = "https://mcp.xmemory.ai/instance/" env_http_headers = { Authorization = "XMEM_AUTH_HEADER" } ``` ```bash export XMEM_AUTH_HEADER="Bearer your-xmemory-api-key" ``` ##### Adding it from the command line `codex mcp add` can register a remote server, but it cannot write custom headers — it only sets `bearer_token_env_var`: ```bash codex mcp add xmemory- \ --url "https://mcp.xmemory.ai/instance/" \ --bearer-token-env-var XMEM_API_KEY ``` > **Note:** `bearer_token_env_var` attaches the token to request POSTs but not to the SSE GET stream, which can return `401` against servers that authenticate every request (xmemory does). If you see `401`s, use the `http_headers` / `env_http_headers` form above instead — those are sent on every request. --- #### Available MCP tools (instance connection type) The instance connection exposes 9 default tools — 6 bound (`get_instance_id`, `get_instance_schema`, `write`, `write_async`, `write_status`, `read`) and the 3-tool schema-evolution suggestion engine (`review_suggestions`, `decide_suggestions`, `apply_pending_decisions`) — plus an opt-in **Schema management** group (5 more). See the [MCP — Tools](https://xmemory.ai/mcp/#tools) reference for full parameter and return-shape details. ### Claude Canonical URL: https://xmemory.ai/claude/ Markdown URL: https://xmemory.ai/claude/index.html.md Connect xmemory's MCP server to Claude and your agents get xmemory's read and write tools with no custom code. This guide covers the **custom connector** for claude.ai and Claude Desktop (it also appears on the mobile apps once added), plus the **Claude Code** CLI. See the [integration overview](https://xmemory.ai/integration-overview/) for **comprehensive guides, API references, and integration examples**. **API key:** To use xmemory APIs or integrations (including MCP), you need an API key. Get one from the [xmemory console](https://console.xmemory.ai). Copy and securely store the key. Never share your API key publicly. #### Claude (Desktop & claude.ai) Add xmemory as a custom connector from claude.ai or Claude Desktop; once added, it shows up on the mobile apps too. The fastest way is the **Add to Claude.ai** button on your instance's **Connect** page in the [xmemory console](https://console.xmemory.ai) — it deep-links straight to the *Add custom connector* dialog with the name and MCP URL pre-filled. To add it manually, follow the steps below. ##### 1) Open Connectors In claude.ai or Claude Desktop, open **Settings → Connectors** (under **Customize**). > Image: Claude Customize settings with the Connectors section ##### 2) Add a custom connector Click the **+** next to **Connectors** and choose **Add custom connector**. > Image: Adding a custom connector in Claude ##### 3) Name it and paste the MCP URL Give the connector a name and paste your xmemory MCP URL: ``` https://mcp.xmemory.ai/instance/ ``` An instance URL binds the tools to that instance up front. (To pick the instance interactively during login instead, use the root URL `https://mcp.xmemory.ai/`.) > Image: Pasting the xmemory MCP URL into the Add custom connector dialog ##### 4) Connect and sign in The connector appears under **Not connected**. Click **Connect** and complete the xmemory sign-in (OAuth) when prompted. > Image: xmemory connector ready to connect in Claude ##### 5) Tools are ready Once connected, xmemory's `read` and `write` tools are available in your chats. You can fine-tune per-tool permissions from the connector's page. > Image: xmemory connector tool permissions in Claude Custom connectors work on all plans, including Free — which allows one custom connector; paid plans (Pro / Max / Team / Enterprise) allow more. #### Claude Code (CLI) For the Claude Code CLI, add xmemory as an MCP server with the instance URL — the same command the **Connect** page generates: ```sh claude mcp add --transport http xmemory https://mcp.xmemory.ai/instance/ ``` The `/instance/` path binds the connection to that instance, and Claude Code completes an OAuth login on first use — no API key needed in the command. For headless use (CI, scripts), pass a fixed API key instead by adding `--header "Authorization: Bearer "`. See the full [MCP guide](https://xmemory.ai/mcp/) for both authentication paths, the complete tool list, and error handling. #### Other connection types The steps above bind to a single instance (`/instance/`) and expose its `read`/`write` tools. The MCP server offers two other connection types, chosen by the URL you connect with: - **Admin (fleet) tools** — connect with `https://mcp.xmemory.ai/admin` to create instances, list clusters, and manage instance metadata. Admin is global-only (there is no `/admin/`). - **Status only** — connect with `https://mcp.xmemory.ai/status` for the read-only service-status tool. In a connector, paste the admin (or status) URL instead of the instance URL — or paste the root `https://mcp.xmemory.ai/` and choose **Admin MCP** on the xmemory login page. For Claude Code, swap the URL in the `claude mcp add` command: ```sh claude mcp add --transport http xmemory-admin https://mcp.xmemory.ai/admin ``` See the [MCP guide](https://xmemory.ai/mcp/#admin-tools) for the full admin tool list. ### ChatGPT Canonical URL: https://xmemory.ai/chatgpt/ Markdown URL: https://xmemory.ai/chatgpt/index.html.md Connect xmemory's MCP server to ChatGPT and your chats get xmemory's read and write tools with no custom code. This guide covers the **custom connector** in ChatGPT, plus the **Codex** CLI. See the [integration overview](https://xmemory.ai/integration-overview/) for **comprehensive guides, API references, and integration examples**. **API key:** To use xmemory APIs or integrations (including MCP), you need an API key. Get one from the [xmemory console](https://console.xmemory.ai). Copy and securely store the key. Never share your API key publicly. #### ChatGPT custom connector ChatGPT has no deep-link pre-fill, so you add the connector manually from **Settings → Apps**. ##### 1) Open Apps settings In ChatGPT, open **Settings → Apps**. > Image: ChatGPT Apps settings ##### 2) Enable Developer Mode Open **Advanced settings** and enable **Developer mode**. > Image: Enabling Developer mode in ChatGPT Apps settings ##### 3) Create the connector Click **Create app**, then give it a **Name**, choose **OAuth** authentication, and paste this MCP URL as the **Server URL**: ``` https://mcp.xmemory.ai/instance/ ``` > Image: Creating the xmemory connector as a new app in ChatGPT ##### 4) Approve and sign in Approve the prompt and sign in with xmemory when prompted. > Image: Signing in to xmemory from ChatGPT ##### 5) Connector ready Once connected, mention the connector by name in a chat to use its tools. You can adjust its permissions from the app's page. > Image: xmemory connector connected in ChatGPT A custom connector requires Plus / Pro / Business / Enterprise / Edu (not Free or Go). On Business / Enterprise / Edu, only workspace admins can add custom connectors. #### Codex (CLI) For the Codex CLI, add xmemory as an MCP server with the instance URL — the same command the **Connect** page generates: ```sh codex mcp add xmemory --url https://mcp.xmemory.ai/instance/ ``` The `/instance/` path binds the connection to that instance and authorizes via OAuth on first use — no API key needed in the command. For headless use, configure a fixed `Authorization: Bearer ` header for the server in your Codex config instead. See the full [MCP guide](https://xmemory.ai/mcp/) for both authentication paths, the complete tool list, and error handling. #### Other connection types The steps above bind to a single instance (`/instance/`). The MCP server also offers two other connection types, chosen by the connect URL: - **Admin (fleet) tools** — use `https://mcp.xmemory.ai/admin` to create instances, list clusters, and manage instance metadata. Admin is global-only (no `/admin/`). - **Status only** — use `https://mcp.xmemory.ai/status` for the read-only status tool. When creating the connector, paste the admin (or status) URL as the **Server URL** instead of the instance URL. For Codex: ```sh codex mcp add xmemory-admin --url https://mcp.xmemory.ai/admin ``` See the [MCP guide](https://xmemory.ai/mcp/#admin-tools) for the full admin tool list. ### About xmemory Canonical URL: https://xmemory.ai/about/ Markdown URL: https://xmemory.ai/about/index.html.md #### Our mission is to bring reliability to agentic workflows and become the storage layer for the next era of software. As software shifts toward language-powered agents, data infrastructure needs to evolve with it. We bridge agents with reliable transactional and structured storage, preserving explicit contracts and a trustworthy source of truth while hiding the complexity required to keep data consistent. #### Team xmemory is built by engineers who have worked on some of the world’s most complex software systems. > Image: Google > Image: Microsoft > Image: Meta > Image: Bloomberg > Image: Alex Gusak ##### Alex Gusak Founding Engineer > Image: Denis Mukha ##### Denis Mukha Founding Engineer > Image: Dima Korolev ##### Dima Korolev Founding Engineer > Image: Konstantin Leontev ##### Konstantin Leontev DevOps Engineer > Image: Aleksey Shnyukov ##### Aleksey Shnyukov Software Engineer > Image: Alex Petrov ##### Alex Petrov Founder & CEO #### Building with great support xmemory is lucky to have amazing support from true professionals, angel investors, and VC funds. > Image: Fly Ventures > Image: Begin Capital > Image: AAL VC > Image: 33East > Image: Inovia Capital > Image: Frederick Bergenlid ##### Frederick Bergenlid Partner at Fly Ventures > Image: Alex Menn ##### Alex Menn Partner at Begin Capital > Image: Aleksey Dosovitsky ##### Aleksey Dosovitsky Angel investor and co-founder of Recursive > Image: Raif Jacobs ##### Raif Jacobs Partner at Inovia Capital > Image: Demetrios Zoppos ##### Demetrios Zoppos Co-founder and Partner at 33East > Image: Ash Vardanian ##### Ash Vardanian Founder at Unum.cloud, Investor at AAL VC > Image: Mark Shmulevich ##### Mark Shmulevich Founder & Managing Partner at Aloniq > Image: Vadim Barshtak ##### Vadim Barshtak Angel investor and former CTO of Miro > Image: Konstantin Vinogradov ##### Konstantin Vinogradov VC investor in Open Source, AI and Infra > Image: Dmitry Stepanov ##### Dmitry Stepanov AAL VC, Yango Ventures, entrepreneur > Image: Yiannis Eftychiou ##### Yiannis Eftychiou Partner at 33East > Image: Eugene Mikhaylov ##### Eugene Mikhaylov GP at AAL VC > Image: Gabriel Matuschka ##### Gabriel Matuschka Partner at Fly Ventures > Image: Pavel Danilov ##### Pavel Danilov 3x founder turned Venture Investor > Image: Ruslan Sarkisyan ##### Ruslan Sarkisyan Partner at Begin Capital #### Our offices ##### London HQ The Frames, 1 Phipp Street London EC2A 4PS United Kingdom ##### San Francisco 595 Pacific Avenue, Floor 4 San Francisco, CA 94133 United States #### Contacts Please get in touch with any questions or enquiries. [General — info@xmemory.ai](mailto:info@xmemory.ai) [Sales — sales@xmemory.ai](mailto:sales@xmemory.ai) [Legal — legal@xmemory.ai](mailto:legal@xmemory.ai) ### Privacy policy Canonical URL: https://xmemory.ai/privacy-policy.html How xmemory collects, uses, stores, and protects personal data. ### Terms and conditions Canonical URL: https://xmemory.ai/terms-and-conditions.html Terms governing access to and use of xmemory services.