{"_id":"@alineo-labs/memory","_rev":"3-af43cc5111303bab492257bf13c71784","name":"@alineo-labs/memory","dist-tags":{"latest":"0.2.2"},"versions":{"0.2.0":{"name":"@alineo-labs/memory","version":"0.2.0","license":"Apache-2.0","_id":"@alineo-labs/memory@0.2.0","maintainers":[{"name":"drejtoolwell","email":"vivekpatel4049@gmail.com"}],"homepage":"https://github.com/DrejT/alineo#readme","bugs":{"url":"https://github.com/DrejT/alineo/issues"},"dist":{"shasum":"79566f54a33e8f7640fc752d2e3b7a1b731a3e83","tarball":"https://registry.npmjs.org/@alineo-labs/memory/-/memory-0.2.0.tgz","fileCount":4,"integrity":"sha512-4uETUcVwAarNL6VHIka6kXIytPGJidyy/rmMeB0RcXtILgVTGnJ5D+u9N21fn9qioYZDJCJILQ22yldWF5Y4SA==","signatures":[{"sig":"MEYCIQCodWZ2rEs3VlXsJnJyKj1Z/NZun91XpXk/m7OyYNs1KAIhAIvO8NldM+U3tRSN8RWDmmG17LO02mFh+0gSNeePBcIM","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@alineo-labs%2fmemory@0.2.0","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":88309},"main":"./dist/index.mjs","type":"module","types":"./dist/index.d.mts","exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs"}},"gitHead":"8bd54ff750438ee67b032d48e20abfade662099a","scripts":{"test":"vitest run","build":"tsdown","test:watch":"vitest"},"_npmUser":{"name":"drejtoolwell","email":"vivekpatel4049@gmail.com"},"repository":{"url":"git+https://github.com/DrejT/alineo.git","type":"git","directory":"packages/memory"},"_npmVersion":"10.9.8","description":"A provider-agnostic memory layer for alineo agents: working memory, semantic recall, and episodic recall over the existing ledger — scoped by a durable `resourceId` that survives past any one sandbox session.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"@alineo-labs/core":"0.3.0"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"0.22.3","vitest":"4.1.9","bun-types":"1.3.14","typescript":"6.0.3"},"_npmOperationalInternal":{"tmp":"tmp/memory_0.2.0_1788102919460_0.16984094500545233","host":"s3://npm-registry-packages-npm-production"}},"0.2.1":{"name":"@alineo-labs/memory","version":"0.2.1","license":"Apache-2.0","_id":"@alineo-labs/memory@0.2.1","maintainers":[{"name":"drejtoolwell","email":"vivekpatel4049@gmail.com"}],"homepage":"https://github.com/DrejT/alineo#readme","bugs":{"url":"https://github.com/DrejT/alineo/issues"},"dist":{"shasum":"f85cbc97ecb2c510ea66eef306982a5505e4d6bb","tarball":"https://registry.npmjs.org/@alineo-labs/memory/-/memory-0.2.1.tgz","fileCount":4,"integrity":"sha512-+J7XUCs1J4p9wFcHIQNj1ox9BY9WfOr9bGFREXGtyItBNJ9hSyUCtBxOlUa6nY4ACqOUy6sQg1kxaVbr4doMiQ==","signatures":[{"sig":"MEYCIQCqGJ2hH5uwamHIXt9lq5vS1SGD8H5B4DuI9YtYgHeuggIhAM9ujuVwIDejeMWeNvnA9nnzhTqBWTgTlSDUyn1SogO5","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@alineo-labs%2fmemory@0.2.1","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":88309},"main":"./dist/index.mjs","type":"module","types":"./dist/index.d.mts","exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs"}},"gitHead":"0a4efc6f2b7e9cc4b02acf303b2439788d55907c","scripts":{"test":"vitest run","build":"tsdown","test:watch":"vitest"},"_npmUser":{"name":"drejtoolwell","email":"vivekpatel4049@gmail.com"},"repository":{"url":"git+https://github.com/DrejT/alineo.git","type":"git","directory":"packages/memory"},"_npmVersion":"10.9.8","description":"A provider-agnostic memory layer for alineo agents: working memory, semantic recall, and episodic recall over the existing ledger — scoped by a durable `resourceId` that survives past any one sandbox session.","directories":{},"_nodeVersion":"22.23.2","dependencies":{"@alineo-labs/core":"0.4.0"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"0.22.3","vitest":"4.1.9","bun-types":"1.3.14","typescript":"6.0.3"},"_npmOperationalInternal":{"tmp":"tmp/memory_0.2.1_1788628601837_0.02751157463023346","host":"s3://npm-registry-packages-npm-production"}},"0.2.2":{"_id":"@alineo-labs/memory@0.2.2","bugs":{"url":"https://github.com/DrejT/alineo/issues"},"dist":{"shasum":"526adc7dababae504c3830670dbc592b6cb0cbea","tarball":"https://registry.npmjs.org/@alineo-labs/memory/-/memory-0.2.2.tgz","fileCount":4,"integrity":"sha512-2mSPaJerNe2ASeAtXJuNS8VhkM4woTpMZtBVCF8PhpY5sseC3rzJEgkP745ObtP06ci49gY9SiG5U7COBUN66Q==","signatures":[{"sig":"MEQCIBZ9Hsd3peBpbpcxT8W2qlrpppElFIqjOMu/hwLwNyMPAiBNZMZhFG2+MLOebuAx/o6HSXpIzG6bu5JM1QzyvfO9Wg==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"},{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEYCIQCXG+fTu2JIaKXDRvkIo0OEr7em5EF+Onrx+cw8wX1L3AIhANkss5qhL81JIC0NO5+r5ryHqN+7IBsqoeuJbrhi7MWu"}],"attestations":{"url":"https://registry.npmjs.org/-/npm/v1/attestations/@alineo-labs%2fmemory@0.2.2","provenance":{"predicateType":"https://slsa.dev/provenance/v1"}},"unpackedSize":87933},"main":"./dist/index.mjs","name":"@alineo-labs/memory","type":"module","types":"./dist/index.d.mts","exports":{".":{"types":"./dist/index.d.mts","import":"./dist/index.mjs"}},"gitHead":"d3323f599d24aade22d8e26ed1ae695e1d236280","license":"Apache-2.0","scripts":{"test":"vitest run","build":"tsdown","test:watch":"vitest"},"version":"0.2.2","_npmUser":{"name":"drejtoolwell","email":"vivekpatel4049@gmail.com"},"homepage":"https://github.com/DrejT/alineo#readme","repository":{"url":"git+https://github.com/DrejT/alineo.git","type":"git","directory":"packages/memory"},"_npmVersion":"10.9.8","description":"A provider-agnostic memory layer for alineo agents: working memory, semantic recall, and episodic recall over the existing ledger — scoped by a durable `resourceId` that survives past any one sandbox session.","directories":{},"maintainers":[{"name":"drejtoolwell","email":"vivekpatel4049@gmail.com"}],"_nodeVersion":"22.23.2","dependencies":{"@alineo-labs/core":"0.4.1"},"publishConfig":{"access":"public","provenance":true},"_hasShrinkwrap":false,"devDependencies":{"tsdown":"0.23.0","vitest":"5.0.0","bun-types":"1.4.2","typescript":"6.0.3"},"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/memory_0.2.2_1789325446342_0.34095104152724987"}}},"time":{"created":"2026-08-30T15:15:19.317Z","modified":"2026-09-13T18:50:46.871Z","0.2.0":"2026-08-30T15:15:19.622Z","0.2.1":"2026-09-05T17:16:41.965Z","0.2.2":"2026-09-13T18:50:46.424Z"},"bugs":{"url":"https://github.com/DrejT/alineo/issues"},"license":"Apache-2.0","homepage":"https://github.com/DrejT/alineo#readme","repository":{"url":"git+https://github.com/DrejT/alineo.git","type":"git","directory":"packages/memory"},"description":"A provider-agnostic memory layer for alineo agents: working memory, semantic recall, and episodic recall over the existing ledger — scoped by a durable `resourceId` that survives past any one sandbox session.","maintainers":[{"name":"drejtoolwell","email":"vivekpatel4049@gmail.com"}],"readme":"# `@alineo-labs/memory`\n\nA provider-agnostic memory layer for alineo agents: working memory, semantic recall, and\nepisodic recall over the existing ledger — scoped by a durable `resourceId` that survives past\nany one sandbox session.\n\nThis package owns memory **concepts, scoping, and pipeline** (injection, compaction,\nlifecycle hooks). It does not own a storage backend itself — bring your own\n`IWorkingMemoryProvider` / `ISemanticMemoryProvider`, whether that's the in-memory reference\nimplementations shipped here, the file-based `@alineo-labs/sqlite-memory` backend, or the\nshared `@alineo-labs/postgres-memory` backend.\n\n## Why `resourceId`, not `sandboxId`\n\nA `sandboxId` identifies one sandbox _session_. A `resourceId` is a durable identity (a user,\nan account, a project) expected to outlive any number of sandbox sessions. Working and\nsemantic memory are keyed by `resourceId` because their entire point is surviving past the\nsession they were learned in.\n\n`resourceId` (and `parentSandboxId`, for forked sandboxes) ride along in the ledger's existing\n`sandbox_created` event payload — the same mechanism `SandboxDetails.runId` already used —\nso there's no ledger schema change. Set it when creating a sandbox:\n\n```ts\nconst sb = await client.sandbox({\n  image: \"node:22\",\n  resources: { cpu: \"500m\", memory: \"512Mi\" },\n  resourceId: \"user-42\", // ties this session's memory to a durable resource\n});\n```\n\n`resume()`, `restoreSnapshot()`, and `sb.fork()` all inherit the originating session's\n`resourceId` automatically, the same way they already inherit `runId`.\n\n```ts\nimport type { ResourceRef } from \"@alineo-labs/memory\";\n\nconst ref: ResourceRef = { resourceId: \"user-42\" }; // teamId?: string for shared/team scoping\n```\n\n## Three capabilities, three independent slots\n\nMemory is split by capability, not unified into one adapter — real backends support genuinely\ndifferent capability subsets, so a monolithic interface would force fake implementations of\ncapabilities a backend doesn't have.\n\n### Working memory — required\n\nStructured per-resource key/value facts. Every `Memory` instance needs at least this.\n\n```ts\nimport { Memory, InMemoryWorkingMemoryProvider } from \"@alineo-labs/memory\";\n\nconst memory = new Memory({ workingMemory: new InMemoryWorkingMemoryProvider() });\n\nawait memory.workingMemory.set(ref, \"preferredLanguage\", \"TypeScript\");\nawait memory.workingMemory.get(ref, \"preferredLanguage\"); // \"TypeScript\"\nawait memory.workingMemory.list(ref); // { preferredLanguage: \"TypeScript\" }\nawait memory.workingMemory.delete(ref, \"preferredLanguage\");\n```\n\n### Semantic memory — optional\n\nVector recall over remembered facts. Omitting it is a first-class, typed state: calling\n`remember()`/`recall()` on a `Memory` with no semantic provider throws `MemoryCapabilityError`.\n\n```ts\nimport {\n  Memory,\n  InMemoryWorkingMemoryProvider,\n  InMemorySemanticMemoryProvider,\n} from \"@alineo-labs/memory\";\n\nconst memory = new Memory({\n  workingMemory: new InMemoryWorkingMemoryProvider(),\n  semantic: new InMemorySemanticMemoryProvider(myEmbeddingProvider),\n});\n\nawait memory.remember(ref, { content: \"prefers dark mode\" });\nconst facts = await memory.recall(ref, \"UI preferences\", { topK: 5 });\n```\n\n`EmbeddingProvider` is a minimal `{ id, embed(texts) }` shape — pass any embedding model you\nlike. `@alineo-labs/model-providers` ships `createNvidiaEmbeddingProvider()`, matching this\nshape structurally with no dependency on this package at all.\n\n#### Verified memory\n\nEvery fact returned by `recall()`/`listAll()` carries a computed `verified` flag: `true` when\n`remember()` was called with a `sourceRef` pointing at a real ledger entry, `false` for a\nfree-form fact. This is computed, never caller-set — passing `verified: true` on a free-form\nfact's input is silently ignored:\n\n```ts\nawait memory.remember(ref, {\n  content: \"user confirmed the refund\",\n  sourceRef: { sandboxId: sb.sandboxId, entryIndex: 42 }, // ties it to a real ledger entry\n});\n\nconst [fact] = await memory.recall(ref, \"refund\");\nfact.verified; // true — traceable back to that ledger entry\n```\n\nA fact worth trusting more than a hallucinated summary is one you can point at real execution\nhistory — no other memory framework can do this because it requires an execution ledger to\npoint _at_ in the first place.\n\n### Episodic memory — a function, not a provider\n\nA read-shaped view over the sandbox ledger, reshaped by `resourceId`. No new storage.\n\n```ts\nimport { episodicRecall } from \"@alineo-labs/memory\";\n\nconst entries = await episodicRecall(adapter, ref, { limit: 100 });\n\n// Include ancestor sessions reached via sb.fork()'s parentSandboxId chain:\nconst withHistory = await episodicRecall(adapter, ref, { branch: \"lineage\" });\n```\n\nBy default, sessions are resolved by matching `SandboxDetails.resourceId` (or, for ledger data\nwritten before that field existed, by matching the ledger's `name` against `resourceId`).\nSupply `resolveSessions` to use a different convention entirely.\n\n#### Branch-true episodic memory\n\n`episodicRecall({branch: \"lineage\"})` flattens fork ancestry into one merged stream — good\nenough for \"what led up to this session,\" but it can't tell you \"what happened on a _sibling_\nbranch forked from the same point.\" `episodicTree()` returns the actual fork tree instead:\n\n```ts\nimport { episodicTree } from \"@alineo-labs/memory\";\n\nconst [root] = await episodicTree(adapter, ref);\nroot.entries; // this session's own ledger entries\nroot.children; // sessions forked from it — each with its own .entries and .children\n```\n\nEvery resolved session's ancestor chain is pulled in automatically (same as `lineage`), so a\ntree with a resolved-but-orphaned node never happens — its ancestors are added as parents even\nif they weren't in the original resolved set.\n\n## Real backends\n\n| Package                              | Working memory                  | Semantic memory                  | Notes                                                                                                                                                                                                                                                  |\n| ------------------------------------ | ------------------------------- | -------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |\n| `@alineo-labs/memory` (this package) | `InMemoryWorkingMemoryProvider` | `InMemorySemanticMemoryProvider` | Process-local, non-durable — reference implementations only.                                                                                                                                                                                           |\n| `@alineo-labs/sqlite-memory`         | `SQLiteWorkingMemoryProvider`   | `SQLiteSemanticMemoryProvider`   | File-based via `bun:sqlite`, zero external services, survives restarts. Ranks recall with `sqlite-vec`'s native `vec0` index when the extension loads (verified on win32/x64); falls back to an in-JS cosine scan otherwise — check `.hasVectorIndex`. |\n| `@alineo-labs/postgres-memory`       | `PostgresWorkingMemoryProvider` | `PostgresSemanticMemoryProvider` | Shared, multi-process backend. Row-level security isolates `teamId`-scoped rows. Ranks recall with a `pgvector` HNSW index when the extension can be installed; falls back to an in-JS cosine scan otherwise — check `.hasVectorIndex`.                |\n\n```ts\nimport { Memory } from \"@alineo-labs/memory\";\nimport {\n  SQLiteWorkingMemoryProvider,\n  SQLiteSemanticMemoryProvider,\n} from \"@alineo-labs/sqlite-memory\";\nimport { createNvidiaEmbeddingProvider } from \"@alineo-labs/model-providers\";\n\nconst memory = new Memory({\n  workingMemory: new SQLiteWorkingMemoryProvider(\"./alineo-memory.db\"),\n  semantic: new SQLiteSemanticMemoryProvider(\"./alineo-memory.db\", createNvidiaEmbeddingProvider()),\n});\n```\n\nNeither backend package has been run against a live database in this repo's own test suite\n(no Postgres instance, and the SQLite one is unit-tested with `bun:sqlite` directly) — they\nship type-checked and, for SQLite, tested against a real file; treat the Postgres one as\nreviewed-but-unverified until it's run against an actual server.\n\n## Compaction\n\nA `remember()`'d fact stays verbatim forever unless pruned. `compactSemanticMemory()` (or\n`Memory.compactSemanticMemory()`) drops old/excess facts, for any provider implementing the\noptional `IPrunableSemanticMemoryProvider` capability (`listAll`/`forget` — all three semantic\nproviders above support it):\n\n```ts\nawait memory.compactSemanticMemory(ref, { maxFacts: 500, maxAgeMs: 30 * 24 * 60 * 60 * 1000 });\n```\n\nAge-based removal runs first; the count cap is then applied to whatever's left. Throws if the\nconfigured provider doesn't support pruning.\n\nPass `summarize` to consolidate instead of just deleting — it receives the facts about to be\nremoved (oldest first) and returns replacement contents, written _before_ the originals are\ndropped:\n\n```ts\nawait memory.compactSemanticMemory(ref, {\n  maxFacts: 500,\n  summarize: async (facts) => [\n    await myLlmCall(`Summarize into one fact: ${facts.map((f) => f.content).join(\"; \")}`),\n  ],\n});\n```\n\n`summarize` is caller-supplied — this package never depends on a concrete model, the same\nprinciple as `EmbeddingProvider`.\n\n### Automatic compaction\n\nConfigure `autoCompact` on `Memory` to run a compaction check after every `remember()`, instead\nof remembering to call `compactSemanticMemory()` yourself on a schedule:\n\n```ts\nconst memory = new Memory({\n  workingMemory: new InMemoryWorkingMemoryProvider(),\n  semantic: new InMemorySemanticMemoryProvider(embeddings),\n  autoCompact: { maxFacts: 500, checkEvery: 10 }, // check once per 10 remember() calls\n});\n```\n\nSilently skipped if the configured provider doesn't support pruning; a failed compaction\ncheck fails the `remember()` call it rode along with (not silently swallowed) — raise\n`checkEvery`, or call `compactSemanticMemory()` on a separate schedule instead, to decouple\nthe two.\n\n## Context injection\n\n`buildContextSnippet(memory, ref, opts)` assembles a plain-text block from a resource's working\nmemory and (if a query is given and a semantic provider is configured) its most relevant\nsemantic memories — ready to prepend to a prompt:\n\n```ts\nimport { buildContextSnippet } from \"@alineo-labs/memory\";\n\nconst context = await buildContextSnippet(memory, ref, { query: userMessage, topK: 5 });\nfor await (const chunk of agent.prompt(context ? `${context}\\n\\n${userMessage}` : userMessage)) {\n  process.stdout.write(chunk);\n}\n```\n\nThis builds the string only — `alineo`'s Pi bridge has no hook today for prepending to a\nsession's system prompt automatically, so injecting it into an actual conversation is still a\ncall the surrounding app makes.\n\n## Structured working memory\n\n`IWorkingMemoryProvider` is raw, untyped key/value. `SchemaWorkingMemory<T>` wraps it with a\nvalidated, typed profile stored under one key — useful for the common \"agent maintains a\nstructured user profile\" shape:\n\n```ts\nimport { z } from \"zod\";\nimport { SchemaWorkingMemory } from \"@alineo-labs/memory\";\n\nconst ProfileSchema = z.object({\n  name: z.string().optional(),\n  preferredLanguage: z.string().optional(),\n});\n\nconst profile = new SchemaWorkingMemory(workingMemoryProvider, ProfileSchema);\nawait profile.update(ref, { preferredLanguage: \"TypeScript\" }); // merges + validates\nawait profile.get(ref); // { preferredLanguage: \"TypeScript\" }\n```\n\n`SchemaValidator<T>` is a minimal `{ parse(data): T }` shape — any Zod schema satisfies it\ndirectly; this package never depends on a concrete schema library.\n\n## Agent-callable memory tools\n\n`createMemoryTools(memory, ref)` returns a set of tool definitions (name, description, JSON\nSchema parameters, executor) in the shape most agent tool-calling conventions expect — so a\n_model_, not just the surrounding application code, can decide to persist or retrieve a fact\nmid-conversation:\n\n```ts\nimport { createMemoryTools } from \"@alineo-labs/memory\";\n\nconst tools = createMemoryTools(memory, ref);\n// [{ name: \"set_working_memory\", ... }, { name: \"get_working_memory\", ... },\n//   { name: \"remember_fact\", ... }, { name: \"recall_facts\", ... } — last two only if\n//   memory.hasSemanticMemory ]\n```\n\n`alineo`'s Pi bridge doesn't yet expose a way to register caller-defined tools into a running\nPi session, so wiring these into an actual live agent conversation is left to the caller today\n— this ships the tool _definitions_, ready to adapt into whatever surface ends up supporting\nthem.\n\n## Forkable memory\n\nSandboxes already fork copy-on-write via `sb.fork()`; `Memory.fork()` gives memory the same\nproperty — an independent snapshot copy the child can mutate without ever touching the\nparent's:\n\n```ts\nconst {\n  ref: childRef,\n  workingKeysCopied,\n  semanticFactsCopied,\n} = await memory.fork(parentRef, \"child-resource-id\");\n\nawait memory.workingMemory.set(childRef, \"note\", \"only visible to the child now\");\nawait memory.workingMemory.get(parentRef, \"note\"); // undefined — the parent is untouched\n```\n\nWorking memory is always copied in full. Semantic memory is copied only if the configured\nprovider supports the pruning capability (`listAll` is what makes enumerating \"everything to\ncopy\" possible) — `semanticFactsCopied` is `0`, not an error, otherwise.\n\n`Alineo.spawn()` calls this automatically when the parent agent has `.memory` configured — a\nspawned child (a sandbox-level fork under the hood) gets its own independent memory copy with\nno extra wiring.\n\n## Team access control\n\n`ResourceRef.teamId` isolates data _structurally_ in every backend (it's part of the storage\nkey), but only `@alineo-labs/postgres-memory` enforces it as an actual access-control boundary\nvia row-level security — a caller for the in-memory or SQLite backends who deliberately passes\nthe \"wrong\" `teamId` can still read it. `withTeamAccessControl()` /\n`withTeamAccessControlSemantic()` close that gap for any backend:\n\n```ts\nimport { withTeamAccessControl, withTeamAccessControlSemantic } from \"@alineo-labs/memory\";\n\nconst checker = { canAccess: (teamId: string) => currentUser.teamIds.includes(teamId) };\n\nconst memory = new Memory({\n  workingMemory: withTeamAccessControl(new SQLiteWorkingMemoryProvider(\"./mem.db\"), checker),\n  semantic: withTeamAccessControlSemantic(mySemanticProvider, checker),\n});\n```\n\nEvery call on a `ResourceRef` carrying a `teamId` is checked against `checker.canAccess()`\nfirst, throwing `MemoryAccessDeniedError` before the wrapped provider is ever touched. A `ref`\nwith no `teamId` always passes through untouched. Works alongside Postgres's own RLS for\ndefense-in-depth — it isn't an either/or.\n\n## Sandbox lifecycle binding\n\n`createMemoryLifecycleHooks(memory, ref)` returns a `SandboxHooks` object (composable via\n`@alineo-labs/core`'s `composeHooks()`) that records the most recently active `sandboxId` and\nthe most recent checkpoint's metadata into working memory — a durable answer to \"what was the\nlast checkpoint for this resource\" without re-deriving it from the ledger every time. It does\nnot restore sandbox state itself; that's `@alineo-labs/core`'s job.\n\n```ts\nimport { composeHooks } from \"@alineo-labs/core\";\nimport { createMemoryLifecycleHooks } from \"@alineo-labs/memory\";\n\nconst sb = await client.sandbox({\n  image: \"node:22\",\n  resources: { cpu: \"500m\", memory: \"512Mi\" },\n  resourceId: ref.resourceId,\n  hooks: composeHooks([createMemoryLifecycleHooks(memory, ref)]),\n});\n```\n\n## Agent wiring\n\n`alineo`'s `Alineo` class accepts an optional `memory` on `load()`/`resume()`/`attach()`, and\n`spawn()` carries it over to the child automatically:\n\n```ts\nconst agent = await Alineo.load(spec, { adapter, memory });\nawait agent.memory?.remember(agent.resourceRef, { content: \"user prefers concise answers\" });\n```\n\n`agent.resourceRef` defaults `resourceId` to the agent's own `name` — the same convention\n`episodicRecall()`'s default resolver expects, so no extra wiring is needed to make episodic\nrecall work for an agent's own sandbox sessions.\n\n`Alineo.spawn()` also calls `memory.fork()` automatically (see \"Forkable memory\" above) when\nthe parent has `.memory` configured — a spawned child gets its own independent memory copy,\nseeded from the parent, with no extra call needed.\n\n## What's intentionally out of scope here\n\n- **Pi tool-call integration** — `createMemoryTools()` produces framework-agnostic tool\n  definitions; actually registering them into a running Pi session needs a change to\n  `alineo`'s Pi bridge that hasn't been made.\n- **Automatic prompt injection** — `buildContextSnippet()` builds the string; nothing calls it\n  automatically at session start. That's still an explicit call the surrounding app makes.\n- **A ledger-native branch/lane concept** — `episodicTree()` reconstructs the fork tree from\n  `parentSandboxId` at read time; alineo's ledger itself still has no first-class branch\n  concept the way e.g. Pi's own session storage does. If the ledger ever grows one,\n  `episodicTree()` would become a thinner read over it instead of doing the reconstruction\n  itself.\n- **`withTeamAccessControl()` is app-layer, not a security boundary of its own** — it's a\n  correct-by-construction gate in front of whatever backend, but the `TeamAccessChecker` you\n  supply is doing all the real work; a buggy or bypassed checker is still a hole. It doesn't\n  replace real infrastructure-level isolation for a genuinely adversarial multi-tenant setup.\n- Restoring sandbox filesystem/process state from a checkpoint — `createMemoryLifecycleHooks`\n  only records checkpoint _metadata_ into working memory; the actual restore is\n  `@alineo-labs/core`'s `resume()`.\n\n## Examples & cookbook\n\n- [`examples/memory-basics`](https://github.com/DrejT/alineo/tree/main/examples/memory-basics)\n  — every capability on this page, demonstrated standalone. No OpenSandbox, no API key.\n- [`cookbooks/persistent-agent-memory`](https://github.com/DrejT/alineo/tree/main/cookbooks/persistent-agent-memory)\n  — the real end-to-end scenario: a Pi agent whose memory survives across separate sandbox\n  sessions entirely, using `@alineo-labs/sqlite-memory`.\n","readmeFilename":"README.md"}