File Reference
A memory layer that tracks
Overview
The File Reference layer watches user messages for #path/to/file.ts-style references. Each referenced file is read, scored for relevance, and injected into the context as a # Referenced Files developer message, ordered by priority. On every new message the layer re-checks tracked files for changes and triggers an immediate context re-render when content has changed, so the model always sees current file contents.
- Slot:
350(Slot.RAG) by default - Scope:
thread - Budget:
'auto' - Re-render timing:
immediate - Hook timeouts:
onItemAppend30000ms — it reads files and runs an LLM scoring call per new reference, so the 5s pipeline default would silently drop the transform
Usage
import { fileReference } from '@noetic-tools/core';
const layer = fileReference({
baseDir: '/workspace/my-project',
maxFileSize: 512 * 1024,
});A user message like Look at #src/index.ts and #package.json tracks both files; the reference text is rewritten to markdown anchor links ([#src/index.ts](#src-index-ts)).
Configuration
interface FileReferenceOptions {
baseDir?: string; // base for resolving relative paths (default: cwd)
slot?: number; // default Slot.RAG (350)
scoringModel?: string; // model for priority scoring
maxFileSize?: number; // default 1 MB
followSymlinks?: boolean; // default false (security)
allowedExtensions?: string[]; // default: common code/text extensions
}| Field | Type | Default | Purpose |
|---|---|---|---|
baseDir | string | process.cwd() | All references resolve relative to this directory |
slot | number | 350 | Position of injected file contents |
scoringModel | string | a small fast model | Used for the relevance-scoring LLM call |
maxFileSize | number | 1048576 | Files larger than this are rejected with FILE_TOO_LARGE |
followSymlinks | boolean | false | When false, any symlinked path component is rejected |
allowedExtensions | string[] | code/text set | Extension (or exact filename, e.g. Dockerfile) allowlist |
How It Works
onItemAppend
- Scans incoming user messages for the
#pathpattern (requires a file-like shape —#hashtag,#123,#regionare not matched). - New references are read and priority-scored 0-100 via an LLM call against the current user query (path-match heuristic when no model is available; parse failures default to 50). Absolute paths are rejected — only relative paths under
baseDirare allowed. - Already-tracked files are re-read for change detection (content hash); changes, deletions, and reappearances update the tracked state and request an immediate re-render.
- Reference text in the message is transformed into markdown anchor links.
recall
Injects a # Referenced Files developer message containing each tracked file's content in a fenced block, ordered by priority (highest first) and trimmed to fit the layer budget (head + tail with a truncation marker for oversized files). Deleted or unreadable files contribute a short warning block instead of content.
Security
Reads go through several gates, each producing a tracked error instead of file content:
- Containment — the resolved path must stay within
baseDir(PATH_TRAVERSALotherwise). - Symlink rejection — unless
followSymlinks: true, every path component belowbaseDirislstat-checked and any symlink (directory or leaf) is rejected. Lexical containment alone is not enough:baseDir/link → /etckeeps the unresolved string insidebaseDirwhile the OS resolves the symlink and escapes the sandbox. - Extension allowlist —
DISALLOWED_EXTENSIONfor anything not inallowedExtensions. - Size cap —
FILE_TOO_LARGEbeyondmaxFileSize.