Files
Synapse/docs/agent-guide/implemented/architecture/2026-08-24-logseq-runtime-and-engineering-guide.md
Dasemu 40631362b3 Initial import of LogDarioSeq baseline
Establishes version control for the personal Logseq DB fork before
starting the server-client architecture migration and domain features
described in docs/agent-guide and the approved implementation plan.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-25 15:32:15 +02:00

35 KiB

Logseq Runtime and Engineering Guide

Status: implemented

This is a compact, source-verified engineering guide for the runtime areas that were previously documented across docs/agent-guide. It was checked against the repository on 2026-08-24.

The guide explains durable architecture, data flow, invariants, failure behavior, and verification entry points. It intentionally omits implementation schedules, branch instructions, temporary measurements, review snapshots, and product research. Source code, tests, generated OpenAPI, and active subsystem documentation remain the detailed contracts behind this guide.

Problem

Engineering knowledge about the CLI, db-worker-node, db-sync, rendering, outliner performance, search, and Graph View was distributed across plans and reports. The useful content was mixed with proposed work, obsolete paths, and time-sensitive observations. Engineers need one short technical map that preserves the valid constraints without treating historical plans as current contracts.

Decision

System model

Logseq uses different runtime processes. Within one local runtime, the worker database is authoritative for graph state. On Desktop, a graph has at most one active local writer daemon; remote db-sync storage is a synchronized copy, not a second local writer.

Runtime Main responsibility Persistent database
Browser renderer UI, editor state, worker client None directly
Browser db-worker Graph transactions and queries SQLite through OPFS
Desktop renderer UI and remote persistence client None directly
Electron main Window and db-worker-node lifecycle Filesystem coordination
db-worker-node Graph transactions, queries, sync, search Filesystem SQLite
CLI Command parsing, orchestration, formatting Uses db-worker-node
db-sync Worker/DO Remote graph access and ordered synchronization D1/DO SQLite and R2

Important ownership rules:

  • The worker database is authoritative for graph state.
  • Desktop and CLI use the same graph directory, lock protocol, and db-worker-node runtime.
  • The renderer must not create a second optimistic graph authority.
  • Disk SQLite is the Desktop source of truth. OPFS export is not the Desktop primary persistence path.
  • A graph must not have two active writer daemons.

Primary source map:

Area Source
Worker database and thread APIs src/main/frontend/worker/
Browser persistence src/main/frontend/persist_db/browser.cljs
Desktop remote persistence src/main/frontend/persist_db/remote.cljs
Electron runtime manager src/electron/electron/db_worker.cljs
Shared daemon lifecycle src/main/logseq/cli/server.cljs
Node daemon src/main/frontend/worker/db_worker_node.cljs
CLI cli/
db-sync server deps/db-sync/src/logseq/db_sync/worker/
Renderer DB subscriptions src/main/frontend/db/

Worker platform boundary

frontend.worker.platform is the worker capability boundary. The required adapter sections are:

  • env
  • storage
  • kv
  • broadcast
  • websocket
  • crypto
  • timers
  • sqlite

Vector and embedding capabilities are present when the runtime supports semantic search.

The browser adapter uses OPFS, browser storage, browser WebSocket, and the worker postMessage channel. The Node adapter uses filesystem paths, Node WebSocket, Node crypto/storage primitives, and node:sqlite DatabaseSync.

Business namespaces should depend on frontend.worker.platform instead of testing the host runtime repeatedly. Missing required capabilities fail during platform validation. Optional capabilities may be absent only where the feature itself is optional.

Interactive requests use frontend.worker.ui-request:

  1. The worker creates a unique request id.
  2. The host receives a typed request and payload.
  3. The host resolves or rejects that exact request.
  4. Cancellation rejects outstanding requests during graph/runtime shutdown.

Browser flows may request UI input. Headless Node flows must use explicit input or return a useful error. New worker business logic should not call renderer functions directly. One non-Node search progress path still uses frontend.worker.state/<invoke-main-thread; do not treat that exception as a general worker API.

db-worker-node daemon

db-worker-node is a graph-bound localhost daemon. It starts the same worker core used by the browser, installs the Node platform adapter, opens the requested repo, writes graph and server-list metadata, and becomes ready only after worker initialization succeeds.

Client-facing endpoints:

Method Path Purpose
GET /healthz Readiness, bound repo, process, owner, revision, host, and port
GET /v1/events Server-Sent Events from worker broadcasts
POST /v1/import-db-binary?repo=... Stream a SQLite database import
POST /v1/invoke Invoke a worker thread API with Transit arguments
POST /v1/shutdown Graceful close and process shutdown

The daemon also answers CORS preflight requests. It binds to 127.0.0.1 on an OS-selected port and has no remote-bind option.

Most thread APIs require the first argument to identify the repo. The daemon rejects:

  • a missing or blank repo with HTTP 400;
  • a repo that differs from the daemon's bound repo with HTTP 409.

Only explicitly listed process-level APIs may omit the repo. This binding keeps a client from accidentally sending graph work to another daemon discovered from stale metadata.

Graph lock and ownership

The three discovery records have deliberately different scopes:

  • db-worker.lock records repo, pid, lock-id, and owner-source.
  • server-list records only pid and port pairs.
  • /healthz reports repo, readiness status, host, port, pid, owner source, root directory, and runtime revision.

Neither the graph lock nor server-list is a readiness or revision authority.

Lifecycle behavior:

  1. Resolve and validate the graph directory under the configured root.
  2. Read the graph lock and remove it only when its owner process is absent.
  3. Read server-list, discard entries whose pid is absent, and probe remaining entries through /healthz.
  4. Spawn only when neither a discovered graph server nor a graph lock exists.
  5. Require both a graph lock and a discovered server, then wait for readiness.
  6. Reuse the daemon only when its reported revision matches the requester.
  7. On revision mismatch, stop that exact server, start once, and verify the new revision; fail if stop, startup, or the second revision check fails.

A discovered process without the expected usable lock is not adopted as a valid runtime; startup fails instead of returning an unowned endpoint.

Normal stop and restart operations respect owner boundaries. A proven revision mismatch may replace the exact stale daemon across owner sources because the requester cannot safely use it. Manual CLI cleanup remains limited to eligible CLI-owned daemons.

Server list

root-dir/server-list is a discovery index of pid/port pairs. Mutations are serialized through root-dir/server-list.lock.

The write protocol:

  1. Create the lock with exclusive wx semantics.
  2. Write lock owner metadata including pid, lock id, and timestamp.
  3. Recover the lock only when its owner process is proven absent.
  4. Read and normalize current entries while holding the lock.
  5. Write a uniquely named temporary file.
  6. Rename the temporary file over server-list atomically.
  7. Remove the lock only when its lock id still matches.

Readers tolerate missing files and ignore malformed or duplicate entries. They must still verify each entry through /healthz.

Electron graph switching and recovery

Electron owns graph-scoped runtimes in electron.db-worker. A window may bind to one graph while another window keeps another graph runtime alive.

setCurrentGraph is metadata synchronization for the window graph path. It does not release a worker. Runtime start/switch is handled by db-worker-runtime IPC, and release is handled by explicit lifecycle or window close. Releasing from setCurrentGraph can stop the newly selected runtime after a switch.

The renderer remote persistence client treats server-unavailable errors as a runtime recovery signal. The current recovery threshold is one failed request. Recovery is graph-scoped and must not silently switch the user to another graph.

The SSE client reconnects after 1000 ms by default and keeps scheduling another attempt while the client remains connected; disconnect! stops that loop. Request recovery is different: the first server-unavailable failure triggers at most one recovery for the active runtime session. Session and repo checks stop a stale recovery result from replacing the currently selected graph runtime.

Graph backups

Desktop and CLI share the graph backup layout:

<root-dir>/graphs/<encoded-graph>/backup/<encoded-backup-name>/
  db.sqlite
  metadata.edn

Backup policy:

  • Snapshot through SQLite backup APIs; do not copy a live database with a plain filesystem copy.
  • Include db.sqlite only.
  • Exclude WAL/SHM sidecars, search-db.sqlite, and client-ops-db.sqlite.
  • Reserve the destination directory atomically.
  • Add a numeric suffix when a generated backup name already exists.
  • Write metadata only after the database snapshot succeeds.
  • Remove an incomplete reserved directory on failure.
  • Resolve shared-helper backup paths as children of the graph backup root.

metadata.edn records schema version, name, repo, source, creation time, and database path. Retention is source-specific so automatic pruning cannot delete manual backups.

Electron runs automatic backups hourly for active graph windows, throttles the same source, and retains 12 automatic versions. Manual Desktop and CLI backups are not subject to that automatic retention policy.

The shared ClojureScript helper validates path containment. The current OCaml CLI backup restore --src and backup remove --src paths only trim the supplied name and concatenate it below the backup root; they do not reuse that containment validator. Do not treat those two --src arguments as safe for untrusted input.

Markdown mirror output keeps page-level identity but does not emit block database ids as HTML comments.

db-sync architecture

db-sync separates four concerns:

  • index-level graph, membership, access, and E2EE operations;
  • graph-scoped ordered sync in a Durable Object;
  • semantic REST operations for external clients;
  • asset storage and signed download access through R2.

Key files:

Concern Source
Entry and dispatch deps/db-sync/src/logseq/db_sync/worker/dispatch.cljs
Sync route registry deps/db-sync/src/logseq/db_sync/worker/routes/sync.cljs
Semantic route registry/OpenAPI deps/db-sync/src/logseq/db_sync/worker/routes/semantic.cljs
WebSocket protocol codec deps/db-sync/src/logseq/db_sync/protocol.cljs
Sync handlers deps/db-sync/src/logseq/db_sync/worker/handler/sync.cljs
Semantic handlers deps/db-sync/src/logseq/db_sync/worker/handler/semantic.cljs
Storage deps/db-sync/src/logseq/db_sync/storage.cljs
Client sync src/main/frontend/worker/sync/
MCP and ChatGPT entry deps/db-sync/worker/entry.mjs

Graph-scoped sync requests use /sync//... and are forwarded to the corresponding Durable Object.

Sync HTTP routes

The graph-scoped route registry contains:

Method Internal path Purpose
GET /health Graph sync readiness
GET /pull Transactions after a server t
GET /checksum/diagnostics Incremental and recomputed checksum details
GET /snapshot/download Snapshot download
GET /snapshot/stream Framed snapshot stream
DELETE /admin/reset Administrative graph reset
POST /tx/batch Ordered transaction batch
POST /snapshot/upload Streaming snapshot upload

Use the route registry for the exact method/path contract. Public dispatch adds graph id, authentication, access checks, and Durable Object forwarding.

WebSocket protocol

Messages are JSON maps. Transaction payloads inside the maps are Transit encoded. UUIDs are serialized as strings.

Client messages:

type Important fields Result
hello none hello with current t and optional checksum
ping none pong
presence editing-block-uuid broadcast presence update
pull since pull/ok with t, txs, and optional checksum
tx/batch t-before, txs, optional client-revision tx/batch/ok or tx/reject

The server also broadcasts online-users, presence, and changed notifications. Malformed JSON, invalid since values, and unknown types return an error message.

Transaction batches use optimistic ordering:

  • t-before must be a non-negative number.
  • t-before must equal the server's current t.
  • snapshot upload must be complete before normal transactions apply.
  • a successful logical batch advances t and returns tx/batch/ok.
  • a stale request returns tx/reject with the current t.
  • a partial failure reports the new t plus successful and failed transaction identifiers when available.
  • peers receive one changed notification after the batch and checksum settle.

Clients must pull/rebase after stale rejection instead of retrying the same batch blindly.

Semantic REST and MCP

The semantic API lives under /api/v1 and exposes operations for:

  • graph discovery;
  • pages and page references;
  • blocks, recursive trees, and block moves;
  • typed properties and batch property updates;
  • journal capture;
  • Task objects;
  • tags and tagged objects;
  • assets;
  • graph search.

The semantic layer uses Logseq outliner and property semantics. It must not implement mutations as arbitrary raw DataScript transactions.

Security and data rules:

  • Every operation declares logseq/read or logseq/write OAuth scope.
  • Graph access is checked before dispatch into the graph Durable Object.
  • E2EE graphs fail closed because the server cannot interpret their content.
  • Rate limiting happens before expensive Durable Object work.
  • Property mutations use typed DB graph properties, not file-graph key:: text.
  • Task creation uses the Task class and typed status/priority/date properties, not Markdown TODO syntax.
  • Asset uploads are limited to 100 MB and require an exact size plus SHA-256 checksum.
  • The generated /openapi.json is the detailed external contract.

MCP is a thin integration over the semantic API. entry.mjs owns request wiring, chatgpt_app.mjs owns app metadata, and chatgpt_asset_upload.mjs adapts asset uploads. Do not create a second independent mutation API for MCP tools.

Authentication and E2EE state

Worker auth belongs in frontend.worker.state/*state under namespaced keys:

  • :auth/id-token
  • :auth/access-token
  • :auth/refresh-token
  • :auth/oauth-token-url
  • :auth/oauth-domain
  • :auth/oauth-client-id

frontend.worker.state/*db-sync-config contains transport configuration such as :ws-url and :http-base. non-auth-db-sync-config strips auth-shaped keys before storing transport config.

E2EE password rules:

  • Never store plaintext passwords in cli.edn or db-sync config.
  • Verify a supplied password against the encrypted private key before saving it.
  • Do not overwrite a valid stored payload after a failed verification.
  • Encrypt the persisted password with material derived from the refresh token.
  • Browser storage uses the platform secret store.
  • The Node platform prefers the OS keychain under the Logseq E2EE service.
  • If keychain access fails, Node falls back to root-dir/kv-store.json.
  • CLI E2E mode uses the KV store directly so tests do not mutate the keychain.
  • Headless flows fail with a missing-password error and a command hint.
  • Interactive browser/Desktop flows may request a password through ui-request.

CLI cloud tokens are stored separately in ~/logseq/auth.json. Runtime sync receives auth state in memory.

Large operations

Large logical transactions must remain one ordered operation while bounding individual allocations and requests.

Client upload behavior:

  • frontend.worker.sync.apply-txs groups datoms without splitting an inseparable datom group.
  • Upload requests target a 5000-datom cap; one inseparable group may exceed it.
  • Chunks are sent sequentially and preserve logical order.

Server apply behavior:

  • A large entry targets 500-item apply chunks; one inseparable group may exceed the target.
  • Intermediate chunks suppress per-chunk checksum updates.
  • The final checksum is updated from the complete logical transaction.
  • The listener is installed and removed around the operation.
  • Failure payloads preserve already successful transaction ids and the failed transaction id when known.

Do not promise full rollback across already committed chunks. Preserve explicit partial-success reporting so clients can reconcile deterministically.

Checksums

logseq.db-sync.checksum owns the checksum algorithm. It normalizes eligible entities into stable tuples and supports both full recomputation and incremental updates.

Checksum comparison is meaningful only when local and remote transaction positions are synchronized. The client records the latest remote checksum and logs a mismatch only after that readiness condition.

Investigation sequence:

  1. Record graph id, local t, remote t, local checksum, and remote checksum.
  2. Query /checksum/diagnostics for the server recomputation.
  3. Recompute the local checksum from the same logical graph state.
  4. Compare normalized tuple diagnostics, not raw SQLite row order.
  5. Replay a captured transaction with the scripts exposed by deps/db-sync/package.json when the incremental transition is suspect.
  6. Distinguish a true state divergence from a stale t/checksum observation.

Snapshot upload may initialize a missing checksum only before transaction history has advanced. It must not overwrite an existing different checksum.

Cycle repair utility

logseq.db-sync.cycle implements and tests reference-cycle repair for:

  • :block/parent, cardinality one;
  • :logseq.property.class/extends, cardinality many.

Its algorithm:

  1. Collect entities touched by remote and local transaction reports.
  2. Detect a reachable cycle with DFS across the configured attribute.
  3. Prefer an edge whose current value differs from the corresponding value in the remote transaction report.
  4. Retract one edge on the cycle.
  5. For block parent, add a safe remote parent or page root when possible.
  6. For class extension, prefer the remote reference set or Root fallback.
  7. Repeat until stable, with a 16-iteration safety cap.

Repair transactions use :outliner-op :fix-cycle, disable undo generation, and set :persist-op? false.

Current integration boundary: the client remote-apply path in frontend.worker.sync.apply-txs repairs duplicate outliner orders but does not call logseq.db-sync.cycle/fix-cycle!. The utility and its unit tests therefore must not be described as active client-side cycle repair.

Schema and failure policy

D1 schema changes require SQL migrations in deps/db-sync/worker/migrations. Runtime schema initialization is not a substitute for a migration.

db-sync should fail closed on invalid auth, graph access, E2EE semantic access, stale ordering, invalid snapshots, and malformed transaction payloads. Do not introduce defaults that hide corrupt state.

Incremental rendering and outliner transactions

The worker commits graph state once and derives renderer state from the committed transaction.

Current flow:

  1. An outliner/thread API transacts in the worker.
  2. frontend.worker.db-listener receives the complete transaction report.
  3. render-affected-keys derives exact resource invalidations.
  4. render-delta builds one renderer delta.
  5. The worker broadcasts :sync-db-changes.
  6. frontend.db.subs applies the graph-scoped revision.
  7. React hooks read immutable snapshots from frontend.db.hooks.

Render delta contract

A delta contains:

  • graph-id;
  • rev, derived from the authoritative database transaction position;
  • optional operation id;
  • complete block replacements keyed by block UUID;
  • deleted block tombstones with revision and previous db/id when available;
  • child membership patches per parent;
  • affected render-resource keys.

Every replacement block carries :block/tx-id. A block cannot be both replaced and deleted in one delta.

Child patches contain base-rev and rev plus ordered remove/upsert operations. Membership changes are derived from:

  • :block/parent;
  • :block/order;
  • :block/closed-value-property;
  • :logseq.property/created-from-property;
  • :logseq.property/deleted-at.

The renderer ignores a whole delta when its graph differs from the active graph or its revision is not newer. For child patches, a mounted slot whose base revision cannot be reconciled is marked stale and reloaded instead of being merged speculatively.

Subscription store

frontend.db.subs owns one immutable external store. Important resource identities include:

  • [:block uuid]
  • [:children parent-uuid]
  • [:resource resource-key]

Subscriptions watch exact keys. Applying a delta updates cached blocks and children, marks affected resources, and notifies only slots whose snapshot changed.

Render-resource computation is registered in frontend.worker.handler.render-resource.engine. Families include basic graph lookups, property resources, views, and queries. The registry and render-affected-keys must evolve together: every cached resource needs an exact invalidation rule.

Checksum updates, db-sync persistence, Markdown mirror work, and search-index updates run after the graph transaction commits. Each registered listener is wrapped so synchronous or promise failures are reported through :capture-error; they do not roll back the committed graph transaction.

Outliner performance guardrails

Outliner mutations use worker-owned transactions and publish their committed result through the renderer delta path. Do not add a second optimistic block tree or publish the same mutation independently from both a command response and the DB listener. Page-window APIs should transfer the requested window, not a full page, and graph-open work must not add graph-wide GC or VACUUM.

Useful transaction-pipeline measurements that are already logged include:

  • performance id, outliner operation, and transaction count;
  • renderer pipeline, delta construction, and broadcast time;
  • checksum, persistence, sync-main, and registered-handler time;
  • total listener time.

These measurements are diagnostics, not a fixed latency guarantee.

CLI engineering guide

The shipped CLI is implemented in OCaml and compiled with Melange:

cli/ OCaml
  -> Dune/Melange
  -> Vite bundle
  -> cli/_build/default/dist/logseq-cli.js
  -> scripts/stage-cli-runtime.mjs
  -> static/logseq-cli.js

db-worker-node remains ClojureScript and is bundled separately to dist/db-worker-node.js. There is no shadow-cljs logseq-cli target.

CLI source map:

Concern Source
Entry/orchestration cli/lib/cli.ml
Command ids cli/lib/command_id.ml
Registry/help/options cli/lib/command_registry.ml
Parsing cli/lib/cli_parse.ml
Actions cli/lib/cli_action.ml
Worker transport cli/lib/transport.ml
Daemon lifecycle cli/lib/server_runtime.ml
Graph/import/export/backup cli/lib/graph.ml
Sync cli/lib/sync.ml
AgentBridge cli/lib/agent.ml
Platform effects cli/lib/platform/
Public command reference docs/cli/logseq-cli.md

cli/spec contains interfaces and cli/test contains unit/parity tests. The CLI uses the existing CLJS daemon; do not duplicate the worker database or sync implementation in OCaml.

Configuration and output

With the default root, runtime files may include:

~/logseq/
  cli.edn
  auth.json
  kv-store.json
  server-list
  graphs/

Configuration precedence is global flag, then environment variable, then cli.edn. Auth tokens and E2EE passwords are not CLI config fields.

Output modes:

  • human is display-oriented and may truncate table titles by display width;
  • json and edn preserve full values for scripts;
  • namespaced keyword keys retain namespace/name form in JSON;
  • UUIDs become strings in JSON;
  • verbose and profile diagnostics go to stderr;
  • normal structured stdout must not contain human warnings;
  • sync progress is disabled by default for structured output unless explicitly requested.

Executed commands are normalized internally to Cli_result.t, with status, data or error, optional command id, output renderer, and optional exit code. Human output may be a table or message; JSON and EDN serialize structured results. skill show deliberately renders the skill body as raw human text.

Data operations

The CLI exposes list, show, search, query, upsert, move, and remove commands. Important shared rules:

  • Human list titles are CJK-aware and truncate by display width, not byte count.
  • Structured list output never uses human truncation.
  • Selectors must be unambiguous; commands reject conflicting id, UUID, ident, name, and page options.
  • list node tag/property filters use all-of semantics.
  • Block and page references are rendered from UUID-backed graph data.
  • show resolves nested block references with a depth cap.
  • show page hierarchy is explicit and does not replace normal page content display.
  • Library is a special hierarchy root, but ordinary page hierarchy uses the same bounded traversal and cycle protection.
  • Mutations use worker outliner APIs rather than raw database editing in the CLI.

SQLite export asks the worker to write the snapshot directly to the destination path. EDN export forwards validated graph export options. SQLite import has a top-level missing-graph check; EDN import may target an existing graph.

Current limitation: graph backup restore imports a SQLite backup but is not included in the CLI's top-level missing-graph check. It must not be documented as rejecting an existing destination until that constraint is implemented.

Sync behavior

CLI sync reuses worker-side sync logic:

  • upload resolves or creates a remote graph identity before snapshot transfer;
  • download starts a create-empty daemon and refuses a non-empty target;
  • download uses a long-running command timeout;
  • progress uses /v1/events and :rtc.log/download;
  • asset download validates the asset entity and local checksum before enqueue;
  • a valid local asset skips the remote request;
  • a checksum mismatch requests a new download;
  • sync config accepts transport keys only;
  • E2EE password verification and storage remain worker-owned.

doctor validates the daemon bundle, root permissions, running server readiness, and revision compatibility. When startup reports missing bundled modules, rebuild db-worker-node and verify every asset in dist/db-worker-node-assets.json.

AgentBridge

cli/lib/agent.ml owns graph-scoped AgentBridge routing.

Implemented routing constraints:

  • one bridge lock exists per graph and agent name;
  • the agent name comes from configuration or hostname and must be non-empty;
  • one master Codex session is started and resumed for task and comment dispatch;
  • a task is routable only when it has a stable UUID, the Task class, TODO status, the matching assignee, and no :logseq.property.agent/session-id;
  • an ancestor task session id is passed to the master as inherited routing context for descendant tasks.

Prompt templates from the AgentBridge registry are loaded and validated at startup, and invalid custom templates are replaced with defaults. The current dispatch path discards that returned template set and constructs hard-coded master prompts instead. Custom registry templates therefore must not be documented as affecting dispatched prompts until the dispatch path consumes them.

The generated master instructions say that only the master may write graph results, subagents are read-only, and a launched child session id must be saved on the task. Those are prompt-level policies, not enforcement by the OCaml bridge, so they must not be presented as runtime guarantees.

Keep graph queries, lock ownership, task routing, prompt construction, and external process effects separately testable.

Desktop vector search uses:

  • Electron embedding server: src/electron/electron/embedding_server.cljs;
  • default sentence-transformer model: all-MiniLM-L6-v2;
  • embedding dimension: 384;
  • vector context version: 3;
  • zvec-backed Node vector index;
  • SQLite keyword search plus vector results.

Each searchable block keeps at most one vector document, keyed by block UUID. Both indexes derive their text from that block's search title; they do not concatenate parent, sibling, or child context. SQLite FTS stores a normalized form for ordinary blocks. The vector document embeds and retains the pre-normalized title for ranking context-term boosts.

Constraints:

  • Do not multiply vector count with rolling windows.
  • Do not add parent/sibling traversal to full rebuild or incremental indexing without a measured need and an explicit invalidation model.
  • Preserve exact keyword matches ahead of weak vector hits.
  • Current vector search returns at most 10 vector candidates and requires score greater than 0.5.
  • A completed vector rebuild writes model id, dimension, and context version to index metadata. The current rebuild decision checks the SQLite search index PRAGMA user_version or an explicit force flag; it does not compare the persisted vector metadata on open. Do not claim automatic metadata-mismatch rebuild until that check exists.

Incremental indexing derives affected searchable entities from the transaction. It intentionally does not reindex adjacent siblings merely because a sibling was reordered, and it does not expand a moved block update to the new parent. Page hierarchy changes do reindex descendant pages whose indexed titles depend on the hierarchy.

Graph View

Graph View is a relationship browser built from worker graph data and rendered with Pixi.

Source map:

Concern Source
Global UI and settings src/main/frontend/components/graph.cljs
Page/block sidebar graph src/main/frontend/components/page.cljs
Worker graph builder src/main/frontend/worker/graph_view.cljs
React/Rum bridge src/main/frontend/extensions/graph.cljs
Pixi renderer src/main/frontend/extensions/graph/pixi.cljs
Layout and visibility logic src/main/frontend/extensions/graph/pixi/logic.cljs
Styling src/main/frontend/extensions/graph.css

The renderer receives plain nodes and links. The worker emits string endpoint ids and deduplicates links by directed endpoint pair. A later nonblank label can replace an earlier label for the same pair, and class-extension links carry an explicit class-extends edge type.

Relationship sources:

  • :block/tags;
  • :block/refs, lifted to owning pages where appropriate;
  • user ref properties, labeled with the property title;
  • :logseq.property.class/extends;
  • :block/parent for page hierarchy in all-pages mode.

Global modes:

  • tags-and-objects is the default;
  • all-pages renders the visible page relationship graph.

tags-and-objects excludes property definitions, hidden/recycled entities, excluded entities, and core internal tag classes. It renders tag/class nodes, their objects, class-extension links, and visible ref-property links.

all-pages excludes hidden/recycled pages and internal property pages. Journals, built-ins, excluded pages, and orphans are controlled by options. The large-graph path activates at 10,000 page-name datoms, bounds visible ref links to 20,000, then appends property, parent, and extension link tuples before applying a 20,000-link cap to the combined sequence. Because ref links come first, structural links are not guaranteed to survive when the cap is exhausted.

The page graph centers on a page and includes references, mentions, tags, class extensions, and selected second-order relationships. The block graph centers on a block and connects incoming/outgoing referenced pages. Both use the same Pixi page-mode renderer.

Renderer invariants:

  • visible-node sets filter rendered nodes and links independently from the source graph data;
  • label selection is bounded and screen-cell based to reduce overlap;
  • all-pages and tags-and-objects share the Pixi renderer plumbing;
  • the renderer exposes explicit visibility updates and destroys its label, edge-label, and application resources during cleanup.

Per-repo settings are stored under logseq.graph.settings.. They cover view mode, selected tags, depth, link distance, grid layout, journal visibility, and open settings groups. Time-travel cutoff is session state and is not persisted as the graph's initial filter.

Frontend state and UI dependencies

frontend.state remains the primary application state namespace. Focused modules such as frontend.graph-tab own selected domains, but there is no general src/main/frontend/state/ facade tree. Add a focused state namespace only when it has real ownership, not to mirror a historical split plan.

The UI stack uses Base UI and Tailwind 4. Read package.json and current component usage before changing dependency or styling conventions.

Engineering workflow

Before changing a subsystem:

  1. Read root and directory-specific AGENTS.md.
  2. Locate the owning source, its callers, and its tests.
  3. Confirm runtime boundaries: browser, renderer, Electron, Node worker, CLI, or server.
  4. Write or update the narrowest behavior test.
  5. Preserve one authoritative data path.
  6. Fail fast on invalid internal state.
  7. Remove obsolete paths instead of adding compatibility fallbacks.
  8. Run focused tests before broad verification.

Useful commands:

# Repository lint and unit tests
bb dev:lint-and-test

# One unit test
bb dev:test -v <namespace/testcase-name>

# Already-compiled CLJS namespaces
bb dev:run-test-namespaces -n <namespace>

# CLI
(cd cli && opam exec --switch=<compatible-ocaml-switch> -- dune runtest)
pnpm cli:release
pnpm db-worker-node:release:bundle
bb -f cli-e2e/bb.edn test --skip-build

# db-sync
pnpm --dir deps/db-sync test
pnpm --dir deps/db-sync test:chatgpt-app
pnpm --dir deps/db-sync test:large-op-128m

# Agent document validation
spec-dev-tool check --all

Use docs/cli/logseq-cli.md for the current CLI command reference and generated OpenAPI for the current semantic API contract.

Alternatives considered

Keep the original plans as the technical reference

Rejected because proposed steps and old file paths are indistinguishable from current behavior during search.

Duplicate every current source contract in prose

Rejected because exhaustive route, command, and schema duplication would become stale quickly. This guide records durable concepts and points detailed contracts to executable registries and tests.

Consequences

The guide preserves useful architecture, protocol, data-flow, performance, and debugging knowledge in one document. Historical task sequencing, temporary benchmarks, and obsolete proposals are intentionally absent.

The guide is concise enough to read as an orientation document, but engineers must still inspect the named source and tests before modifying behavior.