Skip to content

TypeScript

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 guide. For framework-specific setup, see Mastra AI.

API key: To use xmemory APIs or integrations, you need an API key. Please register your interest at https://xmemory.ai and we will reach out to give access. Copy and securely store the key. Never share your API key publicly.


Terminal window
npm install xmemory

import { XmemoryClient, SchemaType } from "xmemory";
// 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"}

Three options, depending on whether you want a health check:

import { XmemoryClient, xmemoryInstance } from "xmemory";
// 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: "..." });
OptionEnv varDefaultDescription
apiKeyXMEM_API_KEYundefinedAPI key for authentication
urlXMEM_API_URLhttps://api.xmemory.aiAPI base URL
timeoutMs60000Default 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.


Use xm.instance(id) to get a scoped handle for data operations:

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);

Send free-form text — xmemory extracts structured objects according to your schema and merges them into the knowledge graph.

const resp = await inst.write("Carol is a senior engineer at Initech.");
console.log(resp.write_id);
console.log(resp.cleaned_objects);
console.log(resp.trace_id); // request trace id when available

Control the speed/accuracy tradeoff:

await inst.write("...", { extractionLogic: "fast" });
ValueWhen to use
"deep"Important or complex information (default)
"fast"High-volume, low-stakes writes

For latency-sensitive code, enqueue a write and return immediately:

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:

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.


Ask questions in natural language:

const resp = await inst.read("Who works at Acme Corp?");
console.log(resp.reader_result);
console.log(resp.trace_id);
// 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" });

Preview what xmemory would extract from a piece of text, without storing anything:

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.


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.

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):

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.


All management operations live on xm.admin:

const clusters = await xm.admin.listClusters();
const clusterId = clusters[0].id;
const schema = await xm.admin.generateSchema(
clusterId,
"Track contacts with name, email, company, and notes.",
);
console.log(schema.data_schema);
import { SchemaType } from "xmemory";
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 directly:

await xm.admin.updateInstanceSchema(instanceId, newSchemaYml, SchemaType.YML);

Evolving a schema (rename / remove / type change)

Section titled “Evolving a schema (rename / remove / type change)”

Non-additive changes 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.

import yaml from "js-yaml";
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);

Suggestion engine (review → decide → apply)

Section titled “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.

import type { DecisionInput } from "xmemory";
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);
}
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.

const instances = await xm.admin.listInstances();
const info = await xm.admin.getInstance(instanceId);
await xm.admin.deleteInstance(instanceId);

All errors throw XmemoryAPIError with optional .status (HTTP status), .code (structured error code, when the server returned one), and .details.

import { XmemoryAPIError, XmemoryHealthCheckError } from "xmemory";
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:

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
}
}

MethodReturnsDescription
new XmemoryClient(options?)XmemoryClientCreate a client (no health check)
XmemoryClient.create(options?)Promise<XmemoryClient>Create a client with health check
xmemoryInstance(options?)Promise<XmemoryClient>Factory with health check (same as .create())
xm.instance(instanceId)InstanceHandleGet a handle for data operations on an instance
xm.checkHealth()Promise<void>Raises XmemoryHealthCheckError if unreachable
MethodReturnsDescription
listClusters(options?)Promise<ClusterInfo[]>List clusters
getCluster(clusterId, options?)Promise<ClusterInfo>Get a cluster by ID
generateSchema(clusterId, description, options?)Promise<GenerateSchemaResult>Generate a schema from a description
createInstance(clusterId, name, schemaText, schemaType, options?)Promise<InstanceHandle>Create an instance; returns a bound handle
listInstances(options?)Promise<InstanceInfo[]>List instances
getInstance(instanceId, options?)Promise<InstanceInfo>Get an instance by ID
deleteInstance(instanceId, options?)Promise<string[]>Delete an instance
getInstanceSchema(instanceId, options?)Promise<InstanceSchemaInfo>Get an instance’s schema
updateInstanceSchema(instanceId, schemaText, schemaType, options?)Promise<InstanceInfo>Update a schema; options accepts migrationPlan and confirmDestructive for non-additive changes
updateInstanceMetadata(instanceId, name, description, options?)Promise<InstanceInfo>Update instance name and description. Both name and description are required strings.
enhanceSchema(clusterId, schemaDescription, currentYmlSchema, options?)Promise<EnhanceSchemaResult>Evolve a schema → new schema + migration plan
dryRunMigration(instanceId, schemaText, schemaType, options?)Promise<DryRunResult>Preview a migration’s DDL
listMigrations(instanceId, options?)Promise<ListMigrationsResult>List applied migrations (limit, beforeId, includeYaml)
getMigration(instanceId, migrationId, options?)Promise<MigrationRecord>Get a single migration record
MethodReturnsDescription
write(text, options?)Promise<WriteResult>Extract and persist objects from text
writeAsync(text, options?)Promise<AsyncWriteResult>Enqueue a write; returns write_id immediately
writeStatus(writeId, options?)Promise<WriteStatusResult>Poll the status of an async write
read(query, options?)Promise<ReadResult>Query the instance in natural language. options accepts readMode and traceId.
extract(text, options?)Promise<ExtractResult>Extract objects without writing them
getSchema(options?)Promise<InstanceSchemaInfo>Get this instance’s schema
reviewSuggestions(options?)Promise<ReviewSuggestionsResult>Get the rolling schema-improvement proposal
decideSuggestions(proposalVersion, decisions, options?)Promise<DecideSuggestionsResult>Record accept/reject/defer decisions in bulk
applyPendingDecisions(proposalVersion, options?)Promise<ApplyPendingDecisionsResult>Apply accepted decisions as one migration
describe(options?)Promise<DescribeResult>Get agent-facing tool descriptions (cached 5 min)
clearDescribeCache()voidForce next describe() to fetch fresh data
idstringThe instance ID (property)
type ExtractionLogic = "fast" | "deep";
type ReadMode = "single-answer" | "raw-tables" | "xresponse";
type WriteQueueStatus = "queued" | "processing" | "completed" | "failed" | "not_found";
const SchemaType = { YML: 0, JSON: 1 } as const;