Skip to content

AI provider setup

The 🤖 AI action category (#156) is optional and off by default. ZettelFlow works fully with AI disabled — every other feature is deterministic and offline. When you opt in, you bring your own OpenAI-compatible provider; ZettelFlow ships no key and no default endpoint.

Turning it on

Settings → ZettelFlow → AI (optional):

  1. Enable AI actions — off by default. While off, no AI action ever makes a network request.
  2. Endpoint URL — the full OpenAI-compatible chat-completions URL. Examples:
  3. OpenAI — https://api.openai.com/v1/chat/completions
  4. OpenRouter — https://openrouter.ai/api/v1/chat/completions
  5. LM Studio (local) — http://localhost:1234/v1/chat/completions
  6. Ollama (local, OpenAI-compat) — http://localhost:11434/v1/chat/completions The endpoint must be https (plain http is accepted only for localhost/127.0.0.1), so note content is never POSTed to an unexpected or insecure host (#301).
  7. Model — the model name your provider expects (e.g. gpt-4o-mini).
  8. API key — your provider key. Stored in this vault's plugin data (.obsidian/plugins/zettelflow/data.json), sent only as a Bearer header to the endpoint above, and never logged.

Any of endpoint / model / key left blank means the provider is not usable, and AI actions no-op with a clear notice.

Guardrails (#301)

  • Nothing is written without your verdict (#337, constitution §XII). Every completion is shown to you as a proposal you can accept, edit or reject. Accepting unchanged text records accepted; accepting an edit records modified; rejecting writes nothing. Dismissing the dialog writes nothing and records nothing — a dismissal is not a verdict. See cognitive agency.
  • AI never runs in automations. It fires only on a build you drive — never during an on-creation pattern or the post-index re-run. There is no setting to change this: an automation has nobody to ask for a verdict, so a call there could only cost you money for something that can never be written. (This replaces the old Allow AI in automations toggle, removed in #337.)
  • Max input characters — the note content sent per request is bounded (default 12000) and truncated if longer, so a huge note (or many linked notes) can't send an unbounded payload.
  • Max output tokens — the requested completion length is bounded (default 800) via max_tokens.
  • Prompt-injection hardening — the task is sent as a system message and the note content is wrapped in <note-content> tags labelled as data, so text like "ignore previous instructions" inside a note is treated as content, not a command.
  • Safe output — a text completion is sanitised (template tokens neutralised, length bounded) before it is written into a note.

What is sent, and where

When you run an AI action, ZettelFlow sends the (bounded) content of the note being built as the prompt to the single https endpoint you configured — and nothing else. There is no telemetry, no bundled key, no default endpoint, and no second endpoint. See Capabilities & privacy.

Handwriting, per press (#748). In the Reader, Read as text under an ink note sends an image of that one ink note's strokes (drawn from its points, dark on plain light, at most 1024 px on its long side) and the passage it sits beside, capped by Max input characters, to the same endpoint — only when you press it, one note per press, never by itself. It is the one request with an image in it: an OpenAI-compatible image_url part holding a data: URL, beside the same system guard and output cap. A model that does not take images answers it with an error (400, 415 or 422), which the Reader reports as Your model did not read the image; choose one that reads images. What comes back is a proposal you accept, edit or reject. See Read as text.

The Reader's moves, per click (#759). In the Reader, six moves on a selected passage — Why?, Challenge, Question, What's the claim, Reflect and Explain — send the move's task, the selected words, the paragraph they sit in and the one before it, and the book's and chapter's titles, capped by Max input characters, to the same endpoint, with the same system guard and output cap. Nothing from your vault is sent. One request per click, at most two waiting at once, never retried by itself and never in an automation. They are offered only while Enable AI is on; turned on but not set up, a click says so and sends nothing. A local provider (Ollama, LM Studio) on http://localhost works, but the gate still needs an API key: any placeholder will do. The prompts are quoted word for word in Moves by your AI provider.

The actions

The category ships three actions, each a thin wrapper over one completion call:

If a request fails (network error, bad config, malformed response), the action degrades to a clear notice and writes nothing — it never crashes the flow.

Provider-agnostic by design

ZettelFlow depends on no specific vendor: a single AiProvider interface (complete(prompt), and an optional see(prompt, image) for reading handwriting) with one built-in OpenAI-compatible client (src/architecture/ai/). The OpenAI-compatible shape already covers OpenAI, OpenRouter, LM Studio and Ollama. Native SDKs, embeddings/RAG, streaming and agentic loops are intentionally out of scope.