Node SDK
The agentmemory client for Node.js 18+ — install, configure, and remember, recall, report and forget with typed results.
agentmemory is a small ES-module client for the /v1 API. It uses the global fetch (Node 18+), has no dependencies and ships TypeScript declarations. Current version: 0.4.0.
Install
The package is not published to a public registry. Install it from the tarball your Agent Memory deployment serves, or from a checkout of the source repository.
Every Agent Memory instance serves its SDK packages under /sdk/:
npm install "$AGENT_MEMORY_BASE_URL/sdk/agentmemory-0.4.0.tgz"Open $AGENT_MEMORY_BASE_URL/sdk/ in a browser to see the available versions.
0.4.0 is a breaking release
search() now returns the whole context (with receiptId) instead of a list, and delete() returns { status }. The 0.3.0 tarball stays available under /sdk/ for pinned installs.
Configure
import { AgentMemoryClient } from 'agentmemory'
const memory = new AgentMemoryClient({
apiKey: process.env.AGENT_MEMORY_API_KEY, // amk_…
baseUrl: process.env.AGENT_MEMORY_BASE_URL, // e.g. http://localhost:5199
workspaceId: process.env.AGENT_MEMORY_WORKSPACE_ID,
})| Option | Required | Notes |
|---|---|---|
apiKey | yes | A workspace API key. It proves who is calling and decides which agent acts. |
baseUrl | yes | Your deployment's URL, without /v1. Trailing slashes are ignored. |
workspaceId | yes | The workspace the key belongs to. Sent on every call. |
principal | no | Defaults { agentId?, userId? } applied to every call. |
Leave agentId out. With no agent ID, the server acts as the agent the key is bound to; set one only when a key is bound to several agents. userId is the end user a memory is about, never the agent — naming one requires a key with act for users enabled. See Identity and visibility.
Remember — add()
const { id, merged, operationId } = await memory.add('Alex is vegetarian', {
type: 'preference',
userId: 'alex',
runId: 'onboarding-42',
visibility: 'user',
sourceKind: 'user',
tags: { facet: 'diet' },
idempotencyKey: 'onboarding-42:diet',
})Writes go to POST /v1/memory/append with layer: "Workspace", so the memory is durable and searchable at once.
| Option | Default | Notes |
|---|---|---|
type | 'decision' | decision, correction, violation, approved_pattern, artifact_ref, preference, raw. |
runId | 'default' | Use one stable runId per task. |
userId | principal | End user the memory is about. |
agentId | principal | Only for multi-agent keys. |
tags | — | String key/value pairs; facet is the conventional facet tag. |
visibility | server default workspace | private, agent, user, workspace, tenant. |
sourceKind | server default agent | agent, user, document or import. |
sourceRef, observedAt, validFrom, validTo | — | Provenance and valid time (ISO 8601 strings). |
idempotencyKey | — | Sent as Idempotency-Key; a retry replays the first answer. |
Resolves to { id, merged, contentReplaced, operationId }. When the write was merged into a near-duplicate, id is the surviving memory.
Recall — search()
const context = await memory.search('what can Alex eat?', {
userId: 'alex',
runId: 'onboarding-42',
tokenBudget: 2000,
})
for (const entry of context.workspaceEntries) console.log(entry.content)
const receiptId = context.receiptIdOptions: runId (default 'default'), userId, agentId, topN, mode ('assisted' | 'deterministic'), rerank, includeSuperseded, asOf, tokenBudget, explain.
Resolves to the full context: receiptId, workspaceEntries, tenantEntries, blocks, procedures, relevantEpisodes, sessionEntries, compilation and the other sections.
Report — outcome() and feedback()
Pass the recall's receiptId and the same runId:
await memory.outcome({
runId: 'onboarding-42',
receiptId,
buildSuccess: true,
testPassRate: 1,
notes: 'user accepted the menu',
})
await memory.feedback({
runId: 'onboarding-42',
receiptId,
usedEntryIds: [context.workspaceEntries[0].id],
})outcome() accepts runId, receiptId, buildSuccess, testPassRate, deploySuccess, errorCount, warningCount, userRating and notes. feedback() accepts runId, receiptId, agentId, usedEntryIds, ignoredEntryIds, appliedProcedureIds and skippedProcedureIds. Both resolve to undefined. See Learning loop.
Read, explain and track
const entry = await memory.get(id) // MemoryEntry | null
const how = await memory.explain(id) // explanation object | null
const receipt = await memory.operation(operationId) // { status: 'pending' | 'indexed' | 'failed', effects, ... } | nullget(), explain() and erasure() take optional { agentId, userId } and resolve to null when the memory does not exist or is not visible.
Update — update()
const result = await memory.update(id, 'Alex is vegan', { ifMatch: entry.version })
// { status: 'updated', version } | { status: 'notFound' } | { status: 'forbidden' }
// | { status: 'versionMismatch', currentVersion }Share — share()
const outcome = await memory.share(id, 'workspace') // 'shared' | 'notFound' | 'forbidden' | 'invalid'Needs a key with the share capability, and ownership of the memory.
Forget — delete() and erasure()
const result = await memory.delete(id)
switch (result.status) {
case 'deleted': break
case 'pending': console.log(await memory.erasure(id)); break // { status: 'pending' | 'complete' | ... }
case 'blocked': await memory.delete(id, { cascade: true }); break // result.dependents lists them
case 'not_found': break
case 'forbidden': console.log(result.reason); break // 'policy' | 'tenant_scope'
}Deleting needs a key with the delete capability. delete() never cascades unless you pass { cascade: true }. See Updating and forgetting.
Errors
Any unexpected HTTP status throws AgentMemoryError:
import { AgentMemoryError } from 'agentmemory'
try {
await memory.add('…')
} catch (err) {
if (err instanceof AgentMemoryError) {
console.error(err.status, err.body) // body is the raw problem+json text
}
}Statuses that are part of a method's result (404 on get, the delete outcomes, 412 on update, …) are returned, not thrown.
Not in the client
The Node client covers the memory lifecycle. For consolidation, blocks, user profiles, episodes, procedures, files and categories, call the REST API directly with the same key — or rely on the background worker to consolidate idle runs.
Method reference
| Method | Endpoint |
|---|---|
add(content, options?) | POST /v1/memory/append |
search(query, options?) | POST /v1/memory/retrieve |
get(memoryId, options?) | GET /v1/memory/{id} |
update(memoryId, content, options?) | PUT /v1/memory/{id} |
delete(memoryId, options?) | DELETE /v1/memory/{id} |
erasure(memoryId, options?) | GET /v1/memory/{id}/erasure |
operation(operationId) | GET /v1/memory/operations/{id} |
explain(memoryId, options?) | GET /v1/memory/{id}/explain |
share(memoryId, visibility, options?) | POST /v1/memory/{id}/share |
outcome(report) | POST /v1/outcomes |
feedback(report) | POST /v1/retrieval-feedback |