@conare/sdk
v1.3.0
Published
Typed, zero-dependency client for the Conare Partner API (per-end-user memory for AI apps).
Maintainers
Readme
@conare/sdk
Typed, zero-dependency TypeScript client for the Conare Partner API: isolated per-end-user memory, versioned source lifecycle, hybrid retrieval, deep recall, and memory-grounded suggestions.
Full reference: docs/openapi.yaml. Caeros handoff:
docs/CAEROS_INTEGRATION_PROMPT.md.
Install
bun add @conare/sdk # or: npm i @conare/sdkIt works in Node 18+, Bun, Deno, and Cloudflare Workers through global fetch.
Keep the scoped cint_... Integration key in a server-side secret manager.
Start safely
import { Conare, ConareError, hasAnswer } from "@conare/sdk";
const conare = new Conare({
apiKey: process.env.CONARE_API_KEY!,
onResponse(meta) {
metrics.timing("conare.request", meta.durationMs, {
status: String(meta.status),
requestId: meta.requestId,
});
},
});
// Side-effect-free deploy smoke test: key, namespace config, and backend hop.
await conare.status({ requestId: `deploy-${process.env.RELEASE_ID}` });
const recalled = await conare.recall({
endUserId: "u_123",
query: "what matters to this user right now",
});
if (hasAnswer(recalled)) {
systemPrompt += `\n\nWhat we know about this user:\n${recalled.answer}`;
}Every request sends one safe X-Request-Id; retries retain it. Every response
returns it, and ConareError.requestId exposes it for support correlation.
Write models
Use save for append-only observations whose source has no durable record ID:
await conare.memories.save({
endUserId: "u_123",
content: "User prefers smaller islands and wants to avoid crowds.",
containerTag: "conversation-observation",
});Use the versioned source lifecycle for database-owned facts. The identity is
(endUserId, source, externalId); higher versions correct or delete the same
logical record without stale retries resurrecting old state:
await conare.memories.upsertSource({
endUserId: "u_123",
source: "caeros-profile",
externalId: "profile_123",
version: 7,
occurredAt: "2026-07-16T10:30:00Z",
content: "User prefers smaller islands.",
idempotencyKey: "profile_123:v7",
});
await conare.memories.deleteSource({
endUserId: "u_123",
source: "caeros-profile",
externalId: "profile_123",
version: 8,
occurredAt: "2026-07-17T09:00:00Z",
idempotencyKey: "profile_123:v8",
});Bootstrap with an outbox
Bulk-mirror database-owned records through memories.lifecycleBatch (at most
100 items and 1 MiB of aggregate content per call). There is no server-side
import job: a client-side outbox that re-sends after a crash is the entire
resume story — already-applied items replay to the same durable receipt, and
superseded versions fail per item with stale_source_version.
const result = await conare.memories.lifecycleBatch({
endUserId: "u_123",
items: [{
source: "caeros-profile",
externalId: "profile_123",
version: 7,
occurredAt: "2026-07-16T10:30:00Z",
content: "User prefers smaller islands.",
}],
});
// result.success means only "the batch was processed" — check each item.
for (const [index, item] of result.items.entries()) {
if (item.success || item.code === "stale_source_version") {
outbox.markDone(index); // that version (or newer) is durable
} else {
outbox.scheduleRetry(index, item.code);
}
}Mark an outbox row done on per-item success or per-item stale_source_version
(both mean the version is already durable), then send the next batch. Ongoing
corrections and deletes use upsertSource / deleteSource with the next
version.
API surface
| Client method | Purpose |
| --- | --- |
| status | Authenticated, side-effect-free deployment readiness |
| memories.save, memories.saveBatch | Append-only distilled observations |
| memories.search | Fast raw hybrid retrieval without synthesis |
| memories.getSource, upsertSource, deleteSource | Durable source-owned lifecycle |
| memories.lifecycleBatch | Versioned bulk bootstrap with per-item outcomes |
| recall | Citation-grounded personalized answer |
| suggestions | Up to five grounded proactive actions |
| deleteUser | Complete end-user memory deletion |
Retry and quota behavior
Safe storage, lifecycle, status, and search operations automatically retry
network failures, 408, 429, and 5xx responses with bounded exponential backoff.
recall and suggestions do not automatically retry because an ambiguous
response could repeat paid synthesis; retry those deliberately with the same
caller request ID.
try {
await conare.memories.search({ endUserId, query });
} catch (error) {
if (error instanceof ConareError) {
logger.warn("Conare call failed", {
code: error.code,
requestId: error.requestId,
retryAfterSeconds: error.retryAfterSeconds,
});
if (error.isUsageExhausted) showBudgetFallback();
else if (!error.isRateLimited) throw error;
}
}recall can also return a typed shallow result on legacy plans; use
hasAnswer(response) to distinguish it. Rate state is exposed on both
ResponseMeta.rateLimit and ConareError.rateLimit.
Client options
new Conare({
apiKey: "cint_...",
baseUrl: "https://api.conare.ai",
fetch: customFetch,
timeoutMs: 30_000, // must outlast recall/suggestions synthesis; lower it for storage-only clients
maxRetries: 2, // fail fast: retries multiply tail latency, and writes are idempotent to retry later
retryBaseMs: 250, // backoff base; retries wait ~base * 2^attempt (+ jitter)
maxRetryDelayMs: 60_000, // cap on any single wait, including server-directed Retry-After
onResponse: (meta) => observe(meta),
});Each default's full rationale is documented on ConareOptions in
src/index.ts.
License
MIT
