Interfaces
Section titled “Interfaces”PlacementInput
Section titled “PlacementInput”What placementFor decides from. path is the caller’s explicit override.
Every optional field also admits undefined explicitly. Under
exactOptionalPropertyTypes a bare path?: string would refuse
placementFor({ path: params.path, … }) for a string | undefined param, forcing every
tool adapter to strip absent fields by hand. The contract therefore accepts the shape the
MCP and CLI layers actually hold.
Properties
Section titled “Properties”entities?
Section titled “entities?”readonly optional entities?: readonly string[];memoryType
Section titled “memoryType”readonly memoryType: string;readonly optional path?: string;readonly optional tags?: readonly string[];workspace?
Section titled “workspace?”readonly optional workspace?: string;Variables
Section titled “Variables”ARCHIVE_BUCKET
Section titled “ARCHIVE_BUCKET”const ARCHIVE_BUCKET: "archive" = "archive";The bucket eviction moves into, partitioned by year.
ARCS_DIR
Section titled “ARCS_DIR”const ARCS_DIR: "areas/arcs" = "areas/arcs";Behavioral arcs. Under areas/ because PARA is fixed at four buckets.
INBOX_DIR
Section titled “INBOX_DIR”const INBOX_DIR: "areas/inbox" = "areas/inbox";Where a memory lands when no rule claims it. memhtml doctor reports inbox depth as a
health signal, so an unplaceable memory is visible rather than lost.
MEMORY_EXTENSION
Section titled “MEMORY_EXTENSION”const MEMORY_EXTENSION: ".html" = ".html";The file extension every memory carries.
PEOPLE_DIR
Section titled “PEOPLE_DIR”const PEOPLE_DIR: "resources/people" = "resources/people";The person plane, folded into resources/ rather than given its own bucket.
TASKS_SUBDIR
Section titled “TASKS_SUBDIR”const TASKS_SUBDIR: "tasks" = "tasks";The directory segment every task file sits under, appended to its workspace’s project directory or to the inbox.
A subdirectory rather than a fifth bucket: PARA is fixed at four, and a task belongs to
whatever the memory beside it belongs to. Keeping tasks in one named segment is what makes
ls projects/<slug>/tasks the list operation, which is the design’s
CRUDL-without-retrieval contract. The segment name is therefore part of the contract.
Functions
Section titled “Functions”archivePathFor()
Section titled “archivePathFor()”function archivePathFor(path, year): string;The archive path a memory moves to on eviction: archive/<YYYY>/<original-path>, with the
original path mirrored exactly beneath the year.
year is a calendar year (a label, not an offset). Mirroring the whole original path is
what makes the mapping injective and invertible, so git log --follow reads through the
move and diff -M reports it as R100 rather than a delete plus an add.
Parameters
Section titled “Parameters”string
number
Returns
Section titled “Returns”string
archiveYearOf()
Section titled “archiveYearOf()”function archiveYearOf(path): number | undefined;The archive year of an archive path, or undefined when the path is not archived.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”number | undefined
isArchivePath()
Section titled “isArchivePath()”function isArchivePath(path): boolean;True when a path sits under the archive bucket with a year partition.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”boolean
isValidMemoryPath()
Section titled “isValidMemoryPath()”function isValidMemoryPath(path): boolean;True when a path is a usable memory path: rooted in a PARA bucket, ending in .html,
carrying no . or .. segment.
memoryPathViolation asked as a boolean, so the predicate and the refusal’s reason cannot disagree about which paths are usable.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”boolean
memoryPathFor()
Section titled “memoryPathFor()”function memoryPathFor(input): string;The full path for a new memory. An explicit valid path is authoritative and returned
verbatim in canonical form; otherwise the directory comes from placementFor and
the filename from filenameFor, which date-prefixes an episodic entry.
Parameters
Section titled “Parameters”PlacementInput & object
Returns
Section titled “Returns”string
memoryPathViolation()
Section titled “memoryPathViolation()”function memoryPathViolation(path): string | undefined;Why a path is not a usable memory path, or undefined when it is one.
The RULE and its explanation in one function, because a refusal that restated the rule in its own words would be a second copy of it, free to name a clause this function does not check. A caller that refuses an unusable path quotes this string; isValidMemoryPath is the same question asked as a boolean.
Each clause names the input it saw rather than the rule in the abstract, so a caller holding the message can act without re-reading the format doc. The traversal clause is the security one: it is what keeps a caller-supplied path from escaping the memory repo.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”string | undefined
normalizePath()
Section titled “normalizePath()”function normalizePath(path): string;Reduce a caller-supplied path to the canonical git-tree form: leading slashes dropped
(callers may pass the <link href> document-reference form), repeated slashes collapsed,
trailing slash dropped.
The trailing slash is removed by endsWith/slice rather than by //+$/, which is not a
style choice: an unanchored-left /+$ is quadratic on a long run of slashes followed by a
non-slash, measured 2026-08-18 at 4 ms / 57 ms / 769 ms / 3049 ms for n = 2k / 8k / 32k / 64k.
The collapse above happens to defuse it — after it, no run of two slashes survives, so the
pattern can only ever match one character — but that made the cost of this function depend on
the ORDER of three chained calls, with nothing stating it and nothing checking it. Reordering
them, or dropping the collapse, would reintroduce a multi-second stall on 64 KB of input across
this function’s callers, all of which sit on the write path that accepts agent-supplied paths.
A single-character slice cannot be reordered into a hazard.
packages/contracts/tests/paths.test.ts asserts the cost curve at adversarial sizes.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”string
originalPathFor()
Section titled “originalPathFor()”function originalPathFor(archivePath): string | undefined;The pre-eviction path behind an archive path, or undefined when the path is not an
archive path. Strips exactly one archive/<YYYY>/ prefix, so it is the left inverse of
archivePathFor even for a memory archived twice.
Parameters
Section titled “Parameters”archivePath
Section titled “archivePath”string
Returns
Section titled “Returns”string | undefined
paraBucketOf()
Section titled “paraBucketOf()”function paraBucketOf(path): "projects" | "areas" | "resources" | "archive" | undefined;The PARA bucket a path sits in, or undefined when it sits outside all four.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”"projects" | "areas" | "resources" | "archive" | undefined
placementFor()
Section titled “placementFor()”function placementFor(input): string;The directory a memory belongs in, following design §2.1’s six rules in order, with the
task rule sitting between the arc rule and the person rule. Total: it always returns a
directory rooted in a PARA bucket, so the write path never guesses twice and never fails.
Returns the directory, not the full path, because the filename needs a title this input
does not carry. memoryPathFor composes the two. An explicit path contributes
its directory; when that path is unusable it is ignored rather than propagated, so the
return stays a valid bucket and this function stays total.
Totality is why the refusal cannot live here. A caller that wants an unusable path refused
rather than re-derived asks the store for it (strictPath on a WriteInput), which gates on
memoryPathViolation before any of this runs and quotes its reason. Refusing here instead
would make placement fallible for every caller, including the sleep phases that place a synthesized
arc and have no caller path to be wrong about.
Parameters
Section titled “Parameters”Returns
Section titled “Returns”string