A small TypeScript package: a config-driven memory engine and a matching HTTP router factory, extracted from a private multi-agent fleet where 7-8 codenamed agents (Quinn, Tessa, Emma, Charlie, Aubrey, Ivy, Stella, and a DevOps agent) each needed persistent memory. Every agent was reinventing the same KV-backed storage and the same Hono router around it. This package is what's left once you factor that duplication out into one engine.
It is not a from-scratch toy. It is the shared core of a real internal system,
pulled out and cleaned up so it can run on its own. bun test runs the same
75 tests (735 assertions) that covered it inside the fleet monorepo.
Every agent needed the same shape of memory: a cheap thing to read first (a brief), a slightly bigger index for filtering, full records fetched on demand, and an append-only log for provenance. Left alone, each agent would hand-roll its own version of all four, with its own bugs and its own drift from the others.
The fix here is to make per-agent behavior data, not code. One engine reads
a BrainConfig (namespace, record kinds, which fields get promoted into the
index, how the brief is assembled) and the engine itself never branches on
which agent is calling it.
Four layers, in src/brain/:
- Brief (
__brief__): the smallest thing, read first. Built deterministically from the index per the agent'sBriefPolicy: which sections, sorted how, limited to how many, projected as a pointer (title only) or a full body. - Index (
__index__): titles and scalar metadata only, no bodies. Cheap enough to read on every request. Metadata is lifted onto index entries from full records at write time (BrainConfig.metaKeys), so briefs can sort and filter without touching record storage. - Records (
rec:<id>): the full items, fetched only when something asks for them by id. - Log (
__log__): append-only history of every ingest, query, write, and rollback. This is what makes the system auditable instead of just eventually-consistent.
src/brain/store.ts is the part that talks to KV. It's written against
Effect (a BrainStore service, a BrainKV context tag for the injected
Agentuity KV client, typed errors for KV failures vs. schema violations, and
Effect.fn spans on every operation) but exposes a plain Promise-based
facade (getIndex, saveRecord, getBrief, ...) so callers that don't want
to think about Effect don't have to. Contract violations throw
ParseResult.ParseError; KV failures throw a typed BrainKVError with the
failing op, namespace, and key attached.
Schema-first throughout: every structural type in src/brain/types.ts is
defined as an Effect Schema, and the TypeScript type is derived from it
(Schema.Schema.Type<typeof X>), not written by hand next to it. The
compile-time contract and the runtime-decodable contract are the same value,
so they can't drift apart.
src/memory-router.ts is a Hono router factory. Instead of every agent
writing its own ~150-line brief/index/items/stale/item-CRUD/seed router
against the brain engine, an agent supplies its BrainConfig, its
validators, and a merge policy, and gets the same router back.
Three auth modes, as an exhaustive discriminated union so a fourth mode can't be added without updating the switch:
single-token: one bearer token guards every route viause('*').bilateral: separate read and write tokens checked per handler; write access implies read access; unconfigured tokens fail closed (401, never silently open).external: auth is handled by upstream middleware; the router adds no gate of its own.
Tokens are read from the Authorization header only. Query-string tokens
were deliberately left out: they end up in access logs and Referer headers.
The router's contract is preserved byte-for-byte from the original
per-agent routers it replaced: reads are tolerant (an unrecognized filter
returns 200 with an empty result, never a 400; stored records are never
schema-decoded on read, so old data keeps loading), writes are strict
(ValidationError maps to a terse 400 that never echoes the rejected
value back to the caller), and POST /seed validates the entire batch
before writing anything.
tests/memory-router.test.ts is 32 contract tests against a fake in-memory
KV standing in for the real Agentuity client, covering all three auth modes,
create-vs-update merge semantics, seed atomicity, and the tolerant-read /
strict-write split above.
src/llm.ts: a thin wrapper around the Anthropic SDK (draft()) with adaptive thinking-token budgeting. If the response hitsmax_tokens, it throws instead of quietly handing back truncated text. Also has the fenced-JSON extraction and schema-validated decode helpers every agent used to parse an LLM reply.src/fleet.ts: fire-and-forget heartbeat and token-usage reporting to an internal cost-monitoring service. Both are best-effort: they never throw, never block the caller, and no-op when the reporting endpoint isn't configured (local dev, tests).
There's no CLI and no server to run here, it's a library. The three things that actually exist:
bun install
bun run typecheck # bunx tsc --noEmit
bun test # 75 tests, 735 assertionssrc/
brain/
types.ts # schema-first structural types (BrainRecord, IndexEntry, BrainConfig, ...)
schema.ts # the engine's runtime contract (scalar meta, Confidence, LogOp)
store.ts # the Effect-native engine + Promise facade, KV-backed
brief.ts # pure brief-building logic (no I/O)
memory.ts # generic scaffolding agent domain modules build their record types from
memory-router.ts # makeMemoryRouter: the shared Hono REST surface
llm.ts # Anthropic draft() wrapper + fenced-JSON decode
fleet.ts # heartbeat + usage reporting, no-op when unconfigured
tests/
brain/ # store + schema + integration tests
memory-router.test.ts # 32 contract tests against a fake KV
modules.test.ts
Apache-2.0, see LICENSE.