# Show HN: Open-source toolkit for AI memory that scales

> Source: <https://github.com/0xJaksun/lithium-core>
> Published: 2026-05-29 04:44:16+00:00

Hierarchical versioned storage on PostgreSQL ltree. Scoped retrieval, built-in versioning, zero runtime deps.

``` js
const lithium = new Lithium(drizzleAdapter(db));

await lithium.clusters.create({ name: "infra" });
await lithium.clusters.create({ name: "database", parentPath: "infra" });

const context = await lithium.getContext({ path: "infra" });
```

Memory graphs don't scale for tree-structured data. Graph traversal becomes a bottleneck. Vector search gives you "similar to X" when you need "everything under X."

PostgreSQL's `ltree`

handles tree queries significantly faster. Index-backed subtree lookups, not traversal. Lithium wraps it in a clean TypeScript API with built-in versioning.

| Lithium | Graph DBs | Vector DBs | |
|---|---|---|---|
Structure |
Tree hierarchy | Arbitrary graph | Flat |
Query speed |
ltree index-backed | Graph traversal | ANN search |
Retrieval |
Deterministic, scoped | Pattern matching | Fuzzy, similarity |
Versioning |
Built-in, immutable | Manual | Overwrite |
Infrastructure |
Your existing Postgres | Separate service | Separate service |

| Package | What | Size |
|---|---|---|
`@lithium-ai/core` |
Zero-dep storage engine | |
`@lithium-ai/postgres` |
PostgreSQL ltree adapter | |
`@lithium-ai/drizzle` |
Drizzle ORM adapter | |
`@lithium-ai/mcp` |
MCP server for AI tools |

**Prerequisites:** PostgreSQL with `ltree`

extension.

**With Drizzle:**

```
npm install @lithium-ai/core @lithium-ai/drizzle drizzle-orm
js
import { Lithium } from "@lithium-ai/core";
import { drizzleAdapter } from "@lithium-ai/drizzle";

const lithium = new Lithium(drizzleAdapter(db));
```

**With raw postgres:**

```
npm install @lithium-ai/core @lithium-ai/postgres postgres
js
import { Lithium } from "@lithium-ai/core";
import { postgresAdapter } from "@lithium-ai/postgres";
import postgres from "postgres";

const sql = postgres("postgres://...");
const lithium = new Lithium(postgresAdapter(sql));
```

**Then:**

``` js
// Create hierarchy
const infra = await lithium.clusters.create({ name: "infra" });
await lithium.clusters.create({ name: "database", parentPath: "infra" });

// Create versioned entries
const entry = await lithium.entries.create({ clusterId: infra.value.id });
await lithium.entries.update({ id: entry.value.entry.id });

// Scoped retrieval: everything under "infra"
const context = await lithium.getContext({ path: "infra" });
npm install @lithium-ai/mcp
js
// server.ts
import { Lithium } from "@lithium-ai/core";
import { postgresAdapter } from "@lithium-ai/postgres";
import { serveMcp } from "@lithium-ai/mcp";
import postgres from "postgres";

const sql = postgres(process.env.DATABASE_URL!);

const lithium = new Lithium(postgresAdapter(sql), async (versionIds) => {
  const rows = await sql`
    SELECT entry_version_id, title, content
    FROM your_content_table
    WHERE entry_version_id = ANY(${versionIds})
  `;
  return new Map(rows.map((r) => [r.entry_version_id, r]));
});

serveMcp(lithium);
```

Add to Claude Code:

```
{
  "mcpServers": {
    "lithium": {
      "command": "npx",
      "args": ["tsx", "server.ts"]
    }
  }
}
```

Entries are pure structure. Your content lives in your own tables, referenced by entry version IDs.

```
Cluster
  id, parentId, path ("infra.database"), name, description, createdAt

Entry
  id, clusterId, createdAt

EntryVersion
  id, entryId, version (auto-incremented), createdAt

Your Content Table
  entryVersionId (FK), title, content, ...whatever you want
```

| Method | What |
|---|---|
`create({ name, parentPath?, description? })` |
Create cluster, resolve parent |
`findByPath({ path })` |
Find by dot-path |
`list()` |
All clusters ordered by path |
`listDescendantIds({ path })` |
ltree subtree query |

| Method | What |
|---|---|
`create({ clusterId })` |
New entry + version 1 |
`update({ id })` |
Auto-increment version |
`get({ id, version? })` |
Entry + version (latest or specific) |
`list({ clusterIds })` |
Entries by cluster IDs |
`listWithLatestVersion({ clusterIds })` |
Entries + latest versions (batch) |

| Method | What |
|---|---|
`getContext({ path })` |
Scoped retrieval with optional content resolver |

Every method returns `Result<T, E>`

. No thrown exceptions.

``` js
const result = await lithium.clusters.create({ name: "infra" });
if (!result.success) {
  // result.error is ValidationError | NotFoundError | SystemError
  // Discriminate via error.kind or instanceof
}
```

**Drizzle users:** Import the schemas and use `drizzle-kit push`

:

```
export { clusters, entries, entryVersions } from "@lithium-ai/drizzle";
npx drizzle-kit push
```

**Raw SQL:** Run the reference migrations from `@lithium-ai/postgres`

:

```
psql -d your_db -f node_modules/@lithium-ai/postgres/src/migrations/001_clusters.sql
psql -d your_db -f node_modules/@lithium-ai/postgres/src/migrations/002_entries.sql
```

Requires `CREATE EXTENSION IF NOT EXISTS ltree;`

before running.

- Core storage engine (
`@lithium-ai/core`

) - PostgreSQL ltree adapter (
`@lithium-ai/postgres`

) - MCP server (
`@lithium-ai/mcp`

) - Content resolver callback for
`getContext`

- Drizzle ORM adapter (
`@lithium-ai/drizzle`

) - GitHub Actions CI
- Integration tests (testcontainers)
- Transaction support (atomic createEntry)
- MCP write tools (create_cluster, create_entry)
- Example projects
- Prisma adapter

- AI agent memory (structured retrieval, scoped context)
- Decision tracking across teams
- Config versioning
- Documentation hierarchies

Read more: [Memory Graphs Don't Scale](https://dev.to/0xjaksun/memory-graphs-dont-scale-4p0i)

Issues and PRs welcome.

MIT
