This is the abridged developer documentation for memhtml
# memhtml
> An agent's long-term memory as a git repository of semantic HTML files, with a rebuildable SQLite index, retrieval that fuses four ranking arms, and a seventeen-phase curation pass.
Memory for Agents, in HTML MEANING · MEMORY · MARKUP ## What memhtml is [Section titled “What memhtml is”](#what-memhtml-is) memhtml stores an agent’s long-term memory as a git repository of small HTML files, one fact per file. A SQLite index over that repository makes the files searchable, and an agent reads and writes the store either through the `memhtml` command line or through an MCP server that serves the same store over stdio. What the agent remembers is a directory on disk that you can open in a browser, diff, and edit with ordinary tools. ## The problem it solves [Section titled “The problem it solves”](#the-problem-it-solves) An agent that writes down what it learns needs three answers later: what do I know, where did it come from, and what has changed since. Keeping those facts as rows in a vector database answers the first one well and drops the other two, because a row carries no history and a re-embedding pass rewrites the store with nothing for a reviewer to read. memhtml answers all three out of git. Each fact is a file, so you read it without a query. Each change to the corpus is exactly one commit, so `git log` in the store is the history of what the agent learned, and a night of automated curation is a diff a person can review. Retiring a memory moves the file into `archive//` with the original path mirrored beneath it, so `git log --follow` reads through a memory’s whole life and no correction destroys the fact it corrected. The index holds no facts of its own: delete `.memhtml/index.db` and `memhtml index rebuild` reconstructs it from the tree. ## What it is made of [Section titled “What it is made of”](#what-it-is-made-of) Each memory is standard HTML5 that a browser renders with no build step. The `` elements in the head carry the typed metadata, one `` span in the body carries the claim the file exists to state, and `` elements carry the relationships an author asserted between memories. One fact per file is what keeps a correction reviewable: `memhtml correct` writes the replacement and archives the original in one commit, so an interrupted run leaves no pair of live contradicting memories behind. Two SQLite databases sit under `.memhtml/`, opened through Node’s built-in `node:sqlite` driver, and they differ in what a rebuild can recover. `index.db` holds the projected rows, the full-text index, the embedding vectors, and the mined edges, all of which the indexer regenerates from the tree. `state.db` holds the facts the tree cannot reproduce: access counts, reinforcement counts, and outcome scores. Its durable copy is a committed file, `.memhtml/state/access.jsonl`. Retrieval runs four independent ranking arms over one query and then combines their orderings. Full-text search ranks on title, claim, and body text. Vector similarity ranks on the embeddings. Recency ranks an episodic memory by when the fact happened rather than by when someone wrote it down. Salience ranks on how often someone has chosen to open the memory. One SQL statement fuses the four orderings by reciprocal rank fusion, where each arm contributes `1/(rank + 60)` under its own weight, and a pass in TypeScript then drops near-duplicate hits. That fusion is what this site means by four-arm RRF. When an arm’s precondition is missing, memhtml drops that arm and sets `degraded: true` on the response, so the search gets narrower and still answers. A store with no embedder bound is the common case, and it still returns ranked hits from the other three arms. Three doors write into the same tree: `memhtml write` and `memhtml apply` on the command line, the write tools on the MCP server, and your own file tools against the checkout. The store owns git staging, so one call becomes exactly one commit and a caller cannot bundle two unrelated writes into it. `memhtml sleep run` is the curation pass you schedule overnight. It walks seventeen ordered phases on a `sleep/` branch and commits each phase’s work on its own, so a reviewer reads the night one phase-shaped diff at a time. It detects contradictions and leaves the choice of a winner to a writer or a human, because that choice is a one-way door. `memhtml sleep merge` then refuses to move `main` when the night made retrieval worse. Every command writes one JSON envelope to stdout and nothing else, sends its logs to stderr, and exits 0 for success, 2 for a usage error, and 1 for a runtime failure. `memhtml manifest` prints every command, flag, response type, error code, and environment variable the binary accepts, and it answers on a machine with no store, no database, and no credentials. ## Where to start [Section titled “Where to start”](#where-to-start) Install the binary and work through the four tutorials in order. One published package, [`memhtml`](https://www.npmjs.com/package/memhtml), carries the whole system, and a clone-and-build path exists for working on it. If you are an AI agent rather than a person, read [For agents](/agents/) first: it states the assumptions to drop and the shortest path to a correct first call. * **1. [Learn](/learn/)**: from a clone to a working store, then task-shaped how-tos * 1.1. [Install memhtml and initialize a store](/learn/tutorial/install/) * 1.2. [Write your first memory](/learn/tutorial/first-memory/) * 1.3. [Retrieve it](/learn/tutorial/first-retrieval/) * 1.4. [Wire up the MCP server](/learn/tutorial/mcp-server/) * 1.5. [Run the store day to day](/learn/operations/run-the-store-day-to-day/) * **2. [Reference](/reference/)**: generated from the registries the binary parses its own arguments with * 2.1. [Global flags](/reference/global-flags/) and [configuration](/reference/config/) * 2.2. [Response types](/reference/response-types/) and [error codes](/reference/error-codes/) * 2.3. [MCP tools](/reference/mcp-tools/) and [resources](/reference/mcp-resources/) * 2.4. [Vocabularies](/reference/vocabulary/), [sleep phases](/reference/sleep-phases/), [RRF arms](/reference/rrf-arms/) * 2.5. [Index schema](/reference/schema/) and the [requirements ledger](/reference/requirements/) * 2.6. [Contracts](/reference/contracts/): every exported symbol of `@memhtml/contracts`, with its doc comment * **3. [Internals](/internals/)**: why the system is shaped this way, and what each decision refuses * 3.1. [Packages and dependency direction](/internals/packages-and-dependency-direction/) * 3.2. [The memory file format](/internals/the-memory-file-format/) * 3.3. [The write path](/internals/the-write-path/) * 3.4. [Four-arm retrieval](/internals/four-arm-retrieval/) * 3.5. [The sleep pipeline](/internals/the-sleep-pipeline/) * 3.6. [Testing posture](/internals/testing-posture/) * **Appendix A. [Glossary](/glossary/)**: the domain vocabulary, each term linked to the chapter that develops it ## How to read this site [Section titled “How to read this site”](#how-to-read-this-site) The Reference tier is generated from the same registries the binary parses its own arguments with, so a flag, error code, or response type on those pages is the one the binary ships. Where this site and the binary disagree, the binary is right and a test is missing. Architectural claims cite the code in repo-relative `path:line` form. Those line numbers point into the commit this site was built from, so read them as a pointer into the source rather than as a permanent address. Every measurement on the site carries the date someone took it. Section numbers live in the heading text, as in `## 3.2. Edge encoding`, so the page, the contents, the search index, and the Markdown this site serves to an agent all agree on which section §3.2 is. Anchors stay unnumbered, so inserting a section does not break an inbound link. Every page also serves its own Markdown source at the same path with `.md` appended, and links that twin from its ``. [`llms.txt`](/llms.txt) indexes the whole corpus for an agent that would rather read the document than the page.
# For agents
> What to unlearn, which surface you are on, and the shortest path from nothing to a working memhtml integration.
This page is addressed to an AI agent working with memhtml, and it is written so the person evaluating memhtml over your shoulder can read it too. It is short, and every claim on it points at behavior a test checks. ## 1. The manifest outranks this page [Section titled “1. The manifest outranks this page”](#1-the-manifest-outranks-this-page) `memhtml manifest` prints the binary’s whole contract: every command, argument, flag, response type, error code, and environment variable. A bare `memhtml` returns the same thing, and so do `memhtml help` and `memhtml --help` when stdout is a pipe. Each of them answers on a machine with no store, no database, and no credentials, which makes the manifest your liveness check as well as your contract. For one command, `memhtml help ` (or `memhtml --help`, or `-h`) returns that command’s entry alone: a usage line, its arguments and flags, the response type it emits, the usage-error codes, examples, and sibling commands. Piped, it is a `cli.help` envelope whose `data` is the manifest’s entry for the command plus those derived fields, so nothing it says can contradict the manifest. On a terminal it is Markdown, for the person behind you; `--json` forces the envelope there too. Every usage error on a command that exists ends its `suggestions` with the `memhtml help ` call that shows the table you needed; an unknown command gets nearest-name candidates instead. **For an agent.** Prefer the manifest to this page wherever the two could disagree. The manifest is generated from the source of truth, so it is right and this page is stale. This page adds what a schema cannot state: what a silence means, and which surface you are on. ## 2. Assumptions to drop [Section titled “2. Assumptions to drop”](#2-assumptions-to-drop) Each item below is a place where memhtml behaves unlike the system you are pattern-matching it to. Branch on `code` and never on `error`. A failure envelope carries four fields: `{apiVersion, error, code, suggestions}`. The `code` values are append-only, so a shipped code keeps its meaning for good and is never removed. The `error` string is prose for a human, and whoever improves the wording rewrites it freely, so a matcher over that string gives you a test that passes until someone edits a sentence. See [error codes](/reference/error-codes/) and [the envelope](/reference/envelope/). An absent path was archived. memhtml removes nothing from a store. Eviction is a `git mv` into `archive//` that mirrors the original path, so `git log --follow` reads through a memory’s whole life. When a path you were holding stops appearing in results, retry with `--include-archived` before you conclude the fact is gone. See [store layout and path algebra](/internals/store-layout-and-path-algebra/). The git tree is the system of record and the index is disposable. `.memhtml/index.db` is a projection, and `memhtml index rebuild` reconstructs it from the tree, so anything that has to survive lives in a file: authored links are `` elements and metadata is `` elements. Read a query result as a pointer to a file, and the file as the fact. See [the index plane and the state plane](/internals/index-plane-and-state-plane/). One memory holds one fact. A memory is a single claim. Writing a five-paragraph summary as one memory defeats retrieval, because the arm that should rank one of those claims scores the whole blob instead. Split the summary into claims and let `memhtml link` carry the relationships between them. See [the memory file format](/internals/the-memory-file-format/). Read the flags instead of guessing them. Every flag the binary accepts is in the manifest, and an unknown flag returns a usage error with a suggestion attached rather than a silent default. A guess costs a round trip, where the manifest answers the same question in one call. See [global flags](/reference/global-flags/). ## 3. Which surface you are on [Section titled “3. Which surface you are on”](#3-which-surface-you-are-on) Two surfaces reach the same store, and the test for which one you have is free. | Signal | Surface | Entry point | | ------------------------------------ | --------- | ---------------------------------------------------------------- | | `memory_search` is in your tool list | MCP | Call the tools directly. Nothing to install. | | You can run a shell command | CLI | `memhtml manifest`, then the command you need. | | Neither | Read-only | You are reading documentation. Fetch the Markdown, not the HTML. | The MCP server is the same binary. `memhtml serve mcp` speaks stdio and composes the same services the command line does, so both surfaces agree on which store, which database, and which vector space a call reaches. A tool’s arguments mirror its command’s flags. See [MCP tools](/reference/mcp-tools/) and the [reference overview](/reference/), which links one page per command. **For an agent.** Read your tool list to find your surface, rather than running a command and inspecting the failure. When `memory_search` is absent from that list, you are on the CLI. ## 4. The shortest path to a working integration [Section titled “4. The shortest path to a working integration”](#4-the-shortest-path-to-a-working-integration) On the MCP surface there is no setup: call `memory_search` with prose, then `memory_read` on a hit’s path. On the command line:
```bash
1
memhtml manifest # the contract, and the liveness check
2
memhtml init # scaffold a store at --repo or $MEMHTML_ROOT
3
memhtml write --title … --claim … # one memory, one fact, one commit
4
memhtml search "prose, not a query language" --dense
```
Make `--dense` a habit on every call. It emits minified JSON with null fields dropped, which is what you want when the output goes into a prompt. `memhtml recall` runs the same retrieval under a character budget, for when you want a context pack rather than a hit list. Every command writes exactly one JSON envelope to stdout and nothing else, and sends its logs to stderr. The one exception is `memhtml help` on a terminal, which writes Markdown; piped, or with `--json`, it is an envelope like every other command. Exit 0 is success. Exit 2 is a usage error you fix by changing the call. Exit 1 is a runtime failure you fix by changing the store or the environment. When you are the operator wiring a host agent rather than the agent being wired, one command is the whole path: it writes the host’s MCP entry, its hooks, an instruction block, and a skill, and records them in a receipt so uninstall removes exactly that.
```bash
1
memhtml integrations install # claude, codex, cursor, opencode; omitted, it detects them
```
## 5. What to avoid [Section titled “5. What to avoid”](#5-what-to-avoid) * Scraping these pages. Every one of them is served as Markdown; section 7 has the URLs. * Parsing the `error` prose. Section 2 explains what that costs you. * Writing a query language into `memhtml search`. The argument is prose, and retrieval fuses four ranking arms, so an operator syntax has nothing to bind to. * Bundling unrelated writes. The store owns staging and every corpus change is exactly one commit, so a caller cannot make one commit mean two things. Use `memhtml apply` when the writes are one change. * Spending a tool call per hop. A question that needs several traversals of the corpus is one `memhtml exec` script against a read-only mount. See [the code-mode guide](/reference/guide/code-mode/). * Editing `.memhtml/index.db`, or hand-writing a memory file’s markup from memory. Write through the commands, which own the commit and the validation. ## 6. Read next [Section titled “6. Read next”](#6-read-next) Each row carries the page and its raw Markdown, so you can fetch rather than render. The [reference tier](/reference/) is generated from the source registries, and it is where every count and every enumerated value lives. | Read this | Raw Markdown | | ----------------------------------------------------------------------------------- | --------------------------------------------------------- | | [First call](/reference/guide/first-call/): the envelope, the exit codes, `--dense` | [`first-call.md`](/reference/guide/first-call.md) | | [Write surfaces](/reference/guide/write-surfaces/): the three ways in | [`write-surfaces.md`](/reference/guide/write-surfaces.md) | | [When to batch](/reference/guide/when-to-batch/): one write or many | [`when-to-batch.md`](/reference/guide/when-to-batch.md) | | [Conflicts](/reference/guide/conflicts/): what a contradiction does | [`conflicts.md`](/reference/guide/conflicts.md) | | [Authoring](/reference/guide/authoring/): the markup a memory is | [`authoring.md`](/reference/guide/authoring.md) | | [Code mode](/reference/guide/code-mode/): many hops in one call | [`code-mode.md`](/reference/guide/code-mode.md) | | [Error codes](/reference/error-codes/): the values you branch on | [`error-codes.md`](/reference/error-codes.md) | | [Response types](/reference/response-types/): the `type` discriminators | [`response-types.md`](/reference/response-types.md) | | [MCP tools](/reference/mcp-tools/): the tools and their arguments | [`mcp-tools.md`](/reference/mcp-tools.md) | | [Commands](/reference/): every command, one page each | [`reference.md`](/reference.md) | ## 7. The machine surfaces [Section titled “7. The machine surfaces”](#7-the-machine-surfaces) Any page is available as Markdown: append `.md` to its path. `/reference/mcp-tools/` is served at `/reference/mcp-tools.md` as `text/markdown`. This holds for every page on the site, including the generated ones, and each page links its own twin from `` with `rel="alternate" type="text/markdown"`. The whole site comes three ways. [`llms.txt`](/llms.txt) is the index, and it lists this page first. [`llms-full.txt`](/llms-full.txt) is every page in one file. [`llms-small.txt`](/llms-small.txt) is the same corpus with non-essential content removed, for a tighter context. **For an agent.** Fetch `llms-small.txt` before `llms-full.txt`. When you already know which page you want, fetch that page’s `.md` twin instead of either bundle: it is a fraction of the tokens and the same text.
# API
> The generated reference for the workspace packages, one directory per package and one page per module, built from the TSDoc in the source on every site build.
Every page below this one is generated from the TSDoc on a package’s exported surface; this index is the tier’s one authored page. The generated pages are not committed: `starlight-typedoc` writes them into `api//` on every `astro check` and `astro build`, those directories are gitignored, and so a page always describes the source at the commit the site was built from. When a signature on a page and a signature in the source disagree, the source is newer and the site is behind. ## What is covered [Section titled “What is covered”](#what-is-covered) | Package | Pages | | -------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `@memhtml/contracts` | [Overview](/api/contracts/) and one page per import path: [edges](/api/contracts/edges/), [errors](/api/contracts/errors/), [paths](/api/contracts/paths/), [slug](/api/contracts/slug/), [types](/api/contracts/types/). | | `@memhtml/traces` | [One page](/api/traces/), because the package publishes a single import path. | | `@memhtml/eval` | [One page](/api/eval/): the corpus generator and the discrimination gate, one import path. | | `@memhtml/domain` | [One page](/api/domain/): the pure arithmetic of retention, decay, fusion, diversification, merge guards and graph scores, one import path. | | `@memhtml/llm` | [One page](/api/llm/): the Bedrock and proxy model clients, embeddings, the model table and the wire helpers, one import path. | Each package’s overview page is its `README.md` from the repository, with the module list appended by the generator. The remaining workspace packages are added one per maintenance run, smallest exported surface first, once their generated pages pass the same gates as the authored ones. ## How to read a page [Section titled “How to read a page”](#how-to-read-a-page) Every declaration is rendered as a fenced code block in the shape TypeScript would print it, followed by the doc comment from the source. Parameters are listed one per line rather than in a table, so an object-literal type keeps its braces inside a code span. “Defined in” lines are omitted on purpose: the module a symbol belongs to is the page it sits on, and the [Reference](/reference/contracts/) tier names each symbol’s file. The generator excludes external symbols, so a type that comes from `effect` is named but not expanded. Follow the import path shown at the top of the page to read it in its own package. ## Adding a package [Section titled “Adding a package”](#adding-a-package) Each package is its own `starlightTypeDoc` plugin instance in `apps/docs/astro.config.ts`, writing to its own directory under `api/`. The plugin refuses two instances whose output directories nest or overlap, which is why the tier is one directory per package rather than one flat directory. The comment block above the first instance records every option and the reason it is set.
# @memhtml/contracts
`@memhtml/contracts`: schemas, enums, errors, and path algebra. Zero I/O. This is the innermost workspace package: it imports only `effect`, and every other package imports it. It holds the words the rest of the system agrees on. The closed vocabularies say what a memory can be. The path algebra says where one lives in the tree. The edge model says how one memory points at another. The slug rules say how a title becomes a filename. The tagged errors say how an operation can fail. ## Modules [Section titled “Modules”](#modules) Each module is its own import path, and the package root re-exports all five. The pages beside this one are generated from the TSDoc in the source, one page per module. | Import path | What it holds | | --------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `@memhtml/contracts/edges` | The edge vocabulary and the `Edge` schema. Four edge classes that never mix, so a person or task edge stays out of PageRank, MMR, and the retention bridge count. | | `@memhtml/contracts/errors` | The tagged errors every operation can fail with, each carrying only what a caller needs to recover and nothing that leaks corpus content. | | `@memhtml/contracts/paths` | Path algebra. Every function is pure and total, and a path is always the repo-root-relative git-tree form: no leading slash, forward slashes only. | | `@memhtml/contracts/slug` | Slugs and filenames: how a title becomes `[a-z0-9-]`, how an episodic entry carries its date, and how a collision gets a suffix. | | `@memhtml/contracts/types` | The closed vocabularies (memory type, status, PARA bucket, task status), the `type:name` entity reference, and the scalar schemas for importance and confidence. | ## Where else this is documented [Section titled “Where else this is documented”](#where-else-this-is-documented) The Reference tier’s [contracts page](https://memhtml.github.io/reference/contracts/) lists every exported symbol with its doc comment, derived from the same source at build time. The [closed vocabularies page](https://memhtml.github.io/reference/vocabulary/) lists each vocabulary’s values. ## Modules [Section titled “Modules”](#modules-1) * [edges](/api/contracts/edges/) * [errors](/api/contracts/errors/) * [paths](/api/contracts/paths/) * [slug](/api/contracts/slug/) * [types](/api/contracts/types/)
# edges
## Type Aliases [Section titled “Type Aliases”](#type-aliases) ### Edge [Section titled “Edge”](#edge)
```ts
1
type Edge = typeof Edge.Type;
```
One edge. `derived` separates a sleep-mined suspicion from an authored assertion: the retention `contested_status` signal counts only `derived: false` contradictions, so an uncorroborated machine guess can never evict a memory. `strength` is unitless in `[0, 1]`; an authored edge is 1.0 and a mined one carries its cosine. `srcPath`/`dstPath` are repo-root-relative with no leading slash. *** ### EdgeClass [Section titled “EdgeClass”](#edgeclass)
```ts
1
type EdgeClass = typeof EdgeClass.Type;
```
*** ### EdgeProvenance [Section titled “EdgeProvenance”](#edgeprovenance)
```ts
1
type EdgeProvenance = typeof EdgeProvenance.Type;
```
*** ### EdgeRel [Section titled “EdgeRel”](#edgerel)
```ts
1
type EdgeRel = typeof EdgeRel.Type;
```
*** ### MemoryRel [Section titled “MemoryRel”](#memoryrel)
```ts
1
type MemoryRel = typeof MemoryRel.Type;
```
*** ### PersonRel [Section titled “PersonRel”](#personrel)
```ts
1
type PersonRel = typeof PersonRel.Type;
```
*** ### ProvenanceRel [Section titled “ProvenanceRel”](#provenancerel)
```ts
1
type ProvenanceRel = typeof ProvenanceRel.Type;
```
*** ### TaskRel [Section titled “TaskRel”](#taskrel)
```ts
1
type TaskRel = typeof TaskRel.Type;
```
## Variables [Section titled “Variables”](#variables) ### ALL\_RELS [Section titled “ALL\_RELS”](#all_rels)
```ts
1
const ALL_RELS: readonly ["supersedes", "contradicts", "caused_by", "leads_to", "part_of", "relates_to", "example_of", "supports", "laterally_related", "about_person", "authored_by", "from_session", "blocks", "subtask_of"];
```
Every rel across all four classes. The `edges.rel` column’s full vocabulary. *** ### Edge [Section titled “Edge”](#edge-1)
```ts
1
const Edge: Struct<{
2
derived: Boolean;
3
dstPath: String;
4
edgeClass: Literals;
5
provenance: Literals;
6
rel: Literals;
7
srcPath: String;
8
strength: Number;
9
}>;
```
One edge. `derived` separates a sleep-mined suspicion from an authored assertion: the retention `contested_status` signal counts only `derived: false` contradictions, so an uncorroborated machine guess can never evict a memory. `strength` is unitless in `[0, 1]`; an authored edge is 1.0 and a mined one carries its cosine. `srcPath`/`dstPath` are repo-root-relative with no leading slash. *** ### EDGE\_CLASSES [Section titled “EDGE\_CLASSES”](#edge_classes)
```ts
1
const EDGE_CLASSES: readonly ["memory", "person", "provenance", "task"];
```
The four non-mixing edge classes. The class is what keeps a person or task edge out of PageRank, MMR, and the retention bridge count. Every memory-graph query filters `edge_class = 'memory'`, and the SQL CHECK constraint refuses a rel that belongs to another class. *** ### EDGE\_PROVENANCES [Section titled “EDGE\_PROVENANCES”](#edge_provenances)
```ts
1
const EDGE_PROVENANCES: readonly ["authored", "sleep", "import"];
```
Where an edge came from. `derived` edges are only ever `sleep`-provenanced. *** ### EdgeClass [Section titled “EdgeClass”](#edgeclass-1)
```ts
1
const EdgeClass: Literals;
```
*** ### EdgeProvenance [Section titled “EdgeProvenance”](#edgeprovenance-1)
```ts
1
const EdgeProvenance: Literals;
```
*** ### EdgeRel [Section titled “EdgeRel”](#edgerel-1)
```ts
1
const EdgeRel: Literals;
```
*** ### MEMORY\_RELS [Section titled “MEMORY\_RELS”](#memory_rels)
```ts
1
const MEMORY_RELS: readonly ["supersedes", "contradicts", "caused_by", "leads_to", "part_of", "relates_to", "example_of", "supports", "laterally_related"];
```
The nine memory rels. `supersedes` and `contradicts` are penalty-bearing: they gate the retention `contested_status` signal, so sleep promotes a corroborated one into both files rather than leaving it in the rebuildable index. *** ### MemoryRel [Section titled “MemoryRel”](#memoryrel-1)
```ts
1
const MemoryRel: Literals;
```
*** ### PERSON\_RELS [Section titled “PERSON\_RELS”](#person_rels)
```ts
1
const PERSON_RELS: readonly ["about_person", "authored_by"];
```
The two person rels, pointing at `resources/people/*`. *** ### PersonRel [Section titled “PersonRel”](#personrel-1)
```ts
1
const PersonRel: Literals;
```
*** ### PROVENANCE\_RELS [Section titled “PROVENANCE\_RELS”](#provenance_rels)
```ts
1
const PROVENANCE_RELS: readonly ["from_session"];
```
The one provenance rel, linking a memory to the session that produced it. *** ### ProvenanceRel [Section titled “ProvenanceRel”](#provenancerel-1)
```ts
1
const ProvenanceRel: Literals;
```
*** ### REL\_TOKEN\_PREFIX [Section titled “REL\_TOKEN\_PREFIX”](#rel_token_prefix)
```ts
1
const REL_TOKEN_PREFIX: "memhtml-" = "memhtml-";
```
A `` token, which is the rel prefixed for the HTML plane. `rel` tokens cannot hold a colon, so the prefix is hyphenated and the rel’s own underscores become hyphens: `laterally_related` ⇒ `memhtml-laterally-related`. *** ### TASK\_RELS [Section titled “TASK\_RELS”](#task_rels)
```ts
1
const TASK_RELS: readonly ["blocks", "subtask_of"];
```
The two task rels, both between two `task` files. Their own class for the same reason the person rels have one: task topology is working state, and a `blocks` edge entering PageRank would let an agent’s to-do list reweight the retention of its knowledge. `@memhtml/store`’s `linkMemories` refuses a task rel unless BOTH endpoints are tasks, and the `edges` CHECK refuses the rel under any other class. *** ### TaskRel [Section titled “TaskRel”](#taskrel-1)
```ts
1
const TaskRel: Literals;
```
## Functions [Section titled “Functions”](#functions) ### isEdgeRel() [Section titled “isEdgeRel()”](#isedgerel)
```ts
1
function isEdgeRel(rel): rel is "supersedes" | "contradicts" | "caused_by" | "leads_to" | "part_of" | "relates_to" | "example_of" | "supports" | "laterally_related" | "about_person" | "authored_by" | "from_session" | "blocks" | "subtask_of";
```
True when `rel` is in the closed vocabulary. Narrows an untrusted string. #### Parameters [Section titled “Parameters”](#parameters) ##### rel [Section titled “rel”](#rel) `string` #### Returns [Section titled “Returns”](#returns) rel is “supersedes” | “contradicts” | “caused\_by” | “leads\_to” | “part\_of” | “relates\_to” | “example\_of” | “supports” | “laterally\_related” | “about\_person” | “authored\_by” | “from\_session” | “blocks” | “subtask\_of” *** ### isWellFormedEdge() [Section titled “isWellFormedEdge()”](#iswellformededge)
```ts
1
function isWellFormedEdge(edge): boolean;
```
True when an edge’s declared class matches its rel and it is not a self-loop, the two conditions the `edges` table’s CHECK constraints enforce, stated once here so a caller can refuse a bad edge before the driver does. #### Parameters [Section titled “Parameters”](#parameters-1) ##### edge [Section titled “edge”](#edge-2) ###### derived [Section titled “derived”](#derived) `boolean` = `Schema.Boolean` ###### dstPath [Section titled “dstPath”](#dstpath) `string` = `Schema.String` ###### edgeClass [Section titled “edgeClass”](#edgeclass-2) `"memory"` | `"person"` | `"provenance"` | `"task"` = `EdgeClass` ###### provenance [Section titled “provenance”](#provenance) `"authored"` | `"sleep"` | `"import"` = `EdgeProvenance` ###### rel [Section titled “rel”](#rel-1) \| `"supersedes"` | `"contradicts"` | `"caused_by"` | `"leads_to"` | `"part_of"` | `"relates_to"` | `"example_of"` | `"supports"` | `"laterally_related"` | `"about_person"` | `"authored_by"` | `"from_session"` | `"blocks"` | `"subtask_of"` = `EdgeRel` ###### srcPath [Section titled “srcPath”](#srcpath) `string` = `Schema.String` ###### strength [Section titled “strength”](#strength) `number` = `...` #### Returns [Section titled “Returns”](#returns-1) `boolean` *** ### relClassFor() [Section titled “relClassFor()”](#relclassfor)
```ts
1
function relClassFor(rel): "memory" | "person" | "provenance" | "task";
```
The class a rel belongs to. Total over [ALL\_RELS](/api/contracts/edges/#all_rels) and injective per class: a rel name appears in exactly one class, which is what lets the class be derived rather than carried alongside the rel and risk disagreeing with it. #### Parameters [Section titled “Parameters”](#parameters-2) ##### rel [Section titled “rel”](#rel-2) \| `"supersedes"` | `"contradicts"` | `"caused_by"` | `"leads_to"` | `"part_of"` | `"relates_to"` | `"example_of"` | `"supports"` | `"laterally_related"` | `"about_person"` | `"authored_by"` | `"from_session"` | `"blocks"` | `"subtask_of"` #### Returns [Section titled “Returns”](#returns-2) `"memory"` | `"person"` | `"provenance"` | `"task"` *** ### relForToken() [Section titled “relForToken()”](#relfortoken)
```ts
1
function relForToken(token):
2
| "supersedes"
3
| "contradicts"
4
| "caused_by"
5
| "leads_to"
6
| "part_of"
7
| "relates_to"
8
| "example_of"
9
| "supports"
10
| "laterally_related"
11
| "about_person"
12
| "authored_by"
13
| "from_session"
14
| "blocks"
15
| "subtask_of"
16
| undefined;
```
The rel behind a `` token, or `undefined` when the token is outside the closed vocabulary. Inverse of [relTokenFor](/api/contracts/edges/#reltokenfor) on its image. #### Parameters [Section titled “Parameters”](#parameters-3) ##### token [Section titled “token”](#token) `string` #### Returns [Section titled “Returns”](#returns-3) \| `"supersedes"` | `"contradicts"` | `"caused_by"` | `"leads_to"` | `"part_of"` | `"relates_to"` | `"example_of"` | `"supports"` | `"laterally_related"` | `"about_person"` | `"authored_by"` | `"from_session"` | `"blocks"` | `"subtask_of"` | `undefined` *** ### relsForClass() [Section titled “relsForClass()”](#relsforclass)
```ts
1
function relsForClass(edgeClass): readonly (
2
| "supersedes"
3
| "contradicts"
4
| "caused_by"
5
| "leads_to"
6
| "part_of"
7
| "relates_to"
8
| "example_of"
9
| "supports"
10
| "laterally_related"
11
| "about_person"
12
| "authored_by"
13
| "from_session"
14
| "blocks"
15
| "subtask_of")[];
```
The rels of one class. The inverse of [relClassFor](/api/contracts/edges/#relclassfor), as a set. #### Parameters [Section titled “Parameters”](#parameters-4) ##### edgeClass [Section titled “edgeClass”](#edgeclass-3) `"memory"` | `"person"` | `"provenance"` | `"task"` #### Returns [Section titled “Returns”](#returns-4) readonly ( | `"supersedes"` | `"contradicts"` | `"caused_by"` | `"leads_to"` | `"part_of"` | `"relates_to"` | `"example_of"` | `"supports"` | `"laterally_related"` | `"about_person"` | `"authored_by"` | `"from_session"` | `"blocks"` | `"subtask_of"`)\[] *** ### relTokenFor() [Section titled “relTokenFor()”](#reltokenfor)
```ts
1
function relTokenFor(rel): string;
```
The HTML `` token for a rel. #### Parameters [Section titled “Parameters”](#parameters-5) ##### rel [Section titled “rel”](#rel-3) \| `"supersedes"` | `"contradicts"` | `"caused_by"` | `"leads_to"` | `"part_of"` | `"relates_to"` | `"example_of"` | `"supports"` | `"laterally_related"` | `"about_person"` | `"authored_by"` | `"from_session"` | `"blocks"` | `"subtask_of"` #### Returns [Section titled “Returns”](#returns-5) `string`
# errors
## Classes [Section titled “Classes”](#classes) ### DirtyTree [Section titled “DirtyTree”](#dirtytree) An operation that requires a clean tree found uncommitted changes. #### Extends [Section titled “Extends”](#extends) * `object` & `YieldableError`<`this`> #### Constructors [Section titled “Constructors”](#constructors) ##### Constructor [Section titled “Constructor”](#constructor)
```ts
1
new DirtyTree(...args): DirtyTree;
```
###### Parameters [Section titled “Parameters”](#parameters) ###### args [Section titled “args”](#args) …\[`object`, `MakeOptions`] ###### Returns [Section titled “Returns”](#returns) [`DirtyTree`](/api/contracts/errors/#dirtytree) ###### Inherited from [Section titled “Inherited from”](#inherited-from)
```ts
1
Schema.TaggedError()("DirtyTree", {
2
paths: Schema.Array(Schema.String)
3
}).constructor
```
#### Properties [Section titled “Properties”](#properties) ##### paths [Section titled “paths”](#paths)
```ts
1
readonly paths: readonly string[];
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-1)
```ts
1
Schema.TaggedError()("DirtyTree", {
2
paths: Schema.Array(Schema.String)
3
}).paths
```
*** ### DuplicateContent [Section titled “DuplicateContent”](#duplicatecontent) The content hash already belongs to an active file. `existingPath` is what the caller wanted to create, so a deduped write is answerable without a second query. #### Extends [Section titled “Extends”](#extends-1) * `object` & `YieldableError`<`this`> #### Constructors [Section titled “Constructors”](#constructors-1) ##### Constructor [Section titled “Constructor”](#constructor-1)
```ts
1
new DuplicateContent(...args): DuplicateContent;
```
###### Parameters [Section titled “Parameters”](#parameters-1) ###### args [Section titled “args”](#args-1) …\[`object`, `MakeOptions`] ###### Returns [Section titled “Returns”](#returns-1) [`DuplicateContent`](/api/contracts/errors/#duplicatecontent) ###### Inherited from [Section titled “Inherited from”](#inherited-from-2)
```ts
1
Schema.TaggedError()("DuplicateContent", {
2
contentHash: Schema.String,
3
existingPath: Schema.String
4
}).constructor
```
#### Properties [Section titled “Properties”](#properties-1) ##### contentHash [Section titled “contentHash”](#contenthash)
```ts
1
readonly contentHash: string = Schema.String;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-3)
```ts
1
Schema.TaggedError()("DuplicateContent", {
2
contentHash: Schema.String,
3
existingPath: Schema.String
4
}).contentHash
```
##### existingPath [Section titled “existingPath”](#existingpath)
```ts
1
readonly existingPath: string = Schema.String;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-4)
```ts
1
Schema.TaggedError()("DuplicateContent", {
2
contentHash: Schema.String,
3
existingPath: Schema.String
4
}).existingPath
```
*** ### InvalidMemory [Section titled “InvalidMemory”](#invalidmemory) A memory that violates the file format or the type/placement vocabulary. #### Extends [Section titled “Extends”](#extends-2) * `object` & `YieldableError`<`this`> #### Constructors [Section titled “Constructors”](#constructors-2) ##### Constructor [Section titled “Constructor”](#constructor-2)
```ts
1
new InvalidMemory(...args): InvalidMemory;
```
###### Parameters [Section titled “Parameters”](#parameters-2) ###### args [Section titled “args”](#args-2) …\[`object`, `MakeOptions`] ###### Returns [Section titled “Returns”](#returns-2) [`InvalidMemory`](/api/contracts/errors/#invalidmemory) ###### Inherited from [Section titled “Inherited from”](#inherited-from-5)
```ts
1
Schema.TaggedError()("InvalidMemory", {
2
reason: Schema.String
3
}).constructor
```
#### Properties [Section titled “Properties”](#properties-2) ##### reason [Section titled “reason”](#reason)
```ts
1
readonly reason: string = Schema.String;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-6)
```ts
1
Schema.TaggedError()("InvalidMemory", {
2
reason: Schema.String
3
}).reason
```
*** ### LlmContractViolation [Section titled “LlmContractViolation”](#llmcontractviolation) The model broke its structured-output contract: an undecodable tool payload, a `max_tokens` stop, or a refusal. The item is reported with no result, and a violation does not become a value. #### Extends [Section titled “Extends”](#extends-3) * `object` & `YieldableError`<`this`> #### Constructors [Section titled “Constructors”](#constructors-3) ##### Constructor [Section titled “Constructor”](#constructor-3)
```ts
1
new LlmContractViolation(...args): LlmContractViolation;
```
###### Parameters [Section titled “Parameters”](#parameters-3) ###### args [Section titled “args”](#args-3) …\[`object`, `MakeOptions`] ###### Returns [Section titled “Returns”](#returns-3) [`LlmContractViolation`](/api/contracts/errors/#llmcontractviolation) ###### Inherited from [Section titled “Inherited from”](#inherited-from-7)
```ts
1
Schema.TaggedError()(
2
"LlmContractViolation",
3
{
4
reason: Schema.String
5
}
6
).constructor
```
#### Properties [Section titled “Properties”](#properties-3) ##### reason [Section titled “reason”](#reason-1)
```ts
1
readonly reason: string = Schema.String;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-8)
```ts
1
Schema.TaggedError()(
2
"LlmContractViolation",
3
{
4
reason: Schema.String
5
}
6
).reason
```
*** ### ModelUnavailable [Section titled “ModelUnavailable”](#modelunavailable) Bedrock refused the call: throttling, an unavailable model, or a denied region. #### Extends [Section titled “Extends”](#extends-4) * `object` & `YieldableError`<`this`> #### Constructors [Section titled “Constructors”](#constructors-4) ##### Constructor [Section titled “Constructor”](#constructor-4)
```ts
1
new ModelUnavailable(...args): ModelUnavailable;
```
###### Parameters [Section titled “Parameters”](#parameters-4) ###### args [Section titled “args”](#args-4) …\[`object`, `MakeOptions`] ###### Returns [Section titled “Returns”](#returns-4) [`ModelUnavailable`](/api/contracts/errors/#modelunavailable) ###### Inherited from [Section titled “Inherited from”](#inherited-from-9)
```ts
1
Schema.TaggedError()("ModelUnavailable", {
2
modelId: Schema.String,
3
reason: Schema.String
4
}).constructor
```
#### Properties [Section titled “Properties”](#properties-4) ##### modelId [Section titled “modelId”](#modelid)
```ts
1
readonly modelId: string = Schema.String;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-10)
```ts
1
Schema.TaggedError()("ModelUnavailable", {
2
modelId: Schema.String,
3
reason: Schema.String
4
}).modelId
```
##### reason [Section titled “reason”](#reason-2)
```ts
1
readonly reason: string = Schema.String;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-11)
```ts
1
Schema.TaggedError()("ModelUnavailable", {
2
modelId: Schema.String,
3
reason: Schema.String
4
}).reason
```
*** ### PathNotFound [Section titled “PathNotFound”](#pathnotfound) A repo-root-relative path with no file behind it. #### Extends [Section titled “Extends”](#extends-5) * `object` & `YieldableError`<`this`> #### Constructors [Section titled “Constructors”](#constructors-5) ##### Constructor [Section titled “Constructor”](#constructor-5)
```ts
1
new PathNotFound(...args): PathNotFound;
```
###### Parameters [Section titled “Parameters”](#parameters-5) ###### args [Section titled “args”](#args-5) …\[`object`, `MakeOptions`] ###### Returns [Section titled “Returns”](#returns-5) [`PathNotFound`](/api/contracts/errors/#pathnotfound) ###### Inherited from [Section titled “Inherited from”](#inherited-from-12)
```ts
1
Schema.TaggedError()("PathNotFound", {
2
path: Schema.String
3
}).constructor
```
#### Properties [Section titled “Properties”](#properties-5) ##### path [Section titled “path”](#path)
```ts
1
readonly path: string = Schema.String;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-13)
```ts
1
Schema.TaggedError()("PathNotFound", {
2
path: Schema.String
3
}).path
```
*** ### StorageFailure [Section titled “StorageFailure”](#storagefailure) A driver or filesystem rejection, reduced to the operation that failed. The payload deliberately excludes SQL text, parameters, and row contents so a storage error can be returned to an agent without leaking corpus content; the driver’s own message goes to `Effect.logError` at the adapter edge instead. #### Extends [Section titled “Extends”](#extends-6) * `object` & `YieldableError`<`this`> #### Constructors [Section titled “Constructors”](#constructors-6) ##### Constructor [Section titled “Constructor”](#constructor-6)
```ts
1
new StorageFailure(...args): StorageFailure;
```
###### Parameters [Section titled “Parameters”](#parameters-6) ###### args [Section titled “args”](#args-6) …\[`object`, `MakeOptions`] ###### Returns [Section titled “Returns”](#returns-6) [`StorageFailure`](/api/contracts/errors/#storagefailure) ###### Inherited from [Section titled “Inherited from”](#inherited-from-14)
```ts
1
Schema.TaggedError()("StorageFailure", {
2
operation: Schema.String
3
}).constructor
```
#### Properties [Section titled “Properties”](#properties-6) ##### operation [Section titled “operation”](#operation)
```ts
1
readonly operation: string = Schema.String;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-15)
```ts
1
Schema.TaggedError()("StorageFailure", {
2
operation: Schema.String
3
}).operation
```
*** ### WriteConflict [Section titled “WriteConflict”](#writeconflict) Two writers touched the same file. `ourSha` is the blob sha this process wrote from, `theirSha` the blob sha now in the tree. Recovery belongs to the caller: re-read the current content and reapply. #### Extends [Section titled “Extends”](#extends-7) * `object` & `YieldableError`<`this`> #### Constructors [Section titled “Constructors”](#constructors-7) ##### Constructor [Section titled “Constructor”](#constructor-7)
```ts
1
new WriteConflict(...args): WriteConflict;
```
###### Parameters [Section titled “Parameters”](#parameters-7) ###### args [Section titled “args”](#args-7) …\[`object`, `MakeOptions`] ###### Returns [Section titled “Returns”](#returns-7) [`WriteConflict`](/api/contracts/errors/#writeconflict) ###### Inherited from [Section titled “Inherited from”](#inherited-from-16)
```ts
1
Schema.TaggedError()("WriteConflict", {
2
path: Schema.String,
3
ourSha: Schema.String,
4
theirSha: Schema.String
5
}).constructor
```
#### Properties [Section titled “Properties”](#properties-7) ##### ourSha [Section titled “ourSha”](#oursha)
```ts
1
readonly ourSha: string = Schema.String;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-17)
```ts
1
Schema.TaggedError()("WriteConflict", {
2
path: Schema.String,
3
ourSha: Schema.String,
4
theirSha: Schema.String
5
}).ourSha
```
##### path [Section titled “path”](#path-1)
```ts
1
readonly path: string = Schema.String;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-18)
```ts
1
Schema.TaggedError()("WriteConflict", {
2
path: Schema.String,
3
ourSha: Schema.String,
4
theirSha: Schema.String
5
}).path
```
##### theirSha [Section titled “theirSha”](#theirsha)
```ts
1
readonly theirSha: string = Schema.String;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-19)
```ts
1
Schema.TaggedError()("WriteConflict", {
2
path: Schema.String,
3
ourSha: Schema.String,
4
theirSha: Schema.String
5
}).theirSha
```
# paths
## Interfaces [Section titled “Interfaces”](#interfaces) ### PlacementInput [Section titled “PlacementInput”](#placementinput) What [placementFor](/api/contracts/paths/#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”](#properties) ##### entities? [Section titled “entities?”](#entities)
```ts
1
readonly optional entities?: readonly string[];
```
##### memoryType [Section titled “memoryType”](#memorytype)
```ts
1
readonly memoryType: string;
```
##### path? [Section titled “path?”](#path)
```ts
1
readonly optional path?: string;
```
##### tags? [Section titled “tags?”](#tags)
```ts
1
readonly optional tags?: readonly string[];
```
##### workspace? [Section titled “workspace?”](#workspace)
```ts
1
readonly optional workspace?: string;
```
## Variables [Section titled “Variables”](#variables) ### ARCHIVE\_BUCKET [Section titled “ARCHIVE\_BUCKET”](#archive_bucket)
```ts
1
const ARCHIVE_BUCKET: "archive" = "archive";
```
The bucket eviction moves into, partitioned by year. *** ### ARCS\_DIR [Section titled “ARCS\_DIR”](#arcs_dir)
```ts
1
const ARCS_DIR: "areas/arcs" = "areas/arcs";
```
Behavioral arcs. Under `areas/` because PARA is fixed at four buckets. *** ### INBOX\_DIR [Section titled “INBOX\_DIR”](#inbox_dir)
```ts
1
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”](#memory_extension)
```ts
1
const MEMORY_EXTENSION: ".html" = ".html";
```
The file extension every memory carries. *** ### PEOPLE\_DIR [Section titled “PEOPLE\_DIR”](#people_dir)
```ts
1
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”](#tasks_subdir)
```ts
1
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//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”](#functions) ### archivePathFor() [Section titled “archivePathFor()”](#archivepathfor)
```ts
1
function archivePathFor(path, year): string;
```
The archive path a memory moves to on eviction: `archive//`, 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”](#parameters) ##### path [Section titled “path”](#path-1) `string` ##### year [Section titled “year”](#year) `number` #### Returns [Section titled “Returns”](#returns) `string` *** ### archiveYearOf() [Section titled “archiveYearOf()”](#archiveyearof)
```ts
1
function archiveYearOf(path): number | undefined;
```
The archive year of an archive path, or `undefined` when the path is not archived. #### Parameters [Section titled “Parameters”](#parameters-1) ##### path [Section titled “path”](#path-2) `string` #### Returns [Section titled “Returns”](#returns-1) `number` | `undefined` *** ### isArchivePath() [Section titled “isArchivePath()”](#isarchivepath)
```ts
1
function isArchivePath(path): boolean;
```
True when a path sits under the archive bucket with a year partition. #### Parameters [Section titled “Parameters”](#parameters-2) ##### path [Section titled “path”](#path-3) `string` #### Returns [Section titled “Returns”](#returns-2) `boolean` *** ### isValidMemoryPath() [Section titled “isValidMemoryPath()”](#isvalidmemorypath)
```ts
1
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](/api/contracts/paths/#memorypathviolation) asked as a boolean, so the predicate and the refusal’s reason cannot disagree about which paths are usable. #### Parameters [Section titled “Parameters”](#parameters-3) ##### path [Section titled “path”](#path-4) `string` #### Returns [Section titled “Returns”](#returns-3) `boolean` *** ### memoryPathFor() [Section titled “memoryPathFor()”](#memorypathfor)
```ts
1
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](/api/contracts/paths/#placementfor) and the filename from [filenameFor](/api/contracts/slug/#filenamefor), which date-prefixes an episodic entry. #### Parameters [Section titled “Parameters”](#parameters-4) ##### input [Section titled “input”](#input) [`PlacementInput`](/api/contracts/paths/#placementinput) & `object` #### Returns [Section titled “Returns”](#returns-4) `string` *** ### memoryPathViolation() [Section titled “memoryPathViolation()”](#memorypathviolation)
```ts
1
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](/api/contracts/paths/#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”](#parameters-5) ##### path [Section titled “path”](#path-5) `string` #### Returns [Section titled “Returns”](#returns-5) `string` | `undefined` *** ### normalizePath() [Section titled “normalizePath()”](#normalizepath)
```ts
1
function normalizePath(path): string;
```
Reduce a caller-supplied path to the canonical git-tree form: leading slashes dropped (callers may pass the `` 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”](#parameters-6) ##### path [Section titled “path”](#path-6) `string` #### Returns [Section titled “Returns”](#returns-6) `string` *** ### originalPathFor() [Section titled “originalPathFor()”](#originalpathfor)
```ts
1
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//` prefix, so it is the left inverse of [archivePathFor](/api/contracts/paths/#archivepathfor) even for a memory archived twice. #### Parameters [Section titled “Parameters”](#parameters-7) ##### archivePath [Section titled “archivePath”](#archivepath) `string` #### Returns [Section titled “Returns”](#returns-7) `string` | `undefined` *** ### paraBucketOf() [Section titled “paraBucketOf()”](#parabucketof)
```ts
1
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”](#parameters-8) ##### path [Section titled “path”](#path-7) `string` #### Returns [Section titled “Returns”](#returns-8) `"projects"` | `"areas"` | `"resources"` | `"archive"` | `undefined` *** ### placementFor() [Section titled “placementFor()”](#placementfor)
```ts
1
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](/api/contracts/paths/#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](/api/contracts/paths/#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”](#parameters-9) ##### input [Section titled “input”](#input-1) [`PlacementInput`](/api/contracts/paths/#placementinput) #### Returns [Section titled “Returns”](#returns-9) `string`
# slug
## Variables [Section titled “Variables”](#variables) ### EPISODIC\_PREFIX\_LENGTH [Section titled “EPISODIC\_PREFIX\_LENGTH”](#episodic_prefix_length)
```ts
1
const EPISODIC_PREFIX_LENGTH: 9 = 9;
```
The `YYYYMMDD-` prefix an episodic filename carries. Time is part of an episodic entry’s identity and it never receives a correction in place, so the date belongs in the name; every other type is timeless and correctable, so it gets a bare slug. *** ### SLUG\_FALLBACK [Section titled “SLUG\_FALLBACK”](#slug_fallback)
```ts
1
const SLUG_FALLBACK: "untitled" = "untitled";
```
The stem used when a title reduces to nothing sluggable, such as an all-punctuation or non-Latin title. A placeholder beats an empty filename, and `memhtml doctor` can find these by name. *** ### SLUG\_MAX\_LENGTH [Section titled “SLUG\_MAX\_LENGTH”](#slug_max_length)
```ts
1
const SLUG_MAX_LENGTH: 80 = 80;
```
Maximum slug length in characters, before any collision suffix. ## Functions [Section titled “Functions”](#functions) ### datePrefix() [Section titled “datePrefix()”](#dateprefix)
```ts
1
function datePrefix(at): string;
```
Format an instant as the `YYYYMMDD` stamp of an episodic filename, in UTC. #### Parameters [Section titled “Parameters”](#parameters) ##### at [Section titled “at”](#at) `Date` #### Returns [Section titled “Returns”](#returns) `string` *** ### filenameFor() [Section titled “filenameFor()”](#filenamefor)
```ts
1
function filenameFor(input): string;
```
The filename for a memory: `20260802-slug.html` for episodic, `slug.html` otherwise. The date prefix sits outside the slug’s length budget, because it is identity, not title. #### Parameters [Section titled “Parameters”](#parameters-1) ##### input [Section titled “input”](#input) ###### at [Section titled “at”](#at-1) `Date` ###### episodic [Section titled “episodic”](#episodic) `boolean` ###### slug [Section titled “slug”](#slug) `string` #### Returns [Section titled “Returns”](#returns-1) `string` *** ### hasDatePrefix() [Section titled “hasDatePrefix()”](#hasdateprefix)
```ts
1
function hasDatePrefix(filename): boolean;
```
True when a filename carries the `YYYYMMDD-` episodic prefix. #### Parameters [Section titled “Parameters”](#parameters-2) ##### filename [Section titled “filename”](#filename) `string` #### Returns [Section titled “Returns”](#returns-2) `boolean` *** ### isSlug() [Section titled “isSlug()”](#isslug)
```ts
1
function isSlug(value): boolean;
```
True when a string is already a valid slug, the fixed point of [slugify](/api/contracts/slug/#slugify). #### Parameters [Section titled “Parameters”](#parameters-3) ##### value [Section titled “value”](#value) `string` #### Returns [Section titled “Returns”](#returns-3) `boolean` *** ### slugify() [Section titled “slugify()”](#slugify)
```ts
1
function slugify(title): string;
```
Kebab-case a title into `[a-z0-9-]`, at most [SLUG\_MAX\_LENGTH](/api/contracts/slug/#slug_max_length) characters. Diacritics are folded to their base letters (`déployé` ⇒ `deploye`) rather than dropped, so a title stays recognizable. Runs of separators collapse to one hyphen and the result carries no leading or trailing hyphen, which makes the function idempotent: a slug fed back in comes out unchanged. Truncation cuts at [SLUG\_MAX\_LENGTH](/api/contracts/slug/#slug_max_length) and then trims any hyphen the cut exposed, so a truncated slug is still a valid slug rather than one ending mid-separator. #### Parameters [Section titled “Parameters”](#parameters-4) ##### title [Section titled “title”](#title) `string` #### Returns [Section titled “Returns”](#returns-4) `string` *** ### withCollisionOrdinal() [Section titled “withCollisionOrdinal()”](#withcollisionordinal)
```ts
1
function withCollisionOrdinal(slug, ordinal): string;
```
Append a collision suffix. `ordinal` is a 1-based ordinal for display in the filename; ordinal 1 is the unsuffixed slug, 2 becomes `-2`, and so on, matching the `-2`/`-3` convention. The suffix is added inside the length budget, so a maximum-length slug is shortened rather than overflowed. **The result never equals the input, at any slug length.** That is what makes the store’s collision loop (`packages/store/src/store.ts`, `pathFor`) terminate rather than re-propose the name that collided. It is not free near the length cap: for a slug whose own tail IS the suffix, cutting to make room and appending the suffix can rebuild the slug — and because the cut also trims any hyphen it exposes, the rebuild can recur at MORE than one cut width. The stem is therefore re-cut from its own post-trim length until appending the suffix no longer reproduces the input; each re-cut strictly shortens the stem, so the loop terminates and the result stays inside the budget. #### Parameters [Section titled “Parameters”](#parameters-5) ##### slug [Section titled “slug”](#slug-1) `string` ##### ordinal [Section titled “ordinal”](#ordinal) `number` #### Returns [Section titled “Returns”](#returns-5) `string`
# types
## Type Aliases [Section titled “Type Aliases”](#type-aliases) ### MemoryStatus [Section titled “MemoryStatus”](#memorystatus)
```ts
1
type MemoryStatus = typeof MemoryStatus.Type;
```
The status a memory file carries in `memhtml-status`. *** ### MemoryType [Section titled “MemoryType”](#memorytype)
```ts
1
type MemoryType = typeof MemoryType.Type;
```
*** ### ParaBucket [Section titled “ParaBucket”](#parabucket)
```ts
1
type ParaBucket = typeof ParaBucket.Type;
```
*** ### TaskStatus [Section titled “TaskStatus”](#taskstatus)
```ts
1
type TaskStatus = typeof TaskStatus.Type;
```
*** ### WritableMemoryType [Section titled “WritableMemoryType”](#writablememorytype)
```ts
1
type WritableMemoryType = typeof WritableMemoryType.Type;
```
## Variables [Section titled “Variables”](#variables) ### Confidence [Section titled “Confidence”](#confidence)
```ts
1
const Confidence: Number;
```
Confidence, unitless in `[0, 1]`. 1.0 is an unqualified assertion. *** ### ENTITY\_SEPARATOR [Section titled “ENTITY\_SEPARATOR”](#entity_separator)
```ts
1
const ENTITY_SEPARATOR: ":" = ":";
```
A `type:name` entity reference, e.g. `service:checkout-api`, `person:sanju`. The prefix before the first colon is the entity type; everything after is the name, which may itself contain colons. *** ### Importance [Section titled “Importance”](#importance)
```ts
1
const Importance: Int;
```
Importance, 1-10 inclusive, 1-based ordinal on a display scale, never an arithmetic input on its own. The retention scorer divides it by 10 to reach `[0, 1]` before it meets any other signal. *** ### MEMORY\_TYPES [Section titled “MEMORY\_TYPES”](#memory_types)
```ts
1
const MEMORY_TYPES: readonly ["episodic", "semantic", "procedural", "agent_insight", "user_preference", "error_pattern", "verdict", "precedent", "arc", "task"];
```
The memory type vocabulary, closed. Ten values, restated by the `files.memory_type` CHECK constraint in SQL. `arc` is in the vocabulary but absent from [WRITABLE\_MEMORY\_TYPES](/api/contracts/types/#writable_memory_types): an arc is synthesized by the sleep cycle from many memories, so an agent naming one directly would be asserting a conclusion the corpus has not yet earned. `task` is ONE axis with the other nine rather than a parallel `kind` column, because three overlapping type vocabularies is what made the predecessor memory system’s classification unanswerable. A task is a memory type whose retrieval, dedup, and curation treatment a filter states, not a second axis. Tasks are default-excluded from search and skipped by sleep. See `@memhtml/index`‘s `assembleScope` and the sleep phases’ `excludeTypes`. *** ### MemoryPath [Section titled “MemoryPath”](#memorypath)
```ts
1
const MemoryPath: String;
```
A repo-root-relative path to a memory file, e.g. `areas/oncall/rollback-order.html`. No leading slash: this is the git-tree form, the `files.path` primary key, and the id of a memory. `` values carry the same path with a leading `/`. That is a document-reference form, converted at the HTML boundary, never stored here. *** ### MemoryStatus [Section titled “MemoryStatus”](#memorystatus-1)
```ts
1
const MemoryStatus: Literals;
```
The status a memory file carries in `memhtml-status`. *** ### MemoryType [Section titled “MemoryType”](#memorytype-1)
```ts
1
const MemoryType: Literals;
```
*** ### PARA\_BUCKETS [Section titled “PARA\_BUCKETS”](#para_buckets)
```ts
1
const PARA_BUCKETS: readonly ["projects", "areas", "resources", "archive"];
```
PARA’s four buckets, closed and ordered. `archive` is a bucket rather than a status because eviction is a `git mv`. The path itself records the state, so `git log --follow` reads through it and `diff -M` reports the move as `R100`. *** ### ParaBucket [Section titled “ParaBucket”](#parabucket-1)
```ts
1
const ParaBucket: Literals;
```
*** ### PERSON\_ENTITY\_PREFIX [Section titled “PERSON\_ENTITY\_PREFIX”](#person_entity_prefix)
```ts
1
const PERSON_ENTITY_PREFIX: "person:";
```
The `person:` entity prefix, which routes a semantic memory to `resources/people/`. *** ### TASK\_STATUSES [Section titled “TASK\_STATUSES”](#task_statuses)
```ts
1
const TASK_STATUSES: readonly ["todo", "doing", "blocked", "done"];
```
A task’s own status, carried in `memhtml-task-status`, a SEPARATE axis from [MemoryStatus](/api/contracts/types/#memorystatus-1), which stays `active | archived` for every type including `task`. Two axes rather than four `memhtml-status` values because `active`/`archived` is what every archive, correction, and publish path switches on, and a fifth value there would silently change the meaning of each of them. Finishing a task stamps `done` AND archives the file through the same `archiveMemory` machinery, so `done` is not a resting state on its own and “what did I finish” is answered by the archive tree plus `git log`. *** ### TaskStatus [Section titled “TaskStatus”](#taskstatus-1)
```ts
1
const TaskStatus: Literals;
```
*** ### WRITABLE\_MEMORY\_TYPES [Section titled “WRITABLE\_MEMORY\_TYPES”](#writable_memory_types)
```ts
1
const WRITABLE_MEMORY_TYPES: (
2
| "task"
3
| "episodic"
4
| "semantic"
5
| "procedural"
6
| "agent_insight"
7
| "user_preference"
8
| "error_pattern"
9
| "verdict"
10
| "precedent")[];
```
The nine types `memory_write` exposes. `arc` is system-written only, so the tool parameter enum is narrower than the storage vocabulary by exactly that one value. *** ### WritableMemoryType [Section titled “WritableMemoryType”](#writablememorytype-1)
```ts
1
const WritableMemoryType: Literals;
```
## Functions [Section titled “Functions”](#functions) ### isPersonEntity() [Section titled “isPersonEntity()”](#ispersonentity)
```ts
1
function isPersonEntity(entity): boolean;
```
True when an entity reference names a person: the `person:` prefix plus a name that survives `trim()`. The trim is what makes this predicate agree with the rest of the person plane. `placementFor` routes on it, and the sleep phase that mints the person file and its `memhtml-about-person` links keys on `entity_name.trim() !== ""`. A whitespace-only name accepted here would land a memory in `resources/people/` that no phase will ever give a person file to link at — and `slugify` maps that name to `untitled`, which names nobody. #### Parameters [Section titled “Parameters”](#parameters) ##### entity [Section titled “entity”](#entity) `string` #### Returns [Section titled “Returns”](#returns) `boolean` *** ### isTaskStatus() [Section titled “isTaskStatus()”](#istaskstatus)
```ts
1
function isTaskStatus(value): value is "todo" | "doing" | "blocked" | "done";
```
True when a string is in the closed task-status vocabulary. Narrows an untrusted value. #### Parameters [Section titled “Parameters”](#parameters-1) ##### value [Section titled “value”](#value) `string` #### Returns [Section titled “Returns”](#returns-1) value is “todo” | “doing” | “blocked” | “done” *** ### isWritableMemoryType() [Section titled “isWritableMemoryType()”](#iswritablememorytype)
```ts
1
function isWritableMemoryType(type): type is "task" | "episodic" | "semantic" | "procedural" | "agent_insight" | "user_preference" | "error_pattern" | "verdict" | "precedent";
```
True for the nine types an agent may write. Narrows, so a caller can branch on it. #### Parameters [Section titled “Parameters”](#parameters-2) ##### type [Section titled “type”](#type) \| `"task"` | `"episodic"` | `"semantic"` | `"procedural"` | `"agent_insight"` | `"user_preference"` | `"error_pattern"` | `"verdict"` | `"precedent"` | `"arc"` #### Returns [Section titled “Returns”](#returns-2) type is “task” | “episodic” | “semantic” | “procedural” | “agent\_insight” | “user\_preference” | “error\_pattern” | “verdict” | “precedent” *** ### normalizeEntityName() [Section titled “normalizeEntityName()”](#normalizeentityname)
```ts
1
function normalizeEntityName(name): string;
```
Lowercase, NFC-normalize, collapse internal whitespace, trim. What it means for two entity names to be the SAME name. It lives in contracts rather than beside a caller because it is a vocabulary rule, and a second copy of a vocabulary rule fails silently: `entity-resolution` decides which `memhtml-entity` metas to rewrite by comparing a name against this form, so a divergent copy would have the phase canonicalize files toward a spelling nothing else recognizes, reporting merges no query can reach. `file_entities` stores names AS AUTHORED, not in this form, and that is deliberate — the phase finds its work by reading those rows back, so a projection that pre-normalized would hide every unnormalized meta from the one pass whose job is to fix it. The two SQL doors fold with `lower()` on both sides instead: a narrower fold (SQLite’s `lower()` is ASCII-only) that cannot disagree with itself across the JS/SQL seam. Full canonicalization is durable in the TREE, applied by the phase. #### Parameters [Section titled “Parameters”](#parameters-3) ##### name [Section titled “name”](#name) `string` #### Returns [Section titled “Returns”](#returns-3) `string` *** ### normalizeEntityRef() [Section titled “normalizeEntityRef()”](#normalizeentityref)
```ts
1
function normalizeEntityRef(entity): string;
```
A whole reference in that form: both halves normalized, rejoined, padding around the separator gone. For callers comparing references in TypeScript — the write path deciding whether a candidate entity is one the corpus already names. The SQL doors do NOT use this; see the seam note above. Total over unparseable input. A string with no separator normalizes as a whole and is returned without one, so the caller still decides what an untyped reference means rather than receiving a silently invented type. #### Parameters [Section titled “Parameters”](#parameters-4) ##### entity [Section titled “entity”](#entity-1) `string` #### Returns [Section titled “Returns”](#returns-4) `string` *** ### parseEntity() [Section titled “parseEntity()”](#parseentity)
```ts
1
function parseEntity(entity):
2
| {
3
entityName: string;
4
entityType: string;
5
}
6
| undefined;
```
Split an entity reference into its type and name at the first separator. `undefined` when the reference has no separator, or when the type or the name on either side of it is empty. #### Parameters [Section titled “Parameters”](#parameters-5) ##### entity [Section titled “entity”](#entity-2) `string` #### Returns [Section titled “Returns”](#returns-5) ##### Type Literal [Section titled “Type Literal”](#type-literal)
```ts
1
{
2
entityName: string;
3
entityType: string;
4
}
```
###### entityName [Section titled “entityName”](#entityname)
```ts
1
readonly entityName: string;
```
Everything after the first separator, colons included. ###### entityType [Section titled “entityType”](#entitytype)
```ts
1
readonly entityType: string;
```
The prefix before the first separator. *** `undefined`
# @memhtml/domain
`@memhtml/domain`: the pure math under memhtml’s retrieval and sleep phases: cosine similarity and pairwise neighbor selection, reciprocal-rank fusion and maximal marginal relevance, PageRank and communities over the memory graph, the frame key, the retention scorer with its triage bands and reprieve gate, confidence decay on a fixed-point grid, the reinforcement cooldown, and the near-duplicate merge guards. Every export is synchronous, deterministic and free of I/O, so a full index rebuild or a sleep re-run reproduces the same numbers, and the package’s `dist` imports nothing but `effect`; `tests/layering.test.ts` is the standing proof. The similarity space starts at `cosine`, which is total and clamped to `[-1, 1]`: a zero-magnitude vector, or one carrying a `NaN` or infinite component, reads `0` rather than `NaN`, because MMR folds a `max` over similarities and `Math.max(penalty, NaN)` disagrees with `NaN > penalty`. The clamp exists because subnormal magnitudes push the unclamped ratio to `1.000000106821595`, mismatched lengths walk the shorter vector, and the argument type is `ArrayLike` so a `Float32Array` decoded off a stored blob is accepted as is, since this function is the body of the `vector_distance_cos` SQL function and runs once per candidate row. `cosineDistance` is `1 - similarity` in `[0, 2]`, the space the vector arm’s SQL works in, and `compensatedSum` is Neumaier summation, needed because naive addition of the retention weight profiles returns `0.9999999999999999` for two of the six. `neighbors.ts` is the n-by-n arm of the same space: the SQL boundary is priced per call and re-copies the corpus n times over, so consumers decode each vector once into a `KeyedVector` and `topNeighborPairs` computes every unordered pair’s similarity once, offers it to both endpoints’ neighborhoods (so `(a, b)` and `(b, a)` can both appear), keeps the per-source top `perSourceK` above `floor`, and orders the result `sim` descending, `src` ascending, `dst` ascending before the `limit`, the three knobs of `NeighborOptions`. Pair similarities are bit-identical to `cosine` because the square roots are hoisted but the operations run in the same order. `rankCandidatePairs` applies the same floor, per-source top-k and ordering to an enumerated `CandidatePair` set, the shape `sharedEntityPairs` in `packages/sleep/src/sql.ts` feeds it with for edge typing’s shared-entity candidates, and `float32View` views a row’s bytes as a `Float32Array` in place, copying only when the offset is misaligned and returning `undefined` for an empty or ragged blob rather than throwing. Each returned `NeighborPair` carries `src`, `dst` and `sim`. Fusion is split between the production constants and a reference fold. `ranking.ts` holds `RRF_K` (60, the published default and the literal the retrieval SQL inlines), `MMR_LAMBDA` (0.5) and `REINFORCE_COOLDOWN_S` (900 seconds), plus `rrfContribution`, which returns an `Option` that is `None` for a zero-weight arm or an out-of-range rank so a disabled arm is absent from the fold rather than a neutral term. `rrf.ts` is the reference implementation, not the production path: `packages/index/src/retrieval-sql.ts` inlines the fusion arithmetic in SQL sharing only `RRF_K`, and this fold is what that SQL is checked against. An `ArmHit` carries a 1-based rank within its own arm’s list, `rrfScore` is `weight / (rank + k)`, `fuseArms` sums contributions into a `path -> score` map (order-insensitive across arms, and a duplicate path inside one arm accumulates, matching the SQL’s `SUM` over a `UNION ALL`), `rankFused` orders best first with ties broken on path ascending, and `fuseLateral` folds a graph walk’s best-first order in as one more arm, returning the scores unchanged for an empty order or non-positive weight so the lateral arm is inert when dark; it is defined and tested here and has no production caller yet. `applyMmr` then reorders `MmrCandidate` rows greedily by `lambda * relevance - (1 - lambda) * maxSimilarityToSelected` with no embedding round-trip, since candidates carry their own vectors; at `lambda >= 1` it returns the input order truncated, a vectorless candidate takes penalty 0 because it cannot be shown to duplicate anything, and each round folds only the newest selection into a cached per-candidate max, so the cost is O(k times n) dot products rather than O(k squared times n). `graph.ts` replaces two networkx calls with deterministic equivalents, because these scores feed the retention `pagerank` and `bridgeImportance` signals and a run-to-run reordering would change which memories are evicted on an unchanged corpus: nodes are sorted before iteration, parallel edges fold to their maximum strength, and label propagation uses sorted order with lexicographic tie-breaking instead of a random seed. `pagerank` is weighted power iteration over `GraphEdge` rows (strength in `[0, 1]`) with `PAGERANK_DAMPING` 0.85 (the networkx default), `PAGERANK_MAX_ITERATIONS` 100 and `PAGERANK_TOLERANCE` 1e-6; passing `seeds` makes it personalized, the form a query-conditioned graph walk would use (the one production caller, the sleep retention pass in `packages/sleep/src/retention.ts`, passes none), seeds naming absent nodes are dropped, and dangling mass is redistributed over the teleport distribution so the scores sum to 1. `labelPropagation` reads the edges as undirected, canonicalizes each community to its smallest member path, and collapses communities below `MIN_COMMUNITY_SIZE` (3) to `undefined` within `LABEL_PROPAGATION_MAX_SWEEPS` (100), since a pair passed off as a community would make every cross-pair edge look like a bridge; `bridgeCounts` then counts each node’s edges that cross a community boundary, with a node in no community counting 0 rather than its full degree. `frameKeyOf` is the other structural key: it reads a claim as a frame (subject plus relation up to the last linking token among `of`, `is`, `in`, `to`, `by`, `as`) and a value, so “The capital of India is New Delhi” keys on `the capital of india is` and a later claim writing a different city into the same slot is recognizable without a model or an embedding. It fails closed to `null` unless the frame is at least three tokens and the value is one to six tokens, is case- and whitespace-insensitive, and is a verbatim port from the evaluation harness’s consolidate adapter, where the rule was measured against the benchmark’s conflict-resolution rows before it was believed; the tokens, thresholds and greedy match are fixed by that measurement, and `tests/frame.test.ts` carries the reference’s own cases. `retention.ts` is the eight-signal scorer ported from the predecessor’s `domain/retention.py`. `scoreRetention` takes a `RetentionInput` of raw per-memory quantities (age in days, access count, confidence, PageRank and the run’s maximum, bridge count, inbound reinforcement count, word count, and authored contradiction count), normalizes them with `computeSignals` into the `Signals` named by `SIGNAL_NAMES` (`recency`, `accessFrequency`, `confidence`, `pagerank`, `bridgeImportance`, `reinforcementCount`, `contentDensity`, `contestedStatus`), weights them with `compositeScore` under the type’s `WeightProfile` from `weightsFor`, and bands the result with `bandFor` into a `RetentionScore`. `WEIGHT_PROFILES` has five typed profiles (`episodic`, `semantic`, `procedural`, `arc`, `error_pattern`) and every other type takes `DEFAULT_WEIGHTS`; each profile’s eight weights sum to exactly 1.0 under `profileWeightSum`, which is what keeps the composite in `[0, 1]`, and `SCORE_PRECISION` rounds it to 4 places. `HALF_LIVES_DAYS` gives the recency half-life per type (`episodic` 10, `semantic` 90, `arc` 30, `error_pattern` 14, `procedural` and `task` `null` for no decay) with `DEFAULT_HALF_LIFE_DAYS` 30 for an unlisted type, and `LN2` is `Math.LN2` so the curve is exactly 0.5 at the half-life. The `TRIAGE_ACTIONS` are `keep`, `compress` and `evict`, with `KEEP_THRESHOLD` 0.7 and `EVICT_THRESHOLD` 0.3 and each boundary owned by the lower band, so exactly 0.7 compresses and exactly 0.3 evicts. The reprieve gate sits beside the scorer: `reprieveScore` sums four terms over a `ReprieveInput` with `REPRIEVE_W_IMPORTANCE` 0.4, `REPRIEVE_W_ACCESS` 0.3, `REPRIEVE_W_OUTCOME` 0.2 and `REPRIEVE_W_RECENCY` 0.1, decaying recency at `SALIENCE_DECAY_RATE` 0.01 per hour; the coefficients sum to 1.0 but the score is not convex because the `log1p(accessCount)` term is unbounded, and a negative outcome score contributes exactly 0 so a memory is not punished twice for one bad outcome. `shouldReprieve` is the boolean gate, with no side effect of its own: it is true when the score clears `REPRIEVE_FLOOR` (0.5) and the memory has been reprieved fewer than `MAX_REPRIEVES` (3) times, where 0 is the kill switch that restores pure-age TTL, and the sleep reprieve phase (`packages/sleep/src/phases/reprieve.ts`) is what then extends the memory’s `memhtml-valid-until` by `REPRIEVE_DAYS` (14). `decay.ts` puts confidence decay and the outcome EWMA on a fixed-point grid of `SCALE` 10^4 (`POS_ONE_FP` and `NEG_ONE_FP` are +1.0 and -1.0 on it, `toFp` and `fromFp` convert) so the boundary claims hold exactly rather than within an ulp: `ewmaStepFp` is one step with floor division, `applyOutcomesFp` folds signals with one rounding per step so an N-signal batch equals N single calls, and `applyNegativeHitsFp` decays toward -1.0 over a count of negative corrections and is a no-op for `hits <= 0`. The confidence half is the production path, called by `@memhtml/sleep`‘s confidence-decay phase: `decayConfidenceFp` is an EWMA toward the floor followed by a `min` with the previous value, so decay is unconditionally non-increasing and never rehabilitates a claim an operator pushed below the floor, `decayConfidenceNFp` folds it over cycles, and `decayConfidence` and `decayConfidenceN` are the `[0, 1]` float wrappers defaulting to `DEFAULT_CONFIDENCE_DECAY_ALPHA` (0.1, a tenth of the remaining distance per sleep cycle) and `DEFAULT_CONFIDENCE_FLOOR` (0.2, because a decayed-to-zero memory would be indistinguishable from a retracted one). The outcome half is the reference implementation: `packages/index/src/reinforce.ts` computes the EWMA in float SQL with its own `OUTCOME_EWMA_ALPHA` twin of `DEFAULT_EWMA_ALPHA` (0.3), and both packages’ tests pin the pair. `CONFIDENCE_COMMIT_DELTA` (0.005) and `isCommittableConfidenceChange` drop a sub-threshold change so the widest commit of a sleep run stays reviewable. `reinforce.ts` is the cooldown predicate whose twin is the salience arm’s SQL guard: `shouldBumpAccess` is true when no stamp exists or the elapsed time is at least the cooldown (`>=`, matching the SQL, so a stamp exactly 900 seconds old is bumpable), `partitionByCooldown` splits paths into `bumped` and `cooledDown` in one order-preserving pass, and `REINFORCE_SIGNALS` (`positive`, `negative`, `neutral`) map through `signalValue` to 1, -1 and 0, where neutral bumps the access count without moving the outcome score because being read is evidence of relevance, not correctness. `merge.ts` guards the near-duplicate merge, which keeps the older file and so would fold a newer correction into an older wrong memory if it trusted cosine alone: `negationDivergent`, `numericTokenDivergent` and `variantQualifierDivergent` catch “safe” against “NOT safe”, “retry 3” against “retry 13” and “M1” against “M1 Pro”. Each memory is a `MergeText` of `gist` (the one-line claim) and `body` (the article), and the predicates read different ones: `mergeVetoReasons` runs the numeric predicate over the gist alone, because a body’s citations (a date, a ticket id, an exit code) differ between two honest tellings of one fact, and runs negation and variant over `joinMergeText` of both, because a polarity flip stated only in the body still contradicts. `mergeVetoed` is true when any reason fires. `mergeOutcomes` classifies oriented `MergePair` rows into a `MergeStage` each (`cap`, `threshold`, `vetoed`, `self`, `role-guard`, `committed`) by applying in order the `MAX_MERGE_PAIRS` cap (100 per sleep cycle), the strict `NEAR_DUPLICATE_THRESHOLD` (0.92), the veto when both texts are present, a self-merge check, and an in-batch role guard under which a dropped path is never dropped again or kept, and a keeper is never dropped, so a transitive chain cannot supersede content into a file the same batch archives; a keeper may keep again, so one page absorbs several duplicates in one batch. `mergeCandidates` is the `committed` subset as `MergeDecision` rows. The guards are a post-filter over every proposal including a model’s, so binding a model to dedup-merge cannot widen the committable set. `connectedComponents` is the order-invariant union-find whose components are dedup’s units of work (the smaller root always wins, so the same edge set yields the same partition in any arrival order), and `excludeSelfSupersede` removes the canonical from a compress batch’s member list so the file just folded into is not archived. ## Modules [Section titled “Modules”](#modules) The package publishes one import path, `@memhtml/domain`, which re-exports the eleven modules below plus a type-only re-export of `InvalidMemory` from `@memhtml/contracts/errors`, so the reference on this page is the whole exported surface. | Module | What it holds | | -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `cosine.ts` | `cosine` (total, clamped similarity), `cosineDistance` (`1 - similarity`) and `compensatedSum` (Neumaier summation). | | `decay.ts` | The grid (`SCALE`, `POS_ONE_FP`, `NEG_ONE_FP`, `toFp`, `fromFp`), the defaults (`DEFAULT_EWMA_ALPHA`, `DEFAULT_CONFIDENCE_FLOOR`, `DEFAULT_CONFIDENCE_DECAY_ALPHA`), the EWMA steps (`ewmaStepFp`, `applyOutcomesFp`, `applyNegativeHitsFp`), confidence decay (`decayConfidenceFp`, `decayConfidenceNFp`, `decayConfidence`, `decayConfidenceN`), and the commit gate (`CONFIDENCE_COMMIT_DELTA`, `isCommittableConfidenceChange`). | | `frame.ts` | `frameKeyOf`, the claim’s slot key or `null`. | | `graph.ts` | `GraphEdge`, the constants (`PAGERANK_DAMPING`, `PAGERANK_MAX_ITERATIONS`, `PAGERANK_TOLERANCE`, `MIN_COMMUNITY_SIZE`, `LABEL_PROPAGATION_MAX_SWEEPS`), `pagerank`, `labelPropagation` and `bridgeCounts`. | | `merge.ts` | `NEAR_DUPLICATE_THRESHOLD`, `MAX_MERGE_PAIRS`, the shapes `MergeText`, `MergePair`, `MergeDecision`, `MergeOutcome` and `MergeStage`, `joinMergeText`, the guards (`negationDivergent`, `numericTokenDivergent`, `variantQualifierDivergent`, `mergeVetoReasons`, `mergeVetoed`), `mergeOutcomes`, `mergeCandidates`, `connectedComponents` and `excludeSelfSupersede`. | | `mmr.ts` | `MmrCandidate` and `applyMmr`. | | `neighbors.ts` | The shapes `KeyedVector`, `NeighborPair`, `NeighborOptions` and `CandidatePair`, `float32View`, `topNeighborPairs` and `rankCandidatePairs`. | | `ranking.ts` | `RRF_K`, `MMR_LAMBDA`, `REINFORCE_COOLDOWN_S` and `rrfContribution`. | | `reinforce.ts` | `shouldBumpAccess`, `REINFORCE_SIGNALS`, `ReinforceSignal`, `signalValue` and `partitionByCooldown`. | | `retention.ts` | The signal and profile types (`SIGNAL_NAMES`, `SignalName`, `Signals`, `WeightProfile`, `TRIAGE_ACTIONS`, `TriageAction`), the tables (`WEIGHT_PROFILES`, `DEFAULT_WEIGHTS`, `HALF_LIVES_DAYS`, `DEFAULT_HALF_LIFE_DAYS`, `LN2`, `KEEP_THRESHOLD`, `EVICT_THRESHOLD`, `SCORE_PRECISION`), the scorer (`RetentionInput`, `RetentionScore`, `weightsFor`, `halfLifeFor`, `profileWeightSum`, `computeSignals`, `compositeScore`, `bandFor`, `scoreRetention`), and the reprieve gate (`REPRIEVE_FLOOR`, `REPRIEVE_DAYS`, `MAX_REPRIEVES`, `SALIENCE_DECAY_RATE`, `REPRIEVE_W_IMPORTANCE`, `REPRIEVE_W_ACCESS`, `REPRIEVE_W_OUTCOME`, `REPRIEVE_W_RECENCY`, `ReprieveInput`, `reprieveScore`, `shouldReprieve`). | | `rrf.ts` | `ArmHit`, `rrfScore`, `fuseArms`, `rankFused` and `fuseLateral`. | | `index.ts` | The eleven re-exports above plus the type-only re-export of `InvalidMemory` from `@memhtml/contracts/errors`, erased by `verbatimModuleSyntax` so `dist` still imports nothing but `effect`. | ## Interfaces [Section titled “Interfaces”](#interfaces) ### ArmHit [Section titled “ArmHit”](#armhit) One arm’s hit. `rank` is a **1-based position within that one arm’s own candidate list**, not a global rank and not a score. That is what lets RRF work across arms whose scores are incomparable. #### Properties [Section titled “Properties”](#properties) ##### path [Section titled “path”](#path)
```ts
1
readonly path: string;
```
##### rank [Section titled “rank”](#rank)
```ts
1
readonly rank: number;
```
*** ### CandidatePair [Section titled “CandidatePair”](#candidatepair) One enumerated candidate pair, oriented by the caller. Pairs must be distinct. #### Properties [Section titled “Properties”](#properties-1) ##### dst [Section titled “dst”](#dst)
```ts
1
readonly dst: string;
```
##### src [Section titled “src”](#src)
```ts
1
readonly src: string;
```
*** ### GraphEdge [Section titled “GraphEdge”](#graphedge) One directed weighted edge. `strength` is unitless in `[0, 1]`. #### Properties [Section titled “Properties”](#properties-2) ##### dst [Section titled “dst”](#dst-1)
```ts
1
readonly dst: string;
```
##### src [Section titled “src”](#src-1)
```ts
1
readonly src: string;
```
##### strength [Section titled “strength”](#strength)
```ts
1
readonly strength: number;
```
*** ### InvalidMemory [Section titled “InvalidMemory”](#invalidmemory) A memory that violates the file format or the type/placement vocabulary. #### Extends [Section titled “Extends”](#extends) * `InvalidMemory_base` #### Properties [Section titled “Properties”](#properties-3) ##### reason [Section titled “reason”](#reason)
```ts
1
readonly reason: string;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from)
```ts
1
InvalidMemory_base.reason
```
*** ### KeyedVector [Section titled “KeyedVector”](#keyedvector) One decoded vector under the key the ranking reports. Keys must be unique. #### Properties [Section titled “Properties”](#properties-4) ##### key [Section titled “key”](#key)
```ts
1
readonly key: string;
```
##### vec [Section titled “vec”](#vec)
```ts
1
readonly vec: Float32Array;
```
*** ### MergeDecision [Section titled “MergeDecision”](#mergedecision) A committed merge: fold `dropPath` into `keepPath`. #### Properties [Section titled “Properties”](#properties-5) ##### dropPath [Section titled “dropPath”](#droppath)
```ts
1
readonly dropPath: string;
```
##### keepPath [Section titled “keepPath”](#keeppath)
```ts
1
readonly keepPath: string;
```
##### similarity [Section titled “similarity”](#similarity)
```ts
1
readonly similarity: number;
```
*** ### MergeOutcome [Section titled “MergeOutcome”](#mergeoutcome) One candidate pair’s fate. `committed` is the only stage that produces a [MergeDecision](/api/domain/#mergedecision). #### Properties [Section titled “Properties”](#properties-6) ##### pair [Section titled “pair”](#pair)
```ts
1
readonly pair: MergePair;
```
##### stage [Section titled “stage”](#stage)
```ts
1
readonly stage: MergeStage;
```
*** ### MergePair [Section titled “MergePair”](#mergepair) One oriented candidate pair. `keepPath` is already the canonical (the older file). The orientation is the caller’s, because it depends on commit dates this module does not read. The texts are optional; when either is absent the divergence guards are skipped. #### Properties [Section titled “Properties”](#properties-7) ##### dropPath [Section titled “dropPath”](#droppath-1)
```ts
1
readonly dropPath: string;
```
##### dropText? [Section titled “dropText?”](#droptext)
```ts
1
readonly optional dropText?: MergeText;
```
##### keepPath [Section titled “keepPath”](#keeppath-1)
```ts
1
readonly keepPath: string;
```
##### keepText? [Section titled “keepText?”](#keeptext)
```ts
1
readonly optional keepText?: MergeText;
```
##### similarity [Section titled “similarity”](#similarity-1)
```ts
1
readonly similarity: number;
```
*** ### MergeText [Section titled “MergeText”](#mergetext) The two texts the veto reads for one memory: its one-line claim (the `` gist) and the whole article’s text. The predicates do not all read the same one, and that is the point of the split. `numericTokenDivergent` reads the GIST alone. A body is where a memory cites its evidence — a date, a pull-request or ticket id, an exit code, a cosine — and two honest tellings of one fact almost never cite the same numbers, so a numeric set-equality over the whole article vetoed nearly every true duplicate the store proposed (349 of 349 candidates over two consecutive runs, every review task naming this predicate). A number in the CLAIM is the fact itself: “retry 3” against “retry 13” is two claims, and the gist is where that difference lives. `negationDivergent` and `variantQualifierDivergent` read the JOINED text, gist and body together. Their markers are rare, load-bearing words (“not”, “never”, “pro”, “deprecated”) that a body does not carry incidentally the way it carries a citation, and a polarity flip stated only in the body — the claim “run the cutover during business hours” over a body that says draining does NOT complete — is a contradiction the veto has to see. The two all-veto runs named neither predicate once. #### Properties [Section titled “Properties”](#properties-8) ##### body [Section titled “body”](#body)
```ts
1
readonly body: string;
```
##### gist [Section titled “gist”](#gist)
```ts
1
readonly gist: string;
```
*** ### MmrCandidate [Section titled “MmrCandidate”](#mmrcandidate) A candidate for diversification. `score` is its fused relevance (unitless, higher is better); `vector` is its embedding, or `undefined` when the chunk has no vector yet, which happens for a lexical-floor hit or a deferred embed. #### Properties [Section titled “Properties”](#properties-9) ##### path [Section titled “path”](#path-1)
```ts
1
readonly path: string;
```
##### score [Section titled “score”](#score)
```ts
1
readonly score: number;
```
##### vector? [Section titled “vector?”](#vector)
```ts
1
readonly optional vector?: readonly number[];
```
*** ### NeighborOptions [Section titled “NeighborOptions”](#neighboroptions) The selection knobs every pair consumer states. #### Properties [Section titled “Properties”](#properties-10) ##### floor [Section titled “floor”](#floor)
```ts
1
readonly floor: number;
```
Pairs below this similarity never enter ranking. ##### limit [Section titled “limit”](#limit)
```ts
1
readonly limit: number;
```
Global cap, applied after the final `sim` DESC, `src` ASC, `dst` ASC ordering. ##### perSourceK [Section titled “perSourceK”](#persourcek)
```ts
1
readonly perSourceK: number;
```
Nearest neighbors kept per `src`, ordered `sim` DESC then `dst` ASC. *** ### NeighborPair [Section titled “NeighborPair”](#neighborpair) One selected pair. `sim` is cosine similarity, unitless in `[-1, 1]`. #### Properties [Section titled “Properties”](#properties-11) ##### dst [Section titled “dst”](#dst-2)
```ts
1
readonly dst: string;
```
##### sim [Section titled “sim”](#sim)
```ts
1
readonly sim: number;
```
##### src [Section titled “src”](#src-2)
```ts
1
readonly src: string;
```
*** ### ReprieveInput [Section titled “ReprieveInput”](#reprieveinput) What the reprieve score reads. Units and scopes: * `importance`: 1-based ordinal in `[1, 10]`, clamped; divided by 10 before use. * `accessCount`: a quantity, per-memory lifetime, 0-based. * `outcomeScore`: unitless in `[-1, 1]`. A negative value contributes 0. * `hoursSinceAccess`: fractional hours, non-negative. #### Properties [Section titled “Properties”](#properties-12) ##### accessCount [Section titled “accessCount”](#accesscount)
```ts
1
readonly accessCount: number;
```
##### decayRate? [Section titled “decayRate?”](#decayrate)
```ts
1
readonly optional decayRate?: number;
```
##### hoursSinceAccess [Section titled “hoursSinceAccess”](#hourssinceaccess)
```ts
1
readonly hoursSinceAccess: number;
```
##### importance [Section titled “importance”](#importance)
```ts
1
readonly importance: number;
```
##### outcomeScore [Section titled “outcomeScore”](#outcomescore)
```ts
1
readonly outcomeScore: number;
```
*** ### RetentionInput [Section titled “RetentionInput”](#retentioninput) The raw per-memory inputs the SQL phase gathers. Kept raw rather than pre-normalized so the normalization curves live in exactly one place, [computeSignals](/api/domain/#computesignals). Units and scopes, per field: * `memoryType` selects the weight profile and the half-life. * `ageDays`: fractional days since the memory’s own last update. Non-negative. * `accessCount`: a quantity, per-memory lifetime, 0-based. * `confidence`: unitless in `[0, 1]`; clamped rather than rejected. * `graphRank` / `maxGraphRank`: PageRank scores in the same run’s scale; `maxGraphRank` is that run’s maximum, so the ratio is corpus-relative, not absolute. * `bridgeCount`: a quantity of cross-community memory edges for this memory. * `reinforcementCount`: a quantity of inbound `supports`/`reinforces` edges. * `wordCount`: a quantity of words in the memory’s body. * `contradictionCount`: a quantity of **authored** (`derived: false`) contradictions; a sleep-mined suspicion is excluded upstream, so a machine guess cannot evict. #### Properties [Section titled “Properties”](#properties-13) ##### accessCount [Section titled “accessCount”](#accesscount-1)
```ts
1
readonly accessCount: number;
```
##### ageDays [Section titled “ageDays”](#agedays)
```ts
1
readonly ageDays: number;
```
##### bridgeCount [Section titled “bridgeCount”](#bridgecount)
```ts
1
readonly bridgeCount: number;
```
##### confidence [Section titled “confidence”](#confidence)
```ts
1
readonly confidence: number;
```
##### contradictionCount [Section titled “contradictionCount”](#contradictioncount)
```ts
1
readonly contradictionCount: number;
```
##### graphRank [Section titled “graphRank”](#graphrank)
```ts
1
readonly graphRank: number;
```
##### maxGraphRank [Section titled “maxGraphRank”](#maxgraphrank)
```ts
1
readonly maxGraphRank: number;
```
##### memoryType [Section titled “memoryType”](#memorytype)
```ts
1
readonly memoryType: string;
```
##### reinforcementCount [Section titled “reinforcementCount”](#reinforcementcount)
```ts
1
readonly reinforcementCount: number;
```
##### wordCount [Section titled “wordCount”](#wordcount)
```ts
1
readonly wordCount: number;
```
*** ### RetentionScore [Section titled “RetentionScore”](#retentionscore) The scoring result: the composite, its band, and the normalized signals behind it. #### Properties [Section titled “Properties”](#properties-14) ##### action [Section titled “action”](#action)
```ts
1
readonly action: "keep" | "compress" | "evict";
```
##### score [Section titled “score”](#score-1)
```ts
1
readonly score: number;
```
##### signals [Section titled “signals”](#signals)
```ts
1
readonly signals: Signals;
```
## Type Aliases [Section titled “Type Aliases”](#type-aliases) ### MergeStage [Section titled “MergeStage”](#mergestage)
```ts
1
type MergeStage = "cap" | "threshold" | "vetoed" | "self" | "role-guard" | "committed";
```
Where a candidate pair stopped in [mergeOutcomes](/api/domain/#mergeoutcomes), in the filter’s own order. *** ### MergeVetoReason [Section titled “MergeVetoReason”](#mergevetoreason)
```ts
1
type MergeVetoReason = "negation" | "numeric" | "variant";
```
The three divergence families, in the veto’s own disjunction order. *** ### ReinforceSignal [Section titled “ReinforceSignal”](#reinforcesignal)
```ts
1
type ReinforceSignal = typeof REINFORCE_SIGNALS[number];
```
*** ### SignalName [Section titled “SignalName”](#signalname)
```ts
1
type SignalName = typeof SIGNAL_NAMES[number];
```
*** ### Signals [Section titled “Signals”](#signals-1)
```ts
1
type Signals = { readonly [K in SignalName]: number };
```
A full set of normalized signal values, each unitless in `[0, 1]`. *** ### TriageAction [Section titled “TriageAction”](#triageaction)
```ts
1
type TriageAction = typeof TRIAGE_ACTIONS[number];
```
*** ### WeightProfile [Section titled “WeightProfile”](#weightprofile)
```ts
1
type WeightProfile = { readonly [K in SignalName]: number };
```
A weight profile: one coefficient per signal, the eight summing to exactly 1.0. ## Variables [Section titled “Variables”](#variables) ### CONFIDENCE\_COMMIT\_DELTA [Section titled “CONFIDENCE\_COMMIT\_DELTA”](#confidence_commit_delta)
```ts
1
const CONFIDENCE_COMMIT_DELTA: 0.005 = 0.005;
```
The smallest confidence change worth committing. Confidence decay is the widest commit in a sleep run, one meta line across many files, so a sub-threshold delta is dropped rather than committed, keeping the night’s diff reviewable. *** ### DEFAULT\_CONFIDENCE\_DECAY\_ALPHA [Section titled “DEFAULT\_CONFIDENCE\_DECAY\_ALPHA”](#default_confidence_decay_alpha)
```ts
1
const DEFAULT_CONFIDENCE_DECAY_ALPHA: 0.1 = 0.1;
```
The per-sleep-cycle confidence decay weight. Gentler than [DEFAULT\_EWMA\_ALPHA](/api/domain/#default_ewma_alpha) on purpose: confidence should erode over many unreinforced nights rather than collapse in one, so a single missed reinforcement is forgiving. At 0.1 a claim closes a tenth of its distance to the floor per cycle. *** ### DEFAULT\_CONFIDENCE\_FLOOR [Section titled “DEFAULT\_CONFIDENCE\_FLOOR”](#default_confidence_floor)
```ts
1
const DEFAULT_CONFIDENCE_FLOOR: 0.2 = 0.2;
```
The floor confidence erodes toward but never past. A claim that stops being reinforced loses weight without vanishing: an old uncorroborated claim is weak evidence, not absent evidence, and a memory decayed to 0 would be indistinguishable from a retracted one. *** ### DEFAULT\_EWMA\_ALPHA [Section titled “DEFAULT\_EWMA\_ALPHA”](#default_ewma_alpha)
```ts
1
const DEFAULT_EWMA_ALPHA: 0.3 = 0.3;
```
The outcome EWMA’s weight on an incoming signal. Its production twin is `OUTCOME_EWMA_ALPHA` in `packages/index/src/reinforce.ts`, the value the reinforce SQL binds; both packages’ tests pin their constant to 0.3 so a fork fails loudly. *** ### DEFAULT\_HALF\_LIFE\_DAYS [Section titled “DEFAULT\_HALF\_LIFE\_DAYS”](#default_half_life_days)
```ts
1
const DEFAULT_HALF_LIFE_DAYS: 30 = 30;
```
Half-life in days for a type with no listed one. *** ### DEFAULT\_WEIGHTS [Section titled “DEFAULT\_WEIGHTS”](#default_weights)
```ts
1
const DEFAULT_WEIGHTS: WeightProfile;
```
The profile for a type with no dedicated one. Also sums to exactly 1.0. *** ### EVICT\_THRESHOLD [Section titled “EVICT\_THRESHOLD”](#evict_threshold)
```ts
1
const EVICT_THRESHOLD: 0.3 = 0.3;
```
*** ### HALF\_LIVES\_DAYS [Section titled “HALF\_LIVES\_DAYS”](#half_lives_days)
```ts
1
const HALF_LIVES_DAYS: Readonly>;
```
Recency half-lives in days. `null` means no time decay. An unlisted type takes [DEFAULT\_HALF\_LIFE\_DAYS](/api/domain/#default_half_life_days). *** ### KEEP\_THRESHOLD [Section titled “KEEP\_THRESHOLD”](#keep_threshold)
```ts
1
const KEEP_THRESHOLD: 0.7 = 0.7;
```
Band edges. KEEP is `> 0.7`, EVICT is `<= 0.3`, and COMPRESS is the open interval between. **Each boundary is owned by the lower band**: exactly 0.7 compresses, exactly 0.3 evicts. The three bands partition `[0, 1]` with no gap and no overlap. *** ### LABEL\_PROPAGATION\_MAX\_SWEEPS [Section titled “LABEL\_PROPAGATION\_MAX\_SWEEPS”](#label_propagation_max_sweeps)
```ts
1
const LABEL_PROPAGATION_MAX_SWEEPS: 100 = 100;
```
Label-propagation sweep cap. Sorted-order propagation settles well inside it. *** ### LN2 [Section titled “LN2”](#ln2)
```ts
1
const LN2: number = Math.LN2;
```
The recency decay constant. `Math.LN2` rather than a `0.693` literal, so `exp(-LN2 * age / halfLife)` is **exactly** 0.5 at `age == halfLife`. The half-life is then the definition of the curve rather than an approximation of it, and a property test can assert the equality instead of a tolerance. *** ### MAX\_MERGE\_PAIRS [Section titled “MAX\_MERGE\_PAIRS”](#max_merge_pairs)
```ts
1
const MAX_MERGE_PAIRS: 100 = 100;
```
Merge decisions applied per sleep cycle, so a flood cannot fan the phase out unboundedly. *** ### MAX\_REPRIEVES [Section titled “MAX\_REPRIEVES”](#max_reprieves)
```ts
1
const MAX_REPRIEVES: 3 = 3;
```
Reprieves a memory may earn before it is forced to expire. `0` is the kill switch that restores pure-age TTL: the floor alone can never force expiry, because the reprieve score is a sum of non-negative terms and is therefore always above 0. *** ### MIN\_COMMUNITY\_SIZE [Section titled “MIN\_COMMUNITY\_SIZE”](#min_community_size)
```ts
1
const MIN_COMMUNITY_SIZE: 3 = 3;
```
Communities smaller than this are not communities; compress batching ignores them. *** ### MMR\_LAMBDA [Section titled “MMR\_LAMBDA”](#mmr_lambda)
```ts
1
const MMR_LAMBDA: 0.5 = 0.5;
```
Maximal-marginal-relevance’s relevance/diversity split. *** ### NEAR\_DUPLICATE\_THRESHOLD [Section titled “NEAR\_DUPLICATE\_THRESHOLD”](#near_duplicate_threshold)
```ts
1
const NEAR_DUPLICATE_THRESHOLD: 0.92 = 0.92;
```
Cosine similarity above which two bodies are the same content. Strict. *** ### NEG\_ONE\_FP [Section titled “NEG\_ONE\_FP”](#neg_one_fp)
```ts
1
const NEG_ONE_FP: number = -SCALE;
```
*** ### PAGERANK\_DAMPING [Section titled “PAGERANK\_DAMPING”](#pagerank_damping)
```ts
1
const PAGERANK_DAMPING: 0.85 = 0.85;
```
PageRank teleport factor. The networkx default, kept from the predecessor memory system. *** ### PAGERANK\_MAX\_ITERATIONS [Section titled “PAGERANK\_MAX\_ITERATIONS”](#pagerank_max_iterations)
```ts
1
const PAGERANK_MAX_ITERATIONS: 100 = 100;
```
Power-iteration cap. Convergence at this graph’s scale is well inside it. *** ### PAGERANK\_TOLERANCE [Section titled “PAGERANK\_TOLERANCE”](#pagerank_tolerance)
```ts
1
const PAGERANK_TOLERANCE: 0.000001 = 1e-6;
```
L1 convergence tolerance per node. *** ### POS\_ONE\_FP [Section titled “POS\_ONE\_FP”](#pos_one_fp)
```ts
1
const POS_ONE_FP: 10000 = SCALE;
```
`+1.0` and `-1.0` on the grid. The outcome EWMA’s domain is `[NEG_ONE_FP, POS_ONE_FP]`. *** ### REINFORCE\_COOLDOWN\_S [Section titled “REINFORCE\_COOLDOWN\_S”](#reinforce_cooldown_s)
```ts
1
const REINFORCE_COOLDOWN_S: 900 = 900;
```
Seconds an access bump waits before it counts again. Stated once here and once in the salience arm’s SQL; a property test pins the two to agree at the boundary. *** ### REINFORCE\_SIGNALS [Section titled “REINFORCE\_SIGNALS”](#reinforce_signals)
```ts
1
const REINFORCE_SIGNALS: readonly ["positive", "negative", "neutral"];
```
The signal a reinforcement carries. `negative` is what drives the outcome EWMA down. *** ### REPRIEVE\_DAYS [Section titled “REPRIEVE\_DAYS”](#reprieve_days)
```ts
1
const REPRIEVE_DAYS: 14 = 14;
```
Days a reprieve extends `memhtml-valid-until` by. *** ### REPRIEVE\_FLOOR [Section titled “REPRIEVE\_FLOOR”](#reprieve_floor)
```ts
1
const REPRIEVE_FLOOR: 0.5 = 0.5;
```
The reprieve gate’s floor. A TTL-passed memory scoring at least this, under the reprieve cap, has its `memhtml-valid-until` extended instead of being archived. *** ### REPRIEVE\_W\_ACCESS [Section titled “REPRIEVE\_W\_ACCESS”](#reprieve_w_access)
```ts
1
const REPRIEVE_W_ACCESS: 0.3 = 0.3;
```
*** ### REPRIEVE\_W\_IMPORTANCE [Section titled “REPRIEVE\_W\_IMPORTANCE”](#reprieve_w_importance)
```ts
1
const REPRIEVE_W_IMPORTANCE: 0.4 = 0.4;
```
The four reprieve coefficients. They sum to 1.0 but the score is NOT convex. *** ### REPRIEVE\_W\_OUTCOME [Section titled “REPRIEVE\_W\_OUTCOME”](#reprieve_w_outcome)
```ts
1
const REPRIEVE_W_OUTCOME: 0.2 = 0.2;
```
*** ### REPRIEVE\_W\_RECENCY [Section titled “REPRIEVE\_W\_RECENCY”](#reprieve_w_recency)
```ts
1
const REPRIEVE_W_RECENCY: 0.1 = 0.1;
```
*** ### RRF\_K [Section titled “RRF\_K”](#rrf_k)
```ts
1
const RRF_K: 60 = 60;
```
Reciprocal-rank-fusion’s rank offset. 60 is the published default and the value the retrieval SQL inlines as a literal, so it lives here once and the assembler reads it rather than restating it. *** ### SALIENCE\_DECAY\_RATE [Section titled “SALIENCE\_DECAY\_RATE”](#salience_decay_rate)
```ts
1
const SALIENCE_DECAY_RATE: 0.01 = 0.01;
```
Salience decay rate per hour for the reprieve score’s recency term. *** ### SCALE [Section titled “SCALE”](#scale)
```ts
1
const SCALE: 10000 = 10_000;
```
The fixed-point scale: 10^4, so the grid step is the 4th decimal place. *** ### SCORE\_PRECISION [Section titled “SCORE\_PRECISION”](#score_precision)
```ts
1
const SCORE_PRECISION: 4 = 4;
```
Decimal places the composite is rounded to, matching the predecessor’s grain. *** ### SIGNAL\_NAMES [Section titled “SIGNAL\_NAMES”](#signal_names)
```ts
1
const SIGNAL_NAMES: readonly ["recency", "accessFrequency", "confidence", "pagerank", "bridgeImportance", "reinforcementCount", "contentDensity", "contestedStatus"];
```
The eight signals, in the fixed order every weight profile keys on. *** ### TRIAGE\_ACTIONS [Section titled “TRIAGE\_ACTIONS”](#triage_actions)
```ts
1
const TRIAGE_ACTIONS: readonly ["keep", "compress", "evict"];
```
The triage verdict for one memory. *** ### WEIGHT\_PROFILES [Section titled “WEIGHT\_PROFILES”](#weight_profiles)
```ts
1
const WEIGHT_PROFILES: Readonly>;
```
Per-type weight profiles. Every profile’s eight weights sum to exactly 1.0 under compensated summation, which is what makes the composite a convex combination of eight `[0, 1]` signals and therefore itself in `[0, 1]`. The five profiles below are the typed ones; every other memory type falls back to [DEFAULT\_WEIGHTS](/api/domain/#default_weights). Recency carries the most weight for `episodic` (time is that type’s identity) and zero for `procedural` (a working procedure does not stale). ## Functions [Section titled “Functions”](#functions) ### applyMmr() [Section titled “applyMmr()”](#applymmr)
```ts
1
function applyMmr(
2
candidates,
3
limit,
4
lambda?
5
): readonly MmrCandidate[];
```
Greedily reorder candidates by `lambda * relevance - (1 - lambda) * maxSimilarityToSelected`. `lambda` is unitless in `[0, 1]`: 1 is pure relevance, 0 is pure diversity. At `lambda >= 1` the function short-circuits and returns the input order truncated, because the penalty term is multiplied by zero and the greedy pass would otherwise burn O(n^2) cosines to reproduce the order it was given. A candidate with no vector takes penalty 0, which is how “unknown similarity” reads here. A vectorless candidate cannot be shown to duplicate anything, so it is not penalized for it. Vectorless candidates therefore keep their relative fusion order among themselves rather than being shuffled by a fabricated distance. The output is always a duplicate-free subsequence of the input by membership: each candidate is selected at most once and nothing is invented. #### Parameters [Section titled “Parameters”](#parameters) ##### candidates [Section titled “candidates”](#candidates) readonly [`MmrCandidate`](/api/domain/#mmrcandidate)\[] ##### limit [Section titled “limit”](#limit-1) `number` ##### lambda? [Section titled “lambda?”](#lambda) `number` = `MMR_LAMBDA` #### Returns [Section titled “Returns”](#returns) readonly [`MmrCandidate`](/api/domain/#mmrcandidate)\[] *** ### applyNegativeHitsFp() [Section titled “applyNegativeHitsFp()”](#applynegativehitsfp)
```ts
1
function applyNegativeHitsFp(
2
alphaFp,
3
prevFp,
4
hits
5
): number;
```
Decay a score toward -1.0 over `hits` negative corrections. `hits <= 0` is a no-op, so re-running a phase over an already-drained watermark writes the same value. #### Parameters [Section titled “Parameters”](#parameters-1) ##### alphaFp [Section titled “alphaFp”](#alphafp) `number` ##### prevFp [Section titled “prevFp”](#prevfp) `number` ##### hits [Section titled “hits”](#hits) `number` #### Returns [Section titled “Returns”](#returns-1) `number` *** ### applyOutcomesFp() [Section titled “applyOutcomesFp()”](#applyoutcomesfp)
```ts
1
function applyOutcomesFp(
2
alphaFp,
3
prevFp,
4
signalsFp
5
): number;
```
Fold signals through [ewmaStepFp](/api/domain/#ewmastepfp), one rounding per step. Rounding inside the fold rather than once at the end is what makes an N-signal batch agree with N single-signal calls. Sleep may process a memory’s corrections in one batch or across several nights, and both paths must reach the same score. #### Parameters [Section titled “Parameters”](#parameters-2) ##### alphaFp [Section titled “alphaFp”](#alphafp-1) `number` ##### prevFp [Section titled “prevFp”](#prevfp-1) `number` ##### signalsFp [Section titled “signalsFp”](#signalsfp) readonly `number`\[] #### Returns [Section titled “Returns”](#returns-2) `number` *** ### bandFor() [Section titled “bandFor()”](#bandfor)
```ts
1
function bandFor(score): "keep" | "compress" | "evict";
```
The band a composite falls in. Boundaries belong to the lower band: 0.7 compresses and 0.3 evicts, so the bands partition `[0, 1]` and no score is ever unbanded. #### Parameters [Section titled “Parameters”](#parameters-3) ##### score [Section titled “score”](#score-2) `number` #### Returns [Section titled “Returns”](#returns-3) `"keep"` | `"compress"` | `"evict"` *** ### bridgeCounts() [Section titled “bridgeCounts()”](#bridgecounts)
```ts
1
function bridgeCounts(
2
nodes,
3
edges,
4
communities
5
): ReadonlyMap;
```
Per-node count of incident memory edges that cross a community boundary. This is the `bridgeImportance` signal’s raw input. A node in no community (below the size floor) has no boundary to cross, so its bridge count is 0 rather than its full degree; counting those would make every isolated pair look maximally structural. #### Parameters [Section titled “Parameters”](#parameters-4) ##### nodes [Section titled “nodes”](#nodes) readonly `string`\[] ##### edges [Section titled “edges”](#edges) readonly [`GraphEdge`](/api/domain/#graphedge)\[] ##### communities [Section titled “communities”](#communities) `ReadonlyMap`<`string`, `string` | `undefined`> #### Returns [Section titled “Returns”](#returns-4) `ReadonlyMap`<`string`, `number`> *** ### compensatedSum() [Section titled “compensatedSum()”](#compensatedsum)
```ts
1
function compensatedSum(values): number;
```
Neumaier compensated summation. Naive left-to-right addition of the retention weight profiles gives `0.9999999999999999` for two of the six (verified in node), which would make a convexity assertion fail against a profile that is correct by construction. This is the `math.fsum` the ported scorer relies on. #### Parameters [Section titled “Parameters”](#parameters-5) ##### values [Section titled “values”](#values) `Iterable`<`number`> #### Returns [Section titled “Returns”](#returns-5) `number` *** ### compositeScore() [Section titled “compositeScore()”](#compositescore)
```ts
1
function compositeScore(signals, profile): number;
```
The weighted composite, unitless in `[0, 1]`, rounded to [SCORE\_PRECISION](/api/domain/#score_precision) places. Compensated summation, so the fold does not accumulate the drift that makes a by-construction-convex profile score marginally above 1. #### Parameters [Section titled “Parameters”](#parameters-6) ##### signals [Section titled “signals”](#signals-2) [`Signals`](/api/domain/#signals-1) ##### profile [Section titled “profile”](#profile) [`WeightProfile`](/api/domain/#weightprofile) #### Returns [Section titled “Returns”](#returns-6) `number` *** ### computeSignals() [Section titled “computeSignals()”](#computesignals)
```ts
1
function computeSignals(input): Signals;
```
Normalize the raw inputs to their `[0, 1]` signal values. #### Parameters [Section titled “Parameters”](#parameters-7) ##### input [Section titled “input”](#input) [`RetentionInput`](/api/domain/#retentioninput) #### Returns [Section titled “Returns”](#returns-7) [`Signals`](/api/domain/#signals-1) *** ### connectedComponents() [Section titled “connectedComponents()”](#connectedcomponents)
```ts
1
function connectedComponents(edges): readonly readonly string[][];
```
Connected components over an undirected edge list, as sorted member lists. The near-duplicate graph’s components are dedup’s units of work. A component is what “these memories might all be one memory” looks like before anything has judged them, and it is the right unit because near-duplication is transitive in practice: three rewordings of one fact produce three edges, and folding them one pair at a time would ask the same question three times and could answer it three different ways. **The partition is order-INVARIANT, not merely order-stable.** A union always keeps the lexicographically smaller root, so every set’s root is the smallest key it holds no matter which order the edges arrive in. Members come back sorted, and components come back ordered by root, which is each component’s own smallest member. So the same edge SET produces the same output whether it arrives mined-first, frame-first, mirrored, or shuffled. That is stronger than sorting the input would be, and it is why no sort happens here: a caller cannot make this disagree with itself by changing how it enumerates. A “larger root wins” or “first root seen wins” rule would break exactly that, because both make the surviving root a fact about arrival order rather than about the set. Each pair is normalized before it is unioned, so `(a, b)` and `(b, a)` are one edge. A self-edge introduces its key and joins nothing. Cost is near-linear in the edge count, and no step here ever enumerates a pair the caller did not hand over. #### Parameters [Section titled “Parameters”](#parameters-8) ##### edges [Section titled “edges”](#edges-1) readonly readonly \[`string`, `string`]\[] #### Returns [Section titled “Returns”](#returns-8) readonly readonly `string`\[]\[] *** ### cosine() [Section titled “cosine()”](#cosine)
```ts
1
function cosine(a, b): number;
```
Cosine similarity of two vectors, unitless and clamped to `[-1, 1]`. Total: every input maps to a number in range, and `NaN` is never returned. A zero-magnitude input yields `0`, and so does a vector carrying a `NaN` or infinite component — the two conditions the arithmetic below cannot answer. `0` is the reading a vectorless candidate already gets in MMR: no usable direction, so no demonstrable duplication, so no penalty. Returning `NaN` would be worse than returning a wrong number. MMR folds a `max` over the similarities to the already-selected set, and the two equivalent ways of writing that fold disagree on `NaN` — `Math.max(penalty, NaN)` is `NaN` while `NaN > penalty` is `false` — so a single degenerate embedding would make the result depend on which fold runs, not on the corpus. The result is clamped because the unclamped ratio does not stay in range. Verified in node 2026-08-02: two vectors whose squared magnitudes fall into the subnormal range return `1.000000106821595`. The squares underflow, so `sqrt` divides by a magnitude smaller than the true one. Similarity is also the input to `1 - similarity` distance and to the MMR penalty, both of which state a range, so the clamp belongs here rather than at each reader. Length mismatch is handled by walking the shorter vector rather than failing. The only way two stored vectors differ in length is a half-migrated embedding model, a condition the index refuses at the `embed_model` watermark, so it does not reach here. `ArrayLike` rather than `ReadonlyArray` so a `Float32Array` decoded straight off a stored blob is an argument: this is the `vector_distance_cos` SQL function’s own body, called once per candidate row, and materialising two 1024-element arrays per call to satisfy a narrower type would allocate more than the arithmetic costs. Every array caller still fits. #### Parameters [Section titled “Parameters”](#parameters-9) ##### a [Section titled “a”](#a) `ArrayLike`<`number`> ##### b [Section titled “b”](#b) `ArrayLike`<`number`> #### Returns [Section titled “Returns”](#returns-9) `number` *** ### cosineDistance() [Section titled “cosineDistance()”](#cosinedistance)
```ts
1
function cosineDistance(a, b): number;
```
Cosine *distance*, `1 - similarity`, unitless in `[0, 2]`. This is the space the vector arm’s SQL works in (`vector_distance_cos`), so a threshold stated as a similarity is converted once here rather than inverted at each call site. #### Parameters [Section titled “Parameters”](#parameters-10) ##### a [Section titled “a”](#a-1) `ArrayLike`<`number`> ##### b [Section titled “b”](#b-1) `ArrayLike`<`number`> #### Returns [Section titled “Returns”](#returns-10) `number` *** ### decayConfidence() [Section titled “decayConfidence()”](#decayconfidence)
```ts
1
function decayConfidence(
2
confidence,
3
alpha?,
4
floor?
5
): number;
```
One confidence-decay step in the `[0, 1]` float space the HTML `memhtml-confidence` meta uses. `alpha` is the fraction of the remaining distance to `floor` closed per cycle, both unitless in `[0, 1]`. #### Parameters [Section titled “Parameters”](#parameters-11) ##### confidence [Section titled “confidence”](#confidence-1) `number` ##### alpha? [Section titled “alpha?”](#alpha) `number` = `DEFAULT_CONFIDENCE_DECAY_ALPHA` ##### floor? [Section titled “floor?”](#floor-1) `number` = `DEFAULT_CONFIDENCE_FLOOR` #### Returns [Section titled “Returns”](#returns-11) `number` *** ### decayConfidenceFp() [Section titled “decayConfidenceFp()”](#decayconfidencefp)
```ts
1
function decayConfidenceFp(
2
alphaFp,
3
confFp,
4
floorFp
5
): number;
```
One confidence-decay step on the grid: an EWMA toward the floor, then a `min` with the previous value. The `min` is what makes the step *unconditionally* non-increasing. Without it, a claim already below the floor (one an operator correction pushed down, say) would be pulled back *up* toward the floor by the same convex combination that erodes a healthy claim, so decay would rehabilitate a discredited memory. The floor is a resting place for a claim that stops being reinforced. A refuted claim is not pulled back up to it. #### Parameters [Section titled “Parameters”](#parameters-12) ##### alphaFp [Section titled “alphaFp”](#alphafp-2) `number` ##### confFp [Section titled “confFp”](#conffp) `number` ##### floorFp [Section titled “floorFp”](#floorfp) `number` #### Returns [Section titled “Returns”](#returns-12) `number` *** ### decayConfidenceN() [Section titled “decayConfidenceN()”](#decayconfidencen)
```ts
1
function decayConfidenceN(
2
confidence,
3
cycles,
4
alpha?,
5
floor?
6
): number;
```
[decayConfidence](/api/domain/#decayconfidence) folded over `cycles` unreinforced sleep cycles. #### Parameters [Section titled “Parameters”](#parameters-13) ##### confidence [Section titled “confidence”](#confidence-2) `number` ##### cycles [Section titled “cycles”](#cycles) `number` ##### alpha? [Section titled “alpha?”](#alpha-1) `number` = `DEFAULT_CONFIDENCE_DECAY_ALPHA` ##### floor? [Section titled “floor?”](#floor-2) `number` = `DEFAULT_CONFIDENCE_FLOOR` #### Returns [Section titled “Returns”](#returns-13) `number` *** ### decayConfidenceNFp() [Section titled “decayConfidenceNFp()”](#decayconfidencenfp)
```ts
1
function decayConfidenceNFp(
2
alphaFp,
3
confFp,
4
floorFp,
5
cycles
6
): number;
```
Fold `cycles` decay steps. `cycles <= 0` is a no-op, so a phase re-run within one night writes the same value. #### Parameters [Section titled “Parameters”](#parameters-14) ##### alphaFp [Section titled “alphaFp”](#alphafp-3) `number` ##### confFp [Section titled “confFp”](#conffp-1) `number` ##### floorFp [Section titled “floorFp”](#floorfp-1) `number` ##### cycles [Section titled “cycles”](#cycles-1) `number` #### Returns [Section titled “Returns”](#returns-14) `number` *** ### ewmaStepFp() [Section titled “ewmaStepFp()”](#ewmastepfp)
```ts
1
function ewmaStepFp(
2
alphaFp,
3
prevFp,
4
signalFp
5
): number;
```
One EWMA step on the grid: `alpha * signal + (1 - alpha) * prev`, divided back down with **floor** division. Floor rather than round-half-away because it is the one rounding mode whose N-fold composition is reproducible without a rounding-mode argument, and the residual drift against an unrounded fold is bounded by one grid step. For `alphaFp` in `[0, SCALE]` and both values in `[NEG_ONE_FP, POS_ONE_FP]` the result stays in that domain and lies between `prevFp` and `signalFp`, so a negative signal can never raise the score. #### Parameters [Section titled “Parameters”](#parameters-15) ##### alphaFp [Section titled “alphaFp”](#alphafp-4) `number` ##### prevFp [Section titled “prevFp”](#prevfp-2) `number` ##### signalFp [Section titled “signalFp”](#signalfp) `number` #### Returns [Section titled “Returns”](#returns-15) `number` *** ### excludeSelfSupersede() [Section titled “excludeSelfSupersede()”](#excludeselfsupersede)
```ts
1
function excludeSelfSupersede(canonicalPath, memberPaths): readonly string[];
```
The compress-path exclusion: the members of a batch to supersede and archive, with the canonical removed and order preserved. When a batch folds into a pre-existing canonical, a member can *be* that canonical, and archiving it would destroy the file just folded into. #### Parameters [Section titled “Parameters”](#parameters-16) ##### canonicalPath [Section titled “canonicalPath”](#canonicalpath) `string` ##### memberPaths [Section titled “memberPaths”](#memberpaths) readonly `string`\[] #### Returns [Section titled “Returns”](#returns-16) readonly `string`\[] *** ### float32View() [Section titled “float32View()”](#float32view)
```ts
1
function float32View(bytes): Float32Array | undefined;
```
A `Float32Array` over stored bytes, copying only when it must. `Float32Array` requires a 4-byte-aligned `byteOffset`, and a driver row’s `Uint8Array` may be a view into a pooled buffer at any offset. Viewing in place is the common case and costs nothing. A misaligned or ragged blob is copied rather than rejected, because the vector arm’s job is to rank and a throw here would fail a whole search over one row. #### Parameters [Section titled “Parameters”](#parameters-17) ##### bytes [Section titled “bytes”](#bytes) `Uint8Array` #### Returns [Section titled “Returns”](#returns-17) `Float32Array`<`ArrayBufferLike`> | `undefined` *** ### frameKeyOf() [Section titled “frameKeyOf()”](#framekeyof)
```ts
1
function frameKeyOf(gist): string | null;
```
The frame key for one claim, or `null` when the claim states no frame+value shape this rule trusts. Pure and synchronous, with no clock, no randomness, no model, and no I/O, so a full index rebuild reproduces byte-identical keys by construction. Case- and whitespace-insensitive, because a restated fact varies in both: `"The Capital of India is X"` and `"the capital of India is Y"` collide. #### Parameters [Section titled “Parameters”](#parameters-18) ##### gist [Section titled “gist”](#gist-1) `string` The claim text. In memhtml, this is a memory’s `` claim. #### Returns [Section titled “Returns”](#returns-18) `string` | `null` The lowercased frame, or `null` for no-frame-shape (stored as SQL NULL). *** ### fromFp() [Section titled “fromFp()”](#fromfp)
```ts
1
function fromFp(valueFp): number;
```
A grid value back to a float. Exact. #### Parameters [Section titled “Parameters”](#parameters-19) ##### valueFp [Section titled “valueFp”](#valuefp) `number` #### Returns [Section titled “Returns”](#returns-19) `number` *** ### fuseArms() [Section titled “fuseArms()”](#fusearms)
```ts
1
function fuseArms(
2
armResults,
3
weights,
4
k?
5
): ReadonlyMap;
```
Fuse per-arm ranked lists into one `path -> score` map by summing contributions. `armResults[i]` pairs with `weights[i]`; an arm with no weight scores 0 throughout. Addition commutes, so the result is order-insensitive across arms and the arm registry’s order is a presentation detail rather than a ranking input. A duplicate path inside one arm’s list accumulates, matching the SQL’s `SUM` over a `UNION ALL`. #### Parameters [Section titled “Parameters”](#parameters-20) ##### armResults [Section titled “armResults”](#armresults) readonly readonly [`ArmHit`](/api/domain/#armhit)\[]\[] ##### weights [Section titled “weights”](#weights) readonly `number`\[] ##### k? [Section titled “k?”](#k) `number` = `RRF_K` #### Returns [Section titled “Returns”](#returns-20) `ReadonlyMap`<`string`, `number`> *** ### fuseLateral() [Section titled “fuseLateral()”](#fuselateral)
```ts
1
function fuseLateral(
2
fused,
3
order,
4
weight,
5
k?
6
): ReadonlyMap;
```
Fold a graph-walk result into existing fused scores, treating the walk’s best-first order as an arm’s ranked list. A node absent from `fused` enters at its lateral-only contribution, which is how a memory the lexical and vector arms both missed can still surface. An empty `order` or a non-positive `weight` returns the scores unchanged, so the lateral arm is inert when dark and cannot fail on a cold graph. #### Parameters [Section titled “Parameters”](#parameters-21) ##### fused [Section titled “fused”](#fused) `ReadonlyMap`<`string`, `number`> ##### order [Section titled “order”](#order) readonly `string`\[] ##### weight [Section titled “weight”](#weight) `number` ##### k? [Section titled “k?”](#k-1) `number` = `RRF_K` #### Returns [Section titled “Returns”](#returns-21) `ReadonlyMap`<`string`, `number`> *** ### halfLifeFor() [Section titled “halfLifeFor()”](#halflifefor)
```ts
1
function halfLifeFor(memoryType): number | null;
```
The recency half-life in days for a memory type; `null` means no time decay. #### Parameters [Section titled “Parameters”](#parameters-22) ##### memoryType [Section titled “memoryType”](#memorytype-1) `string` #### Returns [Section titled “Returns”](#returns-22) `number` | `null` *** ### isCommittableConfidenceChange() [Section titled “isCommittableConfidenceChange()”](#iscommittableconfidencechange)
```ts
1
function isCommittableConfidenceChange(before, after): boolean;
```
True when a decayed confidence differs enough from the stored one to be worth a commit. #### Parameters [Section titled “Parameters”](#parameters-23) ##### before [Section titled “before”](#before) `number` ##### after [Section titled “after”](#after) `number` #### Returns [Section titled “Returns”](#returns-23) `boolean` *** ### joinMergeText() [Section titled “joinMergeText()”](#joinmergetext)
```ts
1
function joinMergeText(text): string;
```
The joined text the negation and variant predicates read: the claim, a newline, the article. #### Parameters [Section titled “Parameters”](#parameters-24) ##### text [Section titled “text”](#text) [`MergeText`](/api/domain/#mergetext) #### Returns [Section titled “Returns”](#returns-24) `string` *** ### labelPropagation() [Section titled “labelPropagation()”](#labelpropagation)
```ts
1
function labelPropagation(
2
nodes,
3
edges,
4
options?
5
): ReadonlyMap;
```
Communities by label propagation over the edge list read as undirected. Deterministic in place of a seed: nodes start labelled by themselves, sweeps visit them in sorted order, and a node adopts the highest-weighted neighboring label with the lexicographically smallest label breaking a tie. The returned labels are canonicalized to the smallest member path of each community, so the partition itself is reproducible and not only the grouping. Communities below `minCommunitySize` collapse to `undefined`: they only feed compress batching and the bridge count, and a pair passed off as a community would make every cross-pair edge look like a bridge. #### Parameters [Section titled “Parameters”](#parameters-25) ##### nodes [Section titled “nodes”](#nodes-1) readonly `string`\[] ##### edges [Section titled “edges”](#edges-2) readonly [`GraphEdge`](/api/domain/#graphedge)\[] ##### options? [Section titled “options?”](#options) ###### maxSweeps? [Section titled “maxSweeps?”](#maxsweeps) `number` ###### minCommunitySize? [Section titled “minCommunitySize?”](#mincommunitysize) `number` #### Returns [Section titled “Returns”](#returns-25) `ReadonlyMap`<`string`, `string` | `undefined`> *** ### mergeCandidates() [Section titled “mergeCandidates()”](#mergecandidates)
```ts
1
function mergeCandidates(pairs, options?): readonly MergeDecision[];
```
Filter oriented candidate pairs into an in-batch-consistent decision list: the `committed` outcomes of [mergeOutcomes](/api/domain/#mergeoutcomes), in input order, as decisions. #### Parameters [Section titled “Parameters”](#parameters-26) ##### pairs [Section titled “pairs”](#pairs) readonly [`MergePair`](/api/domain/#mergepair)\[] ##### options? [Section titled “options?”](#options-1) ###### maxPairs? [Section titled “maxPairs?”](#maxpairs) `number` ###### threshold? [Section titled “threshold?”](#threshold) `number` #### Returns [Section titled “Returns”](#returns-26) readonly [`MergeDecision`](/api/domain/#mergedecision)\[] *** ### mergeOutcomes() [Section titled “mergeOutcomes()”](#mergeoutcomes)
```ts
1
function mergeOutcomes(pairs, options?): readonly MergeOutcome[];
```
Classify every oriented candidate pair, applying in order: the per-cycle cap, the strict similarity threshold, the divergence veto (only when both texts are present), a self-merge check, and the in-batch role guard. [mergeCandidates](/api/domain/#mergecandidates) is the committed subset; this form exists so a caller can COUNT the refusals by cause. A phase that reported `candidates - decisions` as “vetoed” told an operator that a predicate fired on pairs no predicate had seen, which is how two all-refusing runs read as a divergence problem when half of them were role-guard skips. The **in-batch role guard** needs the most explanation of the five. Every committed decision fixes its drop path as DROPPED and its keep path as KEPT for the rest of the batch, and three later uses are refused, each ruling out a distinct corruption on a transitive chain: * A path already **dropped** cannot be dropped again (two keepers would each believe they absorbed it) nor become a keeper (content folded into a file this same batch archives). * A path already a **keeper** cannot later be dropped. Given `(gf, a)` then `(b, gf)`, both would commit, `gf` absorbs `a` and is then archived into `b`, so `a`’s content is superseded into a file that no longer exists. That is the loss the guard exists to prevent. Verified against the input `[(gf → a), (b → gf)]`. A path already a **keeper** MAY keep again. `(k, a)` then `(k, b)` is one page absorbing two duplicates in one batch, and nothing on that chain is archived twice or archived after being folded into: `k` survives with two `supersedes` links and both drops point at it. The earlier rule claimed BOTH roles of a committed pair, which made a model group of N members fold at most ONE of them per run — every pair a group implies names the same keeper — and reported the other N-2 as vetoes. The guard’s job is the two corruptions above, not a fold budget per keeper; the per-cycle cap is that. Fixing roles rather than memberships keeps the output a function of input order alone. The first decision claiming a path in a role wins, matching the SQL result-set iteration upstream, and a later pair contradicting it is skipped rather than reordering anything. #### Parameters [Section titled “Parameters”](#parameters-27) ##### pairs [Section titled “pairs”](#pairs-1) readonly [`MergePair`](/api/domain/#mergepair)\[] ##### options? [Section titled “options?”](#options-2) ###### maxPairs? [Section titled “maxPairs?”](#maxpairs-1) `number` ###### threshold? [Section titled “threshold?”](#threshold-1) `number` #### Returns [Section titled “Returns”](#returns-27) readonly [`MergeOutcome`](/api/domain/#mergeoutcome)\[] *** ### mergeVetoed() [Section titled “mergeVetoed()”](#mergevetoed)
```ts
1
function mergeVetoed(textA, textB): boolean;
```
The veto: the disjunction of the three divergence predicates, each over its own text. Symmetric, pure, total. A vetoed pair is never a duplicate no matter its cosine. #### Parameters [Section titled “Parameters”](#parameters-28) ##### textA [Section titled “textA”](#texta) [`MergeText`](/api/domain/#mergetext) ##### textB [Section titled “textB”](#textb) [`MergeText`](/api/domain/#mergetext) #### Returns [Section titled “Returns”](#returns-28) `boolean` *** ### mergeVetoReasons() [Section titled “mergeVetoReasons()”](#mergevetoreasons)
```ts
1
function mergeVetoReasons(textA, textB): readonly MergeVetoReason[];
```
Which divergence predicates fire on a pair, each over the text it reads (see [MergeText](/api/domain/#mergetext)): negation and variant over the joined text, numeric over the gist alone. Symmetric, pure, total. Empty exactly when [mergeVetoed](/api/domain/#mergevetoed) is false. Exported so a caller that must NAME the refusal (the review task a vetoed pair becomes, the acceptance arm’s per-pair stage) reads the same scoping the filter applied, instead of re-deriving which text each predicate saw. #### Parameters [Section titled “Parameters”](#parameters-29) ##### textA [Section titled “textA”](#texta-1) [`MergeText`](/api/domain/#mergetext) ##### textB [Section titled “textB”](#textb-1) [`MergeText`](/api/domain/#mergetext) #### Returns [Section titled “Returns”](#returns-29) readonly [`MergeVetoReason`](/api/domain/#mergevetoreason)\[] *** ### negationDivergent() [Section titled “negationDivergent()”](#negationdivergent)
```ts
1
function negationDivergent(textA, textB): boolean;
```
True when exactly one side carries a negation marker: “X is safe” against “X is NOT safe”. Symmetric. Both sides negating, or neither, is not divergent. #### Parameters [Section titled “Parameters”](#parameters-30) ##### textA [Section titled “textA”](#texta-2) `string` ##### textB [Section titled “textB”](#textb-2) `string` #### Returns [Section titled “Returns”](#returns-30) `boolean` *** ### numericTokenDivergent() [Section titled “numericTokenDivergent()”](#numerictokendivergent)
```ts
1
function numericTokenDivergent(textA, textB): boolean;
```
True when the two texts carry different numeric tokens: “retry 3 times” against “retry 13 times”. Two texts with no numbers at all, or with identical numbers, do not trip it. Symmetric. [mergeVetoReasons](/api/domain/#mergevetoreasons) hands it the GIST of each memory, not the article — see [MergeText](/api/domain/#mergetext) for why. #### Parameters [Section titled “Parameters”](#parameters-31) ##### textA [Section titled “textA”](#texta-3) `string` ##### textB [Section titled “textB”](#textb-3) `string` #### Returns [Section titled “Returns”](#returns-31) `boolean` *** ### pagerank() [Section titled “pagerank()”](#pagerank)
```ts
1
function pagerank(
2
nodes,
3
edges,
4
options?
5
): ReadonlyMap;
```
PageRank by power iteration, weighted, with a uniform or personalized teleport prior. `seeds` present makes this personalized PageRank: the teleport distribution is the seed weights rather than uniform, so the scores describe reachability *from the query’s own hits*. That is what keeps the lateral retrieval arm query-conditioned. A uniform prior would inject the same hub memories into every query’s results regardless of what was asked. Seeds naming absent nodes are dropped, and an all-absent seed set falls back to the uniform prior, which the caller reads as “no lateral result”. Dangling nodes (no outbound edges) have their mass redistributed over the teleport distribution, so the scores sum to 1 rather than leaking. #### Parameters [Section titled “Parameters”](#parameters-32) ##### nodes [Section titled “nodes”](#nodes-2) readonly `string`\[] ##### edges [Section titled “edges”](#edges-3) readonly [`GraphEdge`](/api/domain/#graphedge)\[] ##### options? [Section titled “options?”](#options-3) ###### damping? [Section titled “damping?”](#damping) `number` ###### maxIterations? [Section titled “maxIterations?”](#maxiterations) `number` ###### seeds? [Section titled “seeds?”](#seeds) `ReadonlyMap`<`string`, `number`> ###### tolerance? [Section titled “tolerance?”](#tolerance) `number` #### Returns [Section titled “Returns”](#returns-32) `ReadonlyMap`<`string`, `number`> *** ### partitionByCooldown() [Section titled “partitionByCooldown()”](#partitionbycooldown)
```ts
1
function partitionByCooldown(
2
entries,
3
now,
4
cooldownSeconds?
5
): object;
```
Split paths into those whose access stamp may be bumped now and those still cooling down. One pass, order-preserving, so `memory_reinforce` can answer both lists from one call. #### Parameters [Section titled “Parameters”](#parameters-33) ##### entries [Section titled “entries”](#entries) readonly `object`\[] ##### now [Section titled “now”](#now) `Date` ##### cooldownSeconds? [Section titled “cooldownSeconds?”](#cooldownseconds) `number` = `REINFORCE_COOLDOWN_S` #### Returns [Section titled “Returns”](#returns-33) `object` ##### bumped [Section titled “bumped”](#bumped)
```ts
1
readonly bumped: readonly string[];
```
##### cooledDown [Section titled “cooledDown”](#cooleddown)
```ts
1
readonly cooledDown: readonly string[];
```
*** ### profileWeightSum() [Section titled “profileWeightSum()”](#profileweightsum)
```ts
1
function profileWeightSum(profile): number;
```
A profile’s weight sum under compensated summation. Every shipped profile returns exactly `1`; this is the convexity fact the composite’s `[0, 1]` range rests on. #### Parameters [Section titled “Parameters”](#parameters-34) ##### profile [Section titled “profile”](#profile-1) [`WeightProfile`](/api/domain/#weightprofile) #### Returns [Section titled “Returns”](#returns-34) `number` *** ### rankCandidatePairs() [Section titled “rankCandidatePairs()”](#rankcandidatepairs)
```ts
1
function rankCandidatePairs(
2
pairs,
3
vectors,
4
options
5
): readonly NeighborPair[];
```
Rank an ENUMERATED pair set: similarity, floor, per-source top-`k`, final ordering, cap. This is the shape for a consumer whose candidate pairs come from a selective predicate — the conflict scan’s shared-entity join — rather than from the whole pair space. The predicate runs BEFORE ranking, exactly as a `WHERE` inside the ranking CTE would, so per-source top-`k` is computed over passing pairs only. A pair naming a key with no vector contributes nothing, the same outcome the SQL join’s missing-embedding row produces. #### Parameters [Section titled “Parameters”](#parameters-35) ##### pairs [Section titled “pairs”](#pairs-2) readonly [`CandidatePair`](/api/domain/#candidatepair)\[] ##### vectors [Section titled “vectors”](#vectors) readonly [`KeyedVector`](/api/domain/#keyedvector)\[] ##### options [Section titled “options”](#options-4) [`NeighborOptions`](/api/domain/#neighboroptions) #### Returns [Section titled “Returns”](#returns-35) readonly [`NeighborPair`](/api/domain/#neighborpair)\[] *** ### rankFused() [Section titled “rankFused()”](#rankfused)
```ts
1
function rankFused(fused): readonly object[];
```
Fused scores as a ranked list, best first. Ties break on path ascending, so the ordering is total and reproducible. Two runs over the same corpus produce the same list, which is what the discrimination gate compares against. #### Parameters [Section titled “Parameters”](#parameters-36) ##### fused [Section titled “fused”](#fused-1) `ReadonlyMap`<`string`, `number`> #### Returns [Section titled “Returns”](#returns-36) readonly `object`\[] *** ### reprieveScore() [Section titled “reprieveScore()”](#reprievescore)
```ts
1
function reprieveScore(input): number;
```
The four-term reprieve score. Deliberately **not** convex: the `log1p(accessCount)` term is unbounded, so the score can exceed 1. It is proven only monotone and sign-clamped. A negative `outcomeScore` contributes exactly 0 and does not subtract, mirroring the salience arm’s `max(coalesce(outcome_score, 0.0), 0.0)`. Without that clamp a memory that once produced a bad outcome would be punished twice, once by the outcome EWMA that already lowered its salience, and again here by having its reprieve pushed below the floor. #### Parameters [Section titled “Parameters”](#parameters-37) ##### input [Section titled “input”](#input-1) [`ReprieveInput`](/api/domain/#reprieveinput) #### Returns [Section titled “Returns”](#returns-37) `number` *** ### rrfContribution() [Section titled “rrfContribution()”](#rrfcontribution)
```ts
1
function rrfContribution(rank, weight): Option;
```
One arm’s contribution to a fused score. `rank` is 1-based within that arm’s candidate list; `weight` is the arm’s configured multiplier. A zero-weight arm and an out-of-range rank both yield `None` rather than 0, so a disabled arm is structurally absent from the fold instead of silently adding a neutral term that later arithmetic could mistake for a real score. #### Parameters [Section titled “Parameters”](#parameters-38) ##### rank [Section titled “rank”](#rank-1) `number` ##### weight [Section titled “weight”](#weight-1) `number` #### Returns [Section titled “Returns”](#returns-38) `Option`<`number`> *** ### rrfScore() [Section titled “rrfScore()”](#rrfscore)
```ts
1
function rrfScore(
2
rank,
3
weight,
4
k?
5
): number;
```
One arm’s contribution: `weight / (rank + k)`, unitless. Strictly decreasing in `rank` and linear in `weight`, so a weight-0 arm contributes exactly 0 and is inert in the fold. `k` damps the difference between the top ranks, which is what stops one arm’s confident first place from dominating four other arms’ consensus. #### Parameters [Section titled “Parameters”](#parameters-39) ##### rank [Section titled “rank”](#rank-2) `number` ##### weight [Section titled “weight”](#weight-2) `number` ##### k? [Section titled “k?”](#k-2) `number` = `RRF_K` #### Returns [Section titled “Returns”](#returns-39) `number` *** ### scoreRetention() [Section titled “scoreRetention()”](#scoreretention)
```ts
1
function scoreRetention(input): RetentionScore;
```
Score one memory: normalize, weight by its type’s profile, band the composite. #### Parameters [Section titled “Parameters”](#parameters-40) ##### input [Section titled “input”](#input-2) [`RetentionInput`](/api/domain/#retentioninput) #### Returns [Section titled “Returns”](#returns-40) [`RetentionScore`](/api/domain/#retentionscore) *** ### shouldBumpAccess() [Section titled “shouldBumpAccess()”](#shouldbumpaccess)
```ts
1
function shouldBumpAccess(
2
lastAccessedAt,
3
now,
4
cooldownSeconds?
5
): boolean;
```
The reinforcement cooldown predicate. Its twin is the salience arm’s SQL guard:
```sql
1
WHERE last_accessed_at IS NULL
2
OR unixepoch('now') - unixepoch(last_accessed_at) >= 900
```
SQL cannot call this function, so the shared source of truth is the window constant [REINFORCE\_COOLDOWN\_S](/api/domain/#reinforce_cooldown_s) and the boundary behavior is pinned by a property test on both sides. The `>=` here matches the SQL’s `>=`: a stamp exactly `cooldownSeconds` old **is** bumpable. The cooldown exists because `access_count` feeds the salience RRF arm. Without it, replaying one query ten times would inflate that memory’s salience tenfold and let a loop in an agent rewrite the corpus’s ranking. #### Parameters [Section titled “Parameters”](#parameters-41) ##### lastAccessedAt [Section titled “lastAccessedAt”](#lastaccessedat) `Date` | `undefined` ##### now [Section titled “now”](#now-1) `Date` ##### cooldownSeconds? [Section titled “cooldownSeconds?”](#cooldownseconds-1) `number` = `REINFORCE_COOLDOWN_S` #### Returns [Section titled “Returns”](#returns-41) `boolean` *** ### shouldReprieve() [Section titled “shouldReprieve()”](#shouldreprieve)
```ts
1
function shouldReprieve(input): boolean;
```
The bounded reprieve gate. A TTL-passed memory is reprieved iff its score clears `floor` AND it has been reprieved fewer than `maxReprieves` times. `maxReprieves: 0` forces every TTL-passed memory to expire regardless of score. #### Parameters [Section titled “Parameters”](#parameters-42) ##### input [Section titled “input”](#input-3) ###### floor? [Section titled “floor?”](#floor-3) `number` ###### maxReprieves? [Section titled “maxReprieves?”](#maxreprieves) `number` ###### reprieveCount [Section titled “reprieveCount”](#reprievecount) `number` ###### score [Section titled “score”](#score-3) `number` #### Returns [Section titled “Returns”](#returns-42) `boolean` *** ### signalValue() [Section titled “signalValue()”](#signalvalue)
```ts
1
function signalValue(signal): number;
```
The outcome-EWMA signal value for a reinforcement, unitless in `[-1, 1]`. `neutral` is 0, so a neutral reinforcement bumps the access count without moving the outcome score. A memory being read is evidence of relevance, not of correctness. #### Parameters [Section titled “Parameters”](#parameters-43) ##### signal [Section titled “signal”](#signal) `"positive"` | `"negative"` | `"neutral"` #### Returns [Section titled “Returns”](#returns-43) `number` *** ### toFp() [Section titled “toFp()”](#tofp)
```ts
1
function toFp(value): number;
```
A float in `[-1, 1]` onto the grid, rounded to the nearest grid point. #### Parameters [Section titled “Parameters”](#parameters-44) ##### value [Section titled “value”](#value) `number` #### Returns [Section titled “Returns”](#returns-44) `number` *** ### topNeighborPairs() [Section titled “topNeighborPairs()”](#topneighborpairs)
```ts
1
function topNeighborPairs(vectors, options): readonly NeighborPair[];
```
Per-source top-`k` nearest neighbors above a similarity floor, over every unordered pair. Each pair’s similarity is computed ONCE and offered to BOTH endpoints’ neighborhoods, so the output can hold `(a, b)` and `(b, a)` — each is a fact about a different source’s neighborhood, and a consumer folding pairs must dedup the mirror itself (dedup-merge does, with its `seen` set). A floor comparison a NaN similarity cannot pass keeps a vector carrying NaN bytes out of every neighborhood rather than poisoning an ordering. #### Parameters [Section titled “Parameters”](#parameters-45) ##### vectors [Section titled “vectors”](#vectors-1) readonly [`KeyedVector`](/api/domain/#keyedvector)\[] ##### options [Section titled “options”](#options-5) [`NeighborOptions`](/api/domain/#neighboroptions) #### Returns [Section titled “Returns”](#returns-45) readonly [`NeighborPair`](/api/domain/#neighborpair)\[] *** ### variantQualifierDivergent() [Section titled “variantQualifierDivergent()”](#variantqualifierdivergent)
```ts
1
function variantQualifierDivergent(textA, textB): boolean;
```
True when the two bodies carry different variant qualifiers: “M1” against “M1 Pro”. Symmetric. #### Parameters [Section titled “Parameters”](#parameters-46) ##### textA [Section titled “textA”](#texta-4) `string` ##### textB [Section titled “textB”](#textb-4) `string` #### Returns [Section titled “Returns”](#returns-46) `boolean` *** ### weightsFor() [Section titled “weightsFor()”](#weightsfor)
```ts
1
function weightsFor(memoryType): WeightProfile;
```
The weight profile for a memory type. #### Parameters [Section titled “Parameters”](#parameters-47) ##### memoryType [Section titled “memoryType”](#memorytype-2) `string` #### Returns [Section titled “Returns”](#returns-47) [`WeightProfile`](/api/domain/#weightprofile)
# @memhtml/eval
`@memhtml/eval`: the fixture corpus generator, the refusable retrieval discrimination gate, and the write-path discrimination arm. The retrieval corpus is never committed; each run regenerates it from a seed. The write-path corpus is committed, because its labels are the oracle. The gate asks one question: can the retrieval stack rank a memory above its own high-similarity WRONG twin? `controls` turns a true claim into a plausible impostor along three divergence families (negation, numeric, variant), validates each control against its own family’s predicate from `@memhtml/domain`, and discards a control that failed to diverge. `corpus` is a pure function of `(seed, now)`: it yields the same memory specs and probes on any machine, with every timestamp anchored as an offset behind the run so that salience decay cannot move the ranks from one calendar day to the next. `fixture` is the one module that writes, rendering a spec into a real git repository under a temp directory. `harness` builds the stack the gate measures (that fixture, a real database with the shipped migrations, the real indexer and the real four-arm retrieval) and substitutes only the embedder: a deterministic one whose cosine relations are a pure function of the text, so the numbers reproduce without credentials. `discriminate` runs the probes and reports two numbers, of which the strict one is the gate: `mrr` against a floor of 0.85, and a per-probe inversion count where one inversion fails the run regardless of MRR. `run` picks the mode and shapes the outcome so that a skipped gate never looks like a passing one: `fake` mode runs everywhere, and `live` mode without credentials reports `skipped: true` and `passed: false`. The write-path arm asks the other question the 2026-08-27 eval plan posed: does the WRITE path tell a good candidate memory from a bad one? `write-path-corpus` is a labeled corpus of 62 candidate memories (31 the gate should write, 31 it should refuse), each with the provenance of its label (the contract clause or design rule that decides it) and two fixture transcripts every good quote is a verbatim span of. `write-path-gate` composes the production gate from its own functions in the order the fleet runs them: the consolidator door’s schema decode, readable-session check and verbatim-quote check, then the sleep phase’s per-candidate refusal (`candidateRefusalFor`). Nothing is re-implemented. `write-path-metrics` scores the decisions with refusal as the positive class (precision, recall, F1, the four-cell confusion matrix, and a per-class breakdown naming the items that disagreed with their label). `write-path-run` runs the arm, derives the floor (the plan named none, so it is the measured baseline F1 0.9333 minus five points, 0.88, stated as such), and freezes every decision into `fixtures/write-path-discrimination.manifest.json`, which the `test:eval` tier replays decision for decision. `pnpm freeze:write-path` is the one way that manifest changes. Three failure classes are labeled bad and accepted by the gate today (a duplicated evidence quote, twice; a claim that is a verbatim transcript span), and one good item is refused for a whitespace-padded session id; `KNOWN_GAP_CLASSES` states exactly that set and the tier fails if the set moves in either direction. The write-path ACCEPTANCE arm asks the question the discrimination arm cannot: does `dedup-merge` FOLD the pairs it should? A phase that refuses everything scores perfectly on refusal, which is what production did on 2026-09-10 and 2026-09-11 (`vetoed: 171/171`, `vetoed: 178/178`, `merged: 0`). `write-path-acceptance-corpus` is 19 labeled groups (12 `merge`, 7 `keep`), each the answer a model would have given, with the provenance of its label. `write-path-acceptance` runs them through the phase’s own fold path — `dedupMergeTextFor`, `groupPairsFor`, and `mergeOutcomes` under `DEDUP_ADMIT_FLOOR`, all imported — with a fold as the positive class, reports acceptance (folds over `merge` pairs), precision, and where every refused pair stopped (read off the filter’s own `MergeStage`, a veto refined to its predicate by `mergeVetoReasons`), and gates on an acceptance floor (measured baseline 1.0 minus five points, 0.95) plus zero folds of a `keep` pair. `ACCEPTANCE_KNOWN_GAP_CLASSES` is empty: the two mechanisms behind the all-veto runs — `numericTokenDivergent` over the whole article vetoing the same claim for an incidental number in the body (`incidental-number`), and a role guard that claimed both roles folding at most one member of a model group (`group-fanout`) — measured 8 of 12 under the old rules and 14 of 14 under the current ones, which read numbers off the gist and admit a repeated keeper. The `keep` side holds the counterexamples the new rules must still refuse (a numeric conflict in the claim, a negation or variant qualifier only in the body, a templated series whose claims quote different ids), and `BODY_NUMBER_ONLY_GROUP` states the shape the gist rule no longer refuses, measured outside the gated corpus. This package sits above `@memhtml/contracts`, `@memhtml/domain`, `@memhtml/html`, `@memhtml/index`, `@memhtml/llm`, `@memhtml/store`, `@memhtml/sleep` and `@memhtml/consolidator` (the last two only for the write-path arm, which imports the gate it measures), and `apps/cli` depends on it: `memhtml eval discriminate` is `runDiscrimination`, and `@memhtml/sleep`’s merge takes `discriminationGate` as its pre-merge gate from the CLI’s wiring, because the sleep package supplies no default. ## Modules [Section titled “Modules”](#modules) The package publishes one import path, `@memhtml/eval`, which re-exports the twelve modules below, so the reference on this page is the whole exported surface. `gen-fixture.ts` is the body of `pnpm gen:fixture` and is not exported; `scripts/freeze-write-path.mjs` is the body of `pnpm freeze:write-path` and lives outside `src/` because it resolves a repo fixture the published package does not ship. | Module | What it holds | | --------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | `controls.ts` | `DIVERGENCE_FAMILIES`, the three flips (`negationFlip`, `numericFlip`, `variantFlip`), `familyPredicate` and `deriveControl`. | | `corpus.ts` | `MemorySpec`, `Probe`, `CorpusSpec`, the defaults (`DEFAULT_SEED`, `DEFAULT_CORPUS_SIZE`, `DEFAULT_PROBE_COUNT`), `quantizeNow`, `articleFor`, `queryFor` and `buildCorpus`. | | `discriminate.ts` | `MRR_FLOOR`, `PROBE_LIMIT`, `runProbes`, `summarize`, `discriminate`, `runFloor`, `describeFailure` and the report shapes. | | `fixture.ts` | `FixtureCorpus`, `FixtureOptions`, `memoryFileFor`, `writeCorpus` and `makeFixtureCorpus`. | | `harness.ts` | `EvalStack`, `StackOptions`, the embedders (`fakeEmbedder`, `failingEmbedder`, `liveEmbedder`, `fakeVector`, `FAKE_DIM`), `buildStack` and `withStack`. | | `run.ts` | `EvalOptions`, `EvalOutcome`, `BEDROCK_TOKEN_VAR`, `hasBedrockCredentials`, `runDiscrimination`, `DiscriminationFailed` and `discriminationGate`. | | `write-path-corpus.ts` | `WRITE_PATH_CORPUS` (62 labeled candidates), `TRANSCRIPTS`, `KNOWN_GAP_CLASSES`, `CEILINGS`, `MANY_SPANS`, `padTo` and the fixture session ids. | | `write-path-gate.ts` | `admitCandidate`, `GATE_STAGES`, `GateDecision`, `GateBatch`: the production gate composed stage by stage. | | `write-path-metrics.ts` | `summarizeWritePath`, `confusionOf`, `breakdownByClass`, `agrees`, `describeWritePath` and the report shapes. | | `write-path-acceptance-corpus.ts` | `ACCEPTANCE_CORPUS` (16 labeled groups), `ACCEPTANCE_KNOWN_GAP_CLASSES` and the group shapes. | | `write-path-acceptance.ts` | `runWritePathAcceptance`, `ACCEPTANCE_FLOOR`, `ACCEPTANCE_BASELINE`, `impliedPairs`, `decideAcceptance`, `summarizeAcceptance`, `acceptanceByClass`, `describeAcceptance` and the report shapes. | | `write-path-run.ts` | `runWritePathDiscrimination`, `WRITE_PATH_F1_FLOOR`, `WRITE_PATH_BASELINE_F1`, `GATE_SOURCES`, `MANIFEST_FILENAME`, `manifestFor`, `replayDrift`, `readManifest`, `writeManifest`, `withBatch`, `decideAll`. | ## Classes [Section titled “Classes”](#classes) ### DiscriminationFailed [Section titled “DiscriminationFailed”](#discriminationfailed) A failed gate, as a tagged error. Tagged so it is a failure like every other in the system rather than a bare value. The CLI’s `codeFor` switches on `_tag`, and `ERR_DISCRIMINATION_FAILED`, the error code design §8 named for exactly this refusal, would otherwise have no producer at all and degrade to `ERR_UNKNOWN`. The whole outcome rides along, because a refusal an operator cannot reproduce is a refusal they will override. `seed` regenerates the corpus that failed, and `inversions` says which probes. #### Constructors [Section titled “Constructors”](#constructors) ##### Constructor [Section titled “Constructor”](#constructor)
```ts
1
new DiscriminationFailed(outcome): DiscriminationFailed;
```
###### Parameters [Section titled “Parameters”](#parameters) ###### outcome [Section titled “outcome”](#outcome) [`EvalOutcome`](/api/eval/#evaloutcome) ###### Returns [Section titled “Returns”](#returns) [`DiscriminationFailed`](/api/eval/#discriminationfailed) #### Properties [Section titled “Properties”](#properties) ##### \_tag [Section titled “\_tag”](#_tag)
```ts
1
readonly _tag: "DiscriminationFailed" = "DiscriminationFailed";
```
##### outcome [Section titled “outcome”](#outcome-1)
```ts
1
readonly outcome: EvalOutcome;
```
#### Accessors [Section titled “Accessors”](#accessors) ##### reason [Section titled “reason”](#reason) ###### Get Signature [Section titled “Get Signature”](#get-signature)
```ts
1
get reason(): string;
```
The one-line summary, used as the envelope’s human message. ###### Returns [Section titled “Returns”](#returns-1) `string` ## Interfaces [Section titled “Interfaces”](#interfaces) ### AcceptanceClassBreakdown [Section titled “AcceptanceClassBreakdown”](#acceptanceclassbreakdown) One class’s agreement with its labels. #### Properties [Section titled “Properties”](#properties-1) ##### agreed [Section titled “agreed”](#agreed)
```ts
1
readonly agreed: number;
```
Decisions that matched the label: folded for `merge`, refused for `keep`. ##### class [Section titled “class”](#class)
```ts
1
readonly class: string;
```
##### disagreed [Section titled “disagreed”](#disagreed)
```ts
1
readonly disagreed: readonly string[];
```
##### label [Section titled “label”](#label)
```ts
1
readonly label: AcceptanceLabel;
```
##### pairs [Section titled “pairs”](#pairs)
```ts
1
readonly pairs: number;
```
*** ### AcceptanceMember [Section titled “AcceptanceMember”](#acceptancemember) One member of a group, as the phase’s `CorpusRow` would carry it. Order in the array is corpus order. #### Properties [Section titled “Properties”](#properties-2) ##### body [Section titled “body”](#body)
```ts
1
readonly body: string;
```
The ``’s text content, `files.body_text`. ##### gist [Section titled “gist”](#gist)
```ts
1
readonly gist: string;
```
The `` claim, `files.gist`. ##### path [Section titled “path”](#path)
```ts
1
readonly path: string;
```
*** ### AcceptanceOptions [Section titled “AcceptanceOptions”](#acceptanceoptions) What [runWritePathAcceptance](/api/eval/#runwritepathacceptance) takes. #### Properties [Section titled “Properties”](#properties-3) ##### acceptanceFloor? [Section titled “acceptanceFloor?”](#acceptancefloor)
```ts
1
readonly optional acceptanceFloor?: number;
```
##### corpus? [Section titled “corpus?”](#corpus)
```ts
1
readonly optional corpus?: readonly LabeledGroup[];
```
*** ### AcceptanceReport [Section titled “AcceptanceReport”](#acceptancereport) The report the gate reads. #### Properties [Section titled “Properties”](#properties-4) ##### acceptance [Section titled “acceptance”](#acceptance)
```ts
1
readonly acceptance: number;
```
`accepted / eligible`, four decimals; `0` when nothing was eligible. ##### acceptanceFloor [Section titled “acceptanceFloor”](#acceptancefloor-1)
```ts
1
readonly acceptanceFloor: number;
```
##### accepted [Section titled “accepted”](#accepted)
```ts
1
readonly accepted: number;
```
Folds among the eligible. ##### decisions [Section titled “decisions”](#decisions)
```ts
1
readonly decisions: readonly PairDecision[];
```
##### eligible [Section titled “eligible”](#eligible)
```ts
1
readonly eligible: number;
```
Pairs from `merge` groups: the ones that should fold. ##### f1 [Section titled “f1”](#f1)
```ts
1
readonly f1: number;
```
##### foldedKeep [Section titled “foldedKeep”](#foldedkeep)
```ts
1
readonly foldedKeep: number;
```
Folds among the guarded. Any value above zero fails the tier. ##### groups [Section titled “groups”](#groups)
```ts
1
readonly groups: number;
```
##### guarded [Section titled “guarded”](#guarded)
```ts
1
readonly guarded: number;
```
Pairs from `keep` groups. ##### pairs [Section titled “pairs”](#pairs-1)
```ts
1
readonly pairs: number;
```
##### passed [Section titled “passed”](#passed)
```ts
1
readonly passed: boolean;
```
True when both labels are present AND no `keep` pair folded AND `acceptance >= acceptanceFloor`. One label alone is a failure, not a vacuous pass, for the reason the discrimination arm gives. ##### perClass [Section titled “perClass”](#perclass)
```ts
1
readonly perClass: readonly AcceptanceClassBreakdown[];
```
##### precision [Section titled “precision”](#precision)
```ts
1
readonly precision: number;
```
`accepted / (accepted + foldedKeep)`, four decimals; `0` when nothing folded. ##### recall [Section titled “recall”](#recall)
```ts
1
readonly recall: number;
```
Same as `acceptance`, stated under the name the discrimination arm uses. *** ### ClaimText [Section titled “ClaimText”](#claimtext) The text of a memory as the flips see it, meaning the claim then each body paragraph. #### Extended by [Section titled “Extended by”](#extended-by) * [`DerivedControl`](/api/eval/#derivedcontrol) #### Properties [Section titled “Properties”](#properties-5) ##### body [Section titled “body”](#body-1)
```ts
1
readonly body: readonly string[];
```
##### claim [Section titled “claim”](#claim)
```ts
1
readonly claim: string;
```
*** ### ClassBreakdown [Section titled “ClassBreakdown”](#classbreakdown) One class’s agreement with its labels. #### Properties [Section titled “Properties”](#properties-6) ##### agreed [Section titled “agreed”](#agreed-1)
```ts
1
readonly agreed: number;
```
Decisions that matched the label: rejected for `bad`, accepted for `good`. ##### class [Section titled “class”](#class-1)
```ts
1
readonly class: string;
```
##### disagreed [Section titled “disagreed”](#disagreed-1)
```ts
1
readonly disagreed: readonly string[];
```
Ids of the items whose decision contradicted the label. ##### items [Section titled “items”](#items)
```ts
1
readonly items: number;
```
##### label [Section titled “label”](#label-1)
```ts
1
readonly label: WritePathLabel;
```
*** ### ConfusionMatrix [Section titled “ConfusionMatrix”](#confusionmatrix) The four cells, named by what happened rather than by TP/FP letters a reader has to re-derive. `badRejected` is the true positive, `goodRejected` the false positive, `badAccepted` the false negative, `goodAccepted` the true negative. #### Properties [Section titled “Properties”](#properties-7) ##### badAccepted [Section titled “badAccepted”](#badaccepted)
```ts
1
readonly badAccepted: number;
```
##### badRejected [Section titled “badRejected”](#badrejected)
```ts
1
readonly badRejected: number;
```
##### goodAccepted [Section titled “goodAccepted”](#goodaccepted)
```ts
1
readonly goodAccepted: number;
```
##### goodRejected [Section titled “goodRejected”](#goodrejected)
```ts
1
readonly goodRejected: number;
```
*** ### CorpusSpec [Section titled “CorpusSpec”](#corpusspec) The whole fixture: the memories to write, their access history, and the probes. #### Properties [Section titled “Properties”](#properties-8) ##### access [Section titled “access”](#access)
```ts
1
readonly access: readonly AccessSpec[];
```
`state.access` rows the harness seeds before running the probes. ##### memories [Section titled “memories”](#memories)
```ts
1
readonly memories: readonly MemorySpec[];
```
##### now [Section titled “now”](#now)
```ts
1
readonly now: number;
```
The quantized run instant every stamp is anchored behind, UTC millis. See [quantizeNow](/api/eval/#quantizenow). ##### probes [Section titled “probes”](#probes)
```ts
1
readonly probes: readonly Probe[];
```
##### seed [Section titled “seed”](#seed)
```ts
1
readonly seed: number;
```
*** ### DerivedControl [Section titled “DerivedControl”](#derivedcontrol) A derived control, holding the flipped text plus the family that produced it. #### Extends [Section titled “Extends”](#extends) * [`ClaimText`](/api/eval/#claimtext) #### Properties [Section titled “Properties”](#properties-9) ##### body [Section titled “body”](#body-2)
```ts
1
readonly body: readonly string[];
```
###### Inherited from [Section titled “Inherited from”](#inherited-from) [`ClaimText`](/api/eval/#claimtext).[`body`](/api/eval/#claimtext) ##### claim [Section titled “claim”](#claim-1)
```ts
1
readonly claim: string;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-1) [`ClaimText`](/api/eval/#claimtext).[`claim`](/api/eval/#claimtext) ##### family [Section titled “family”](#family)
```ts
1
readonly family: DivergenceFamily;
```
##### note [Section titled “note”](#note)
```ts
1
readonly note: string;
```
What changed, for the probe’s own record. *** ### DiscriminationReport [Section titled “DiscriminationReport”](#discriminationreport) The whole run’s outcome. This is the `eval.discrimination` envelope payload. #### Extended by [Section titled “Extended by”](#extended-by-1) * [`EvalOutcome`](/api/eval/#evaloutcome) #### Properties [Section titled “Properties”](#properties-10) ##### corpusMrr [Section titled “corpusMrr”](#corpusmrr)
```ts
1
readonly corpusMrr: number;
```
Mean reciprocal rank over the WHOLE hit list, unitless in `[0, 1]`, four decimals. Reported rather than gated, because corpus size dominates it more than ranking quality does. `DEFAULT_ARM_LIMIT` is 40, which is 13% of a 300-file fixture and under 1% of a real corpus, so the two query-blind arms cover a far larger share of a fixture than of production. The value is seed- and parameter-dependent — it moves with the corpus size, the probe count, and the seed — but it rises with corpus size on this generator while the inversion count stays flat, which is exactly why a gate on this number would be a gate on how big the fixture is. ##### degradedProbes [Section titled “degradedProbes”](#degradedprobes)
```ts
1
readonly degradedProbes: number;
```
Probes ranked by a degraded (vector-arm-free) search. ##### discriminated [Section titled “discriminated”](#discriminated)
```ts
1
readonly discriminated: number;
```
Probes whose target strictly outranked all of its controls. ##### inversions [Section titled “inversions”](#inversions)
```ts
1
readonly inversions: readonly ProbeResult[];
```
Probes with at least one inversion. The gate fails when this is non-zero. ##### mode [Section titled “mode”](#mode)
```ts
1
readonly mode: EvalMode;
```
##### mrr [Section titled “mrr”](#mrr)
```ts
1
readonly mrr: number;
```
Mean reciprocal rank over `{target} ∪ controls`, unitless in `[0, 1]`, four decimals. **This is the gated number, and its coordinate space is the discrimination set, not the corpus.** Design §5 states the floor in the same clause as `rank(target) < min(rank(control))`, so the space the sentence is about is the target against its own impostors. `1.0` means every target beat every control outright. See [corpusMrr](/api/eval/#discriminationreport) for the other reading, which is reported and not gated. ##### mrrFloor [Section titled “mrrFloor”](#mrrfloor)
```ts
1
readonly mrrFloor: number;
```
The floor [mrr](/api/eval/#discriminationreport) was measured against. ##### passed [Section titled “passed”](#passed-1)
```ts
1
readonly passed: boolean;
```
True when there are zero inversions AND `mrr >= mrrFloor`. ##### probes [Section titled “probes”](#probes-1)
```ts
1
readonly probes: number;
```
##### results [Section titled “results”](#results)
```ts
1
readonly results: readonly ProbeResult[];
```
*** ### EvalEmbedder [Section titled “EvalEmbedder”](#evalembedder) Both embed ports plus a call counter. #### Extends [Section titled “Extends”](#extends-1) * `EmbedPort`.`QueryEmbedPort` #### Properties [Section titled “Properties”](#properties-11) ##### calls [Section titled “calls”](#calls)
```ts
1
readonly calls: () => number;
```
###### Returns [Section titled “Returns”](#returns-2) `number` ##### embed [Section titled “embed”](#embed)
```ts
1
readonly embed: (texts) => Effect[], ModelUnavailable>;
```
###### Parameters [Section titled “Parameters”](#parameters-1) ###### texts [Section titled “texts”](#texts) readonly `string`\[] ###### Returns [Section titled “Returns”](#returns-3) `Effect`\\[], `ModelUnavailable`> ###### Inherited from [Section titled “Inherited from”](#inherited-from-2)
```ts
1
EmbedPort.embed
```
##### embedQuery [Section titled “embedQuery”](#embedquery)
```ts
1
readonly embedQuery: (text) => Effect, ModelUnavailable>;
```
###### Parameters [Section titled “Parameters”](#parameters-2) ###### text [Section titled “text”](#text) `string` ###### Returns [Section titled “Returns”](#returns-4) `Effect`<`Float32Array`<`ArrayBufferLike`>, `ModelUnavailable`> ###### Inherited from [Section titled “Inherited from”](#inherited-from-3)
```ts
1
QueryEmbedPort.embedQuery
```
*** ### EvalOptions [Section titled “EvalOptions”](#evaloptions) What [runDiscrimination](/api/eval/#rundiscrimination) takes. #### Properties [Section titled “Properties”](#properties-12) ##### env? [Section titled “env?”](#env)
```ts
1
readonly optional env?: Readonly>;
```
Injected for tests, so the credential branch is drivable without touching the environment. ##### mode? [Section titled “mode?”](#mode-1)
```ts
1
readonly optional mode?: EvalMode;
```
`fake` by default, the mode that works in CI and whose numbers are reproducible. ##### mrrFloor? [Section titled “mrrFloor?”](#mrrfloor-1)
```ts
1
readonly optional mrrFloor?: number;
```
##### now? [Section titled “now?”](#now-1)
```ts
1
readonly optional now?: number;
```
The run instant to anchor the corpus’s stamps behind, UTC millis. The clock when absent. ##### probes? [Section titled “probes?”](#probes-2)
```ts
1
readonly optional probes?: number;
```
##### seed? [Section titled “seed?”](#seed-1)
```ts
1
readonly optional seed?: number;
```
##### size? [Section titled “size?”](#size)
```ts
1
readonly optional size?: number;
```
*** ### EvalOutcome [Section titled “EvalOutcome”](#evaloutcome) What `memhtml eval discriminate` emits, the report plus which mode was asked for. #### Extends [Section titled “Extends”](#extends-2) * [`DiscriminationReport`](/api/eval/#discriminationreport) #### Properties [Section titled “Properties”](#properties-13) ##### corpusMrr [Section titled “corpusMrr”](#corpusmrr-1)
```ts
1
readonly corpusMrr: number;
```
Mean reciprocal rank over the WHOLE hit list, unitless in `[0, 1]`, four decimals. Reported rather than gated, because corpus size dominates it more than ranking quality does. `DEFAULT_ARM_LIMIT` is 40, which is 13% of a 300-file fixture and under 1% of a real corpus, so the two query-blind arms cover a far larger share of a fixture than of production. The value is seed- and parameter-dependent — it moves with the corpus size, the probe count, and the seed — but it rises with corpus size on this generator while the inversion count stays flat, which is exactly why a gate on this number would be a gate on how big the fixture is. ###### Inherited from [Section titled “Inherited from”](#inherited-from-4) [`DiscriminationReport`](/api/eval/#discriminationreport).[`corpusMrr`](/api/eval/#discriminationreport) ##### corpusSize [Section titled “corpusSize”](#corpussize)
```ts
1
readonly corpusSize: number;
```
##### degradedProbes [Section titled “degradedProbes”](#degradedprobes-1)
```ts
1
readonly degradedProbes: number;
```
Probes ranked by a degraded (vector-arm-free) search. ###### Inherited from [Section titled “Inherited from”](#inherited-from-5) [`DiscriminationReport`](/api/eval/#discriminationreport).[`degradedProbes`](/api/eval/#discriminationreport) ##### discriminated [Section titled “discriminated”](#discriminated-1)
```ts
1
readonly discriminated: number;
```
Probes whose target strictly outranked all of its controls. ###### Inherited from [Section titled “Inherited from”](#inherited-from-6) [`DiscriminationReport`](/api/eval/#discriminationreport).[`discriminated`](/api/eval/#discriminationreport) ##### inversions [Section titled “inversions”](#inversions-1)
```ts
1
readonly inversions: readonly ProbeResult[];
```
Probes with at least one inversion. The gate fails when this is non-zero. ###### Inherited from [Section titled “Inherited from”](#inherited-from-7) [`DiscriminationReport`](/api/eval/#discriminationreport).[`inversions`](/api/eval/#discriminationreport) ##### mode [Section titled “mode”](#mode-2)
```ts
1
readonly mode: EvalMode;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-8) [`DiscriminationReport`](/api/eval/#discriminationreport).[`mode`](/api/eval/#discriminationreport) ##### mrr [Section titled “mrr”](#mrr-1)
```ts
1
readonly mrr: number;
```
Mean reciprocal rank over `{target} ∪ controls`, unitless in `[0, 1]`, four decimals. **This is the gated number, and its coordinate space is the discrimination set, not the corpus.** Design §5 states the floor in the same clause as `rank(target) < min(rank(control))`, so the space the sentence is about is the target against its own impostors. `1.0` means every target beat every control outright. See [corpusMrr](/api/eval/#discriminationreport) for the other reading, which is reported and not gated. ###### Inherited from [Section titled “Inherited from”](#inherited-from-9) [`DiscriminationReport`](/api/eval/#discriminationreport).[`mrr`](/api/eval/#discriminationreport) ##### mrrFloor [Section titled “mrrFloor”](#mrrfloor-2)
```ts
1
readonly mrrFloor: number;
```
The floor [mrr](/api/eval/#discriminationreport) was measured against. ###### Inherited from [Section titled “Inherited from”](#inherited-from-10) [`DiscriminationReport`](/api/eval/#discriminationreport).[`mrrFloor`](/api/eval/#discriminationreport) ##### now [Section titled “now”](#now-2)
```ts
1
readonly now: number;
```
The quantized run instant the corpus’s stamps were anchored behind, UTC millis. The other half of reproducing a failing run, since the corpus is a function of `(seed, now)`. `0` on a skipped run, where no corpus was generated. ##### passed [Section titled “passed”](#passed-2)
```ts
1
readonly passed: boolean;
```
True when there are zero inversions AND `mrr >= mrrFloor`. ###### Inherited from [Section titled “Inherited from”](#inherited-from-11) [`DiscriminationReport`](/api/eval/#discriminationreport).[`passed`](/api/eval/#discriminationreport) ##### probes [Section titled “probes”](#probes-3)
```ts
1
readonly probes: number;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-12) [`DiscriminationReport`](/api/eval/#discriminationreport).[`probes`](/api/eval/#discriminationreport) ##### requested [Section titled “requested”](#requested)
```ts
1
readonly requested: EvalMode;
```
The mode the caller asked for, which differs from `mode` only on a refused live run. ##### results [Section titled “results”](#results-1)
```ts
1
readonly results: readonly ProbeResult[];
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-13) [`DiscriminationReport`](/api/eval/#discriminationreport).[`results`](/api/eval/#discriminationreport) ##### seed [Section titled “seed”](#seed-2)
```ts
1
readonly seed: number;
```
The seed the corpus was generated at, so a failing run is reproducible. ##### skipped [Section titled “skipped”](#skipped)
```ts
1
readonly skipped: boolean;
```
True when live mode was requested with no credentials present. A skipped run is `passed: false` and carries zero probes, so it is a refusal rather than a result. ##### skipReason? [Section titled “skipReason?”](#skipreason)
```ts
1
readonly optional skipReason?: string;
```
Why it was skipped, absent otherwise. *** ### EvalStack [Section titled “EvalStack”](#evalstack) A built stack, holding the fixture, the database, and retrieval over both. #### Properties [Section titled “Properties”](#properties-14) ##### db [Section titled “db”](#db)
```ts
1
readonly db: DatabaseShape;
```
##### embedCalls [Section titled “embedCalls”](#embedcalls)
```ts
1
readonly embedCalls: () => number;
```
###### Returns [Section titled “Returns”](#returns-5) `number` ##### fixture [Section titled “fixture”](#fixture)
```ts
1
readonly fixture: FixtureCorpus;
```
##### indexed [Section titled “indexed”](#indexed)
```ts
1
readonly indexed: number;
```
How many files the indexer projected, so a caller can assert the corpus really landed. ##### retrieval [Section titled “retrieval”](#retrieval)
```ts
1
readonly retrieval: RetrievalShape;
```
*** ### FixtureCorpus [Section titled “FixtureCorpus”](#fixturecorpus) A generated fixture repo, with the root, the git service over it, and the spec behind it. #### Properties [Section titled “Properties”](#properties-15) ##### cleanup [Section titled “cleanup”](#cleanup)
```ts
1
readonly cleanup: () => Promise;
```
###### Returns [Section titled “Returns”](#returns-6) `Promise`<`void`> ##### git [Section titled “git”](#git)
```ts
1
readonly git: GitShape;
```
##### root [Section titled “root”](#root)
```ts
1
readonly root: string;
```
##### spec [Section titled “spec”](#spec)
```ts
1
readonly spec: CorpusSpec;
```
##### written [Section titled “written”](#written)
```ts
1
readonly written: number;
```
How many files were written, generated artifacts excluded. *** ### FixtureOptions [Section titled “FixtureOptions”](#fixtureoptions) What [makeFixtureCorpus](/api/eval/#makefixturecorpus) takes. Every field has a default, so a caller can pass nothing. #### Extended by [Section titled “Extended by”](#extended-by-2) * [`StackOptions`](/api/eval/#stackoptions) #### Properties [Section titled “Properties”](#properties-16) ##### now? [Section titled “now?”](#now-3)
```ts
1
readonly optional now?: number;
```
The run instant the corpus’s stamps are anchored behind, UTC millis. Effect’s `Clock` when absent, which is the one place the fixture pipeline consults time at all — `buildCorpus` requires it, so a test pinning this value pins every stamp in the tree. ##### probes? [Section titled “probes?”](#probes-4)
```ts
1
readonly optional probes?: number;
```
##### root? [Section titled “root?”](#root-1)
```ts
1
readonly optional root?: string;
```
Where to generate. A fresh temp directory when absent. ##### seed? [Section titled “seed?”](#seed-3)
```ts
1
readonly optional seed?: number;
```
##### size? [Section titled “size?”](#size-1)
```ts
1
readonly optional size?: number;
```
*** ### FixtureTranscript [Section titled “FixtureTranscript”](#fixturetranscript) One fixture transcript, as the batch would offer it. #### Properties [Section titled “Properties”](#properties-17) ##### jsonl [Section titled “jsonl”](#jsonl)
```ts
1
readonly jsonl: string;
```
JSONL, one object per line, in the shape Claude Code writes under `~/.claude/projects`. ##### sessionId [Section titled “sessionId”](#sessionid)
```ts
1
readonly sessionId: string;
```
*** ### FloorReport [Section titled “FloorReport”](#floorreport) The lexical-floor scenario runs the same suite with no vector arm. Design §5 requires two things of it: no error, and the lexical arm still beating the controls on the probes that carry lexical signal. Only the SECOND half is a subset claim, and it has to be. A numeric-family control differs from its target by one token, which the FTS arm cannot order, so demanding the full gate without vectors would be demanding the vector arm be unnecessary. `lexicallyDiscriminated` is therefore the number reported and asserted, not `passed`. #### Properties [Section titled “Properties”](#properties-18) ##### allDegraded [Section titled “allDegraded”](#alldegraded)
```ts
1
readonly allDegraded: boolean;
```
True when every probe was ranked degraded, which asserts that the arm really is absent. ##### lexicallyDiscriminated [Section titled “lexicallyDiscriminated”](#lexicallydiscriminated)
```ts
1
readonly lexicallyDiscriminated: number;
```
Probes the lexical floor still discriminated. ##### probes [Section titled “probes”](#probes-5)
```ts
1
readonly probes: number;
```
##### results [Section titled “results”](#results-2)
```ts
1
readonly results: readonly ProbeResult[];
```
Every result, so a caller can see which families survive without vectors. *** ### FrozenDecision [Section titled “FrozenDecision”](#frozendecision) One frozen decision: enough to detect drift, nothing that needs a transcript to read. #### Properties [Section titled “Properties”](#properties-19) ##### accepted [Section titled “accepted”](#accepted-1)
```ts
1
readonly accepted: boolean;
```
##### class [Section titled “class”](#class-2)
```ts
1
readonly class: string;
```
##### id [Section titled “id”](#id)
```ts
1
readonly id: string;
```
##### label [Section titled “label”](#label-2)
```ts
1
readonly label: "good" | "bad";
```
##### stage [Section titled “stage”](#stage)
```ts
1
readonly stage: GateStage;
```
*** ### GateBatch [Section titled “GateBatch”](#gatebatch) What one batch made readable: the transcripts on disk, keyed the way the door keys them. #### Properties [Section titled “Properties”](#properties-20) ##### reachable [Section titled “reachable”](#reachable)
```ts
1
readonly reachable: readonly ReachableTranscript[];
```
*** ### GateDecision [Section titled “GateDecision”](#gatedecision) One candidate’s outcome. #### Properties [Section titled “Properties”](#properties-21) ##### accepted [Section titled “accepted”](#accepted-2)
```ts
1
readonly accepted: boolean;
```
##### reason [Section titled “reason”](#reason-1)
```ts
1
readonly reason: string | null;
```
The production function’s own reason text, `null` when accepted. Truncated to one line. ##### stage [Section titled “stage”](#stage-1)
```ts
1
readonly stage: GateStage;
```
*** ### LabeledCandidate [Section titled “LabeledCandidate”](#labeledcandidate) One labeled candidate. `candidate` is `unknown` on purpose: a `bad` item may violate the type the door decodes (a bare string where an entity object belongs, a missing field), and a typed field would make those shapes unrepresentable. The gate’s first stage IS the decode, so the corpus has to be able to hand it something the type refuses. #### Properties [Section titled “Properties”](#properties-22) ##### candidate [Section titled “candidate”](#candidate)
```ts
1
readonly candidate: unknown;
```
##### class [Section titled “class”](#class-3)
```ts
1
readonly class: string;
```
For a `bad` item, its failure class (what is wrong with it). For a `good` item, its shape (what the gate has to get RIGHT to accept it). The per-class breakdown groups on this. ##### id [Section titled “id”](#id-1)
```ts
1
readonly id: string;
```
Stable id, `g01`…`g31` for good, `b01`…`b31` for bad. The manifest keys on it. ##### label [Section titled “label”](#label-3)
```ts
1
readonly label: WritePathLabel;
```
##### provenance [Section titled “provenance”](#provenance)
```ts
1
readonly provenance: string;
```
The rule that decides the label, with the file that holds it. *** ### LabeledDecision [Section titled “LabeledDecision”](#labeleddecision) One candidate’s decision beside its label. #### Properties [Section titled “Properties”](#properties-23) ##### accepted [Section titled “accepted”](#accepted-3)
```ts
1
readonly accepted: boolean;
```
##### class [Section titled “class”](#class-4)
```ts
1
readonly class: string;
```
##### id [Section titled “id”](#id-2)
```ts
1
readonly id: string;
```
##### label [Section titled “label”](#label-4)
```ts
1
readonly label: WritePathLabel;
```
##### reason [Section titled “reason”](#reason-2)
```ts
1
readonly reason: string | null;
```
##### stage [Section titled “stage”](#stage-2)
```ts
1
readonly stage: GateStage;
```
*** ### LabeledGroup [Section titled “LabeledGroup”](#labeledgroup) One labeled group. #### Properties [Section titled “Properties”](#properties-24) ##### class [Section titled “class”](#class-5)
```ts
1
readonly class: string;
```
For a `merge` group, its shape (what the fold has to get RIGHT to accept it). For a `keep` group, its divergence (why the veto must refuse it). The per-class breakdown groups on this. ##### id [Section titled “id”](#id-3)
```ts
1
readonly id: string;
```
Stable id, `m01`… for merge, `k01`… for keep. Reports and gap sets key on it. ##### label [Section titled “label”](#label-5)
```ts
1
readonly label: AcceptanceLabel;
```
##### members [Section titled “members”](#members)
```ts
1
readonly members: readonly AcceptanceMember[];
```
Two or more members, oldest first. ##### provenance [Section titled “provenance”](#provenance-1)
```ts
1
readonly provenance: string;
```
The rule that decides the label, with the file that holds it. *** ### MemorySpec [Section titled “MemorySpec”](#memoryspec) One memory to write, before it becomes HTML. #### Properties [Section titled “Properties”](#properties-25) ##### archivedAt? [Section titled “archivedAt?”](#archivedat)
```ts
1
readonly optional archivedAt?: string;
```
Set on the archived members, which carry `memhtml-status: archived` and a `memhtml-archived` stamp. ##### body [Section titled “body”](#body-3)
```ts
1
readonly body: readonly string[];
```
##### claim [Section titled “claim”](#claim-2)
```ts
1
readonly claim: string;
```
The sentence the memory turns on. It becomes the `` span and therefore `files.gist`. ##### confidence [Section titled “confidence”](#confidence)
```ts
1
readonly confidence: number;
```
##### createdAt [Section titled “createdAt”](#createdat)
```ts
1
readonly createdAt: string;
```
##### entities [Section titled “entities”](#entities)
```ts
1
readonly entities: readonly string[];
```
##### extras [Section titled “extras”](#extras)
```ts
1
readonly extras: readonly string[];
```
Extra article markup after the claim paragraph, the element kit this memory exercises. ##### importance [Section titled “importance”](#importance)
```ts
1
readonly importance: number;
```
##### links [Section titled “links”](#links)
```ts
1
readonly links: readonly object[];
```
##### memoryType [Section titled “memoryType”](#memorytype)
```ts
1
readonly memoryType: string;
```
##### path [Section titled “path”](#path-1)
```ts
1
readonly path: string;
```
##### sessionId? [Section titled “sessionId?”](#sessionid-1)
```ts
1
readonly optional sessionId?: string;
```
##### tags [Section titled “tags”](#tags)
```ts
1
readonly tags: readonly string[];
```
##### title [Section titled “title”](#title)
```ts
1
readonly title: string;
```
##### updatedAt [Section titled “updatedAt”](#updatedat)
```ts
1
readonly updatedAt: string;
```
##### validUntil? [Section titled “validUntil?”](#validuntil)
```ts
1
readonly optional validUntil?: string;
```
*** ### PairDecision [Section titled “PairDecision”](#pairdecision) One oriented pair’s decision beside its group’s label. #### Properties [Section titled “Properties”](#properties-26) ##### class [Section titled “class”](#class-6)
```ts
1
readonly class: string;
```
##### dropPath [Section titled “dropPath”](#droppath)
```ts
1
readonly dropPath: string;
```
##### folded [Section titled “folded”](#folded)
```ts
1
readonly folded: boolean;
```
##### group [Section titled “group”](#group)
```ts
1
readonly group: string;
```
##### id [Section titled “id”](#id-4)
```ts
1
readonly id: string;
```
`/`, stable across runs. ##### keepPath [Section titled “keepPath”](#keeppath)
```ts
1
readonly keepPath: string;
```
##### label [Section titled “label”](#label-6)
```ts
1
readonly label: AcceptanceLabel;
```
##### stage [Section titled “stage”](#stage-3)
```ts
1
readonly stage: AcceptanceStage;
```
*** ### Probe [Section titled “Probe”](#probe) One discrimination probe: a query, the memory that answers it, and the near-twins that do not. `controlPaths` are ordered by family so a failure names the axis. Each control is validated against its OWN family’s divergence predicate at generation time. See `controls.ts`. #### Properties [Section titled “Properties”](#properties-27) ##### controlPaths [Section titled “controlPaths”](#controlpaths)
```ts
1
readonly controlPaths: readonly string[];
```
##### families [Section titled “families”](#families)
```ts
1
readonly families: readonly DivergenceFamily[];
```
##### query [Section titled “query”](#query)
```ts
1
readonly query: string;
```
##### targetPath [Section titled “targetPath”](#targetpath)
```ts
1
readonly targetPath: string;
```
*** ### ProbeResult [Section titled “ProbeResult”](#proberesult) One probe’s outcome. #### Properties [Section titled “Properties”](#properties-28) ##### controlRanks [Section titled “controlRanks”](#controlranks)
```ts
1
readonly controlRanks: readonly object[];
```
Each control’s 1-based rank in the whole hit list, `null` when absent from the hits. ##### corpusReciprocalRank [Section titled “corpusReciprocalRank”](#corpusreciprocalrank)
```ts
1
readonly corpusReciprocalRank: number;
```
`1 / targetRank`, or `0` when the target was absent. The term of `corpusMrr`. ##### degraded [Section titled “degraded”](#degraded)
```ts
1
readonly degraded: boolean;
```
True when the vector arm did not fire for this probe. ##### discriminated [Section titled “discriminated”](#discriminated-2)
```ts
1
readonly discriminated: boolean;
```
True when the target strictly outranks EVERY control. A control the search never returned counts as outranked, since being absent is worse than being last. ##### discriminationRank [Section titled “discriminationRank”](#discriminationrank)
```ts
1
readonly discriminationRank: number;
```
1-based rank of the target within `{target} ∪ controls` ALONE, the space the gate is stated in. `1` means the target beat every one of its own impostors. Never `null`, because the target is always a member of this set, so an absent target ranks last rather than nowhere. ##### query [Section titled “query”](#query-1)
```ts
1
readonly query: string;
```
##### reciprocalRank [Section titled “reciprocalRank”](#reciprocalrank)
```ts
1
readonly reciprocalRank: number;
```
`1 / discriminationRank`. The term of [DiscriminationReport.mrr](/api/eval/#discriminationreport). ##### targetPath [Section titled “targetPath”](#targetpath-1)
```ts
1
readonly targetPath: string;
```
##### targetRank [Section titled “targetRank”](#targetrank)
```ts
1
readonly targetRank: number | null;
```
1-based rank of the target within the WHOLE returned hit list, or `null` when it was not returned. The scope is every active memory in the corpus. Compare against [discriminationRank](/api/eval/#proberesult), which measures the same position in a different space. *** ### ReplayDrift [Section titled “ReplayDrift”](#replaydrift) One decision that replayed differently from the frozen one. #### Properties [Section titled “Properties”](#properties-29) ##### fresh [Section titled “fresh”](#fresh)
```ts
1
readonly fresh:
2
| {
3
accepted: boolean;
4
stage: GateStage;
5
}
6
| null;
```
##### frozen [Section titled “frozen”](#frozen)
```ts
1
readonly frozen:
2
| {
3
accepted: boolean;
4
stage: GateStage;
5
}
6
| null;
```
##### id [Section titled “id”](#id-5)
```ts
1
readonly id: string;
```
*** ### StackOptions [Section titled “StackOptions”](#stackoptions) What [buildStack](/api/eval/#buildstack) takes beyond the fixture options. #### Extends [Section titled “Extends”](#extends-3) * [`FixtureOptions`](/api/eval/#fixtureoptions) #### Properties [Section titled “Properties”](#properties-30) ##### embedder? [Section titled “embedder?”](#embedder)
```ts
1
readonly optional embedder?: EvalEmbedder;
```
The embedder to measure through. `fakeEmbedder()` when absent. ##### now? [Section titled “now?”](#now-4)
```ts
1
readonly optional now?: number;
```
The run instant the corpus’s stamps are anchored behind, UTC millis. Effect’s `Clock` when absent, which is the one place the fixture pipeline consults time at all — `buildCorpus` requires it, so a test pinning this value pins every stamp in the tree. ###### Inherited from [Section titled “Inherited from”](#inherited-from-14) [`FixtureOptions`](/api/eval/#fixtureoptions).[`now`](/api/eval/#fixtureoptions) ##### probes? [Section titled “probes?”](#probes-6)
```ts
1
readonly optional probes?: number;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-15) [`FixtureOptions`](/api/eval/#fixtureoptions).[`probes`](/api/eval/#fixtureoptions) ##### root? [Section titled “root?”](#root-2)
```ts
1
readonly optional root?: string;
```
Where to generate. A fresh temp directory when absent. ###### Inherited from [Section titled “Inherited from”](#inherited-from-16) [`FixtureOptions`](/api/eval/#fixtureoptions).[`root`](/api/eval/#fixtureoptions) ##### seed? [Section titled “seed?”](#seed-4)
```ts
1
readonly optional seed?: number;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-17) [`FixtureOptions`](/api/eval/#fixtureoptions).[`seed`](/api/eval/#fixtureoptions) ##### size? [Section titled “size?”](#size-2)
```ts
1
readonly optional size?: number;
```
###### Inherited from [Section titled “Inherited from”](#inherited-from-18) [`FixtureOptions`](/api/eval/#fixtureoptions).[`size`](/api/eval/#fixtureoptions) *** ### VariantOptions [Section titled “VariantOptions”](#variantoptions) What a variant-family derivation needs beyond the target’s own text. #### Properties [Section titled “Properties”](#properties-31) ##### anchor [Section titled “anchor”](#anchor)
```ts
1
readonly anchor: string;
```
The token the qualifier is inserted after, a product or host name in the claim. ##### qualifier [Section titled “qualifier”](#qualifier)
```ts
1
readonly qualifier: string;
```
A `VARIANT_QUALIFIERS` member, such as `pro`, `beta`, or `legacy`. *** ### WritePathManifest [Section titled “WritePathManifest”](#writepathmanifest) The freeze/replay manifest. #### Properties [Section titled “Properties”](#properties-32) ##### corpusSha256 [Section titled “corpusSha256”](#corpussha256)
```ts
1
readonly corpusSha256: string;
```
sha256 of the corpus as JSON, so a changed candidate is attributable. ##### decisions [Section titled “decisions”](#decisions-1)
```ts
1
readonly decisions: readonly FrozenDecision[];
```
##### f1Floor [Section titled “f1Floor”](#f1floor)
```ts
1
readonly f1Floor: number;
```
##### frozenAt [Section titled “frozenAt”](#frozenat)
```ts
1
readonly frozenAt: string;
```
When the freeze ran, ISO-8601. Informational; not compared on replay. ##### gate [Section titled “gate”](#gate)
```ts
1
readonly gate: readonly object[];
```
The stages, in order, and the production functions each one calls. ##### memhtmlVersion [Section titled “memhtmlVersion”](#memhtmlversion)
```ts
1
readonly memhtmlVersion: string;
```
The memhtml workspace version the decisions were frozen at. ##### metrics [Section titled “metrics”](#metrics)
```ts
1
readonly metrics: object;
```
###### accuracy [Section titled “accuracy”](#accuracy)
```ts
1
readonly accuracy: number;
```
###### confusion [Section titled “confusion”](#confusion)
```ts
1
readonly confusion: ConfusionMatrix;
```
###### f1 [Section titled “f1”](#f1-1)
```ts
1
readonly f1: number;
```
###### items [Section titled “items”](#items-1)
```ts
1
readonly items: number;
```
###### precision [Section titled “precision”](#precision-1)
```ts
1
readonly precision: number;
```
###### recall [Section titled “recall”](#recall-1)
```ts
1
readonly recall: number;
```
##### schemaVersion [Section titled “schemaVersion”](#schemaversion)
```ts
1
readonly schemaVersion: number;
```
##### transcriptsSha256 [Section titled “transcriptsSha256”](#transcriptssha256)
```ts
1
readonly transcriptsSha256: string;
```
sha256 of the two transcripts’ JSONL, so a changed fixture line is attributable. *** ### WritePathOptions [Section titled “WritePathOptions”](#writepathoptions) What [runWritePathDiscrimination](/api/eval/#runwritepathdiscrimination) takes. #### Properties [Section titled “Properties”](#properties-33) ##### corpus? [Section titled “corpus?”](#corpus-1)
```ts
1
readonly optional corpus?: readonly LabeledCandidate[];
```
##### f1Floor? [Section titled “f1Floor?”](#f1floor-1)
```ts
1
readonly optional f1Floor?: number;
```
##### transcripts? [Section titled “transcripts?”](#transcripts)
```ts
1
readonly optional transcripts?: readonly FixtureTranscript[];
```
*** ### WritePathReport [Section titled “WritePathReport”](#writepathreport) The report the gate reads. This is the `eval.write-path` payload shape. #### Properties [Section titled “Properties”](#properties-34) ##### accuracy [Section titled “accuracy”](#accuracy-1)
```ts
1
readonly accuracy: number;
```
`(badRejected + goodAccepted) / items`, four decimals. ##### bad [Section titled “bad”](#bad)
```ts
1
readonly bad: number;
```
##### confusion [Section titled “confusion”](#confusion-1)
```ts
1
readonly confusion: ConfusionMatrix;
```
##### decisions [Section titled “decisions”](#decisions-2)
```ts
1
readonly decisions: readonly LabeledDecision[];
```
##### f1 [Section titled “f1”](#f1-2)
```ts
1
readonly f1: number;
```
Harmonic mean of the two, four decimals; `0` when either is `0`. ##### f1Floor [Section titled “f1Floor”](#f1floor-2)
```ts
1
readonly f1Floor: number;
```
##### good [Section titled “good”](#good)
```ts
1
readonly good: number;
```
##### items [Section titled “items”](#items-2)
```ts
1
readonly items: number;
```
##### passed [Section titled “passed”](#passed-3)
```ts
1
readonly passed: boolean;
```
True when the corpus is non-empty AND carries both labels AND `f1 >= f1Floor`. An empty corpus, or one with a single label, is a FAILURE rather than a vacuous pass: a mean over nothing is not a measurement, and a gate scored only on good items (or only on bad) cannot have discriminated anything. ##### perClass [Section titled “perClass”](#perclass-1)
```ts
1
readonly perClass: readonly ClassBreakdown[];
```
##### precision [Section titled “precision”](#precision-2)
```ts
1
readonly precision: number;
```
`badRejected / (badRejected + goodRejected)`, four decimals; `0` when nothing was rejected. ##### recall [Section titled “recall”](#recall-2)
```ts
1
readonly recall: number;
```
`badRejected / (badRejected + badAccepted)`, four decimals; `0` when nothing was bad. ## Type Aliases [Section titled “Type Aliases”](#type-aliases) ### AcceptanceLabel [Section titled “AcceptanceLabel”](#acceptancelabel)
```ts
1
type AcceptanceLabel = "merge" | "keep";
```
Which way the label points. `merge`: every member folds into the oldest. `keep`: none does. *** ### AcceptanceStage [Section titled “AcceptanceStage”](#acceptancestage)
```ts
1
type AcceptanceStage =
2
| "threshold"
3
| "veto:negation"
4
| "veto:numeric"
5
| "veto:variant"
6
| "self"
7
| "role-guard"
8
| "cap"
9
| "folded";
```
Where a pair’s fate was decided. `folded` is the outcome the corpus’s `merge` label asks for. *** ### DivergenceFamily [Section titled “DivergenceFamily”](#divergencefamily)
```ts
1
type DivergenceFamily = "negation" | "numeric" | "variant";
```
Which divergence a control embodies. Named on the probe, so a failure says which axis broke. *** ### EvalMode [Section titled “EvalMode”](#evalmode)
```ts
1
type EvalMode = "live" | "fake";
```
Which embedder produced the numbers. Recorded on the report so a pass is never ambiguous. *** ### GateStage [Section titled “GateStage”](#gatestage)
```ts
1
type GateStage =
2
| "door:schema"
3
| "door:grounding"
4
| "door:quote"
5
| "phase:refusal"
6
| "accepted";
```
Where a candidate’s fate was decided. `accepted` is the fifth outcome: it cleared all four. *** ### WritePathLabel [Section titled “WritePathLabel”](#writepathlabel)
```ts
1
type WritePathLabel = "good" | "bad";
```
Which way the label points. ## Variables [Section titled “Variables”](#variables) ### ACCEPTANCE\_BASELINE [Section titled “ACCEPTANCE\_BASELINE”](#acceptance_baseline)
```ts
1
const ACCEPTANCE_BASELINE: 1 = 1;
```
The measured baseline this floor was derived from, so the derivation is checkable. Measured 2026-09-11 at memhtml 0.14.0 on corpus v2 (12 merge groups implying 14 pairs, 7 keep groups implying 7 pairs): acceptance 1.0 (14 of 14), precision 1.0 (no keep pair folded). Corpus v1 under the rules before this one measured 0.6667 (8 of 12): three `incidental-number` pairs stopped at `veto:numeric` because the numeric predicate read the whole article, and one `group-fanout` pair stopped at `role-guard` because the guard claimed the keeper after the first fold. Both classes now fold, and the known-gap set is empty. *** ### ACCEPTANCE\_CORPUS [Section titled “ACCEPTANCE\_CORPUS”](#acceptance_corpus)
```ts
1
const ACCEPTANCE_CORPUS: ReadonlyArray;
```
*** ### ACCEPTANCE\_FLOOR [Section titled “ACCEPTANCE\_FLOOR”](#acceptance_floor)
```ts
1
const ACCEPTANCE_FLOOR: number;
```
Acceptance floor: the baseline minus five points, floored to two decimals — the discrimination arm’s derivation, for the same reason. The floor is the alarm for a class-sized regression (a predicate that starts firing on a class it did not), and the known-gap set is the alarm for a one-pair one. A floor at the baseline would fire on every deliberate corpus edit. *** ### ACCEPTANCE\_KNOWN\_GAP\_CLASSES [Section titled “ACCEPTANCE\_KNOWN\_GAP\_CLASSES”](#acceptance_known_gap_classes)
```ts
1
const ACCEPTANCE_KNOWN_GAP_CLASSES: ReadonlyArray = [];
```
The classes whose label the fold path is KNOWN to disagree with, stated so the tier fails if the set moves in either direction. Empty as of the rules measured 2026-09-11: every `merge` pair folds and every `keep` pair is refused. Two classes used to be here. `incidental-number` (the same claim, one side carrying a date, a ticket id, or an exit code the other lacks) stopped at `veto:numeric` while `numericTokenDivergent` read the whole `gist\nbody_text`; it reads the gist now. `group-fanout` (a group of three implying two pairs with one keeper) stopped its second pair at `role-guard` while the guard claimed both roles of a committed pair; it admits a repeated keeper now. *** ### BATCH\_SESSION\_IDS [Section titled “BATCH\_SESSION\_IDS”](#batch_session_ids)
```ts
1
const BATCH_SESSION_IDS: ReadonlyArray;
```
The batch’s session ids: what the door treats as READABLE. *** ### BEDROCK\_TOKEN\_VAR [Section titled “BEDROCK\_TOKEN\_VAR”](#bedrock_token_var)
```ts
1
const BEDROCK_TOKEN_VAR: "AWS_BEARER_TOKEN_BEDROCK" = "AWS_BEARER_TOKEN_BEDROCK";
```
The variable whose presence decides whether live mode can run. *** ### CEILINGS [Section titled “CEILINGS”](#ceilings)
```ts
1
const CEILINGS: object;
```
The ceilings, restated from `apps/consolidator/src/contract.ts` so the corpus reads on its own. #### Type Declaration [Section titled “Type Declaration”](#type-declaration) ##### claim [Section titled “claim”](#claim-3)
```ts
1
readonly claim: 300 = 300;
```
##### entities [Section titled “entities”](#entities-1)
```ts
1
readonly entities: 64 = 64;
```
##### evidence [Section titled “evidence”](#evidence)
```ts
1
readonly evidence: 32 = 32;
```
##### gist [Section titled “gist”](#gist-1)
```ts
1
readonly gist: 1500 = 1500;
```
##### quote [Section titled “quote”](#quote)
```ts
1
readonly quote: 600 = 600;
```
*** ### DEFAULT\_CORPUS\_SIZE [Section titled “DEFAULT\_CORPUS\_SIZE”](#default_corpus_size)
```ts
1
const DEFAULT_CORPUS_SIZE: 200 = 200;
```
How many base memories a default corpus carries, before controls and the archive tier. *** ### DEFAULT\_PROBE\_COUNT [Section titled “DEFAULT\_PROBE\_COUNT”](#default_probe_count)
```ts
1
const DEFAULT_PROBE_COUNT: 36 = 36;
```
How many probes a default corpus carries. Design §5 requires at least 30. *** ### DEFAULT\_SEED [Section titled “DEFAULT\_SEED”](#default_seed)
```ts
1
const DEFAULT_SEED: 20260802 = 20_260_802;
```
The default seed. Named so a caller changing it is making a visible choice. *** ### DIVERGENCE\_FAMILIES [Section titled “DIVERGENCE\_FAMILIES”](#divergence_families)
```ts
1
const DIVERGENCE_FAMILIES: ReadonlyArray;
```
*** ### FAKE\_DIM [Section titled “FAKE\_DIM”](#fake_dim)
```ts
1
const FAKE_DIM: 1024 = EMBED_DIM;
```
The vector width the fake produces. It is the real one, so a width check cannot pass by luck. *** ### GATE\_SOURCES [Section titled “GATE\_SOURCES”](#gate_sources)
```ts
1
const GATE_SOURCES: ReadonlyArray<{
2
source: string;
3
stage: GateStage;
4
}>;
```
The gate’s stage table as the manifest records it. One place, so the manifest cannot drift. *** ### GATE\_STAGES [Section titled “GATE\_STAGES”](#gate_stages)
```ts
1
const GATE_STAGES: ReadonlyArray;
```
*** ### KNOWN\_GAP\_CLASSES [Section titled “KNOWN\_GAP\_CLASSES”](#known_gap_classes)
```ts
1
const KNOWN_GAP_CLASSES: ReadonlyArray;
```
The failure classes the gate is KNOWN not to cover, as of the frozen manifest. Stated in code so the eval can assert the gap is exactly this and no wider: a class that starts failing here is a regression, and a class that starts passing is a change the manifest should record. Three classes: * `restatement/*`: the TRACE-2 bar is checked as a COUNT of quotes, and nothing dedupes them, so the same line twice clears it. * `content-leak/*`: nothing compares a claim against its own evidence, so a claim that is a verbatim transcript span is written into the corpus as prose. * `shape/padded-session-id`: the door’s `ungroundedEvidenceReason` compares ids untrimmed while the phase trims them, so a real quote from a real session is refused for its whitespace. A GOOD item the gate rejects, which is the one false rejection on this corpus. *** ### MANIFEST\_FILENAME [Section titled “MANIFEST\_FILENAME”](#manifest_filename)
```ts
1
const MANIFEST_FILENAME: "write-path-discrimination.manifest.json" = "write-path-discrimination.manifest.json";
```
The frozen manifest’s file name, under `packages/eval/fixtures/`. The PATH is the caller’s to resolve: the eval tier resolves it from its own location and `scripts/freeze-write-path.mjs` from its own. This module deliberately holds no module-relative URL resolution, because `@memhtml/eval` is bundled into the published binary and the packaging census (`tests-integration/tests/packaging.test.ts`) requires every run-time asset resolution in shipped source to be a declared, shipped asset. The manifest is a repo fixture, not a shipped one: no command reads it, so declaring it would ship a file nothing in the package uses. *** ### MANIFEST\_SCHEMA\_VERSION [Section titled “MANIFEST\_SCHEMA\_VERSION”](#manifest_schema_version)
```ts
1
const MANIFEST_SCHEMA_VERSION: 1 = 1;
```
*** ### MANY\_SPANS [Section titled “MANY\_SPANS”](#many_spans)
```ts
1
const MANY_SPANS: ReadonlyArray<{
2
quote: string;
3
sessionId: string;
4
}>;
```
Distinct verbatim spans, enough for the evidence-count ceiling cases. Twenty-six named spans plus windows cut from the long line. Every entry is a substring of its transcript, and the corpus test asserts that for each one. *** ### MRR\_FLOOR [Section titled “MRR\_FLOOR”](#mrr_floor)
```ts
1
const MRR_FLOOR: 0.85 = 0.85;
```
The MRR floor, from design §5. A gate below this admits a target that loses to one of its own negation-flipped twins on one probe in seven. An agent cannot trust that retrieval layer to answer with the right fact. *** ### PROBE\_LIMIT [Section titled “PROBE\_LIMIT”](#probe_limit)
```ts
1
const PROBE_LIMIT: 40 = 40;
```
How many hits each probe requests. Wide enough that a control’s rank is observable rather than truncated into `null`. A window of 10 would report an inversion and a merely-narrow miss identically, and the two need different fixes. The gate itself compares ranks, so widening the window can only make it stricter. *** ### SESSION\_A [Section titled “SESSION\_A”](#session_a)
```ts
1
const SESSION_A: "session-a" = "session-a";
```
*** ### SESSION\_B [Section titled “SESSION\_B”](#session_b)
```ts
1
const SESSION_B: "session-b" = "session-b";
```
*** ### SESSION\_OUTSIDE\_BATCH [Section titled “SESSION\_OUTSIDE\_BATCH”](#session_outside_batch)
```ts
1
const SESSION_OUTSIDE_BATCH: "session-z" = "session-z";
```
A session id no transcript in the batch carries. *** ### TRANSCRIPT\_A [Section titled “TRANSCRIPT\_A”](#transcript_a)
```ts
1
const TRANSCRIPT_A: FixtureTranscript;
```
*** ### TRANSCRIPT\_B [Section titled “TRANSCRIPT\_B”](#transcript_b)
```ts
1
const TRANSCRIPT_B: FixtureTranscript;
```
*** ### TRANSCRIPTS [Section titled “TRANSCRIPTS”](#transcripts-1)
```ts
1
const TRANSCRIPTS: ReadonlyArray;
```
*** ### WRITE\_PATH\_BASELINE\_F1 [Section titled “WRITE\_PATH\_BASELINE\_F1”](#write_path_baseline_f1)
```ts
1
const WRITE_PATH_BASELINE_F1: 0.9333 = 0.9333;
```
The measured baseline this floor was derived from. Recorded so the derivation is checkable. *** ### WRITE\_PATH\_CORPUS [Section titled “WRITE\_PATH\_CORPUS”](#write_path_corpus)
```ts
1
const WRITE_PATH_CORPUS: ReadonlyArray;
```
The whole labeled corpus: 31 good, 31 bad, in id order. *** ### WRITE\_PATH\_F1\_FLOOR [Section titled “WRITE\_PATH\_F1\_FLOOR”](#write_path_f1_floor)
```ts
1
const WRITE_PATH_F1_FLOOR: number;
```
F1 floor: the baseline minus five points, rounded down to two decimals. Five points and not zero, because the floor is the alarm for a class-sized regression and the manifest is the alarm for a one-item one; a floor pinned at the baseline would fire on every deliberate corpus edit and train the reader to refreeze without looking. ## Functions [Section titled “Functions”](#functions) ### acceptanceAgrees() [Section titled “acceptanceAgrees()”](#acceptanceagrees)
```ts
1
function acceptanceAgrees(decision): boolean;
```
True when the decision matches the label. #### Parameters [Section titled “Parameters”](#parameters-3) ##### decision [Section titled “decision”](#decision) [`PairDecision`](/api/eval/#pairdecision) #### Returns [Section titled “Returns”](#returns-7) `boolean` *** ### acceptanceByClass() [Section titled “acceptanceByClass()”](#acceptancebyclass)
```ts
1
function acceptanceByClass(decisions): readonly AcceptanceClassBreakdown[];
```
Group by class, in first-seen order. #### Parameters [Section titled “Parameters”](#parameters-4) ##### decisions [Section titled “decisions”](#decisions-3) readonly [`PairDecision`](/api/eval/#pairdecision)\[] #### Returns [Section titled “Returns”](#returns-8) readonly [`AcceptanceClassBreakdown`](/api/eval/#acceptanceclassbreakdown)\[] *** ### admitCandidate() [Section titled “admitCandidate()”](#admitcandidate)
```ts
1
function admitCandidate(raw, batch): Effect;
```
Run one candidate through the gate. Never fails: a decision is the answer, and the only I/O (`fabricatedQuoteReason` re-reading a transcript) already folds an unreadable file into a refusal rather than an error. #### Parameters [Section titled “Parameters”](#parameters-5) ##### raw [Section titled “raw”](#raw) `unknown` ##### batch [Section titled “batch”](#batch) [`GateBatch`](/api/eval/#gatebatch) #### Returns [Section titled “Returns”](#returns-9) `Effect`<[`GateDecision`](/api/eval/#gatedecision)> *** ### agrees() [Section titled “agrees()”](#agrees)
```ts
1
function agrees(decision): boolean;
```
True when the decision matches the label. #### Parameters [Section titled “Parameters”](#parameters-6) ##### decision [Section titled “decision”](#decision-1) [`LabeledDecision`](/api/eval/#labeleddecision) #### Returns [Section titled “Returns”](#returns-10) `boolean` *** ### articleFor() [Section titled “articleFor()”](#articlefor)
```ts
1
function articleFor(spec): string;
```
The article markup for a spec: the claim paragraph, the first body paragraph joined onto it, then the remaining paragraphs and the element kit. Written here rather than left to `renderTemplate`’s claim/body path because the kits carry real markup, and `renderTemplate` escapes its `body` strings as text. That is correct for a tool parameter and wrong for a `
`. #### Parameters [Section titled “Parameters”](#parameters-7) ##### spec [Section titled “spec”](#spec-1) [`MemorySpec`](/api/eval/#memoryspec) #### Returns [Section titled “Returns”](#returns-11) `string` *** ### breakdownByClass() [Section titled “breakdownByClass()”](#breakdownbyclass)
```ts
1
function breakdownByClass(decisions): readonly ClassBreakdown[];
```
Group by class, in first-seen order, so the breakdown reads in corpus order. #### Parameters [Section titled “Parameters”](#parameters-8) ##### decisions [Section titled “decisions”](#decisions-4) readonly [`LabeledDecision`](/api/eval/#labeleddecision)\[] #### Returns [Section titled “Returns”](#returns-12) readonly [`ClassBreakdown`](/api/eval/#classbreakdown)\[] *** ### buildCorpus() [Section titled “buildCorpus()”](#buildcorpus)
```ts
1
function buildCorpus(options): CorpusSpec;
```
The whole fixture specification. The order is fixed as people, base, archive tier, then controls, so the generated tree is written in a stable order and two runs at one `(seed, now)` produce byte-identical files. `now` is REQUIRED rather than defaulted from a clock, because this module’s contract is purity: every caller threads its own run instant (the harness reads Effect’s `Clock` once), it is quantized to the UTC day, and it is recorded on the spec, so a failing run is reproducible by passing the reported `now` back in. #### Parameters [Section titled “Parameters”](#parameters-9) ##### options [Section titled “options”](#options) ###### now [Section titled “now”](#now-5) `number` The run instant to anchor stamps behind, UTC millis. ###### probes? [Section titled “probes?”](#probes-7) `number` ###### seed? [Section titled “seed?”](#seed-5) `number` ###### size? [Section titled “size?”](#size-3) `number` #### Returns [Section titled “Returns”](#returns-13) [`CorpusSpec`](/api/eval/#corpusspec) *** ### buildStack() [Section titled “buildStack()”](#buildstack)
```ts
1
function buildStack(options?): Effect;
```
Build the whole stack inside a scope: generate the corpus, index it, and return retrieval over it. `Effect.acquireRelease` owns the database, so the caller’s `Effect.scoped` closes the connection, and the fixture’s own `cleanup` removes the temp tree. Both matter in a CLI, because a command that leaked a database handle would keep a WAL file alive under `/tmp` for the life of the process. #### Parameters [Section titled “Parameters”](#parameters-10) ##### options? [Section titled “options?”](#options-1) [`StackOptions`](/api/eval/#stackoptions) = `{}` #### Returns [Section titled “Returns”](#returns-14) `Effect`<[`EvalStack`](/api/eval/#evalstack), `never`, `Scope`> *** ### confusionOf() [Section titled “confusionOf()”](#confusionof)
```ts
1
function confusionOf(decisions): ConfusionMatrix;
```
The four cells from the decisions. #### Parameters [Section titled “Parameters”](#parameters-11) ##### decisions [Section titled “decisions”](#decisions-5) readonly [`LabeledDecision`](/api/eval/#labeleddecision)\[] #### Returns [Section titled “Returns”](#returns-15) [`ConfusionMatrix`](/api/eval/#confusionmatrix) *** ### corpusSha256() [Section titled “corpusSha256()”](#corpussha256-1)
```ts
1
function corpusSha256(corpus?): string;
```
The corpus hash. Plain `JSON.stringify` over static data with a fixed key order. #### Parameters [Section titled “Parameters”](#parameters-12) ##### corpus? [Section titled “corpus?”](#corpus-2) readonly [`LabeledCandidate`](/api/eval/#labeledcandidate)\[] = `WRITE_PATH_CORPUS` #### Returns [Section titled “Returns”](#returns-16) `string` *** ### decideAcceptance() [Section titled “decideAcceptance()”](#decideacceptance)
```ts
1
function decideAcceptance(corpus?): readonly PairDecision[];
```
Run the corpus through the composed fold path. Pure. #### Parameters [Section titled “Parameters”](#parameters-13) ##### corpus? [Section titled “corpus?”](#corpus-3) readonly [`LabeledGroup`](/api/eval/#labeledgroup)\[] = `ACCEPTANCE_CORPUS` #### Returns [Section titled “Returns”](#returns-17) readonly [`PairDecision`](/api/eval/#pairdecision)\[] *** ### decideAll() [Section titled “decideAll()”](#decideall)
```ts
1
function decideAll(batch, corpus?): Effect;
```
Run every labeled candidate through the gate, in corpus order. #### Parameters [Section titled “Parameters”](#parameters-14) ##### batch [Section titled “batch”](#batch-1) [`GateBatch`](/api/eval/#gatebatch) ##### corpus? [Section titled “corpus?”](#corpus-4) readonly [`LabeledCandidate`](/api/eval/#labeledcandidate)\[] = `WRITE_PATH_CORPUS` #### Returns [Section titled “Returns”](#returns-18) `Effect`\ *** ### deriveControl() [Section titled “deriveControl()”](#derivecontrol)
```ts
1
function deriveControl(
2
target,
3
family,
4
options?
5
): DerivedControl | undefined;
```
Derive one control from a target, or refuse. Refusal, returned as `undefined`, is the behavior callers depend on. It has two causes, and each one would otherwise produce a control that LOOKS adversarial and is not: 1. The family does not apply (no number to flip). 2. The pair fails the family’s own predicate. For `negation` that means the target body already carried a marker, so the flip is invisible to the guard that defines the family. A caller that ignores the refusal and ships the pair anyway gets a probe whose control is a paraphrase, and a gate built on paraphrases passes no matter how badly retrieval discriminates. #### Parameters [Section titled “Parameters”](#parameters-15) ##### target [Section titled “target”](#target) [`ClaimText`](/api/eval/#claimtext) ##### family [Section titled “family”](#family-1) [`DivergenceFamily`](/api/eval/#divergencefamily) ##### options? [Section titled “options?”](#options-2) [`VariantOptions`](/api/eval/#variantoptions) #### Returns [Section titled “Returns”](#returns-19) [`DerivedControl`](/api/eval/#derivedcontrol) | `undefined` *** ### describeAcceptance() [Section titled “describeAcceptance()”](#describeacceptance)
```ts
1
function describeAcceptance(report): string;
```
A one-line summary: the numbers, then the first disagreement. #### Parameters [Section titled “Parameters”](#parameters-16) ##### report [Section titled “report”](#report) [`AcceptanceReport`](/api/eval/#acceptancereport) #### Returns [Section titled “Returns”](#returns-20) `string` *** ### describeFailure() [Section titled “describeFailure()”](#describefailure)
```ts
1
function describeFailure(report): string;
```
A one-line summary of a failure, for stderr and for the sleep merge’s refusal log. Names the first inversion rather than every one, because an operator needs a probe to reproduce and a thirty-line dump of a failing gate is a thirty-line dump nobody reads. The full list is on the report. #### Parameters [Section titled “Parameters”](#parameters-17) ##### report [Section titled “report”](#report-1) [`DiscriminationReport`](/api/eval/#discriminationreport) #### Returns [Section titled “Returns”](#returns-21) `string` *** ### describeWritePath() [Section titled “describeWritePath()”](#describewritepath)
```ts
1
function describeWritePath(report): string;
```
A one-line summary for stderr and a report subject: the four numbers, then the first disagreement. #### Parameters [Section titled “Parameters”](#parameters-18) ##### report [Section titled “report”](#report-2) [`WritePathReport`](/api/eval/#writepathreport) #### Returns [Section titled “Returns”](#returns-22) `string` *** ### discriminate() [Section titled “discriminate()”](#discriminate)
```ts
1
function discriminate(
2
retrieval,
3
probes,
4
options
5
): Effect;
```
Run the suite and summarize it in one call. #### Parameters [Section titled “Parameters”](#parameters-19) ##### retrieval [Section titled “retrieval”](#retrieval-1) `RetrievalShape` ##### probes [Section titled “probes”](#probes-8) readonly [`Probe`](/api/eval/#probe)\[] ##### options [Section titled “options”](#options-3) ###### limit? [Section titled “limit?”](#limit) `number` ###### mode [Section titled “mode”](#mode-3) [`EvalMode`](/api/eval/#evalmode) ###### mrrFloor? [Section titled “mrrFloor?”](#mrrfloor-3) `number` #### Returns [Section titled “Returns”](#returns-23) `Effect`<[`DiscriminationReport`](/api/eval/#discriminationreport), `StorageFailure`> *** ### discriminationGate() [Section titled “discriminationGate()”](#discriminationgate)
```ts
1
function discriminationGate(options?): Effect;
```
The gate, for `MergeOptions.preMergeGate`. A failing gate FAILS this effect, which is what `@memhtml/sleep`’s `merge` reads to refuse. It wraps the gate in `Effect.result` and turns a failure into `refusal: "gate-failed"` with `main` never moving. The shape matters, because a version returning a boolean would let a caller forget to check it, and a refusable gate must not be optional at its call site. #### Parameters [Section titled “Parameters”](#parameters-20) ##### options? [Section titled “options?”](#options-4) [`EvalOptions`](/api/eval/#evaloptions) = `{}` #### Returns [Section titled “Returns”](#returns-24) `Effect`<[`EvalOutcome`](/api/eval/#evaloutcome), [`DiscriminationFailed`](/api/eval/#discriminationfailed), `never`> *** ### failingEmbedder() [Section titled “failingEmbedder()”](#failingembedder)
```ts
1
function failingEmbedder(): EvalEmbedder;
```
An embedder that always fails, for the lexical-floor scenario. The failure travels through the ERROR channel as a typed `ModelUnavailable` rather than a throw. The floor only holds if retrieval can catch it, and a defect would kill the fiber instead of narrowing the search. #### Returns [Section titled “Returns”](#returns-25) [`EvalEmbedder`](/api/eval/#evalembedder) *** ### fakeEmbedder() [Section titled “fakeEmbedder()”](#fakeembedder)
```ts
1
function fakeEmbedder(): EvalEmbedder;
```
#### Returns [Section titled “Returns”](#returns-26) [`EvalEmbedder`](/api/eval/#evalembedder) *** ### fakeVector() [Section titled “fakeVector()”](#fakevector)
```ts
1
function fakeVector(text): Float32Array;
```
The deterministic embedder, built as a hash-seeded bag of words, L2-normalized. The same construction `@memhtml/index` and `@memhtml/sleep` use in their own harnesses. Two texts sharing vocabulary have a genuinely high cosine and two disjoint texts a low one, which makes a negation-flipped control a real adversary here instead of a random vector the arm trivially separates. A random fake would make the gate meaningless in the easy direction and a constant fake in the hard one. #### Parameters [Section titled “Parameters”](#parameters-21) ##### text [Section titled “text”](#text-1) `string` #### Returns [Section titled “Returns”](#returns-27) `Float32Array` *** ### familyPredicate() [Section titled “familyPredicate()”](#familypredicate)
```ts
1
function familyPredicate(family): (textA, textB) => boolean;
```
The predicate a family’s control must satisfy against its target. #### Parameters [Section titled “Parameters”](#parameters-22) ##### family [Section titled “family”](#family-2) [`DivergenceFamily`](/api/eval/#divergencefamily) #### Returns [Section titled “Returns”](#returns-28) (`textA`, `textB`) => `boolean` *** ### hasBedrockCredentials() [Section titled “hasBedrockCredentials()”](#hasbedrockcredentials)
```ts
1
function hasBedrockCredentials(env?): boolean;
```
True when a Bedrock bearer token is present in the environment. #### Parameters [Section titled “Parameters”](#parameters-23) ##### env? [Section titled “env?”](#env-1) `Readonly`<`Record`<`string`, `string` | `undefined`>> = `process.env` #### Returns [Section titled “Returns”](#returns-29) `boolean` *** ### impliedPairs() [Section titled “impliedPairs()”](#impliedpairs)
```ts
1
function impliedPairs(corpus?): readonly object[];
```
The oriented pairs the corpus implies, in group order, exactly as the phase would hand them to `mergeCandidates`: `groupPairsFor` over each group, with corpus order as the age order and `dedupMergeTextFor` as the veto’s texts. `simFor` is empty because no pair here was mined, so every similarity is the recall floor — the phase’s own value for a group pair it never scored. A path named by two groups takes its age from its FIRST appearance, the way one corpus row has one offset however many groups a model puts it in. The labeled corpus never repeats a path; the role-guard cases the tests build do, and a last-wins map would re-age a keeper under them. #### Parameters [Section titled “Parameters”](#parameters-24) ##### corpus? [Section titled “corpus?”](#corpus-5) readonly [`LabeledGroup`](/api/eval/#labeledgroup)\[] = `ACCEPTANCE_CORPUS` #### Returns [Section titled “Returns”](#returns-30) readonly `object`\[] *** ### liveEmbedder() [Section titled “liveEmbedder()”](#liveembedder)
```ts
1
function liveEmbedder(): Effect;
```
The real Bedrock embedder, for `live` mode. Built only when a caller asks for it. #### Returns [Section titled “Returns”](#returns-31) `Effect`<[`EvalEmbedder`](/api/eval/#evalembedder)> *** ### makeFixtureCorpus() [Section titled “makeFixtureCorpus()”](#makefixturecorpus)
```ts
1
function makeFixtureCorpus(options?): Effect;
```
A scaffolded memory repo carrying a generated corpus. `initRepo` is the real one, so the fixture carries the real `.gitignore`, the real `.gitattributes`, and the real `merge.ours.driver` config. That config is per-clone, and the `merge=ours` attribute does nothing without it. #### Parameters [Section titled “Parameters”](#parameters-25) ##### options? [Section titled “options?”](#options-5) [`FixtureOptions`](/api/eval/#fixtureoptions) = `{}` #### Returns [Section titled “Returns”](#returns-32) `Effect`<[`FixtureCorpus`](/api/eval/#fixturecorpus)> *** ### manifestFor() [Section titled “manifestFor()”](#manifestfor)
```ts
1
function manifestFor(report, context): WritePathManifest;
```
Build the manifest a run would freeze. #### Parameters [Section titled “Parameters”](#parameters-26) ##### report [Section titled “report”](#report-3) [`WritePathReport`](/api/eval/#writepathreport) ##### context [Section titled “context”](#context) ###### frozenAt [Section titled “frozenAt”](#frozenat-1) `string` ###### memhtmlVersion [Section titled “memhtmlVersion”](#memhtmlversion-1) `string` #### Returns [Section titled “Returns”](#returns-33) [`WritePathManifest`](/api/eval/#writepathmanifest) *** ### memoryFileFor() [Section titled “memoryFileFor()”](#memoryfilefor)
```ts
1
function memoryFileFor(spec): string;
```
One spec as a memory file’s bytes. Hand-assembled rather than routed through `@memhtml/html`’s `renderTemplate`, because of the element kits. `renderTemplate` escapes each `body` string as TEXT, which is right for an agent’s tool parameter and wrong for a `