cortex-sdk
v0.3.0
Published
Typed TypeScript client for Cortex — agent memory that catches contradictions at write time and returns what is still true.
Maintainers
Readme
cortex-sdk
Typed TypeScript client for Cortex — agent memory that catches contradictions at write time and returns what is still true.
When your agent learns something that conflicts with what it already knew, Cortex notices at the moment of writing, records why, and ranks the current fact above the stale one. Nothing is deleted; superseded facts stay queryable and demoted, so you can always show your work.
- Zero dependencies. ESM + CommonJS. Node 18+.
- Every response fully typed against the live API.
- Typed errors, automatic retry with backoff, configurable timeouts, polling helper.
Preview software. Cortex is a preview-stage service run by one developer. See the Terms and Security pages before pointing production at it.
Install
npm install cortex-sdkWant to pin an exact commit instead of a published version? See Install from GitHub.
Quickstart
import { Cortex } from "cortex-sdk";
const cortex = new Cortex({ apiKey: process.env.CORTEX_API_KEY! });
await cortex.store({ agentId: "support", content: "Jordan is now CTO at BetaWorks." });
const hits = await cortex.retrieve({ agentId: "support", query: "Where does Jordan work?" });
console.log(hits.results[0].fact);Getting a key
curl -X POST https://your-cortex-host/signup \
-H 'content-type: application/json' \
-d '{"email":"[email protected]","accept_terms":true}'Open the returned onboard_url, paste your own Anthropic or OpenAI key, and you get a crtx_live_… key — shown once. Cortex is bring-your-own-key: fact extraction and contradiction judging are billed to your provider account. Retrieval is local and costs nothing.
Configuration
const cortex = new Cortex({
apiKey: process.env.CORTEX_API_KEY!,
baseUrl: "https://your-cortex-host", // or set CORTEX_BASE_URL; defaults to localhost:4000
timeoutMs: 30_000, // per request
retry: { maxRetries: 2, baseDelayMs: 250, maxDelayMs: 8_000 },
headers: { "x-trace-id": "…" }, // merged into every request
onRetry: (i) => console.warn(`retry ${i.attempt}/${i.maxRetries} on ${i.endpoint}`),
});baseUrl resolves in order: the explicit option → CORTEX_BASE_URL → http://localhost:4000.
Methods
Every method takes an optional last argument { signal?, timeoutMs? } to cancel that call or give it its own timeout.
store(input)
const { id, status, contradictions } = await cortex.store({
agentId: "support",
content: "Jordan left Sable Freight — he is now CTO at BetaWorks.",
type: "semantic", // optional; auto-detected
sync: false, // optional; see below
});Returns { id, status, memory, contradictions, … }. id and status are lifted to the top level for the common path.
Async by default. The memory is persisted and searchable immediately with status: "pending", while extraction and contradiction detection run on the write queue. Use waitFor(id) to await the result.
Pass sync: true to block until extraction and judging finish, so contradictions is populated in the same response — good for demos, slower under load.
store()is never retried automatically. Replaying a store the server already accepted would duplicate the memory, and duplicate memories are the thing this product exists to prevent.
get(id)
const memory = await cortex.get("mem_abc123");
console.log(memory.status, memory.fact, memory.confidence);Throws CortexNotFoundError if the id doesn't exist under your key.
waitFor(id, options?)
Polls until extraction finishes (pending → active).
const memory = await cortex.waitFor(id, {
timeoutMs: 60_000,
intervalMs: 1_000,
onPoll: (m, elapsedMs) => console.log(m.status, elapsedMs),
});Throws CortexTimeoutError if the timeout elapses first. In practice that means the backend's LLM credential is missing, unfunded, or rate-limited — a pending memory is retried until it succeeds. The memory is never lost; it stays stored and searchable with its raw text as the fact.
retrieve(input)
const res = await cortex.retrieve({
agentId: "support", // optional; omit to search every agent
query: "Where does Jordan work now?",
limit: 5, // server default 5, max 50
});Returns:
| Field | Meaning |
|---|---|
| results | Ranked hits, each with score, relevance_pct, confidence_pct, lifecycle_state, stale, contested, via |
| excluded | Candidates that matched but fell below threshold, each with an excluded_reason |
| reasoning | Plain-language account of how the ranking was produced |
| latency_ms | Server-side time, excluding network |
| output_id | Provenance handle — GET /outputs/{output_id}/trace shows exactly what informed this answer |
Ranking is semantic + keyword relevance, then weighted by live confidence and lifecycle state. A superseded fact can score higher on raw relevance and still rank below the current one — that is the point:
1. 0.273 [active] Jordan left Sable Freight — he is now CTO at BetaWorks.
relevance 30% · confidence 90%
2. 0.041 [superseded] Jordan works at Sable Freight as a backend engineer.
relevance 38% · confidence 90%listContradictions(input?)
const page = await cortex.listContradictions({ status: "open", limit: 20 });
for (const c of page.contradictions) {
console.log(c.memory_a?.fact, "vs", c.memory_b?.fact);
console.log(c.suggested_resolution?.reasoning);
}Newest first. With limit you get one page plus a nextCursor; without it you get everything and a null cursor. Paging is keyset-based, so a contradiction detected while you are iterating never causes a row to be skipped or repeated.
Iterating every page:
let cursor: string | undefined;
do {
const page = await cortex.listContradictions({ status: "open", limit: 50, cursor });
for (const c of page.contradictions) handle(c);
cursor = page.nextCursor ?? undefined;
} while (cursor);resolve(contradictionId, options?)
await cortex.resolve("ctr_abc123"); // accept the suggestion
await cortex.resolve("ctr_abc123", { choice: "supersede" }); // retire the loser
await cortex.resolve("ctr_abc123", { choice: "keep_both" }); // both stay active
await cortex.resolve("ctr_abc123", { choice: "supersede", supersedeId: "mem_x" });With no choice, the backend applies its own suggestion — usually superseding the older memory. Superseding never deletes: the losing memory stays queryable, marked superseded, and demoted in ranking. Returns the recomputed trust_score and stats.
Throws CortexConflictError (409) if it was already resolved.
entities.list() and entities.graph(id, options?)
const entities = await cortex.entities.list();
const graph = await cortex.entities.graph(entities[0].id, { hops: 2 });
// graph.entity — the seed
// graph.facts — facts mentioning it
// graph.linked_entities — entities within `hops`, each tagged with its hop distance
// graph.edges — entity→fact MENTIONS, plus fact→fact SUPERSEDES / EVOLUTION_OF / CONTRADICTShops is clamped to 1–4 server-side; the default is 2. Entities are extracted by the same LLM step as facts, so they appear once a memory reaches active.
usage()
const usage = await cortex.usage();
console.log(usage.totals.cost_usd, usage.by_type.store?.count);This calendar month's metered operations, token counts and estimated cost. comparison estimates what an equivalent managed vector stack would have cost — an illustration with its assumptions shown, not a charge.
stats()
const stats = await cortex.stats();
console.log(stats.trust_score, stats.open_contradictions, stats.by_state);Error handling
Every failure is a subclass of CortexError, so one instanceof catches everything the SDK throws.
import {
CortexError,
CortexAuthError,
CortexRateLimitError,
CortexValidationError,
CortexConnectionError,
} from "cortex-sdk";
try {
await cortex.store({ agentId: "support", content: text });
} catch (err) {
if (err instanceof CortexAuthError) {
// 401 — key missing, unknown, or revoked. Retrying will not help.
} else if (err instanceof CortexRateLimitError) {
console.log(err.retryAfterSeconds, err.resetAt, err.limit, err.isQuota);
// isQuota distinguishes the monthly store quota from a per-minute rate limit.
} else if (err instanceof CortexValidationError) {
console.log(err.field, err.fields); // e.g. "content"
} else if (err instanceof CortexConnectionError) {
console.log(err.timedOut); // true when our timeout fired, not the network
} else if (err instanceof CortexError) {
console.log(err.status, err.endpoint, err.requestId);
}
}| Class | Status | Carries |
|---|---|---|
| CortexAuthError | 401 | — |
| CortexForbiddenError | 403 | — |
| CortexNotFoundError | 404 | — |
| CortexConflictError | 409 | — |
| CortexValidationError | 400 / 413 / 422 | field, fields |
| CortexRateLimitError | 429 | retryAfterSeconds, resetAt, limit, isQuota |
| CortexServerError | 5xx | — |
| CortexConnectionError | 0 | timedOut |
| CortexTimeoutError | 0 | memoryId, waitedMs (from waitFor) |
All of them also carry status, body, endpoint and requestId — quote requestId when reporting a bug and the server-side log line is one grep away.
Retries
Retried automatically, with exponential backoff and full jitter:
- 429 — the server's
Retry-Afteralways wins over the computed delay. - 5xx and network failures/timeouts.
Not retried:
- 4xx other than 429. A bad key or malformed body fails identically the second time.
store()— see above. Every other method is safe to replay and is retried.- Caller aborts.
signalis an instruction, not a failure.
Disable entirely with retry: { maxRetries: 0 }.
Example
examples/quickstart.ts stores two facts that cannot both be true and walks the whole flow: store → wait → detect → resolve → trust-weighted retrieve → graph → usage.
export CORTEX_API_KEY=crtx_live_...
export CORTEX_BASE_URL=https://your-cortex-host
npx tsx examples/quickstart.tsIf the backend has no working LLM credential, the example says so explicitly and continues with the steps that don't need one — rather than printing an empty result and looking broken.
Install from GitHub
For pinning an exact commit rather than a published version. The package builds itself on install (prepare runs tsc), so a git install needs no extra step. But npm installs the repository root, and this SDK lives in cortex-sdk/ — so install it by path or tarball:
# clone and install by path
git clone https://github.com/AAGAM17/prism.git ~/prism
npm install ~/prism/cortex-sdk
# or pack a tarball
cd ~/prism/cortex-sdk && npm pack # → cortex-sdk-0.3.0.tgz
npm install /path/to/cortex-sdk-0.3.0.tgzBoth give you the same import { Cortex } from "cortex-sdk". For a reproducible install, clone at a specific commit (git checkout <sha>) rather than tracking a branch.
Development
npm run typecheck # src + examples, no emit
npm run build # ESM + CJS + .d.ts into dist/
npm run smoke # offline transport tests (errors, retries, timeouts, paging)
npm run verify:example # boots cortex-core + a stub LLM, runs the example, asserts its output
npm test # typecheck + build + smokenpm run verify:example needs the monorepo checkout (it uses ../cortex-core); the others are self-contained.
Links
- cortex-core — the backend this talks to
- contradiction-bench — the numbers, reproducible
- Issues
MIT © Aagam Shah
