npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@cuylabs/agent-memory-filesystem

v5.1.0

Published

Filesystem-backed memory provider for @cuylabs/agent-core

Readme

Agent Memory Filesystem

Filesystem-backed memory provider for @cuylabs/agent-core.

The package is organized by implementation area:

src/
  provider.ts       # provider surface for agent-core
  settings.ts       # provider options and public filesystem memory types
  capture.ts        # connects agent-core capture hooks to provider-owned extraction
  agents/
    capture.ts      # optional private memory writer built on agent-core
    recall.ts       # optional private read-only memory recall worker
  storage/
    io.ts           # low-level file reads and directory traversal
    reader.ts       # turns durable record files into records
    store.ts        # file I/O and writable record operations
    records.ts      # record IDs and serialization
    paths.ts        # path defaults and resolution
  tools/
    memory-tools.ts # foreground memory tools installed by the provider
  search/
    filesystem.ts # line-aware filesystem search and ranking
    tokenizer.ts # query and record tokenization

The provider is intentionally filesystem-specific. The generic boundary lives in @cuylabs/agent-core; sibling packages can implement the same provider contract for graph, vector, or service-backed memory without sharing this package's storage code.

import { createAgent } from "@cuylabs/agent-core";
import { createFilesystemMemoryProvider } from "@cuylabs/agent-memory-filesystem";

const agent = createAgent({
  model,
  memory: createFilesystemMemoryProvider({ root: ".agent-memory" }),
});

Memory remains opt-in. @cuylabs/agent-core owns the provider contract and lifecycle wiring; this package owns concrete provider adapters. There is no YAML discovery layer in the default path: applications import a provider factory, pass provider-specific options, and give the resulting provider to createAgent({ memory }).

The filesystem provider uses line-aware local file search underneath its memory tools and private recall worker. When automatic recall is configured, agent-core calls provider.recall(...) once per user turn by default, using the latest user message as the recall task, then reuses that result across tool-loop steps. The provider searches durable records/*.md files. Search results include bounded snippets with source path and line metadata, so the recall worker can inspect the exact file location instead of receiving a whole memory file blindly.

For memory_search, the query is primary: the provider scans visible record content and metadata, then ranks matching snippets. Tags are only ranking hints for search because model-guessed tags should not hide content matches. For memory_list, tags remain exact filters because list is an explicit inventory operation.

There is no database, vector service, or hardcoded stop-word policy in the default path. Callers can provide a custom tokenizer, explicit stop words, snippet sizing, or a different search engine through createFilesystemMemoryProvider({ recall: ... }) when the default lexical ranking is not enough.

Automatic recall is agentic-only. Configure recall.agent with a private recall worker model. Agent-core still calls provider.recall(...); the filesystem provider owns the worker and only gives it read-only memory tools such as memory_search, memory_get, and memory_list.

const memory = createFilesystemMemoryProvider({
  root: ".agent-memory",
  recall: {
    agent: {
      model: memoryModel,
      maxSteps: 4,
      timeoutMs: 10_000,
    },
  },
});

The intended model-based flow is:

agent-core before LLM call
  -> provider.recall()
    -> filesystem recall agent
      -> memory_search returns ranked file snippets with line ranges
      -> memory_get reads an exact memory record when needed
      -> recall agent returns one compact summary
  -> core injects that summary
  -> main agent LLM call

When no recall agent is configured, the provider does not install automatic recall. The foreground agent can still use provider-owned memory_search, memory_get, and memory_list tools, and application code can still call provider.search(...) directly.

The default on-disk layout is:

.agent-memory/
  records/
    2026-05-14-package-manager-a1b2c3d4.md

records/ is the canonical durable memory corpus. Raw conversation history belongs to SessionStore implementations such as @cuylabs/agent-session-store-file, not to the memory provider. The provider does not read or generate root-level MEMORY.md, memory_summary.md, index.json, or journal files in the default path. If those become first-class artifacts later, they should be added as explicit provider-owned components instead of implicit side files.

Each record is Markdown with JSON frontmatter:

---
{
  "id": "2026-05-15-release-plan-a1b2c3d4",
  "title": "Release plan",
  "kind": "fact",
  "scope": "project",
  "tags": ["release"],
  "metadata": {
    "sessionId": "s1",
    "memoryKey": "release-plan"
  }
}
---

The release codename is copper.

The JSON block is intentionally not YAML. It is still a frontmatter block, but the provider can parse it with the platform JSON parser, avoid adding a YAML dependency, and scan only the header when it later needs metadata-first operations. The Markdown body remains the searchable memory text.

Memory vs Sessions

@cuylabs/agent-core sessions and this provider's memory files are separate layers:

| Layer | Package | Default durable shape | Purpose | | --- | --- | --- | --- | | Raw chat history | @cuylabs/agent-core/sessions plus a SessionStore | Store-specific, for example <session-id>.jsonl in @cuylabs/agent-session-store-file | Reconstruct conversation messages, branches, and compaction entries. | | Durable memory | @cuylabs/agent-memory-filesystem | .agent-memory/records/*.md | Searchable facts, preferences, summaries, and lessons that should survive beyond the current prompt window. |

Normal turn capture and compaction-commit capture both write durable memory by calling the provider's remember path. That path creates, updates, or skips one canonical records/*.md file. Record metadata includes the originating sessionId, optional turnId, source hook, tags, scope, and provider-managed dedupe metadata.

Records do not need to be split into per-session folders. Isolation is handled by record scope:

  • session records are only recalled when the current sessionId matches the metadata on the record.
  • project records are recalled across sessions in the same memory root.
  • user and global are available for providers that want broader sharing.

By default, filesystem memory writes use project scope because durable memory is usually meant to help future sessions in the same project. Set defaultScope: "session" or have your capture worker/tool call pass scope: "session" when a memory should stay private to one chat session.

Pluggability model

The public integration point is the provider factory:

const memory = createFilesystemMemoryProvider({
  root: ".agent-memory",
  defaultScope: "session", // omit this for project-wide memory by default
  recall: {
    tokenizer: customTokenizer,
    engine: customSearchEngine,
  },
  remember: {
    captureTurn: customTurnExtractor,
  },
});

That keeps the hookup simple while still allowing the implementation to get more powerful. Recall, storage, and remembering can evolve behind the provider boundary without changing application code.

Memory Capture

agent-core calls provider capture hooks when memory capture is enabled with memory.capture. This package keeps the low-level core hook names internal and exposes remember.captureTurn and remember.captureBeforeCompactionCommit as the memory-facing options. src/capture.ts adapts core lifecycle input to the remember pipeline.

The default provider does not silently write every conversation turn into durable memory. Turn-end writes are enabled by passing a provider-owned extractor:

const memory = createFilesystemMemoryProvider({
  root: ".agent-memory",
  remember: {
    async captureTurn({ input, output }) {
      if (!output) return [];
      if (!input.includes("remember")) return [];

      return {
        title: "User preference",
        content: output,
        kind: "turn-capture",
        scope: "project",
        tags: ["preference"],
      };
    },
  },
});

That extractor receives the full AgentMemoryTurnEndInput from core and returns memory drafts. The file provider stores those drafts as normal records with source: "turn_capture", so recall and memory_search can find them on later turns. If memory.capture is disabled in createAgent({ memory: { ... } }), core does not call turn-start or turn-end capture hooks.

Compaction-commit capture is separate from turn capture. When a provider implements remember.captureBeforeCompactionCommit, agent-core awaits it after the cut and summary are prepared, before the compacted history is committed. Use it for last-chance durable extraction from context that is about to be compacted. Omit remember.captureBeforeCompactionCommit to disable this path:

const memory = createFilesystemMemoryProvider({
  root: ".agent-memory",
  remember: {
    async captureBeforeCompactionCommit({ removedMessages, nextSummary }) {
      if (removedMessages.length === 0) return [];

      return {
        title: "Compacted conversation facts",
        content:
          nextSummary ?? removedMessages.map((m) => String(m.content)).join("\n"),
        kind: "compaction-capture",
        scope: "project",
      };
    },
  },
});

The provider decides whether captureTurn and captureBeforeCompactionCommit are deterministic functions, calls to a smaller extraction model, or wrappers around another service. From core's perspective they are provider-owned memory writes, not a replacement for the compaction algorithm.

The filesystem store enforces a conservative dedupe policy after the extractor or private writer proposes a record:

  • exact duplicate content in the same visible scope is skipped and returns the existing record id;
  • memoryKey can target an existing logical memory, including an existing record id;
  • onExisting: "append" adds new content to that keyed record;
  • onExisting: "replace" rewrites the keyed record's body;
  • fuzzy "related" memories are not merged automatically because semantic merge can accidentally combine contradictory facts.

Use keyed append for deliberate incremental records:

await memory.remember?.({
  sessionId,
  cwd,
  title: "Release plan",
  content: "The release codename is copper.",
  scope: "project",
  memoryKey: "release-plan",
});

await memory.remember?.({
  sessionId,
  cwd,
  title: "Release plan",
  content: "The launch window is June.",
  scope: "project",
  memoryKey: "release-plan",
  onExisting: "append",
});

For model-based extraction, use the agentic helper. It creates a private agent-core worker with an isolated in-memory session and a narrow memory tool set bound to the file provider. The tool names intentionally match the AgentMemoryProvider read/write contract used by foreground memory tools:

  • memory_search calls provider search for related durable memory.
  • memory_get reads one exact durable memory record by id.
  • memory_list calls provider list, or the provider's search fallback, for a filtered inventory.
  • memory_remember writes one durable memory through the provider's normal remember pipeline. It can pass a memoryKey plus onExisting: "append" when extending an existing record found through search/list.

The normal workflow is: search/list existing memory, remember only new durable facts, use keyed append for explicit incremental updates, then return NO_REPLY. The store still verifies the write, so exact duplicate content is skipped even if the private worker misses it. The worker never receives generic file read/write tools, memory_forget, or the foreground agent's full tool set. Automatic recall is still triggered by core through provider.recall(...); when agentic recall is configured, the provider implements that hook with its own private read-only worker.

A profile carries the model, prompt, tool filters, and step limits in the same shape used by subagents.

import {
  createAgenticMemoryCapture,
  createFilesystemMemoryProvider,
} from "@cuylabs/agent-memory-filesystem";

const memory = createFilesystemMemoryProvider({
  root: ".agent-memory",
  remember: {
    captureBeforeCompactionCommit: createAgenticMemoryCapture({
      model: memoryModel,
      profile: memoryCaptureProfile,
      maxSteps: 4,
      timeoutMs: 15_000,
    }),
  },
});

That worker is provider-owned host policy. It is not exposed as a foreground tool, and agent-core still awaits captureBeforeCompactionCommit before committing the compacted session history. Pass any additional read helpers through tools, and apply the private worker's own middleware, approval, or toolExecutionMode policy there. The memory worker should not inherit the foreground agent's full tool set.

No SQL database is required for the default provider. Search is built in-process from local files on each call, with exact/snippet matching and lexical ranking over Markdown memory files. A SQLite FTS or vector backend should be added behind the same recall/provider boundary when memory size or latency requires a persistent index.

See examples/README.md for runnable local examples. After building @cuylabs/agent-core and this package, run them with:

pnpm run example:basic
pnpm run example:records
pnpm run example:agentic
pnpm run example:automatic-recall