@locusgraph/client
v0.4.0
Published
TypeScript SDK for LocusGraph memory system
Maintainers
Readme
LocusGraph TypeScript SDK
TypeScript/JavaScript SDK for the LocusGraph memory system — store events, recall memories with semantic search, traverse relationships, and reason over them.
npm install @locusgraph/clientQuick start
import { LocusGraphClient } from '@locusgraph/client';
const client = new LocusGraphClient({
serverUrl: 'https://us-east-1.locusgraph.com', // region server (see "Connecting")
agentSecret: process.env.LOCUSGRAPH_AGENT_SECRET,
graphId: 'my-graph', // default graph for calls below
});
// Remember something
await client.storeEvent({
graph_id: 'my-graph',
event_kind: 'fact',
source: 'user',
context_id: 'preference:dark_mode',
payload: { data: 'prefers dark mode' },
});
// Recall it later
const result = await client.retrieveMemories({ query: 'what theme do they like?' });
console.log(result.memories);Concepts
A few ideas make the API click:
- Graph — an isolated memory space, addressed by an id. One per user, workspace, or project.
- Context — a named anchor in the graph, written
type:slug(e.g.preference:dark_mode,user:alice). It's the stable handle you address memories by. - Locus (memory) — an actual stored memory event tied to one or more contexts. You store and search loci; you address them by context. One context can have many loci (reinforcement); one locus can reference many contexts.
- Source (trust ladder) — every memory is stamped with where it came from. Higher = more trusted, and it feeds retrieval ranking. So a
verifiedfact outranks anassistantguess.
The slug is the filing location — keep it short and canonical so the same idea always lands on the same context.
Connecting
The SDK authenticates with an agent bearer secret and talks directly to a region server (e.g. https://us-east-1.locusgraph.com), not the dashboard/proxy host.
const client = new LocusGraphClient({
serverUrl: process.env.LOCUSGRAPH_SERVER_URL, // default: https://us-east-1.locusgraph.com
agentSecret: process.env.LOCUSGRAPH_AGENT_SECRET,
graphId: process.env.LOCUSGRAPH_GRAPH_ID, // optional default graph
});| Option | Env var | Notes |
|--------|---------|-------|
| serverUrl | LOCUSGRAPH_SERVER_URL | Region server base URL |
| agentSecret | LOCUSGRAPH_AGENT_SECRET | Bearer token. Unscoped tokens can create graphs; graph-scoped tokens are limited to one graph |
| graphId | — | Default graph; per-call graphId overrides it |
graphId can be passed per call; methods throw a clear error if no graph is resolvable.
API reference
All methods return the unwrapped response (the { success, data } envelope is handled for you) and throw an Error on non-2xx responses.
Graphs
// Create a graph (needs an unscoped secret)
const g = await client.createGraph('research-notes', 'Papers and findings');
// → { graph_id, name, description, owner_agent_id, created_at }
// List accessible graphs
const { graphs } = await client.listGraphs({ page: 0, page_size: 50 });Events (writing memory)
// One event
await client.storeEvent({
graph_id: 'my-graph',
event_kind: 'fact', // see "Event kinds"
source: 'user', // see "Sources"
context_id: 'person:alice', // primary context (type:slug)
related_to: ['org:acme'], // optional relationship links
extends: ['user:alice'],
payload: { data: 'Alice joined Acme' },
});
// Many events in one round-trip, processed IN ORDER (max 1000)
const batch = await client.storeEventsBatch([
{ event_kind: 'fact', context_id: 'service:auth', payload: { data: 'Auth API' } },
{ event_kind: 'fact', context_id: 'endpoint:login', extends: ['service:auth'], payload: { data: '/login' } },
], 'my-graph');
// → { stored, filtered, failed, results: [...] }Memories & insights (reading)
// Semantic search
const result = await client.retrieveMemories({
query: 'what do we know about billing?',
limit: 10,
format: 'markdown', // 'markdown' (default) | 'toon' | 'json'
});
// Reason over memories → a synthesized answer with confidence
const insight = await client.generateInsights({ task: 'Why did we choose Postgres?' });
// → { insight, recommendation, confidence }Advanced recall — retrieveMemories accepts:
| Option | What it does |
|--------|--------------|
| contextIds | Restrict search to these contexts (intersect) |
| boostContextIds | Additively widen: pull in memories linked to these contexts even if text search misses them |
| coverageContextIds + coverageGroupPrefix | Spread a share of results across the contexts' timeline — for "list everything" questions |
| contextTypes | Filter by { type: [names] } |
| sources | Only these provenance sources (e.g. ['user', 'verified']) |
| expandDepth | Expand context hierarchy / locus links (default 1) |
// "Everything about Alice", even what text search would miss
await client.retrieveMemories({
query: 'recent activity',
boostContextIds: ['user:alice'],
coverageContextIds: ['user:alice'],
coverageGroupPrefix: 'session:',
sources: ['user', 'verified'],
});Contexts (inspecting & curating)
await client.listContextTypes(); // types + counts
await client.listContextsByType('preference'); // all contexts of a type
await client.searchContexts('billing'); // keyword/name search
await client.getContextByName('dark_mode'); // by name (throws if 404)
await client.getContext({ context_id: 'preference:dark_mode' }); // by id
await client.batchGetContext(['preference:dark_mode', 'fact:works_at_acme']);
// Delete a context; cascades to its links and orphaned loci
await client.forgetContext({ contextType: 'preference', contextName: 'dark_mode' });Relationship-aware search. Pass include / format to fold in how matched contexts already connect:
const res = await client.searchContexts('dark', undefined, {
include: 'relationships',
format: 'mindmap', // 'json' (default) | 'mindmap' | 'toon'
});
console.log(res.relationships_text);
// preference:dark_mode
// └─ contradicted_by ← preference:light_modeformat: 'json'→res.relationships(a{ contextId: ContextLink[] }map)format: 'mindmap'/'toon'→res.relationships_text(one LLM-ready string)
Relationship traversal
// A context's edges
await client.getContextRelationships({
contextType: 'service', contextName: 'auth',
linkType: 'extends', // related_to | extends | reinforces | contradicts
direction: 'incoming', // outgoing | incoming | both
});
// Memories from related contexts, in one call
await client.getRelatedMemories({
contextType: 'service', contextName: 'auth',
linkType: 'extends', direction: 'incoming',
query: 'error rates',
});Resolution (forward references)
When a memory references a context whose locus doesn't exist yet, the link is left unresolved. Reconcile it later:
await client.getUnresolvedOverview(); // graph-wide unresolved links
await client.getUnresolvedLinks('person:alice'); // for one context
await client.resolve({ context_id: 'person:alice', locus_id: 'locus_123' });
await client.batchResolve({ resolutions: [{ context_id: '...', locus_id: '...' }] });Event kinds
event_kind categorizes a memory and maps to an internal kind + a default source.
| Group | Kinds |
|-------|-------|
| Knowledge | fact, knowledge, observation, learned, feedback |
| Rules | constraint, rule, constraint_violation |
| Actions | action, task, operation, execution, completed |
| Decisions | decision, choice, selection, determination |
| Routine | routine, heartbeat, status |
| Noise | noise, debug, log (filtered from ranking) |
Sources (trust ladder)
source is the provenance of a memory. Higher trust = higher base confidence, and confidence is ~25% of retrieval rank — so the source you stamp matters when memories conflict.
| Source | Trust | Meaning | Aliases |
|--------|------|---------|---------|
| policy | 0.95 | Organizational mandate | |
| verified | 0.90 | Authoritative / validated | validator |
| tool | 0.80 | Reliable tool output | executor |
| document | 0.75 | Published source (e.g. a file) | |
| user | 0.70 | User feedback | |
| assistant | 0.60 | Model inference | agent, model |
| derived | 0.55 | Second-hand synthesis | |
| system | 0.50 | System-generated | |
Context links
Connect contexts when storing an event (each takes context ids):
| Field | Meaning |
|-------|---------|
| context_id | The memory's primary context |
| extends | This is a more specific detail of another (hierarchy) |
| related_to | Lateral association |
| reinforces | Strengthens an existing memory |
| contradicts | Conflicts with / supersedes an existing memory |
Error handling
Methods throw an Error with the status and body on failure; single-context reads throw on 404.
try {
await client.getContext({ context_id: 'missing:thing' });
} catch (err) {
console.error(err.message); // "Context not found"
}TypeScript
Fully typed — every request and response has an exported interface (CreateEventApiRequest, ContextQuery, ContextSearchResponse, …). Import them from the package root:
import { LocusGraphClient, type ContextQuery, type StoreEventResponse } from '@locusgraph/client';License
MIT
