@crewhaus/memory-service
v0.3.1
Published
v0.3.0 composition root (design §1 principle 1): wireMemory/wireContinuity/wireWiki construct the memory fabric's stores, register its tools, and return runChatLoop-ready seams — one stable call replacing per-emitter memory codegen.
Downloads
161
Readme
@crewhaus/memory-service
The composition root of the CrewHaus memory fabric (v0.3.0 design §1
principle 1, module catalog critical-path #2). Every consumer — compiled
bundles, the crewhaus run interpreter, and (PR 13) sub-agent bridges —
makes one stable call instead of templating store/tool/seam wiring per
emitter:
import { wireMemory } from "@crewhaus/memory-service";
import { defaultCatalog } from "@crewhaus/tool-catalog";
import { runChatLoop } from "@crewhaus/runtime-core";
const wired = await wireMemory(
{ specName: "support-bot", memory: { autoRecall: true }, continuity: {} },
{ catalog: defaultCatalog, cwd: process.cwd() },
);
await runChatLoop({
model, instructions,
tools: defaultCatalog.list(),
...wired.options, // memory + continuity seams, skills, slash commands
});Before this package, renderMemory codegen lived only in target-cli and the
"keep in sync" mirrors in other emitters were fiction; extending memory to 14
target shapes under that pattern would have multiplied the duplication.
Emitters now emit one line (typed IR in, one call out — Pillar 1) and
runtime-core stays store-free (injected closures via RunChatLoopOptions,
the #53 inverted-DI pattern). Every future memory feature lands here, not in
codegen.
The map of the memory fabric
The 0.3.0 memory stack is a tiered, externalized, provenance-carrying hybrid (design §0). This package is where the tiers meet; each lives in its own module:
| Tier | Package | Storage | Tools | Runtime seam |
|---|---|---|---|---|
| Episodic facts | @crewhaus/memory-store (v2: TTL, supersede tombstones, hybrid BM25+embedding recall, provenance) | .crewhaus/memories/<spec>.jsonl | Remember / Recall / MemoryForget (via @crewhaus/tool-memory) | options.memory — autoRecall injection + provenance-stamping auto-capture |
| Working memory | @crewhaus/continuity-store (focus, REQ ledger, plans with the claimed→proven proof ladder, goals, handoff, trash/restore) | .crewhaus/state/<spec>/ | FocusRead/FocusWrite, PlanRead/PlanUpdate/PlanComplete, GoalWrite/GoalUpdate/GoalList, MemoryClear (via @crewhaus/tool-plan) | options.continuity — loadPlan/onPlanDirty (the §2.5 mutable tail), the §2.3 ledger flag, onHandoff |
| Semantic knowledge | @crewhaus/wiki-store (versioned articles, supersede-never-delete, optimistic concurrency, one-hop link expansion) | .crewhaus/wiki/<spec>/ | the ten thredz-vocabulary wiki_* tools (via @crewhaus/tool-wiki) | wiki.autoRecall: true fuses top-recallK wiki hits into options.memory's recall bundle (§3.4) |
| Model discipline | @crewhaus/default-skills | compile-time-embedded SKILL.md / command bodies | — | options.skills / options.slashCommands, merged at lowest precedence |
Consumers: all five agent-loop emitters — @crewhaus/target-cli,
@crewhaus/target-channel-bot (per turn, session-scoped),
@crewhaus/target-managed (per turn, tenant-fenced),
@crewhaus/target-research-bundle and
@crewhaus/target-crew (once at boot) — emit the call into
compiled bundles; apps/cli's crewhaus run and the eval runner's default
invoker (per sample, isolated under the sample dir — §7.2) make the identical
call. @crewhaus/runtime-core consumes the
seams without ever importing a store.
wireMemory(fragment, deps)
The fragment
A serializable description of what to wire — emitters embed it as a JSON
literal; the interpreter builds it with memoryFragmentFromIr(ir). It is the
shape IrMemory/IrContinuity (design §9) lower into (PR 11); the mapping
stays structural, so nothing here imports @crewhaus/ir.
type MemoryWiringFragment = {
specName: string;
memory?: {
enabled?: boolean; // block presence implies true
backend?: "file" | "thredz"; // "thredz" reserved — fails fast until PR 16
ttlMs?: number; // TTL stamped on auto-captured facts (§3.4)
autoCapture?: boolean;
autoCaptureThreshold?: number;
autoRecall?: boolean;
recallK?: number;
wiki?: {
enabled?: boolean;
recallK?: number; // wiki hits fused into auto-recall (default 6)
embedder?: string; // embedder factory grammar, e.g. "mock/deterministic"
autoRecall?: boolean; // fuse wiki hits into the session-start recall bundle (§3.4)
requireSources?: boolean; // §3.3 write governance (the learning: lowering sets it)
};
};
continuity?: {
enabled?: boolean;
plan?: boolean; // false ⇒ only FocusRead/FocusWrite + MemoryClear
proof?: "ladder" | "require" | "off"; // §2.4 — require/off carried, degrade to the ladder (boot note)
ledger?: boolean; // §2.3 requirements ledger flag
handoff?: boolean; // deterministic teardown handoff.md
scope?: "spec" | "session"; // "auto" is resolved by the compiler, not here
focusMaxChars?: number;
};
};The fragment carries only knobs this root actually wires — no dead
config. §9 fields whose engines land later join with their PRs: dream
(PR 14), backend: "thredz" implementations (PR 16). The one carried-but-
degraded field is continuity.proof: require/off need a proof-mode seam
on tool-plan's createPlanTools, so until that lands they run the default
claimed→proven ladder and say so in a boot note.
The deps
type WireMemoryDeps = {
catalog: { register(tool: RegisteredTool): void }; // defaultCatalog, or a shim
cwd: string; // stores live under <cwd>/.crewhaus/
tenant?: Tenant; // §2.7 — re-roots + fences every store
sessionRootDir?: string; // session-log root for proof/capture reads (§7.2 eval isolation)
sessionScope?: string; // session id for continuity scope "session"
appendEvent?: (e: ContinuityEvent | WikiEvent) => void | Promise<void>;
embedder?: { embed(texts) }; // beats memory.wiki.embedder
log?: (line: string) => void; // "[memory] …" status lines (interpreter)
homeDir?: string; // skill/command discovery override (tests)
now?: () => Date;
};The output
type WiredMemory = {
stores: { memory?, continuity?, wiki? }; // for CLI verbs, tests, dream ticks
tools: RegisteredTool[]; // everything registered, in order
options: { // spread into runChatLoop(...)
memory?; continuity?; skills?; slashCommands?;
};
};options keys exist only for wired features, so the spread never clobbers a
caller's own skills/slashCommands unless continuity took ownership of
them (in which case they are the FULL merged set — builtins at lowest
precedence, then ~/.crewhaus, then <cwd>/.crewhaus — and replace the
caller's own discovery).
Granular entry points
wireContinuity(fragment, deps) and wireWiki(fragment, deps) expose the
individual slices (returning null when their block is absent/disabled) for
callers that need only one tier — e.g. a dream tick that wants the stores
without the chat-loop seams. wireMemory composes them and additionally:
- routes
log_knowledge_gapinto the plan store as a[gap]goal when both wiki and continuity are wired (§3.2); - assembles the skills/commands surface (the builtin
continuityskill;/plan /focus /next /handoff /clear-plan /clear-focus, plus/forgetwhen facts are on)./study /reflect /exam /dreamstay out until their skills/engines are wired (PR 14/17).
Scoping, tenancy, backends
- Scope (§2.7):
continuity.scope: "session"nests working state under<spec>/sessions/<sessionScope>/(channel daemons get per-conversation focus). Facts and wiki stay spec-scoped always (§14.5). - Tenancy: with
deps.tenant, continuity/wiki construct under the tenant root with their own fail-closed path fencing; the fact store is created inside<tenantRoot>/memories. Commingling tenants' stores is a data-isolation bug, not a gap. - Backends (§4):
backend: "file"is implemented.backend: "thredz"is a reserved discriminator that fails fast with a clear error; PR 16 flips it to Thredz-via-McpHost store implementations behind these same interfaces.
Equivalence guarantees (the PR 10 refactor contract)
- Specs without a
memory:block compile to byte-identical bundles (pinned by target-cli's emit tests + the smoke matrix's no-memory cli fixture). - Specs with a
memory:block keep behavioral equivalence with the retired inline codegen: same recall lines, same captured fact texts/tags, same seam flags (pinned bysrc/equivalence.test.tsand target-cli's emitted-fragment round-trip test). Two sanctioned upgrades, both from the design:MemoryForgetis now registered alongside Remember/Recall (§3.4), and compiled bundles gain the provenance-stamping capture path the interpreter already had (§2.4).
