@memhtml/html is the memory file format: parse, serialize, hash, fence-language detection, the surgical head editors, the template that writes a new file, and the closed vocabulary all of them read. The format is semantic HTML5 over that vocabulary and this package is its only implementation, so a memory file stays a document a browser renders with no server, and the index stays derivable from the tree alone. Every element in the vocabulary earns its place by carrying indexer semantics, which is why the parse output is named for what the indexer stores rather than for the markup it came from: gist is the one <mark>, eventAt is the first <time>, facets are the <dl> pairs. The package sits on parse5, effect, a pinned highlight.js, and @memhtml/contracts for the closed edge, type and error vocabularies.
vocabulary.ts is the bottom of the stack and holds no logic, because the vocabulary IS the policy: there is no sanitizer library here and no allow/deny engine, only data. Both metadata planes use the same hyphenated prefix (META_PREFIX, LINK_REL_PREFIX) because HTML5 metadata names are a flat token space and a rel token cannot hold a colon at all. REQUIRED_META is the four facts no pure function can invent, a type, a status, and the two write stamps; memhtml-confidence, memhtml-importance and memhtml-author are deliberately absent because the files table documents a default for each, so a hand-authored file missing them is completed rather than refused. META_ORDER is the emit order and a diff-stability contract: a new scalar goes at the end of the scalar block and a new repeatable at the end of the repeatable block, so two agents stamping different keys leave each other’s lines in place. REPEATABLE_META names the three keys that may appear more than once, one element per value so correcting one tag is a one-line diff, and isMemoryMetaName and isRepeatableMeta are the predicates over both lists. The element tables split by role: DOCUMENT_ELEMENTS is the skeleton that carries no indexer semantics, ARTICLE_ELEMENTS is one entry per row of the format’s element table (with tr added, because a <table> cannot hold a cell without a row), PERSON_ELEMENTS is the <address> a person file adds, FIGURE_SCOPED_ELEMENTS is <div> and <span> permitted only inside a <figure> where a pasted sample carries its own containers, and KNOWN_ELEMENTS is their union. The rest are serialization and scoping facts the writer and the hash need: VOID_ELEMENTS, GIST_EXCLUDED_ELEMENTS (a command line is body text and searchable, and it is not the claim), FORBIDDEN_ELEMENTS and FORBIDDEN_ATTRIBUTES.
tree.ts is parse5 navigation narrowed to what the format needs, pure, total, and ignorant of the vocabulary. Source locations are always on, because the head editors splice by byte offset and a second parse to obtain them would let two views of the same bytes disagree. parseArticleFragment parses article inner HTML in <article> context on purpose: fragment parsing without a context element runs in <template> content, where table-scoped elements are foster-parented out and their text would vanish from the digest, so parsing in the element the markup came from is what makes contentHash agree on a doc and on the whole file. The walks are iterative rather than recursive so a pathologically deep hand-authored file cannot overflow the stack. markup.ts is the writer, and it exists because parse5’s own serializer is unusable here for two reasons. The costlier one is <pre>: the parser swallows one newline after the start tag and parse5 does not re-emit it, so a file loses one newline on every write, and <pre> whitespace is inside the hash scope verbatim, so that drift would move a memory’s dedup key on a pass that edited nothing. The second is attribute order, which parse5 preserves, leaving two files differing only in attribute order as different bytes forever; sorting by name canonicalizes them. escapeText writes U+00A0 as because an invisible character in a claim is a trap, and escapeAttribute turns line breaks into character references so an emitted <meta> or <link> is always one source line, which is what the line-splicing editors depend on.
hash.ts owns the one value in the system that must be invariant under head edits. The content hash is sha256 over the whitespace-normalized text content of <article>, except inside <pre> where whitespace passes through byte for byte, so meta and <link> edits are outside the scope by construction and confidence decay, access bookkeeping and the sleep phases’ own stamping never look like content changes. Without that invariance a corpus-wide decay pass would present the whole corpus as new content and dedup would collapse. Only ASCII whitespace collapses; U+00A0 is content, because a non-breaking space inside a claim is a typographic decision worth preserving. A block element contributes a collapsible space at each edge, so the digest is a function of the article’s words and not of its indentation, and the outer trim applies only to a leading or trailing collapsible segment, since trimming the whole result would let <pre> a</pre> and <pre>a</pre> hash identically. canonicalText takes CanonicalTextOptions, whose excludeCode omits <pre> and <code> per the gist rule, canonicalArticleText is the same fold with code kept, and contentHash accepts a parsed doc, an article node, a HashableArticle, or raw HTML, locating <article> first so head content never reaches the digest and hashing a string with no article as article-inner-HTML rather than failing, because refusing malformed input is parseMemory’s job and not the digest’s. HASH_ALGORITHM is the prefix that makes a stored digest self-describing and isContentHash recognizes the sha256:<64 hex> form.
constraints.ts is the six format constraints as pure predicates over a parsed document. Constraints 1 through 5 are violations and the file is not a memory; constraint 6 is a warning, because an element outside the vocabulary still indexes and the format has to degrade gracefully on a file a human hand-wrote in a hurry. Nothing here throws and nothing repairs: a checker that silently fixed a violation would make memhtml doctor report a clean corpus that the next hand-edit breaks again. isValidDatetime admits exactly two forms, an ISO date and a canonical UTC instant, and is narrower than HTML’s own datetime grammar because files.event_at, files.due_at and files.valid_until are compared and ordered as raw strings, so every admitted value has to sort lexicographically as it sorts chronologically; a space separator, a non-UTC offset, or optional seconds each break that, and range is checked too so 2026-13-45 is refused rather than stored as an unsortable date. isRootRelativeHref requires the document-reference form, repo-root-relative with a leading slash and no scheme, host or .. segment, since a relative href breaks on the first git mv of the source file and a protocol-relative one silently leaves the repo. The <mark> rules are the format’s center: exactly one, non-empty, leading the article’s first <p> or <li>, and never inside an <aside> or <details>, so a recall line cannot quote a caveat or something the author chose to hide. Emptiness is tested with the real gist rule (canonicalText with excludeCode, then trimmed) rather than a convenient approximation, because parse.ts derives gist from exactly that text, so <mark><code>drain --vip</code></mark> indexes with no gist and is refused. checkDocument collects rather than short-circuits, so one parse tells an author everything wrong with the file, and returns a CheckResult of violations and warnings in constraint order then document order, with warnings deduplicated by element name so forty stray <div>s produce one actionable line. VIOLATION_SEPARATOR is how that list is joined into the single string InvalidMemory.reason carries.
parse.ts implements the extraction table one element at a time, and the interesting part is what it refuses versus what it drops. A malformed optional meta is dropped and left for memhtml doctor: memhtml-confidence="high" means the file stated no confidence and the files default applies, and a blank value is malformed rather than zero, since Number("") is 0 and a blank confidence must not index as 0.00. The datetime metas and the task metas are refusals instead, because a drop is only safe where the files table owns a default and an instant has none: dropping an unsortable memhtml-valid-until WIDENS the window to always-valid, so the file would answer an as-of query for instants it said the fact was already dead, and a task with no memhtml-task-status has no lifecycle position at all, so it would be invisible to the surface that exists to show it. parseMemory fails with InvalidMemory and no partial doc, and checkMemory is the total variant that returns the structured violation and warning lists without building one. document.ts holds the shapes, as Effect schemas paired with the interfaces they decode to, and they live here rather than in @memhtml/contracts because they are format types: the extraction fields exist only because the HTML has those elements. MemoryMetas states each field’s coordinate space on the field, and an absent value means the file did not state it, with no default substituted, so a hand-authored omission stays distinguishable from a deliberate assertion. ArticleExtractions is what the indexer reads out of <article>, with html a serialization fixed point, bodyText the FTS and embedding input including asides, details bodies, captions and <pre>, gist the one span nothing derives, eventAt the world time the recency arm coalesces with the write stamp, and the document-order lists (facets, citations, definedTerms, summaryTexts, asideTexts, captions, codeLangs, abbreviations) each feeding their own table or entity promotion. MemoryLink carries an unprefixed rel from the closed edge vocabulary with the href as the file spells it, Facet pairs a <dt> with each of its <dd>s and carries a numericValue only when a <data value> parses as a finite number, deliberately unitless because the unit lives in the human phrasing, and Citation is a <cite> or a <q> with its optional source URI.
serialize.ts is the inverse: parseMemory(serializeMemory(doc)) yields the doc again, which is the property that lets sleep read, adjust and write a file back without touching content it did not mean to touch. The output is deterministic, one <meta> per line in META_ORDER with no head indentation, so stamping memhtml-updated produces a one-line git diff a human actually reviews, and article.html is re-parsed and re-serialized rather than interpolated so a hand-assembled doc still gets the canonical fixed-point markup. metaPairs is the ordered [name, content] expansion, repeatable keys one pair per value, confidence rendered to two decimals and every other number as an integer. editors.ts is the alternative to that round trip for a bookkeeping pass: byte-level splices that change exactly one line and provably do not touch the article. The sleep phases stamp memhtml-updated, decay memhtml-confidence, bump memhtml-reprieves and set memhtml-needs-revision on files they otherwise leave alone, and re-serializing the whole document for that would make the pass look like a content change in git diff and risk moving the dedup key. Splicing by source offset keeps the hash invariant by construction, so contentHash(setMeta(html, name, value)) equals contentHash(html) for every name. setMeta replaces the first meta of a name or inserts a line in META_ORDER position and refuses an unknown name by returning the input unchanged, addMeta appends another value of a repeatable name and is a no-op on a value already present so a re-run of a phase cannot grow the head, addLink is idempotent on the (rel, href) pair because the conflict phase promotes the same corroborated edge on each run and a duplicated <link> would become a duplicated edges row, removeMeta and removeLink cut whole lines (omitting href drops every link of that rel), and readMeta and readLinks are the deliberately tolerant read halves: the document is parsed and nothing is validated, so a caller holding raw bytes can ask what a file points at without the file having to be a well-formed memory.
fences.ts owns the fenced-code grammar and only the grammar: what opens a fence, what closes it, and what language its info string names. There is one copy because the template renders a block and the write doors split prose into paragraphs, and two answers to “is this line a fence?” would let the splitter keep a block intact that the template then fails to recognize. Backtick fences only, per CommonMark: three or more open, a run at least as long closes, and the info string may hold no backtick; tilde fences are deliberately absent because agents write backticks. LANG_TOKEN is the language-token grammar, wide enough for ts, c++, c# and objective-c and closed to whitespace and markup characters, so a data-lang value is always safe in view-source and safe to promote to a lang: entity. fenceOpeningOf and closesFence are the line-level predicates, and fencedBlockOf returns a FencedBlock only when the whole paragraph is the fence: prose sharing a paragraph with a fence is not a fence, and an unterminated fence is undefined, because rendering either as code would commit markup the author may not have finished while escaping it as text keeps the file saying what the author typed.
detect.ts is a port of a measured detector, not a fresh design, because an unlabeled fence carries no language and wrong metadata costs more than none. A private evaluation corpus swept two candidates, flourite and highlight.js highlightAuto, over 332 real fences and file slices and picked the operating point where measured precision first reaches a 95% floor: highlight.js won at threshold 0.28685957116771854 with precision 95.18% at 25.0% coverage, where flourite reached 100% precision at only 3.0% coverage, abstaining exactly where this one stamps. DEPLOY_THRESHOLD is 0.30, a conservative rounding up of that point, safe because confidence is monotone in evidence: re-measured on the same corpus it stamped 81 snippets, 77 correctly, for 95.06% precision at 24.4% coverage, and both numbers are recorded because the measured one is the evidence and the deployed one is the decision. detect returns a Detection with the threshold not yet applied, scoring 1 - exp(-(top - runnerUp) / lines), the per-line evidence margin between the winning grammar and its closest real competitor, where a runner-up normalizing to the same canonical token is not disagreement and so charges no penalty, and where hljs runs its full grammar set deliberately: restricting it to the twelve-name vocabulary measured 0.9% coverage against 25%, because with the true language’s grammar absent a runner-up wins unopposed and wins confidently. detectLang applies both independent gates, in-vocabulary and at or above the threshold, since a confident detection of a language outside CANONICAL_LANGS is still nothing, and the corpus has hljs naming smali at 0.86 and autohotkey at 0.55 for snippets that are really bash and TypeScript. CANONICAL_LANGS is one lowercase token per language because data-lang promotes to a lang: entity by exact string match, and normalizeLang folds the aliases seen in detector output, info strings and file extensions (highlight.js names its HTML grammar “xml”, ships no TOML grammar, and SQL dialects collapse because a dialect distinction is noise at entity granularity). Three operational decisions come with the port. highlight.js is pinned exactly, no caret, because relevance scores are grammar-dependent and a bump silently moves every confidence and therefore which fences get stamped; measured against 11.12.0, one snippet fell from 0.5406 to 0.1993 and stopped stamping toml, so a bump is a deliberate decision that re-runs the eval and re-derives the threshold. The library loads lazily on the first detection, because eagerly imported its 192 grammars cost roughly 100ms and 12MB in every process that touches the format layer, including the read path, which detects nothing. Fences longer than 4096 characters abstain without running hljs at all, since highlightAuto is super-linear in input size (5KB is about 450ms and 100KB about 122s of blocking CPU on the pinned build) and the MCP server is one single-threaded process, so an uncapped detector would let one large unlabeled fence wedge every other request. Detection is also write-time only: the stamp goes into the file, which is the system of record, and rebuild reads data-lang back through parse.ts and never re-detects, so a dropped and rebuilt index stays a pure function of the tree instead of a function of the installed hljs version. Author strings are untouched throughout, since this vocabulary gates detector output alone.
template.ts is what the write path calls instead of the serializer, because an agent supplies a title, a claim and prose, and something has to turn that into the <mark>-led first paragraph the format requires: an agent hand-authoring that placement would violate constraint 1 regularly, and a template that places it always places it correctly. NewMemoryInput mirrors the write tool’s parameters, with claim required because a memory with no claim has nothing to disclose at Tier 1, body one string per paragraph, and articleHtml the escape hatch for a correction carrying its own <dl> or <figure>, where the caller then owns constraint 1. articleHtmlFor joins the first body paragraph onto the claim’s own paragraph as its tail, drops empty paragraphs, and renders a body paragraph that is a fenced block as <figure><pre><code data-lang> rather than a <p>, never joined onto the claim, so the claim still leads the article and code stays out of the sentence; the language rides on data-lang because class is a constraint-3 violation and lang is a human-language attribute, and the info string wins outright, so detectLang runs only on an unlabeled fence and no detector opinion overrides what a human wrote. DEFAULT_TASK_STATUS is todo, defaulted rather than required of the caller because parseMemory refuses a task with no memhtml-task-status and it is stamped only for a task, since a non-task carrying one is a file git holds and the indexer skips. newMemoryDoc builds the doc with memhtml-content-hash already stamped from the article the template just built, so the file reaching disk is self-consistent and the indexer’s own recomputation agrees with it on the first read, and renderTemplate is the one function the write path calls for bytes.
The package publishes one import path, @memhtml/html, which re-exports the eleven modules below, so the reference on this page is the whole exported surface. tree.ts is internal: it reaches consumers only as the parse5 node types the other modules accept and return.
| Module | What it contributes |
|---|---|
constraints.ts |
checkDocument and its CheckResult, VIOLATION_SEPARATOR, and the two value grammars isValidDatetime and isRootRelativeHref. |
detect.ts |
CANONICAL_LANGS and CanonicalLang, normalizeLang, DEPLOY_THRESHOLD, detect with its Detection, and detectLang. |
document.ts |
MemoryDoc, MemoryMetas, ArticleExtractions, MemoryLink, Facet and Citation, each a schema paired with the interface it decodes to. |
editors.ts |
The write half (setMeta, addMeta, removeMeta, addLink, removeLink) and the tolerant read half (readMeta, readLinks). |
fences.ts |
LANG_TOKEN, fenceOpeningOf, closesFence, and fencedBlockOf with its FencedBlock. |
hash.ts |
HASH_ALGORITHM, canonicalText with CanonicalTextOptions, canonicalArticleText, contentHash with HashableArticle, and isContentHash. |
markup.ts |
escapeText, escapeAttribute, writeChildren and writeOuter. |
parse.ts |
parseMemory (the doc or InvalidMemory) and checkMemory (the total violation and warning lists). |
serialize.ts |
serializeMemory and metaPairs. |
template.ts |
NewMemoryInput, articleHtmlFor, newMemoryDoc, renderTemplate and DEFAULT_TASK_STATUS. |
tree.ts |
Nothing on the package surface: the parse5 navigation and the two parsers the modules above share. |
vocabulary.ts |
The naming conventions (META_PREFIX, LINK_REL_PREFIX), the metadata lists (REQUIRED_META, META_ORDER, MemoryMetaName, isMemoryMetaName, REPEATABLE_META, RepeatableMeta, isRepeatableMeta), the element tables (DOCUMENT_ELEMENTS, ARTICLE_ELEMENTS, PERSON_ELEMENTS, FIGURE_SCOPED_ELEMENTS, KNOWN_ELEMENTS), and the serialization and scoping facts (VOID_ELEMENTS, GIST_EXCLUDED_ELEMENTS, FORBIDDEN_ELEMENTS, FORBIDDEN_ATTRIBUTES). |
Interfaces
Section titled “Interfaces”ArticleExtractions
Section titled “ArticleExtractions”The parsed shape ArticleExtractions decodes to, named so a signature can refer to it.
Extends
Section titled “Extends”ArticleExtractionsShape
Properties
Section titled “Properties”abbreviations
Section titled “abbreviations”readonly abbreviations: readonly string[];<abbr title> expansions in document order. FTS-visible.
Inherited from
Section titled “Inherited from”ArticleExtractionsShape.abbreviations
asideTexts
Section titled “asideTexts”readonly asideTexts: readonly string[];<aside> texts in document order. In bodyText and therefore searchable, and left out of
a recall index line. An aside is a scope caveat, so quoting it as the memory
would present the exception as the rule.
Inherited from
Section titled “Inherited from”ArticleExtractionsShape.asideTexts
bodyText
Section titled “bodyText”readonly bodyText: string = Schema.String;All article text, whitespace-collapsed. The FTS body and the embedding input. Includes
<aside>, <details> bodies, <figcaption>, and <pre>, so everything is searchable.
Inherited from
Section titled “Inherited from”ArticleExtractionsShape.bodyText
captions
Section titled “captions”readonly captions: readonly string[];<figcaption> texts in document order. FTS-visible; the <pre> body is not gist-visible.
Inherited from
Section titled “Inherited from”ArticleExtractionsShape.captions
citations
Section titled “citations”readonly citations: readonly object[];<cite> and <q> in document order, one row each in file_citations.
Inherited from
Section titled “Inherited from”ArticleExtractionsShape.citations
codeLangs
Section titled “codeLangs”readonly codeLangs: readonly string[];data-lang values of <code> elements, lowercased, in document order. Each promotes to a
lang:<value> entity the way a <dfn> promotes to concept:. A memory carrying a
TypeScript snippet is findable by --entity lang:ts without the author restating the
language in a memhtml-entity meta the fence’s info string already carried.
Inherited from
Section titled “Inherited from”ArticleExtractionsShape.codeLangs
definedTerms
Section titled “definedTerms”readonly definedTerms: readonly string[];<dfn> terms, in document order. Each promotes to a concept:<term> entity, so a
semantic memory that defines a term is findable by the term without the author also
writing a memhtml-entity meta.
Inherited from
Section titled “Inherited from”ArticleExtractionsShape.definedTerms
eventAt?
Section titled “eventAt?”readonly eventAt?: string;The FIRST <time datetime> value, as authored (ISO date or ISO datetime, so 2026-07-28
and 2026-07-28T14:03:11Z both occur). This is when the remembered fact HAPPENED, so it is
world time and not write time. The recency arm ranks by coalesce(event_at, updated_at), so an
episodic memory backdates correctly. Absent when the article names no time.
Inherited from
Section titled “Inherited from”ArticleExtractionsShape.eventAt
facets
Section titled “facets”readonly facets: readonly object[];<dt>/<dd> pairs in document order, one row each in file_facets.
Inherited from
Section titled “Inherited from”ArticleExtractionsShape.facets
readonly gist: string = Schema.String;The ONE <mark> span’s text, the claim and only the claim. It is the span the author chose,
the Tier-1 disclosure line, and the span a correction targets. Nothing derives it, so it is
not a summary and not the first sentence.
Inherited from
Section titled “Inherited from”ArticleExtractionsShape.gist
readonly html: string = Schema.String;The article’s inner HTML, trimmed. A serialization fixed point: re-parsing and re-serializing it yields the same bytes, which is what makes the round-trip property hold.
Inherited from
Section titled “Inherited from”ArticleExtractionsShape.html
summaryTexts
Section titled “summaryTexts”readonly summaryTexts: readonly string[];<summary> texts in document order. Always disclosed in recall, as Tier 2 of the fold.
The matching <details> body is Tier 3 and reaches an agent only through memory_read.
Inherited from
Section titled “Inherited from”ArticleExtractionsShape.summaryTexts
BlockTagInArticle
Section titled “BlockTagInArticle”Which element’s text holds a block tag, and the tag as the text reads.
Properties
Section titled “Properties”element
Section titled “element”readonly element: "mark" | "p";readonly tag: string;BlockTagInRun
Section titled “BlockTagInRun”Where a block tag sits in a text: the position it starts at, and the tag as written.
Properties
Section titled “Properties”offset
Section titled “offset”readonly offset: number;readonly tag: string;CanonicalTextOptions
Section titled “CanonicalTextOptions”What canonicalText may skip.
Properties
Section titled “Properties”excludeCode?
Section titled “excludeCode?”readonly excludeCode?: boolean;Omit <pre> and <code> subtrees, per the gist rule. A command line is body text and it
is searchable, and it is not part of the claim.
CheckResult
Section titled “CheckResult”The outcome of checking a document. Both lists are in constraint order, then document order.
Properties
Section titled “Properties”violations
Section titled “violations”readonly violations: readonly string[];warnings
Section titled “warnings”readonly warnings: readonly string[];Citation
Section titled “Citation”The parsed shape Citation decodes to, named so a signature can refer to it.
Extends
Section titled “Extends”CitationShape
Properties
Section titled “Properties”readonly href?: string;Inherited from
Section titled “Inherited from”CitationShape.href
readonly text: string = Schema.String;Inherited from
Section titled “Inherited from”CitationShape.text
Detection
Section titled “Detection”What the detector concluded, threshold not yet applied. Exported for tests and for the sweep.
Properties
Section titled “Properties”confidence
Section titled “confidence”readonly confidence: number;Monotone evidence score in [0, 1). NOT a calibrated probability, and meaningful only against DEPLOY_THRESHOLD.
readonly lang: | "html" | "javascript" | "typescript" | "python" | "bash" | "json" | "sql" | "yaml" | "toml" | "css" | "go" | "rust" | undefined;Canonical language, or undefined when hljs abstained or named a language outside the vocabulary.
The parsed shape Facet decodes to, named so a signature can refer to it.
Extends
Section titled “Extends”FacetShape
Properties
Section titled “Properties”readonly name: string = Schema.String;Inherited from
Section titled “Inherited from”FacetShape.name
numericValue?
Section titled “numericValue?”readonly numericValue?: number;Inherited from
Section titled “Inherited from”FacetShape.numericValue
readonly value: string = Schema.String;Inherited from
Section titled “Inherited from”FacetShape.value
FencedBlock
Section titled “FencedBlock”One recognized fenced block: its code verbatim, and the info string’s language when it names one.
Properties
Section titled “Properties”readonly code: string;The lines between the fences, joined verbatim, with indentation and blank lines preserved.
readonly lang?: string;The first info-string token, lowercased, when it matches LANG_TOKEN.
HashableArticle
Section titled “HashableArticle”What contentHash accepts. A caller that already holds a parsed article passes the node; the store and indexer, which hold bytes, pass a string or the article HTML.
Properties
Section titled “Properties”article
Section titled “article”readonly article: object;readonly html: string;MemoryDoc
Section titled “MemoryDoc”The parsed shape MemoryDoc decodes to, named so a signature can refer to it.
Extends
Section titled “Extends”MemoryDocShape
Properties
Section titled “Properties”aliases
Section titled “aliases”readonly aliases: readonly string[];memhtml-alias values as authored, in document order. The other names this file’s subject is
recorded under — a person file’s declaration that laura and l.desabelin are the same person
as its own person: entity.
Meant as EVIDENCE for entity resolution, which can merge an alias-backed pair whatever
the name similarity says. That is why the field is a bare string list rather than a parsed
type:name: the declaring file’s own memhtml-entity already carries the type, and an alias
that restated it could disagree with it.
Inherited from
Section titled “Inherited from”MemoryDocShape.aliases
article
Section titled “article”readonly article: object = ArticleExtractions;abbreviations
Section titled “abbreviations”readonly abbreviations: readonly string[];<abbr title> expansions in document order. FTS-visible.
asideTexts
Section titled “asideTexts”readonly asideTexts: readonly string[];<aside> texts in document order. In bodyText and therefore searchable, and left out of
a recall index line. An aside is a scope caveat, so quoting it as the memory
would present the exception as the rule.
bodyText
Section titled “bodyText”readonly bodyText: string = Schema.String;All article text, whitespace-collapsed. The FTS body and the embedding input. Includes
<aside>, <details> bodies, <figcaption>, and <pre>, so everything is searchable.
captions
Section titled “captions”readonly captions: readonly string[];<figcaption> texts in document order. FTS-visible; the <pre> body is not gist-visible.
citations
Section titled “citations”readonly citations: readonly object[];<cite> and <q> in document order, one row each in file_citations.
codeLangs
Section titled “codeLangs”readonly codeLangs: readonly string[];data-lang values of <code> elements, lowercased, in document order. Each promotes to a
lang:<value> entity the way a <dfn> promotes to concept:. A memory carrying a
TypeScript snippet is findable by --entity lang:ts without the author restating the
language in a memhtml-entity meta the fence’s info string already carried.
definedTerms
Section titled “definedTerms”readonly definedTerms: readonly string[];<dfn> terms, in document order. Each promotes to a concept:<term> entity, so a
semantic memory that defines a term is findable by the term without the author also
writing a memhtml-entity meta.
eventAt?
Section titled “eventAt?”readonly eventAt?: string;The FIRST <time datetime> value, as authored (ISO date or ISO datetime, so 2026-07-28
and 2026-07-28T14:03:11Z both occur). This is when the remembered fact HAPPENED, so it is
world time and not write time. The recency arm ranks by coalesce(event_at, updated_at), so an
episodic memory backdates correctly. Absent when the article names no time.
facets
Section titled “facets”readonly facets: readonly object[];<dt>/<dd> pairs in document order, one row each in file_facets.
readonly gist: string = Schema.String;The ONE <mark> span’s text, the claim and only the claim. It is the span the author chose,
the Tier-1 disclosure line, and the span a correction targets. Nothing derives it, so it is
not a summary and not the first sentence.
readonly html: string = Schema.String;The article’s inner HTML, trimmed. A serialization fixed point: re-parsing and re-serializing it yields the same bytes, which is what makes the round-trip property hold.
summaryTexts
Section titled “summaryTexts”readonly summaryTexts: readonly string[];<summary> texts in document order. Always disclosed in recall, as Tier 2 of the fold.
The matching <details> body is Tier 3 and reaches an agent only through memory_read.
Inherited from
Section titled “Inherited from”MemoryDocShape.article
entities
Section titled “entities”readonly entities: readonly string[];memhtml-entity values as authored, e.g. service:checkout-api, in document order.
Inherited from
Section titled “Inherited from”MemoryDocShape.entities
readonly links: readonly object[];Inherited from
Section titled “Inherited from”MemoryDocShape.links
readonly metas: object = MemoryMetas;archivedAt?
Section titled “archivedAt?”readonly archivedAt?: string;When eviction moved it under archive/<YYYY>/. Present iff status is archived.
author?
Section titled “author?”readonly author?: string;Who wrote it, as agent:<model> or human:<name>.
confidence?
Section titled “confidence?”readonly confidence?: number;Unitless in [0, 1]. 1.0 is an unqualified assertion.
contentHash?
Section titled “contentHash?”readonly contentHash?: string;The sha256:<hex> the file claims for its own article. Advisory: the parser reports it
verbatim and leaves it alone, so a stale value is visible to memhtml doctor rather than
silently corrected into agreement.
createdAt
Section titled “createdAt”readonly createdAt: string = Schema.String;dueAt?
Section titled “dueAt?”readonly dueAt?: string;When a task is due. An ISO date or datetime, ordered as a string exactly as event_at is,
so due_at < now is a lexicographic comparison rather than a parse per row.
A wall-clock DEADLINE. validUntil says when a remembered fact stops being true, and this
says when work is late, so neither a write time nor a validity bound belongs here.
importance?
Section titled “importance?”readonly importance?: number;1-10 inclusive, a display ordinal. The retention scorer divides by 10 before using it.
memoryType
Section titled “memoryType”readonly memoryType: | "episodic" | "semantic" | "procedural" | "agent_insight" | "user_preference" | "error_pattern" | "verdict" | "precedent" | "arc" | "task" = MemoryType;needsRevision?
Section titled “needsRevision?”readonly needsRevision?: boolean;A curation pass flagged the claim as stale or contested and wants an agent to revisit it.
promptId?
Section titled “promptId?”readonly promptId?: string;The prompt within that session. Joins trace_prompts.prompt_id.
reprieves?
Section titled “reprieves?”readonly reprieves?: number;How many times a retention pass spared this memory from eviction. A count, monotonically
non-decreasing, >= 0.
sessionId?
Section titled “sessionId?”readonly sessionId?: string;The Claude Code session that produced it. Joins traces.session_id.
status
Section titled “status”readonly status: "active" | "archived" = MemoryStatus;supersededBy?
Section titled “supersededBy?”readonly supersededBy?: string;The path that replaced it, repo-root-relative with a leading / (the document-reference
form). The inverse of a memhtml-supersedes link on the newer file.
taskStatus?
Section titled “taskStatus?”readonly taskStatus?: "todo" | "doing" | "blocked" | "done";A task’s own lifecycle position, present iff memoryType is task. The parser reports a
violation either way round. A separate axis from MemoryMetas.status, which stays
active/archived for a task as for anything else.
turnUuid?
Section titled “turnUuid?”readonly turnUuid?: string;The turn within that session. Joins trace_prompts.turn_uuid.
updatedAt
Section titled “updatedAt”readonly updatedAt: string = Schema.String;validFrom?
Section titled “validFrom?”readonly validFrom?: string;Bitemporal validity of the fact, not of the row. Absent means always-valid.
validUntil?
Section titled “validUntil?”readonly validUntil?: string;Inherited from
Section titled “Inherited from”MemoryDocShape.metas
readonly tags: readonly string[];memhtml-tag values as authored, in document order. Open vocabulary.
Inherited from
Section titled “Inherited from”MemoryDocShape.tags
readonly title: string = Schema.String;The <title> text. The human name of the memory and the slug’s source.
Inherited from
Section titled “Inherited from”MemoryDocShape.title
warnings
Section titled “warnings”readonly warnings: readonly string[];Vocabulary warnings, such as an element outside the closed vocabulary or a <div> outside
a <figure>. Format constraint 6: the file still parses and still indexes, so a
hand-authored file degrades gracefully instead of being refused.
Inherited from
Section titled “Inherited from”MemoryDocShape.warnings
MemoryLink
Section titled “MemoryLink”The parsed shape MemoryLink decodes to, named so a signature can refer to it.
Extends
Section titled “Extends”MemoryLinkShape
Properties
Section titled “Properties”readonly href: string = Schema.String;Inherited from
Section titled “Inherited from”MemoryLinkShape.href
readonly rel: | "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;Inherited from
Section titled “Inherited from”MemoryLinkShape.rel
MemoryMetas
Section titled “MemoryMetas”The parsed shape MemoryMetas decodes to, named so a signature can refer to it.
Extends
Section titled “Extends”MemoryMetasShape
Properties
Section titled “Properties”archivedAt?
Section titled “archivedAt?”readonly archivedAt?: string;When eviction moved it under archive/<YYYY>/. Present iff status is archived.
Inherited from
Section titled “Inherited from”MemoryMetasShape.archivedAt
author?
Section titled “author?”readonly author?: string;Who wrote it, as agent:<model> or human:<name>.
Inherited from
Section titled “Inherited from”MemoryMetasShape.author
confidence?
Section titled “confidence?”readonly confidence?: number;Unitless in [0, 1]. 1.0 is an unqualified assertion.
Inherited from
Section titled “Inherited from”MemoryMetasShape.confidence
contentHash?
Section titled “contentHash?”readonly contentHash?: string;The sha256:<hex> the file claims for its own article. Advisory: the parser reports it
verbatim and leaves it alone, so a stale value is visible to memhtml doctor rather than
silently corrected into agreement.
Inherited from
Section titled “Inherited from”MemoryMetasShape.contentHash
createdAt
Section titled “createdAt”readonly createdAt: string = Schema.String;Inherited from
Section titled “Inherited from”MemoryMetasShape.createdAt
dueAt?
Section titled “dueAt?”readonly dueAt?: string;When a task is due. An ISO date or datetime, ordered as a string exactly as event_at is,
so due_at < now is a lexicographic comparison rather than a parse per row.
A wall-clock DEADLINE. validUntil says when a remembered fact stops being true, and this
says when work is late, so neither a write time nor a validity bound belongs here.
Inherited from
Section titled “Inherited from”MemoryMetasShape.dueAt
importance?
Section titled “importance?”readonly importance?: number;1-10 inclusive, a display ordinal. The retention scorer divides by 10 before using it.
Inherited from
Section titled “Inherited from”MemoryMetasShape.importance
memoryType
Section titled “memoryType”readonly memoryType: | "episodic" | "semantic" | "procedural" | "agent_insight" | "user_preference" | "error_pattern" | "verdict" | "precedent" | "arc" | "task" = MemoryType;Inherited from
Section titled “Inherited from”MemoryMetasShape.memoryType
needsRevision?
Section titled “needsRevision?”readonly needsRevision?: boolean;A curation pass flagged the claim as stale or contested and wants an agent to revisit it.
Inherited from
Section titled “Inherited from”MemoryMetasShape.needsRevision
promptId?
Section titled “promptId?”readonly promptId?: string;The prompt within that session. Joins trace_prompts.prompt_id.
Inherited from
Section titled “Inherited from”MemoryMetasShape.promptId
reprieves?
Section titled “reprieves?”readonly reprieves?: number;How many times a retention pass spared this memory from eviction. A count, monotonically
non-decreasing, >= 0.
Inherited from
Section titled “Inherited from”MemoryMetasShape.reprieves
sessionId?
Section titled “sessionId?”readonly sessionId?: string;The Claude Code session that produced it. Joins traces.session_id.
Inherited from
Section titled “Inherited from”MemoryMetasShape.sessionId
status
Section titled “status”readonly status: "active" | "archived" = MemoryStatus;Inherited from
Section titled “Inherited from”MemoryMetasShape.status
supersededBy?
Section titled “supersededBy?”readonly supersededBy?: string;The path that replaced it, repo-root-relative with a leading / (the document-reference
form). The inverse of a memhtml-supersedes link on the newer file.
Inherited from
Section titled “Inherited from”MemoryMetasShape.supersededBy
taskStatus?
Section titled “taskStatus?”readonly taskStatus?: "todo" | "doing" | "blocked" | "done";A task’s own lifecycle position, present iff memoryType is task. The parser reports a
violation either way round. A separate axis from MemoryMetas.status, which stays
active/archived for a task as for anything else.
Inherited from
Section titled “Inherited from”MemoryMetasShape.taskStatus
turnUuid?
Section titled “turnUuid?”readonly turnUuid?: string;The turn within that session. Joins trace_prompts.turn_uuid.
Inherited from
Section titled “Inherited from”MemoryMetasShape.turnUuid
updatedAt
Section titled “updatedAt”readonly updatedAt: string = Schema.String;Inherited from
Section titled “Inherited from”MemoryMetasShape.updatedAt
validFrom?
Section titled “validFrom?”readonly validFrom?: string;Bitemporal validity of the fact, not of the row. Absent means always-valid.
Inherited from
Section titled “Inherited from”MemoryMetasShape.validFrom
validUntil?
Section titled “validUntil?”readonly validUntil?: string;Inherited from
Section titled “Inherited from”MemoryMetasShape.validUntil
NewMemoryInput
Section titled “NewMemoryInput”What memory_write supplies. Mirrors the tool’s parameters, with the format’s own additions.
Properties
Section titled “Properties”aliases?
Section titled “aliases?”readonly aliases?: readonly string[];Other names this file’s subject is recorded under, as memhtml-alias metas. A person file’s
identity declaration, meant as merge evidence for entity resolution.
articleHtml?
Section titled “articleHtml?”readonly articleHtml?: string;Pre-authored article markup, used verbatim in place of claim/body. The escape hatch for
a correction that carries a <dl> or a <figure>. It must already contain its own
<mark>, so the caller owns constraint 1 when it uses this.
readonly at: string;ISO-8601 UTC instant of write time. Stamps both memhtml-created and memhtml-updated.
author?
Section titled “author?”readonly author?: string;readonly body?: readonly string[];Prose after the claim, one paragraph per element. The claim’s own paragraph is the first
one, so body[0] becomes the claim paragraph’s tail rather than a second <p>.
readonly claim: string;The claim: exactly the text that becomes the one <mark> span and therefore files.gist.
Required, because a memory with no claim has nothing to disclose at Tier 1.
confidence?
Section titled “confidence?”readonly confidence?: number;dueAt?
Section titled “dueAt?”readonly dueAt?: string;When a task is due. An ISO date or datetime, string-ordered.
entities?
Section titled “entities?”readonly entities?: readonly string[];type:name entity references, e.g. service:checkout-api.
importance?
Section titled “importance?”readonly importance?: number;links?
Section titled “links?”readonly links?: readonly object[];memoryType
Section titled “memoryType”readonly memoryType: | "episodic" | "semantic" | "procedural" | "agent_insight" | "user_preference" | "error_pattern" | "verdict" | "precedent" | "arc" | "task";promptId?
Section titled “promptId?”readonly promptId?: string;sessionId?
Section titled “sessionId?”readonly sessionId?: string;readonly tags?: readonly string[];taskStatus?
Section titled “taskStatus?”readonly taskStatus?: "todo" | "doing" | "blocked" | "done";A task’s lifecycle position. Absent on a task defaults to DEFAULT_TASK_STATUS;
absent on any other type stays absent, because only a task carries one.
readonly title: string;The <title>, and the slug’s source.
turnUuid?
Section titled “turnUuid?”readonly turnUuid?: string;validFrom?
Section titled “validFrom?”readonly validFrom?: string;Bitemporal validity of the fact. Absent means always-valid.
validUntil?
Section titled “validUntil?”readonly validUntil?: string;Type Aliases
Section titled “Type Aliases”CanonicalLang
Section titled “CanonicalLang”type CanonicalLang = typeof CANONICAL_LANGS[number];MemoryMetaName
Section titled “MemoryMetaName”type MemoryMetaName = typeof META_ORDER[number];ProseParagraph
Section titled “ProseParagraph”type ProseParagraph = | { claim?: string; index?: number; kind: "p"; text?: string;} | { block: FencedBlock; index: number; kind: "figure";};One paragraph of the article the prose path renders, before escaping: a <p> holding text, or
a <figure> holding a fenced block. index is the body paragraph it came from.
Union Members
Section titled “Union Members”Type Literal
Section titled “Type Literal”{ claim?: string; index?: number; kind: "p"; text?: string;}claim?
Section titled “claim?”readonly claim?: string;The claim, on the first paragraph only: the text of its <mark>.
index?
Section titled “index?”readonly index?: number;readonly kind: "p";readonly text?: string;The body paragraph’s text, absent on a claim that stands alone.
Type Literal
Section titled “Type Literal”{ block: FencedBlock; index: number; kind: "figure";}RepeatableMeta
Section titled “RepeatableMeta”type RepeatableMeta = typeof REPEATABLE_META[number];Variables
Section titled “Variables”ARTICLE_ELEMENTS
Section titled “ARTICLE_ELEMENTS”const ARTICLE_ELEMENTS: readonly ["article", "mark", "time", "dl", "dt", "dd", "data", "cite", "q", "dfn", "figure", "figcaption", "details", "summary", "aside", "section", "abbr", "pre", "code", "kbd", "samp", "var", "table", "caption", "thead", "tbody", "tr", "th", "td", "p", "ul", "ol", "li", "a", "strong", "em"];The body vocabulary, one entry per row of the format’s element table.
tr is here although the table lists only caption/thead/tbody/th/td: a <table> cannot
hold a cell without a row, so refusing tr would make every real table warn. Nothing else
is added by inference. An element the format does not name is a warning, which is the
graceful-degradation rule for hand-authored files.
ArticleExtractions
Section titled “ArticleExtractions”const ArticleExtractions: Struct<{ abbreviations: $Array<String>; asideTexts: $Array<String>; bodyText: String; captions: $Array<String>; citations: $Array<Struct<{ href: optional<String>; text: String; }>>; codeLangs: $Array<String>; definedTerms: $Array<String>; eventAt: optional<String>; facets: $Array<Struct<{ name: String; numericValue: optional<Number>; value: String; }>>; gist: String; html: String; summaryTexts: $Array<String>;}>;What the indexer reads out of <article>. Field names are the indexer’s own, so the indexing
pipeline consumes this struct without a translation layer.
CANONICAL_LANGS
Section titled “CANONICAL_LANGS”const CANONICAL_LANGS: readonly ["typescript", "javascript", "python", "bash", "json", "sql", "yaml", "html", "toml", "css", "go", "rust"];The languages a DETECTION may name. One canonical lowercase token each, because data-lang
promotes to a lang: entity by exact string match, so two spellings of TypeScript would be
two entities. Anything outside this list is “do not stamp”.
Citation
Section titled “Citation”const Citation: Struct<{ href: optional<String>; text: String;}>;One <cite> or <q>. href is the <q cite> URI when present, an absolute or
root-relative source URI, not necessarily a memory path.
DEFAULT_TASK_STATUS
Section titled “DEFAULT_TASK_STATUS”const DEFAULT_TASK_STATUS: TaskStatus = "todo";The status a new task starts in.
Defaulted rather than required of the caller because parseMemory REFUSES a task with no
memhtml-task-status. A template that omitted it would render a file the format rejects, and
every memhtml task add would have to restate the obvious opening state.
DEPLOY_THRESHOLD
Section titled “DEPLOY_THRESHOLD”const DEPLOY_THRESHOLD: 0.3 = 0.3;The confidence at or above which a detection is stamped: 0.30.
See the module header for provenance. This is the measured 0.28685957116771854 rounded in the safe direction, not a guess and not a round number chosen for looking tidy.
DOCUMENT_ELEMENTS
Section titled “DOCUMENT_ELEMENTS”const DOCUMENT_ELEMENTS: readonly ["html", "head", "body", "title", "meta", "link"];The document skeleton. These carry no indexer semantics of their own; they exist so the file is a valid HTML5 document a browser renders with no server.
const Facet: Struct<{ name: String; numericValue: optional<Number>; value: String;}>;One <dt>/<dd> pair. name is the <dt> text, value the <dd> text.
numericValue is present only when the <dd> holds a <data value> whose attribute parses
as a finite number. It is unitless here on purpose: the unit lives in the human phrasing
(<data value="120">about two minutes</data> is seconds because the prose says so), so a
consumer cannot read a unit off the number alone.
FIGURE_SCOPED_ELEMENTS
Section titled “FIGURE_SCOPED_ELEMENTS”const FIGURE_SCOPED_ELEMENTS: readonly ["div", "span"];<div> and <span> are permitted only inside a <figure>, where a pasted code sample
legitimately carries its own markup. Outside a figure they are the generic-container habit
the closed vocabulary exists to refuse.
FORBIDDEN_ATTRIBUTES
Section titled “FORBIDDEN_ATTRIBUTES”const FORBIDDEN_ATTRIBUTES: ReadonlySet<string>;Attributes forbidden anywhere: presentation is the stylesheet’s job, not the memory’s.
FORBIDDEN_ELEMENTS
Section titled “FORBIDDEN_ELEMENTS”const FORBIDDEN_ELEMENTS: ReadonlySet<string>;Elements forbidden anywhere: a memory file is data, and data does not execute.
Beyond the script/style pair, the list bans every element that embeds a browsing context or
foreign resource (iframe, frame, frameset, object, embed, applet), rewrites the
document’s own URL space (base), or submits data to a target (form). These are violations
rather than constraint-6 warnings because the corpus is published as a static site: a file
carrying one would execute, embed, or navigate in every reader’s browser under the corpus’s
own origin. link/meta need attribute-level rules instead (the scheme check and the
http-equiv refusal in constraints.ts), since both are required skeleton elements.
GIST_EXCLUDED_ELEMENTS
Section titled “GIST_EXCLUDED_ELEMENTS”const GIST_EXCLUDED_ELEMENTS: ReadonlySet<string>;Elements whose text the gist must not absorb. A command line is body text and it is
searchable. The claim is prose in the <mark> span.
HASH_ALGORITHM
Section titled “HASH_ALGORITHM”const HASH_ALGORITHM: "sha256" = "sha256";The digest’s algorithm prefix. A hash is self-describing so a stored value can be re-verified.
KNOWN_ELEMENTS
Section titled “KNOWN_ELEMENTS”const KNOWN_ELEMENTS: ReadonlySet<string>;Every element name the vocabulary knows, at any position.
LANG_TOKEN
Section titled “LANG_TOKEN”const LANG_TOKEN: RegExp;The language token grammar. Covers the identifiers real info strings carry, such as ts,
c++, c#, objective-c, and python3, while refusing whitespace and markup characters, so
a data-lang value is always safe in view-source and safe to promote to a lang: entity.
LINK_REL_PREFIX
Section titled “LINK_REL_PREFIX”const LINK_REL_PREFIX: "memhtml-" = "memhtml-";MemoryDoc
Section titled “MemoryDoc”const MemoryDoc: Struct<{ aliases: $Array<String>; article: Struct<{ abbreviations: $Array<String>; asideTexts: $Array<String>; bodyText: String; captions: $Array<String>; citations: $Array<Struct<{ href: optional<String>; text: String; }>>; codeLangs: $Array<String>; definedTerms: $Array<String>; eventAt: optional<String>; facets: $Array<Struct<{ name: String; numericValue: optional<Number>; value: String; }>>; gist: String; html: String; summaryTexts: $Array<String>; }>; entities: $Array<String>; links: $Array<Struct<{ href: String; rel: Literals<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"]>; }>>; metas: Struct<{ archivedAt: optional<String>; author: optional<String>; confidence: optional<Number>; contentHash: optional<String>; createdAt: String; dueAt: optional<String>; importance: optional<Int>; memoryType: Literals<readonly ["episodic", "semantic", "procedural", "agent_insight", "user_preference", "error_pattern", "verdict", "precedent", "arc", "task"]>; needsRevision: optional<Boolean>; promptId: optional<String>; reprieves: optional<Number>; sessionId: optional<String>; status: Literals<readonly ["active", "archived"]>; supersededBy: optional<String>; taskStatus: optional<Literals<readonly ["todo", "doing", "blocked", "done"]>>; turnUuid: optional<String>; updatedAt: String; validFrom: optional<String>; validUntil: optional<String>; }>; tags: $Array<String>; title: String; warnings: $Array<String>;}>;A parsed memory file. Frozen: a consumer that wants a changed doc builds a new one, so a shared doc cannot be mutated out from under the hash a caller already computed.
MemoryLink
Section titled “MemoryLink”const MemoryLink: Struct<{ href: String; rel: Literals<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"]>;}>;One <link rel="memhtml-*">. rel is the unprefixed rel from the closed edge vocabulary; the
memhtml--prefixed hyphenated token is the wire form and is re-derived on serialize.
href is the document-reference form, repo-root-relative with a leading slash, as it
appears in the file. The git-tree form the edges table stores drops that slash;
@memhtml/contracts’s normalizePath is the conversion, applied at the store boundary.
MemoryMetas
Section titled “MemoryMetas”const MemoryMetas: Struct<{ archivedAt: optional<String>; author: optional<String>; confidence: optional<Number>; contentHash: optional<String>; createdAt: String; dueAt: optional<String>; importance: optional<Int>; memoryType: Literals<readonly ["episodic", "semantic", "procedural", "agent_insight", "user_preference", "error_pattern", "verdict", "precedent", "arc", "task"]>; needsRevision: optional<Boolean>; promptId: optional<String>; reprieves: optional<Number>; sessionId: optional<String>; status: Literals<readonly ["active", "archived"]>; supersededBy: optional<String>; taskStatus: optional<Literals<readonly ["todo", "doing", "blocked", "done"]>>; turnUuid: optional<String>; updatedAt: String; validFrom: optional<String>; validUntil: optional<String>;}>;The head’s typed metadata. Absent means the file did not state it, and no default is
substituted. The files table owns the defaults, and a parser that invented confidence: 1.0 would make a hand-authored omission indistinguishable from a deliberate assertion.
Timestamps are ISO-8601 UTC instants of write time. createdAt is when the memory was
first written and updatedAt when it last changed. When the remembered fact happened is
article.eventAt instead.
META_ORDER
Section titled “META_ORDER”const META_ORDER: readonly ["memhtml-type", "memhtml-status", "memhtml-created", "memhtml-updated", "memhtml-confidence", "memhtml-importance", "memhtml-content-hash", "memhtml-author", "memhtml-session", "memhtml-prompt", "memhtml-turn", "memhtml-valid-from", "memhtml-valid-until", "memhtml-reprieves", "memhtml-archived", "memhtml-superseded-by", "memhtml-needs-revision", "memhtml-task-status", "memhtml-due", "memhtml-entity", "memhtml-tag", "memhtml-alias"];Every metadata name in the closed vocabulary, in the order the serializer emits them. A stable order is what makes a meta-only edit a one-line git diff, so two writers stamping different keys leave each other’s lines in place.
META_PREFIX
Section titled “META_PREFIX”const META_PREFIX: "memhtml-" = "memhtml-";The memory file format’s naming conventions. HTML5 metadata names are a flat token
space where colons are reserved-ish, and rel tokens cannot hold a colon at all,
so both planes use the same hyphenated prefix.
PERSON_ELEMENTS
Section titled “PERSON_ELEMENTS”const PERSON_ELEMENTS: readonly ["address"];<address> is the contact surface a person file adds. Permitted everywhere rather than
only under resources/people/, because this module sees HTML and no path, and a warning
keyed on a directory would belong to memhtml doctor, not to the parser.
REPEATABLE_META
Section titled “REPEATABLE_META”const REPEATABLE_META: readonly ["memhtml-entity", "memhtml-tag", "memhtml-alias"];Meta keys that may appear more than once. Each value is its own <meta> element
rather than a comma-joined string, so correcting one tag is a one-line diff.
memhtml-alias is the third: a person file declares the other names the same person is recorded
under, so laura de-sabelin can state that laura is them. Entity resolution can read it as
EVIDENCE and merge an alias-backed pair whatever the character distance says, which no
string similarity can supply — laura against laura de-sabelin scores 0.476, below even the
review band. One meta per alias, for the same one-line-diff reason.
REQUIRED_META
Section titled “REQUIRED_META”const REQUIRED_META: readonly ["memhtml-type", "memhtml-status", "memhtml-created", "memhtml-updated"];The metas a file must carry. Each of the five is a fact no pure function can invent: a
type cannot be guessed from prose, a status cannot be inferred, and a timestamp cannot be
synthesized without a clock. memhtml-confidence, memhtml-importance, and memhtml-author are
deliberately absent, because the files table documents a default for each (1.0, 5, agent),
so a hand-authored file missing them is completed rather than refused.
VIOLATION_SEPARATOR
Section titled “VIOLATION_SEPARATOR”const VIOLATION_SEPARATOR: "; " = "; ";How violations are joined into InvalidMemory.reason, which carries a single string.
VOID_ELEMENTS
Section titled “VOID_ELEMENTS”const VOID_ELEMENTS: ReadonlySet<string>;Elements with no end tag, per the HTML serialization algorithm. The serializer emits
these as a start tag alone; emitting </meta> would make the file invalid HTML5.
Functions
Section titled “Functions”addLink()
Section titled “addLink()”function addLink( html, rel, href): string;Append a <link rel="memhtml-…"> edge. Idempotent on the (rel, href) pair, in any spelling of
the rel the reader accepts, because a caller can add the same edge on every run, as a session’s
link op can, and a duplicated <link> would become a duplicated edges row.
Parameters
Section titled “Parameters”string
| "supersedes"
| "contradicts"
| "caused_by"
| "leads_to"
| "part_of"
| "relates_to"
| "example_of"
| "supports"
| "laterally_related"
| "about_person"
| "authored_by"
| "from_session"
| "blocks"
| "subtask_of"
string
Returns
Section titled “Returns”string
addMeta()
Section titled “addMeta()”function addMeta( html, name, value): string;Append another <meta> of a repeatable name, a new memhtml-entity or memhtml-tag, after the
last one already present. Adding a value that is already there is a no-op, so the operation
is idempotent and a re-run of a bookkeeping pass cannot grow the head.
Parameters
Section titled “Parameters”string
string
string
Returns
Section titled “Returns”string
articleHtmlFor()
Section titled “articleHtmlFor()”function articleHtmlFor(input): string;The article markup for an input: articleHtml verbatim when it is supplied, and otherwise the
paragraphs proseParagraphs plans for the claim and body.
Parameters
Section titled “Parameters”Returns
Section titled “Returns”string
blockTagInArticle()
Section titled “blockTagInArticle()”function blockTagInArticle(html): BlockTagInArticle | undefined;The first <p>, or <mark> outside any <p>, in a memory file’s article whose text holds a
block tag outside code, or undefined when none does.
A <p> is read whole, its <mark> included, because that is the paragraph the prose path
rendered: the claim, a space, and the first body paragraph. A <mark> outside a <p> (the
first <li> of authored markup) is read on its own. The text is what a reader sees, so
<p> in the file reads as <p> here.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”BlockTagInArticle | undefined
blockTagInRun()
Section titled “blockTagInRun()”function blockTagInRun(text): BlockTagInRun | undefined;The first block tag in a paragraph’s text outside a backtick span, or undefined when there is
none. The text is read as the HTML parser will hold it in a <p>: line breaks in all three
spellings, NULs dropped. A backtick span opens and closes on one line, so a tag on a line of its
own is prose even when the lines around it carry backticks.
offset is the tag’s position in text as given, and tag is the tag sliced from the line as
the parser reads it (backticks kept, NULs dropped), cut to TAG_SHOWN characters.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”BlockTagInRun | undefined
canonicalArticleText()
Section titled “canonicalArticleText()”function canonicalArticleText(root): string;The digest’s input. canonicalText with <pre>/<code> kept, so everything is hashed.
Parameters
Section titled “Parameters”Node
Returns
Section titled “Returns”string
canonicalText()
Section titled “canonicalText()”function canonicalText(root, options?): string;The exact string the digest is taken over: article text with runs of ASCII whitespace
collapsed to one space, <pre> descendants passed through byte for byte.
Block-element edges contribute a collapsible space, so the hash is a function of the article’s
words and not of its indentation: <li>one</li><li>two</li> and the same list pretty-printed
across lines yield one digest. Without that edge space the flat form would canonicalize to
onetwo, so reformatting a file would move its dedup key while changing nothing a reader can
see.
The outer trim is applied only to a leading or trailing collapsible segment. Trimming the
whole result would let <pre> a</pre> and <pre>a</pre> hash identically, and the leading
whitespace of a code sample is exactly the kind of difference a <pre> exists to keep.
Parameters
Section titled “Parameters”Node
options?
Section titled “options?”CanonicalTextOptions = {}
Returns
Section titled “Returns”string
checkDocument()
Section titled “checkDocument()”function checkDocument(document): CheckResult;Check a parsed document against all six constraints. Violations are collected rather than short-circuited, so one parse tells an author everything wrong with the file.
Parameters
Section titled “Parameters”document
Section titled “document”Document
Returns
Section titled “Returns”checkMemory()
Section titled “checkMemory()”function checkMemory(html): object;Check a file without building a doc: the structured { violations, warnings } memhtml doctor
reports. Total, so a completely malformed string yields violations instead of throwing.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”object
violations
Section titled “violations”readonly violations: readonly string[];warnings
Section titled “warnings”readonly warnings: readonly string[];closesFence()
Section titled “closesFence()”function closesFence(line, opening): boolean;True when a line closes a fence opened by opening: same-or-longer backtick run, nothing else.
Parameters
Section titled “Parameters”string
opening
Section titled “opening”string
Returns
Section titled “Returns”boolean
contentHash()
Section titled “contentHash()”function contentHash(input): string;The content hash of an article, from a parsed document, an article node, or the article’s
inner HTML. Passing whole-file HTML works too: the <article> element is located first, so
head content does not reach the digest.
Parameters
Section titled “Parameters”string | Node | HashableArticle
Returns
Section titled “Returns”string
detect()
Section titled “detect()”function detect(code): Detection;hljs’s own evidence, squeezed into the sweepable scalar the eval calibrated.
confidence = 1 - exp(-(top - runnerUp) / lines), the per-line evidence MARGIN between the
best language and its closest REAL competitor. Two refinements carry their weight, and a grid
search over the alternatives (relative margin, top score alone, saturating absolute margin) put
all of them at ≤9% coverage against this shape’s 25%:
- Canonical-aware runner-up: when the runner-up normalizes to the SAME token as the winner
(
pgsqlvsn1ql,xmlvsxhtml), the margin is the FULL top score. A dialect duel is not disagreement about what to stamp, and charging it as one would abstain on the cases the detector is most right about. - Per-line normalization: absolute relevance grows with snippet length, so a raw margin conflates “confident” with “long”. Margin per line does not.
hljs runs its FULL grammar set deliberately. Restricting highlightAuto to the 12-name
vocabulary measured 0.9% coverage against 25% at the precision floor: with the true language’s
grammar absent, a runner-up wins unopposed and wins CONFIDENTLY, so the false positives land
exactly where the threshold cannot see them.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”detectLang()
Section titled “detectLang()”function detectLang(code): | "html" | "javascript" | "typescript" | "python" | "bash" | "json" | "sql" | "yaml" | "toml" | "css" | "go" | "rust" | undefined;The canonical language to stamp on an unlabeled fence, or undefined to stamp nothing.
Both gates are independent and both must pass: the detection must be IN the vocabulary, and its
confidence must reach DEPLOY_THRESHOLD. A confident detection of a language outside the
vocabulary is still undefined. Measured on the corpus, hljs names smali at confidence 0.86
and autohotkey at 0.55 for snippets that are really bash and TypeScript. Confidence says only
“this grammar won by a wide margin”, never “this grammar is one we stamp”.
Pure and synchronous: hljs is synchronous, so the write path calls this inline with no Effect.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”| "html"
| "javascript"
| "typescript"
| "python"
| "bash"
| "json"
| "sql"
| "yaml"
| "toml"
| "css"
| "go"
| "rust"
| undefined
escapeAttribute()
Section titled “escapeAttribute()”function escapeAttribute(value): string;Escape an attribute value. Double quotes are the fixed quote style, so < and > need no
escape. Line breaks become character references so an emitted <meta> or <link> is always
one source line: the head editors splice by line boundaries, so a literal newline inside an
attribute would split one head entry across two of their lines. \r is referenced too,
because the HTML preprocessor folds a literal one into \n on re-parse.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”string
escapeText()
Section titled “escapeText()”function escapeText(text): string;Escape a text run. U+00A0 becomes because a literal no-break space is invisible in
an editor and reads as an ordinary space in review. An invisible character in a memory’s
claim is a trap, so the file names it.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”string
fencedBlockOf()
Section titled “fencedBlockOf()”function fencedBlockOf(paragraph): FencedBlock | undefined;Parse a paragraph as a fenced block, or undefined when it is not one.
The whole paragraph must be the fence: an opening line, the code, a closing line. Prose sharing a paragraph with a fence is not a fence. The paragraph splitter keeps a block intact as its own paragraph, so a mixed paragraph means the author did not blank-line-separate their fence, and escaping it as text is the graceful reading of that.
An unterminated fence (opener, no closer) is also undefined. Rendering it as code would commit
markup the author may not have finished. Rendering it as escaped text keeps the backticks
visible, so the file says what the author typed.
Parameters
Section titled “Parameters”paragraph
Section titled “paragraph”string
Returns
Section titled “Returns”FencedBlock | undefined
fenceOpeningOf()
Section titled “fenceOpeningOf()”function fenceOpeningOf(line): string | undefined;The backtick run of a fence-opening line, or undefined when the line opens no fence.
The run’s length is what a closing line must meet or exceed.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”string | undefined
isContentHash()
Section titled “isContentHash()”function isContentHash(value): boolean;True when a value is a well-formed sha256:<64 hex> digest.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”boolean
isMemoryMetaName()
Section titled “isMemoryMetaName()”function isMemoryMetaName(name): name is "memhtml-entity" | "memhtml-tag" | "memhtml-alias" | "memhtml-type" | "memhtml-status" | "memhtml-created" | "memhtml-updated" | "memhtml-confidence" | "memhtml-importance" | "memhtml-content-hash" | "memhtml-author" | "memhtml-session" | "memhtml-prompt" | "memhtml-turn" | "memhtml-valid-from" | "memhtml-valid-until" | "memhtml-reprieves" | "memhtml-archived" | "memhtml-superseded-by" | "memhtml-needs-revision" | "memhtml-task-status" | "memhtml-due";True when a memhtml--prefixed metadata name is in the closed vocabulary.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”name is “memhtml-entity” | “memhtml-tag” | “memhtml-alias” | “memhtml-type” | “memhtml-status” | “memhtml-created” | “memhtml-updated” | “memhtml-confidence” | “memhtml-importance” | “memhtml-content-hash” | “memhtml-author” | “memhtml-session” | “memhtml-prompt” | “memhtml-turn” | “memhtml-valid-from” | “memhtml-valid-until” | “memhtml-reprieves” | “memhtml-archived” | “memhtml-superseded-by” | “memhtml-needs-revision” | “memhtml-task-status” | “memhtml-due”
isRepeatableMeta()
Section titled “isRepeatableMeta()”function isRepeatableMeta(name): name is "memhtml-entity" | "memhtml-tag" | "memhtml-alias";True when a metadata name may legitimately appear more than once in one head.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”name is “memhtml-entity” | “memhtml-tag” | “memhtml-alias”
isRootRelativeHref()
Section titled “isRootRelativeHref()”function isRootRelativeHref(href): boolean;True when a <link> href is the document-reference form the format requires:
repo-root-relative with a leading slash, no scheme, no host, no .. segment.
A relative href would break on the first git mv of the source file and a protocol-relative
//host/x would silently leave the repo, so both are refused rather than normalized.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”boolean
isValidDatetime()
Section titled “isValidDatetime()”function isValidDatetime(value): boolean;True when a datetime value is one this format accepts: a calendar date, or a date with a
UTC time. Narrower than HTML’s own datetime grammar (which admits durations, weeks, bare
times, offsets, and variable precision) because files.event_at and files.due_at are
compared and ordered as strings. A value that does not sort lexicographically alongside the
others would corrupt the recency arm and the overdue query alike.
Range is checked too, so 2026-13-45 is refused rather than stored as an unsortable date.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”boolean
metaPairs()
Section titled “metaPairs()”function metaPairs(doc): readonly (readonly [ | "memhtml-entity" | "memhtml-tag" | "memhtml-alias" | "memhtml-type" | "memhtml-status" | "memhtml-created" | "memhtml-updated" | "memhtml-confidence" | "memhtml-importance" | "memhtml-content-hash" | "memhtml-author" | "memhtml-session" | "memhtml-prompt" | "memhtml-turn" | "memhtml-valid-from" | "memhtml-valid-until" | "memhtml-reprieves" | "memhtml-archived" | "memhtml-superseded-by" | "memhtml-needs-revision" | "memhtml-task-status" | "memhtml-due", string])[];The metas of a doc as [name, content] pairs in META_ORDER, repeatable keys expanded
to one pair per value. A meta whose value is absent contributes nothing, so the file states
what it knows and the files defaults cover the rest.
Parameters
Section titled “Parameters”Returns
Section titled “Returns”readonly (readonly [
| "memhtml-entity"
| "memhtml-tag"
| "memhtml-alias"
| "memhtml-type"
| "memhtml-status"
| "memhtml-created"
| "memhtml-updated"
| "memhtml-confidence"
| "memhtml-importance"
| "memhtml-content-hash"
| "memhtml-author"
| "memhtml-session"
| "memhtml-prompt"
| "memhtml-turn"
| "memhtml-valid-from"
| "memhtml-valid-until"
| "memhtml-reprieves"
| "memhtml-archived"
| "memhtml-superseded-by"
| "memhtml-needs-revision"
| "memhtml-task-status"
| "memhtml-due", string])[]
newMemoryDoc()
Section titled “newMemoryDoc()”function newMemoryDoc(input): MemoryDoc;A fresh MemoryDoc for an input, with memhtml-content-hash already stamped from the article the
template just built, so the file that reaches disk is self-consistent and the indexer’s own
recomputation agrees with it on the first read.
Parameters
Section titled “Parameters”Returns
Section titled “Returns”normalizeLang()
Section titled “normalizeLang()”function normalizeLang(raw): | "html" | "javascript" | "typescript" | "python" | "bash" | "json" | "sql" | "yaml" | "toml" | "css" | "go" | "rust" | undefined;Canonical token for a raw language name, or undefined when it is outside the vocabulary.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”| "html"
| "javascript"
| "typescript"
| "python"
| "bash"
| "json"
| "sql"
| "yaml"
| "toml"
| "css"
| "go"
| "rust"
| undefined
parseMemory()
Section titled “parseMemory()”function parseMemory(html): Effect<MemoryDoc, InvalidMemory>;Parse a memory file. Fails with InvalidMemory whose reason is every violation joined by
VIOLATION_SEPARATOR. The error type carries one string, so the list is joined rather
than smuggled through a field the frozen contract does not have. A caller that wants the
structured list calls checkMemory.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”Effect<MemoryDoc, InvalidMemory>
proseParagraphs()
Section titled “proseParagraphs()”function proseParagraphs(claim, body): readonly ProseParagraph[];The paragraphs the prose path renders for a claim and body, in order: the claim’s <p>, with
the first body paragraph joined onto it as its tail, then each remaining paragraph. Empty
paragraphs are dropped rather than emitted, so a trailing blank in the tool payload does not
become an empty element.
A body paragraph that is a fenced code block, whole, becomes a <figure><pre><code> instead of
a <p>, the one way the prose path can author real code markup. A fence is not joined onto the
claim’s paragraph. When the first body paragraph is a fence, the claim stands alone in its <p>
and the figure follows, so the claim still leads the article (constraint 1) and code stays out
of the sentence.
Exported because the doors judge a body by what it renders to: the text of each <p> here,
read by blockTagInRun, is the text blockTagInArticle reads from the file.
Parameters
Section titled “Parameters”string
readonly string[]
Returns
Section titled “Returns”readonly ProseParagraph[]
proseParagraphText()
Section titled “proseParagraphText()”function proseParagraphText(paragraph): string;The text a rendered <p> holds once read: its claim, a space, and its body paragraph.
Parameters
Section titled “Parameters”paragraph
Section titled “paragraph”ProseParagraph & object
Returns
Section titled “Returns”string
readLinks()
Section titled “readLinks()”function readLinks(html): readonly MemoryLink[];Every <link rel="memhtml-*"> edge in the head, in document order. The read half of
addLink/removeLink, tolerant the way readMeta is: the document is
parsed but nothing is validated — no MemoryDoc, no constraint check — so a caller holding raw
bytes can ask what a file points at without the file having to be a well-formed memory. An
unknown rel token is dropped exactly as parseMemory’s own reader drops it.
Parameters
Section titled “Parameters”string
Returns
Section titled “Returns”readonly MemoryLink[]
readMeta()
Section titled “readMeta()”function readMeta(html, name): string | undefined;The value of a head meta, or undefined. The read half of setMeta, no parse needed.
Parameters
Section titled “Parameters”string
string
Returns
Section titled “Returns”string | undefined
removeLink()
Section titled “removeLink()”function removeLink( html, rel, href?): string;Drop a <link rel="memhtml-…"> edge, in every spelling of the rel the reader accepts. Omitting
href drops every link of that rel; naming one drops just that pair, which is what the
integrity phase does when it replaces a dangling href with the target’s new path.
Parameters
Section titled “Parameters”string
| "supersedes"
| "contradicts"
| "caused_by"
| "leads_to"
| "part_of"
| "relates_to"
| "example_of"
| "supports"
| "laterally_related"
| "about_person"
| "authored_by"
| "from_session"
| "blocks"
| "subtask_of"
string
Returns
Section titled “Returns”string
removeMeta()
Section titled “removeMeta()”function removeMeta( html, name, value?): string;Drop a <meta> of a name, one whole line each. Omitting value drops every meta of that name;
naming one drops just the metas carrying it, which is how a session’s unlabel op takes one
memhtml-entity off a file and leaves its other entities alone. A name or value that is absent
is a no-op. Like removeLink, the cut is outside the article, so the content hash is
unchanged.
Parameters
Section titled “Parameters”string
string
value?
Section titled “value?”string
Returns
Section titled “Returns”string
renderTemplate()
Section titled “renderTemplate()”function renderTemplate(input): string;A fresh memory file as bytes. The one function the write path calls. The extraction fields on
the intermediate doc are empty because serialization reads only article.html, and the real
extractions come back from parseMemory on the next read.
Parameters
Section titled “Parameters”Returns
Section titled “Returns”string
serializeMemory()
Section titled “serializeMemory()”function serializeMemory(doc): string;Serialize a doc to a whole file. Inverse of parseMemory on the fields the format carries:
parseMemory(serializeMemory(doc)) yields doc again, which is the property that lets a caller
read, adjust, and write a file back without touching content it did not mean to touch.
article.html is re-parsed and re-serialized rather than interpolated, so a caller that
hand-assembled a doc still gets the canonical, fixed-point markup the round-trip needs.
Parameters
Section titled “Parameters”Returns
Section titled “Returns”string
setMeta()
Section titled “setMeta()”function setMeta( html, name, value): string;Set a head meta to one value, replacing the first <meta> of that name in place or inserting
a line in META_ORDER position. Exactly one line changes, and the article stays outside
the edited range, so contentHash(setMeta(html, name, value)) === contentHash(html) holds
for every name, repeatable ones included.
On a repeatable key this sets the FIRST value. addMeta is the append. An unknown name
is refused by returning the input unchanged rather than writing a meta memhtml doctor would
immediately flag.
Parameters
Section titled “Parameters”string
string
string
Returns
Section titled “Returns”string
writeChildren()
Section titled “writeChildren()”function writeChildren(parent): string;The inner markup of a parent node: its children, the node itself excluded.
Parameters
Section titled “Parameters”parent
Section titled “parent”ParentNode
Returns
Section titled “Returns”string
writeOuter()
Section titled “writeOuter()”function writeOuter(node): string;A node and its subtree, the node included.
Parameters
Section titled “Parameters”Node
Returns
Section titled “Returns”string