Files
zeekay aad2b6998c brain: scaffold the canonical Hanzo Brain monorepo
The single binary brain that compounds knowledge across every channel,
every agent, every runtime.

Drop markdown into ~/.hanzo/workspace/. Edges auto-extract (zero LLM).
Facts queryable via MCP. One SQLite file at ~/.hanzo/brain/brain.db.
Same file readable by every Hanzo runtime — TS, Python, Rust, Go.

Packages
- brain          meta-pack registering memory + graph-links + recipes
- memory         pluggable BrainStore (SQLite default; FTS5 hybrid search)
- graph-links    zero-LLM typed-edge extractor (6 edge types)
- recipes-brain  YAML daily-life automations (email flagship)

Sister runtimes
- TS canonical   ships in @hanzo/bot (this monorepo is the source)
- Python         hanzo-memory (graph_links + recipes ports)
- Rust           hanzoai/mcp::brain crate
- Go             hanzoai/bot-go (single static binary)

Canonical paths
- ~/.hanzo/brain/brain.db     the brain
- ~/.hanzo/workspace/         markdown source — auto-ingested
- ~/.hanzo/recipes/           user-authored YAML recipes
- ~/.hanzo/config.toml        per-machine config
- ~/.hanzo/cache/             embedding cache, tool-output cache
- ~/.hanzo/logs/              structured logs

A brain.db written by ANY runtime is byte-identical to one written by
every other. Same schema, same FTS5 setup, same pages / edges / facts
shape.

Pluggable backends — same BrainStore contract everywhere:
sqlite (default) · qdrant · meilisearch · zapdb · replicate · vfs · postgres.
2026-05-11 10:33:33 -07:00

102 lines
3.4 KiB
TypeScript

import { mkdtemp, rm } from "node:fs/promises";
import { tmpdir } from "node:os";
import { join } from "node:path";
import { describe, it, expect, beforeAll, afterAll } from "vitest";
import { SqliteStore } from "./sqlite.js";
describe("memory: SQLite (canonical default)", () => {
let dir: string;
let store: SqliteStore;
beforeAll(async () => {
dir = await mkdtemp(join(tmpdir(), "hanzo-brain-test-"));
store = new SqliteStore({ dbPath: join(dir, "brain.db") });
try {
await store.init();
} catch (e: any) {
// CI runners without bun:sqlite or better-sqlite3 — skip the suite.
if (String(e.message).includes("neither bun:sqlite nor better-sqlite3")) {
console.warn("[memory test] SQLite driver unavailable, skipping suite");
// Mark as skipped by short-circuiting subsequent tests.
store = null as any;
} else {
throw e;
}
}
});
afterAll(async () => {
await store?.close?.();
await rm(dir, { recursive: true, force: true });
});
it("upserts and reads a page", async () => {
if (!store) return;
await store.upsertPage("people/alice", "Alice is the CEO of Acme.", { type: "person" });
const got = await store.getPage("people/alice");
expect(got?.slug).toBe("people/alice");
expect(got?.content).toContain("Alice");
});
it("upserts edges and reads them back", async () => {
if (!store) return;
await store.upsertEdges("meetings/m1", [
{ source: "meetings/m1", target: "people/alice", type: "attended" },
{ source: "meetings/m1", target: "people/bob", type: "attended" },
]);
const out = await store.edgesFor("meetings/m1", "out");
expect(out).toHaveLength(2);
const inAlice = await store.edgesFor("people/alice", "in");
expect(inAlice).toHaveLength(1);
});
it("upserts and recalls facts", async () => {
if (!store) return;
await store.upsertFact({
subject: "people/alice",
predicate: "preference",
object: "Always replies in under 2 hours",
});
const facts = await store.recall("people/alice");
expect(facts.length).toBeGreaterThan(0);
expect(facts[0].predicate).toBe("preference");
});
it("hybrid search returns FTS hits", async () => {
if (!store) return;
const hits = await store.hybridSearch("Acme");
expect(hits.length).toBeGreaterThan(0);
expect(hits[0].slug).toBe("people/alice");
});
});
describe("memory: pluggability", () => {
it("registerBackend exposes new backends to open()", async () => {
const { open, registerBackend, listBackends } = await import("./index.js");
// Stub backend that records init() being called.
let initialized = false;
registerBackend("test-stub", () => ({
init: async () => {
initialized = true;
},
upsertPage: async () => {},
getPage: async () => null,
upsertEdges: async () => {},
edgesFor: async () => [],
upsertFact: async () => {},
recall: async () => [],
hybridSearch: async () => [],
close: async () => {},
}));
expect(listBackends()).toContain("test-stub");
expect(listBackends()).toContain("sqlite");
await open({ backend: "test-stub" });
expect(initialized).toBe(true);
});
it("unknown backend throws with a helpful message", async () => {
const { open } = await import("./index.js");
await expect(open({ backend: "no-such-thing" })).rejects.toThrow(/unknown backend/);
});
});