Wanderland

Oculus Session Overlay

Ephemeral per-session writes that live above the disk. A session holds a frozen snapshot of a node's tokens plus an append-only op log; every read replays the ops over the snapshot to return the session's current effective state. Disk is never touched for session-scoped writes.

The engine for a web app that wants my view of a shared document without mutating the file underneath everyone else.

The Algorithm

Pause → Fetch → Splice → Continue. Every session write funnels through one primitive:

splice_tokens(slug, token_start, token_end, updated_tokens)

Reads run the replay:

effective = snapshot
for op in ops_in_order:
    effective = apply_splice(effective, op.start, op.end, op.tokens)
return effective

One write primitive. One read funnel. Everything else is optimization around that spine.

Layers

┌────────────────────────────────────────┐
│  callers (path_parser, vfence, …)      │
└────────────┬───────────────────────────┘
             │ splice_tokens(…)
┌────────────▼───────────────────────────┐
│  token_splice.py  (write funnel)       │
│    session active?                     │
│      yes → overlay store               │
│      no  → disk write + cache bust     │
└────────────┬───────────────────────────┘
             │
┌────────────▼───────────────────────────┐
│  session_overlay.py  (SQLite)          │
│    overlays(session_id, slug, base)    │
│    ops(session_id, slug, start, end,   │
│        updated_tokens, ts)             │
└────────────────────────────────────────┘

Reads flow the other way: get_ast → overlay fast-path → effective_tokens → snapshot + replay → ASTParser re-detects virtual fences → caller.

Surfaces

Write

oculus/token_splice.py::splice_tokens(slug, start, end, updated, registry, ast_graph, context)

Single write chokepoint. Every mutation — yaml poke from path_parser.write_path, vfence splice from graph_api_ast.execute_virtual_fence, anything else — goes through here. Session-active writes append to the op log; session-less writes serialize + persist to disk. The distinction is invisible to callers.

Read

oculus/token_splice.py::effective_tokens_for(slug, registry, ast_graph)

Returns the session's effective token list, or None if no session is active (caller falls back to base). Wrapping is opt-in per read path.

oculus/graph_api_ast.py::get_ast is wired into this funnel for format='ast' reads. Any downstream call that uses get_ast under a session sees overlay-effective tokens — get_hierarchical_graph, peek, execute_virtual_fence's internal graph traversal, the /render endpoint.

Admin

HTTP endpoints at /api/oculus/overlay-sessions:

Method Path Purpose
GET / list sessions (id, age, op count, snapshot count)
GET /{id} inspect (snapshots + ops, includes full updated_tokens per op)
GET /{id}/state merged effective tokens per touched slug
POST /{id}/reset drop ops + snapshots, session id stays reusable
DELETE /{id} hard-delete session
POST /purge TTL-based cleanup

Session Identity

The active session is bound to an X-Oculus-Session header read by FastAPI middleware into a contextvar. Downstream code calls session_context.get_session_id() — no explicit threading through parameters. Middleware scope is one HTTP request; the contextvar clears on exit.

Clients mint session ids in the browser (crypto.randomUUID()) and persist them in localStorage keyed by a scope string. <oculus-fence> and <oculus-node> both read from the same lantern.fence-session.v1 bucket via data-session-scope — identical scopes share a session, so writes and reads compose.

Snapshot Lifecycle

A snapshot is taken on first touch per (session, slug). The first splice_tokens for a slug the session hasn't seen loads base tokens (from get_ast with the session context suppressed), persists them as the base, then appends the op. Subsequent ops go straight to the op log.

Snapshots are per-slug, whole-document token streams. Not per-fence. That choice predates the vfence-atoms work and was the point of the v2 pivot: ops are token-stream splices, not yaml-path mutations, so any write (code fence body, yaml key, virtual fence mutation) fits the same shape.

Read-Time Compaction

Before each replay, consecutive ops with identical (token_start, token_end, updated_tokens) collapse to one — the last in the run, since a later op fully determines the state of its range. Safe by construction (same-range same-content means the earlier op is pure waste). Deletion runs in-transaction; the replay uses the compacted log.

Write-time diff narrowing (see oculus-virtual-fence-atoms) eliminates most duplicates before they enter, so compaction is mostly a safety net. Catches raw splice_tokens callers that don't narrow, future vfence types without _rebuild_tokens, and cross-path coincidences.

Virtual Fence Re-Annotation

The AST parser annotates tokens with token['virtual_fence'] = {...} during initial markdown parse. Splice ops store raw replacement tokens without those annotations. If the overlay returned un-annotated effective tokens, the next hg rebuild would fail to recognize virtual fences that were spliced — execute_virtual_fence would resolve the section but find no fences in it.

get_ast's overlay branch runs ASTParser._detect_virtual_fences on effective tokens before returning. Matches the invariant the disk path provides (markdown → re-parse → annotated). Detection is a whole-document scan, so it runs once at read time, not per splice op. Annotating per-op would be wrong — fence positions shift with each splice.

Isolation

Two sessions writing to the same slug don't see each other. Each holds its own snapshot (frozen at its own first touch) and its own op stream. The base document on disk is the common ancestor; divergence is bounded by each session's op log.

Sessions don't automatically persist. "Commit this session to disk" would be a new endpoint that reads effective tokens, converts to markdown, writes the file, and resets the session. None of that is built — sessions today are ephemeral working state.

What's Not Yet Built

Slots