DreamLake

Addressed Notes reads

Specification · addressed snapshot reads

This page defines the read contract and its acceptance criteria. It is the design reference; the Notes guide and distributed agent skills describe how to use it. Guides may use different explanations, but must preserve these semantics.

The two read modes

RequestMeaningResult
read NOTEObserve current stateComplete source, content hash, write revision
read NOTE --at REVRetrieve retained stateThe same snapshot every time, while retained and accessible
read NOTE --since HASHCompare prior content with current contentUnified line diff, base hash, result hash and revision

--toc, --section and --tag select a snapshot view. They never change the meaning of --since. --linger delivers an initial current snapshot and then line diffs through its existing SSE transport.

Actions the representation must support

Assistant actionRequired information
Orient within an articleFull heading outline with levels and source-derived section IDs
Read a section or subsectionSemantic nested section subtree, including child sections
Read one paragraph or headingExact element ID, source slice, global string and line ranges
Generate an editOriginal source spelling, code-point offsets and the original write revision
Verify an editRead the revision in the acknowledged write receipt
Continue during collaborationKeep the draft baseline separate from newer streamed observations

Normative snapshot contract

There are two read modes. read returns the current snapshot; --at REVISION returns the retained snapshot identified by that opaque revision. --since HASH returns a unified line diff against the current source. A hash is a content reference, not a write revision. Snapshot views do not change differential reads. Explicit --format inline-dff remains available for character diffs; patch input continues to default to inline-dff.

bash
# NOTE_ID identifies a note you can read. Plain reads show hash and revision.
dreamlake notes read "$NOTE_ID"
dreamlake notes read "$NOTE_ID" --view html
# Copy REVISION from the read receipt; keep it with the draft it describes.
dreamlake notes read "$NOTE_ID" --at "$REVISION" --toc
dreamlake notes read "$NOTE_ID" --at "$REVISION" --section s1.1
dreamlake notes read "$NOTE_ID" --at "$REVISION" --tag s1.1.p1
# HASH is the content hash from that receipt. This returns line diffs.
dreamlake notes read "$NOTE_ID" --since "$HASH"

TOC, section and tag selectors return mapped HTML without needing --view html. They are mutually exclusive and cannot combine with --since. --tag is an exact generated element ID, not an arbitrary CSS selector. A paragraph ID selects one paragraph; a section's slug ID selects the entire nested section. --section accepts a numeric section index or its content-derived ID. --at cannot combine with --if-match: the former retrieves history, the latter requires the current revision to match. Missing snapshots or elements return 404. Every read, including cached historical reads, rechecks current note permissions.

Elements, identifiers and ranges

Snapshot HTML uses real nested section elements and h1–h6 headings:

html
<section id="section-plan" data-index="s1" data-char="0:90" data-lines="1:11">
  <h1 id="s1.h" data-index="s1" data-char="0:7" data-lines="1:1">Plan</h1>
  <p id="s1.p1" data-char="8:25" data-lines="3:3">We build robots.</p>
  <section id="section-training" data-index="s1.1" data-char="26:56" data-lines="5:8">
    <h2 id="s1.1.h" data-index="s1.1" data-char="26:38" data-lines="5:5">Training</h2>
    <p id="s1.1.p1" data-char="39:55" data-lines="7:7">Train 😀 safely.</p>
  </section>
</section>

This abbreviated example omits mapping spans and later section content. Source ranges count Unicode code points, start at zero, and exclude the end. Line ranges are one-based and include both endpoints. Ranges refer to original source, including Markdown syntax and line terminators, never rendered text. Section ranges include subsections. Heading ranges cover only the heading. Paragraphs are numbered within their nearest section; lists, quotes, code and tables do not advance the prose counter. Preamble paragraphs use s0.pN. Skipped heading levels add a nesting level, without invented empty headings; sibling numbering remains unique when heading levels change. IDs and ranges are local to one revision.

Section slugs follow the existing editor convention: NFC normalization, lowercase, remove characters other than Unicode letters/numbers, whitespace, _ and -, then collapse whitespace to hyphens. Prefix with section-; an empty slug uses untitled. Duplicate slugs get -1, -2, etc., in document order. No truncation is applied. Reverse lookup uses the element's exact source range, not reversal of the lossy slug. Headings use sN.h; paragraphs use sN.pN.

List items and Markdown hints

List items use section-local IDs in document order: s1.li1, s1.li2, and so on. Nested items get their own IDs; a parent item's range includes its nested list. Ordered and task-list items use the same rule. Markers, indentation and line endings belong to the original source range. Lists do not advance the paragraph counter. Preamble items use s0.liN.

html
<li id="s1.li1" data-char="11:29" data-lines="3:4">
  😀 One
  <ul><li id="s1.li2" data-char="20:29" data-lines="4:4">Two</li></ul>
</li>

Use read NOTE --tag s1.li2 to select a single item. For Markdown notes, --view markdown prints the original selected Markdown with generated address comments before headings, paragraphs and list items. It uses the same validated HTML snapshot and supports --at, --toc, --section, or --tag:

bash
dreamlake notes read "$NOTE_ID" --at "$REVISION" --section s1 --view markdown
markdown
<!-- s1.li1 chars=11:29 lines=3:4 -->
- 😀 One
<!-- s1.li2 chars=20:29 lines=4:4 -->
  - Two

This is a reading view, not canonical source. Its header identifies the revision and hash; ranges always refer to original source, excluding generated hints. Do not write the annotated output back as article content. Default source reads stay exact and unannotated. --since and --linger retain their existing source contracts; they cannot combine with the annotated view. HTML-source notes use --view html instead.

Scoped source and edit commands

A scoped root retains the whole document's data-hash and data-revision, and adds data-scope, data-source-start, data-source-end and data-source-hash. Its data-source contains only the selected source slice, not the entire note. For a mapped element, subtract data-source-start when slicing this local source; retain absolute offsets in edit commands. A TOC has empty root source, with exact heading source attached to each heading's data-source. It does not imply the assistant read omitted section bodies. Full HTML keeps the existing full-source contract. The CLI checks the returned scope, revision and source hash before printing. Never write the HTML or a partial source slice as the complete note.

For the fixture paragraph above, a directly generated patch is:

@@ chars 39:55 @@
~ [-Train 😀 safely.\n-]{+Train safely and quickly.\n+}
bash
# edit.dff contains the patch prepared against REVISION, with exact old text.
dreamlake notes patch "$NOTE_ID" --file edit.dff --base-revision "$REVISION" --exact
# Copy NEXT_REVISION from the write receipt; read that exact acknowledged state.
dreamlake notes read "$NOTE_ID" --at "$NEXT_REVISION" --tag s1.1.p1

Exact mode refuses concurrent changes with 412. Without --exact, the existing native RTC merge mode uses the original baseline identities. A later read or streamed revision must never silently replace the baseline of a prepared draft.

Linger and presence

--linger retains its existing contract: an initial source snapshot, then SSE-driven, debounced line diffs. It does not stream HTML or scoped fragments. Read a focused view in a separate command using a revision emitted by linger: read NOTE --at REVISION --tag s1.1.p1. Pinned reads do not overwrite the live presence selection with historical offsets. Live scoped reads report their actual range; a TOC reports no whole-document passage highlight. Selection events carry their own content hash and must not be interpreted against a different source. --linger rejects --at, --toc, --tag, sections and HTML before joining.

Server retrieval

On the server, a pinned read performs permission checks and a direct retained baseline lookup, without initializing or reading RTC and without uploading new snapshots. Rendering shares a bounded content cache (16 entries, 8 MiB estimated payload budget); a cold read still loads and parses the complete source once. Current reads retain the existing coherent RTC observation and persistence path. No claim of constant-time cold reads or eliminated live-read storage costs is made.

Acceptance evidence

All tests below run against isolated fixtures. The HTTP harness launches the real CLI and the real Fastify routes on localhost, with fixture storage and RTC authority. It is not production or a live hosted RTC test.

RequirementVerified behavior
Efficient retrievalPinned read bypasses RTC initialization, RTC observation and snapshot uploads. Scope rendering shares a bounded cache.
Repeated reads are consistentOutline and paragraph read at the same revision remain identical despite an intervening edit. Old snapshots remain readable after a later write.
Editing is directParagraph offsets and exact source generate a patch accepted by the existing edit endpoint; unrelated source stays byte-for-byte unchanged.
Stale writes failExact-mode edit against an intervening change returns 412, without applying the draft.
Read scopes are clearWhole article, TOC, nested section and one paragraph are separately verified; scoped reads do not include unrelated paragraph source.
Linger remains compatibleReal CLI receives snapshot B and line diff B→C; --at B --tag … still returns B. SIGINT sends leave.
Authorization persistsRevoked note access prevents historical retrieval, even with a valid retained token.
Offset integrityEmoji, CRLF, formatting, skipped heading levels, duplicate titles and repeated projections are tested.

Measured example

A local fixture containing 100 sections and 400 paragraphs produced these results:

ViewResponse sizeLocal rendering time
Entire article586 KB39 ms, cold
TOC35.6 KB1.8 ms, warm median
One section6.9 KB1.8 ms, warm median
One paragraph2.4 KB1.7 ms, warm median

Source size: 237 KB. Warm measurements use 20 iterations. Timings exclude network, permission checks, and object-storage latency; they are not a service SLA. A cold read still loads and parses the complete source. Current reads retain the existing RTC observation and persistence cost. Historical reads load and validate a retained baseline by direct object key, without listing history.

Reproduce

From a workspace checkout containing dreamlake-server, set CLI_SOURCE to the companion CLI checkout and ARTIFACTS to an output directory:

bash
NOTES_SCOPED_CLI="$CLI_SOURCE" NOTES_READ_ARTIFACTS="$ARTIFACTS" \
  pnpm -C dreamlake-server exec vitest run --project unit \
  src/services/notesV2Routes.test.ts src/services/notesReadBaseline.test.ts src/lib/noteHtml
pnpm -C dreamlake-server exec tsx scripts/benchmark-notes-read.ts "$ARTIFACTS"

The harness emits article, outline, section and paragraph HTML; the generated patch; the write receipt and exact readback; the linger event stream; and a JSON report. The benchmark emits benchmark.json. No real notes are modified.