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

@three-ws/agent-memory

v0.1.0

Published

Persistent, embeddings-backed memory for agents. Store facts and entities, recall them semantically, and surface a working context — over the three.ws Memory engine.

Readme


@three-ws/agent-memory is the official client for the three.ws Memory engine — the durable, embeddings-backed memory that gives an agent continuity across sessions. Your agent remember()s a fact, then recall()s it later by meaning, not keyword: a real semantic search over stored vectors. It wraps the live /api/agent-memory and /api/memory/* endpoints — a tiered store (working / recall / archival), free-first embeddings, and an auto-mined entity graph of the mints, tickers, wallets, people, and strategies your agent has seen. It pairs with the talking-agent runtime: the same memories that feed the Brain Memory node power your agent's replies.

Why

Every "agent memory" you reach for is either an in-process array that dies with the process, or a raw vector DB that leaves embedding, tiering, entity extraction, ownership, and the cross-model "different vectors, different space" trap to you. Memory is the whole pipeline, done once:

  • One import, durable recall. remember('User prefers SOL over EVM') writes a memory that's still there next session — and recall('which chain?') finds it by meaning.
  • Free first. Embeddings default to NVIDIA NIM's nv-embedqa-e5-v5 (1024-dim, free with one key); OpenAI text-embedding-3-small is the paid backstop. No vector DB to stand up.
  • No fabricated similarity. Each stored vector is tagged with the model that made it; queries are scored strictly within their own space. When no provider is configured, recall degrades to substring + salience — never a fake cosine score.
  • It builds a graph for free. Every memory is mined (deterministically, no LLM call) for the entities it mentions, so you can ask "what does my agent remember about this mint?" and walk a co-occurrence graph.

This is the SDK twin of the three.ws Memory Studio — the same engine, exposed as plain functions instead of a UI.

Install

npm install @three-ws/agent-memory

Zero runtime dependencies. Works in Node 18+ and the browser (uses fetch). Writes require a signed-in three.ws account or a bearer token (see Auth); reads are owner-only.

Quick start

Bind a client to one agent, then remember and recall:

import { AgentMemory } from '@three-ws/agent-memory';

const memory = new AgentMemory({ agentId: AGENT_ID, token: process.env.THREE_WS_TOKEN });

await memory.remember('User prefers Solana over EVM chains', { tags: ['preference'] });

const hits = await memory.recall('which blockchain does the user like?');
console.log(hits[0].content); // → "User prefers Solana over EVM chains"
console.log(hits[0].score);   // → 0.83  (real cosine; null on a lexical fallback)

Pin something to the always-in-context working core, then read what the agent carries into every reply:

await memory.remember('Never risk more than 2% per trade', {
  tags: ['strategy'],
  pinned: true, // → working tier, always in context
});

const ctx = await memory.context();
console.log(ctx.tokens, '/', ctx.budget, 'tokens in core'); // → 41 / 2000

Walk the entity graph the store mined for you:

const { nodes, edges } = await memory.graph();
// nodes: [{ kind: 'strategy', label: 'Never risk more than 2%…', mentions: 1 }, …]

API

new AgentMemory(options)

| Option | Type | Default | Notes | |---|---|---|---| | agentId | string | — | Required. The agent these memories belong to. | | token | string | — | Bearer token for writes (or use cookie session in-browser). | | baseUrl | string | https://three.ws | Override for self-host / preview. | | fetch | typeof fetch | global fetch | Inject a custom fetch (e.g. an x402 payer). |

remember(content, options?) → Promise<Memory>

Store (or upsert) a memory. Wraps POST /api/agent-memory { agentId, entry }.

| Option | Type | Default | Notes | |---|---|---|---| | type | 'user' \| 'feedback' \| 'project' \| 'reference' | 'project' | Memory category. Invalid values fall back to project. | | tags | string[] | [] | Topic tags — also mined into entity/topic nodes. | | context | object | {} | Structured fields ({ mint, symbol, wallet }) extracted as entities. | | salience | number | 0.5 | Importance, 0..1. Drives ranking and tier. | | pinned | boolean | false | Pin to the working core (forces tier: 'working'). | | tier | 'working' \| 'recall' \| 'archival' | auto | Defaults: pinned → working, low-salience reference → archival, else recall. | | id | string | — | Pass to upsert an existing memory idempotently. | | expiresAt | number \| string | — | Auto-expiry timestamp. |

Returns the stored Memory (see shape). Content is capped at 10,000 chars. Changing an existing memory's content resets its vector and mined entities, so it re-indexes on the next read.

recall(query, options?) → Promise<Memory[]>

Semantic search over the agent's memories. Wraps POST /api/memory/search.

| Option | Type | Default | Notes | |---|---|---|---| | topK | number | 8 | Max results, clamped 1..50. | | minScore | number | 0.25 | Min cosine similarity, 0..1. | | tiers | string[] | all | Restrict to e.g. ['working', 'recall']. | | type | string | all | Restrict to one memory type. |

Each result carries a score (cosine, 4-dp) and match of 'semantic' or 'lexical'. Results are semantic-first, then back-filled with salience-ranked substring hits so a query is never empty just because a provider is down. Recalling a memory bumps its accessCount and salience slightly — the recency/reinforcement loop the tiers rely on.

entities(options?) → Promise<Entity[]>

The knowledge-graph nodes the agent's memories mention. Convenience over graph() — returns nodes ranked by mention count.

graph() → Promise<{ nodes, edges, stats }>

The full temporal knowledge graph. Wraps GET /api/memory/graph. nodes are entities (kindmint | ticker | wallet | person | strategy | topic); edges are co-occurrence links weighted by how often two entities share a memory. Lazily mines any not-yet-processed memories on read.

memoriesFor(entityId, options?) → Promise<Memory[]>

Memories that mention a given entity node. Wraps GET /api/memory/graph?entityId=. Powers node drilldown ("what does my agent remember about this mint?").

context() → Promise<Context>

The "in-context now" working set: pinned + working-tier memories with a live token budget. Wraps GET /api/memory/context.

type Context = {
  entries: Memory[];
  tokens: number;       // estimated tokens in the working core
  budget: number;       // 2000 — the working-core token budget
  overBudget: boolean;  // tokens > budget
  counts: { total, working, recall, archival, embedded };
};

list(options?) → Promise<Memory[]>

Every memory for the agent (newest, salience-first). Wraps GET /api/agent-memory?agentId=. Options: type, since (ms), limit (≤ 500, default 200).

Curation

Owner-only edits over POST /api/memory/curate:

| Method | Op | Effect | |---|---|---| | pin(id) / unpin(id) | pin / unpin | Move in/out of the working core. | | retier(id, tier) | tier | Set working \| recall \| archival. | | setSalience(id, n) | salience | Set importance, 0..1. | | edit(id, { content?, tags? }) | edit | Re-embeds + re-mines on content change. | | merge([target, ...dupes]) | merge | Fold duplicates into one; re-indexes. | | forget(id) | forget | Delete a memory. |

Memory shape

type Memory = {
  id: string;
  type: 'user' | 'feedback' | 'project' | 'reference';
  content: string;
  tags: string[];
  context: object;
  salience: number;            // 0..1
  tier: 'working' | 'recall' | 'archival';
  pinned: boolean;
  embedder: string | null;     // e.g. "nvidia/nv-embedqa-e5-v5@1024"
  hasEmbedding: boolean;
  accessCount: number;
  tokens: number;              // ~chars/4
  createdAt: number; updatedAt: number;
  lastAccessedAt: number | null; expiresAt: number | null;
  // recall() only:
  score?: number | null;       // cosine, or null for a lexical hit
  match?: 'semantic' | 'lexical';
};

How it works

Three layers turn a string into durable, recall-able memory — a write path, a read-triggered indexing pass, and a query path:

 remember(text) ──▶ POST /api/agent-memory ──▶ agent_memories (Postgres)
                                                     │
        read of recall()/graph() triggers lazy indexing (best-effort, bounded)
                                                     ▼
        ┌──────────────── embed ────────────────┐   ┌──────── mine ────────┐
        │ NIM nv-embedqa-e5-v5 @1024 (free)      │   │ deterministic entity │
        │  → OpenAI text-embedding-3-small @256  │   │ extraction (no LLM)  │
        │ vector tagged with its embedder        │   │ → entity nodes+links │
        └────────────────────┬───────────────────┘   └──────────┬──────────┘
                             ▼                                   ▼
 recall(query) ──▶ cosine within the SAME vector space ──▶ ranked Memory[]
                   (lexical + salience fallback)        graph(): nodes + edges
  • Tiers (Letta/MemGPT model). working is the small, always-in-context core (pinned + active rules), token-budgeted at 2000 tokens. recall is recent, searchable interactions. archival is the long-term store. A new memory's tier is chosen for you unless you set it.
  • Free-first embeddings. New writes embed with NVIDIA NIM nv-embedqa-e5-v5 (1024-dim, free with one NVIDIA_API_KEY); OpenAI text-embedding-3-small at 256 dims is the paid backstop. Embedding happens lazily on read, in bounded batches, so no write blocks on a provider.
  • One space, never crossed. Every vector is stored tagged with the exact model that produced it. A query is embedded once per stored space and cosine runs strictly within each — vectors of different dimensionality score 0, never a plausible-looking garbage similarity.
  • Auto-mined graph. Each memory is pattern-matched (deterministic, dependency- free) for mints, $TICKER cashtags, wallet addresses, @handles, and trading strategies. Entities become graph nodes; co-occurrence within a memory becomes a weighted edge.

Auth & errors

Reads are owner-only: anonymous or non-owner requests get an empty result (an empty array / zeroed context), never a leak and never a 401 that spams a public embed console. Writes require auth and surface as a thrown MemoryError with a code:

| code | HTTP | Meaning | Recovery | |---|---|---|---| | unauthorized | 401 | Write with no session / token. | Pass a token, or sign in. | | forbidden | 403 | Agent isn't yours. | Use an agentId you own. | | not_found | 404 | Agent or memory doesn't exist. | Check the id. | | id_conflict | 409 | Upsert id already used by another agent. | Omit id to mint a new one. | | validation_error | 400 | Missing agentId / content / bad field. | Fix the input. |

Browser writes also need a CSRF token (handled automatically by the three.ws session cookie flow); server-to-server calls authenticate with a bearer token.

Examples

Agent loop — remember every turn, recall before replying:

const memory = new AgentMemory({ agentId, token });

async function onUserMessage(text) {
  const recalled = await memory.recall(text, { topK: 5 });
  const reply = await llm(text, { memories: recalled.map((m) => m.content) });
  await memory.remember(`User said: ${text}\nAgent: ${reply}`, { type: 'user' });
  return reply;
}

Record a trade with structured context — it extracts the entities for you:

await memory.remember('Sniped a fresh launch, took profit at 3x', {
  tags: ['trade', 'lesson'],
  salience: 0.8,
  context: { mint: 'THREEsynthetic1111111111111111111111111111', symbol: '$THREE' },
});

const { nodes } = await memory.graph(); // mint + ticker nodes now linked

Curate the working core so it stays under budget:

const ctx = await memory.context();
if (ctx.overBudget) {
  // demote the least-salient working memory to recall
  const weakest = ctx.entries.sort((a, b) => a.salience - b.salience)[0];
  await memory.retier(weakest.id, 'recall');
}

Related