npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@locusgraph/client

v2.0.0

Published

TypeScript SDK for LocusGraph memory system

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.

Documentation: docs.locusgraph.com/locusgraph — concepts, the HTTP API, and the guides. This page is the SDK reference.

npm install @locusgraph/client

Upgrading? 2.0 made every field you pass snake_case; 1.0 renamed four fields in the response.


Quick start

import { LocusGraphClient } from '@locusgraph/client';

const client = new LocusGraphClient({
  server_url: 'https://api.locusgraph.com', // region server (see "Connecting")
  agent_secret: process.env.LOCUSGRAPH_AGENT_SECRET,
  graph_id: '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 verified fact outranks an assistant guess.

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://api.locusgraph.com), not the dashboard/proxy host.

const client = new LocusGraphClient({
  server_url: process.env.LOCUSGRAPH_SERVER_URL,   // default: https://api.locusgraph.com
  agent_secret: process.env.LOCUSGRAPH_AGENT_SECRET,
  graph_id: process.env.LOCUSGRAPH_GRAPH_ID,       // optional default graph
});

| Option | Env var | Notes | |--------|---------|-------| | server_url | LOCUSGRAPH_SERVER_URL | Region server base URL | | agent_secret | LOCUSGRAPH_AGENT_SECRET | Bearer token. Unscoped tokens can create graphs; graph-scoped tokens are limited to one graph | | graph_id | — | Default graph; per-call graph_id overrides it |

graph_id 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 (reading)

// Semantic search
const result = await client.retrieveMemories({
  query: 'what do we know about billing?',
  limit: 10,
  format: 'markdown',          // 'markdown' (default) | 'toon' | 'json'
});

// Agentic deep recall — a walker agent explores the context graph (≤3 hops),
// retrieves with aimed queries, and returns a synthesized answer + evidence.
// Prefer over retrieveMemories for multi-hop questions.
const recall = await client.deepRecall({
  question: 'What did we decide about churn, and why?',
  context_ids: ['session:retention_research'],  // optional starting hint
});
// → { summary, facts: [{ text, source, locus, contexts, match_score }],
//     contexts_walked, coverage: 'full' | 'partial', rounds_used, fallback, usage }

Advanced recall — retrieveMemories accepts:

| Option | What it does | |--------|--------------| | context_ids | Restrict search to these contexts (intersect) | | boost_context_ids | 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 | | context_types | Filter by { type: [names] } | | sources | Only these provenance sources (e.g. ['user', 'verified']) | | expand_depth | Expand context hierarchy / locus links (default 1) |

// "Everything about Alice", even what text search would miss
await client.retrieveMemories({
  query: 'recent activity',
  boost_context_ids: ['user:alice'],
  coverage_context_ids: ['user:alice'],
  coverage_group_prefix: '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({ context_type: 'preference', context_name: '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_mode
  • format: '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({
  context_type: 'service', context_name: 'auth',
  link_type: 'extends',          // related_to | extends | reinforces | contradicts
  direction: 'incoming',        // outgoing | incoming | both
});

// Memories from related contexts, in one call
await client.getRelatedMemories({
  context_type: 'service', context_name: 'auth',
  link_type: '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';

Upgrading to 2.0

Every field you pass is snake_case, including the ones on the constructor. Before, the constructor took graphId and the call beside it took graph_id, and four option objects wanted both spellings at once.

// 1.x
const client = new LocusGraphClient({ serverUrl, agentSecret, graphId });
await client.getContext({ graphId, context_id: "fact:pricing" });

// 2.0
const client = new LocusGraphClient({ server_url, agent_secret, graph_id });
await client.getContext({ graph_id, context_id: "fact:pricing" });

The rule is now one sentence: what you send and what you get back are spelled the same way, because both are the wire. Nothing is translated on the way out, so there is no second vocabulary to learn and nothing to keep in step.

Renamed: serverUrl, agentSecret, graphId, contextType, contextName, linkType, contextIds, contextTypes, expandDepth, semanticWeight, includeSelf, observedAt, fromContextId, toContextId, boostContextIds, coverageContextIds, coverageGroupPrefix.

Positional arguments are unchanged: getLocus(locusId, graphId) still takes two strings in that order.

New in 2.0: offset on a search, beside the has_more that 1.0 added.

Upgrading to 1.0

The engine gave each thing on the wire one name, and 1.0 follows it. Four fields moved; nothing was added to replace them, so an 0.x client reads undefined against the current API rather than failing loudly.

| 0.x | 1.0 | why | |---|---|---| | event_id | locus_id | it named the context in a request and the memory in a response, and two events written to one context returned the same value under a name promising per-event identity | | kind | value | it sat beside event_kind meaning something unrelated: not what you wrote, but how long the memory survives decay. Values are lowercase now — high, medium, low, permanent | | relevance | confidence | it was confidence bucketed into three, under a word that means the query match score everywhere else. The number is unbucketed now | | score | match_score | indistinguishable from confidence beside it. One belongs to the search, the other to the memory |

status changed with them. A write used to answer recorded or filtered; it now says what happened to the context:

created     the context held nothing
unchanged   byte-identical to what it held — a retry, not an edit
replaced    different text, and the write sent replace: true
filtered    the admission gate did not admit it

Writing text a context does not already hold without replace: true is refused with 409, and the refusal carries held — the memory it would have written over, with its text, source and confidence — so you can decide in one round trip.

New, and unread by 0.x: offset and has_more on a search, committed on a transaction.

Documentation

docs.locusgraph.com/locusgraph — what a context is, how the admission gate decides what becomes a memory, and the HTTP API this client speaks.

License

MIT