Skip to content

Purpose-led inquiry checkpoints (#401)

An explicitly saved inquiry stores sensitive user text (purpose, response and gaps), selected and consulted paths, and its human resolution in the plugin's local data. This is not the content-free activity counter. One inquiry is retained until explicit clear/replace; the optional judgement log's 500-entry cap and recording toggle do not control it. Nothing is transmitted. Ordinary vault/device sync remains under the user's configuration, not a new plugin sync or encryption guarantee.

Drafts are not persisted by unrelated settings changes. A successful Save/Pause acknowledges one revision; newer edits or failed saves are not guaranteed after restart. Unsupported saved data is retained for explicit recovery/reset. Clearing inquiry bookkeeping never removes authored Markdown.

Capabilities & privacy

ZettelFlow discloses every capability it uses, ahead of Obsidian's upcoming capability-label system (network / file system / clipboard / script execution). The plugin collects no telemetry and transmits no personal data or vault contents.

What the plugin accesses

Capability Used? What for How
File system (vault) Always Read your Canvas flow files; create and edit notes as the output of a flow; install community templates into your vault. Obsidian's Vault API and FileManager โ€” no hardcoded .obsidian paths, path-normalised, desktop and mobile.
Network Opt-in Only the community templates browser: read-only GETs that fetch example flows/steps/actions/systems and preview images from the static ZettelFlow community catalog on GitHub raw. No backend, no account, no uploads. request/requestUrl in src/application/community/. No requests are made unless you open the community browser.
AI provider (external) Opt-in, off by default The optional ๐Ÿค– AI action category (summarize, classify, generate-questions, โ€ฆ). When you enable it and run an AI action, the (length-bounded) note content is sent to the single https OpenAI-compatible endpoint you configure to get a completion back. Nothing the model writes reaches a note until you accept it โ€” every completion is a proposal you accept, edit or reject (#337, ยงXII) โ€” and AI never runs in automations at all. requestUrl in src/architecture/ai/OpenAiCompatibleProvider.ts only. No bundled key, no default endpoint, no other endpoint, no telemetry. Off by default; input/output are capped and the endpoint must be https (or localhost). Your verdict on each proposal is recorded in the judgement record. See AI provider setup.
Clipboard On your click Writes only, never reads. The โ€œcopyโ€ buttons put a step / action / canvas-node configuration on the clipboard as JSON so you can paste it elsewhere or share it. navigator.clipboard.writeText in the step, action and canvas-node editors. Nothing is ever read from your clipboard, and nothing is copied unless you press a copy button.
Vault enumeration Always Lists every markdown file path in the vault to build the offline knowledge model that every analysis reads (health, discovery, the graph, Cultivate). vault.getMarkdownFiles() via the ObsidianApi facade. Paths and metadata only, all local, no network. You can keep folders out of it entirely with the knowledge scope.
Dynamic code execution Opt-in Runs the JavaScript you author: the Script action, dynamic selectors, vault hooks and workflow-event conditions. Your code is handed zf and Obsidian's app, so it can do whatever the plugin itself can โ€” including reading the whole offline knowledge model through zf.knowledge. That is a read-only surface: it exposes no vault writer, and your script still writes only through the note it is building or a hook's response. Reaching the AI provider from a script goes through zf.ai.propose, which cannot write without your verdict (ยงXII). Built in exactly one module, architecture/api/lib/FnConstructor.ts โ€” both the async form the script surfaces use and the synchronous CommonJS wrapper that loads your library scripts (a guardrail test fails the build if a second site appears). A library script is loaded as a CommonJS module and is handed Node's require on desktop only, where Obsidian exposes it; on mobile it is simply absent. No remote code is ever fetched or executed โ€” only code stored in your own vault or settings. A community system may ship JavaScript, and the install now says so before it writes anything, naming the steps that carry code and asking you to acknowledge it.
Judgement record On by default Stores the decisions you make on your own ideas โ€” accept / modify / reject / confirm / challenge โ€” so cognitive agency can be derived instead of invented. A local, bounded log (most recent 500) in the plugin's data.json: a note path, a short locale-free subject id, an origin and a verdict. Ruling a gap out (#534) adds one such descriptor โ€” subject: "gap:<the other note's path>" โ€” and writes nothing to the vault: two paths, an origin and a verdict, verified byte-for-byte by a write-path test. No note content, no AI output, no network. Honours the knowledge scope and can be turned off.

The satellite note (#419)

A step can declare a second, linked note, so one walk produces the literature/permanent pair. That is one additional file per declaring step, at a path announced before anything is written, and create-only: an existing note is never touched, and a name collision aborts the write instead of overwriting. No network, no clipboard, no code execution โ€” and nothing interpretive: the template and the relation are the author's own choices, so ยงXII imposes no verdict gate.

Note-builder drafts (#410)

Closing the creation wizard mid-flow keeps what you answered, so you can resume instead of starting over. The draft lives in the plugin's data.json โ€” local, never transmitted โ€” and holds the note title, the step template paths, the recorded results of the steps you completed, the links you accepted and the ids of the nodes you walked. It can therefore contain text you typed into a step, which is why it is listed here next to the inquiry checkpoint rather than treated as metadata.

It is bounded (five canvases, newest kept), expires after 30 days, is deleted the moment the note is created or you choose start fresh, and can be switched off entirely in settings. A draft the current version cannot parse is retained, not deleted โ€” the plugin never destroys work it merely fails to understand.

What the automated scan sees that is not ours

Obsidian's Community-hub scan reports runtime base64 encode/decode. That call does not come from ZettelFlow code โ€” there is no atob/btoa anywhere in src/. It comes from the bundled 3D graph library (3d-force-graph, which vendors three.js loaders) used by the graph view. It decodes assets the library ships with; it does not encode anything about your vault, and it makes no request.

No telemetry

ZettelFlow does not embed analytics, crash reporting, or any phone-home. The only outbound requests are the opt-in community fetches (read-only) and the opt-in AI provider above. Nothing about your notes leaves your machine unless you enable AI and run an AI action (which sends the note content only to the endpoint you set). Sharing a template is a manual GitHub contribution you make yourself โ€” the plugin never uploads anything.

Diagnostics stay content-free (#401)

The inquiry journey reads the knowledge index, so a diagnostic emitted while indexing the very note you are working on must not become a side channel. The index diagnostics that journey can trigger โ€” inline-relation enrichment, per-note upsert, and the conceptual-timeline/development-journal hooks โ€” log only a fixed failure category (the error type, e.g. TypeError), never the note's path, its body, or the raw exception message. A private inquiry note cannot leak into the log this way.

The thinking-heatmap journal (local only)

The thinking heatmap is fed by a development-event journal stored in the plugin's own data.json as a per-day count map (day โ†’ count) โ€” no note names, no content. It is pruned to the last ~year and makes no network request. It is on by default (it only records benign aggregate counts) and can be disabled under Settings โ†’ ZettelFlow โ†’ Thinking journal.

The community gallery is fully static: the plugin only ever does read-only GETs of catalog files from GitHub raw. There is no backend, no account, and the plugin never uploads anything โ€” contributions are made through GitHub (an issue form + a PR).

Ahead of Obsidian's labels

Obsidian has announced machine-readable disclosure labels on the community hub. When that format ships, ZettelFlow will adopt it so these capabilities are declared in the plugin manifest as well as here. Until then, this page and the README section are the authoritative disclosure.