agentskit.js
Concepts

Memory

Two contracts, not one. ChatMemory persists conversation history. VectorMemory stores embeddings for semantic recall. Different problems, different backends.

Memory in AgentsKit is two contracts, deliberately split:

  • ChatMemory — the ordered history of a session. Append to it, load it back. Redis is great at this.
  • VectorMemory — embeddings indexed for semantic retrieval. The substrate for RAG. pgvector, Pinecone, Qdrant are great at this.

Plus a third primitive — EmbedFn — for turning text into vectors.

LangChain unifies these. The result is implementations that half-fulfill both. We split them so a backend implements only what it does well.

ChatMemory

import type { ChatMemory } from '@agentskit/core'

export interface ChatMemory {
  load: () => MaybePromise<Message[]>
  save: (messages: Message[]) => MaybePromise<void>
  clear?: () => MaybePromise<void>
}

save is replace-all — not append. Controllers append in memory and flush the whole state at the end of a turn. This removes the "did I dedupe?" branching that haunts append-based stores.

import { sqliteChatMemory } from '@agentskit/memory'

const memory = sqliteChatMemory({ path: './sessions/user-42.db' })

createRuntime({ adapter, memory, /* ... */ })

The runtime calls load() at the start of each run() and save() after a successful run. Aborted or failed runs do not save — atomicity comes for free.

VectorMemory

import type { VectorMemory, VectorDocument } from '@agentskit/core'

export interface VectorMemory {
  store: (docs: VectorDocument[]) => MaybePromise<void>
  search: (
    embedding: number[],
    options?: { topK?: number; threshold?: number },
  ) => MaybePromise<RetrievedDocument[]>
  delete?: (ids: string[]) => MaybePromise<void>
}

store is upsert by id. Re-indexing the same document is cheap and idempotent — no duplicate-id errors.

import { fileVectorMemory } from '@agentskit/memory'
import { openaiEmbed } from '@agentskit/adapters'

const store = fileVectorMemory({ path: './embeddings.json' })
const embed = openaiEmbed({ apiKey: KEY, model: 'text-embedding-3-small' })

await store.store([
  { id: 'doc-1', content: 'AgentsKit core is 10KB gzipped.', embedding: await embed('AgentsKit core is 10KB gzipped.') },
])

const hits = await store.search(await embed('how big is the core?'), { topK: 3 })

Search results come descending by score. threshold is exclusive from below (> 0.7, not >= 0.7).

EmbedFn

export type EmbedFn = (text: string) => Promise<number[]>

A pure function. Same input + same model = same output. No randomness allowed.

Built-in embedders:

import { openaiEmbed, ollamaEmbed, geminiEmbed } from '@agentskit/adapters'

const embed = openaiEmbed({ apiKey: KEY, model: 'text-embedding-3-small' })

Built-in backends

BackendChatMemoryVectorMemory
fileChatMemory
fileVectorMemory
sqliteChatMemory
redisChatMemory
redisVectorMemory

More coming in Phase 3 (pgvector, Pinecone, Qdrant, Chroma, Weaviate, Turso, Cloudflare Vectorize, Upstash). Each is a small adapter implementing six or eight invariants.

When to write your own

  • Your storage backend isn't covered yet. A new ChatMemory backend is ~50 lines. A new VectorMemory backend is ~100. See the existing implementations as templates.
  • You need encryption at rest. Wrap an existing backend with an encrypting proxy — neither contract changes.
  • You're testing. A pure in-memory ChatMemory is twelve lines and instantly replayable.

Common pitfalls

PitfallWhat to do instead
Implementing save as append-with-dedupReplace-all (CM2). Trust the consumer to send the correct full state.
Returning null from load() on emptyReturn []. Empty state is success, not absence.
Mixing embedding dimensions in one VectorMemoryPick one model per store; reject mismatches at construction.
Padding search results to reach topKReturn fewer documents — topK is an upper bound, not a floor.
Random embeddings or non-deterministic embeddersCache by input; never inject randomness. EmbedFn must be stable.

Going deeper

The full list of invariants (six for ChatMemory, eight for VectorMemory, three for EmbedFn) is in ADR 0003 — Memory contract.

✎ Edit this page on GitHub·Found a problem? Open an issue →·How to contribute →

On this page