Model or dataset
TarsLab/obsidian-tars avatar
TarsLab/obsidian-tars

Obsidian Tars: tag completion is the trigger, and blank lines are the grammar

Obsidian tars plugin that supports text generation based on tag suggestions, using services like DeepSeek, Claude, OpenAI, OpenRouter, SiliconFlow, Gemini, Ollama, Kimi, Doubao, Qwen, Zhipu, QianFan & more.

330 stars31 forksTypeScriptMIT

At a glance

What is it?
Tars turns a tag typed into an Obsidian note into an assistant call, which makes the vault file itself the conversation log. The consequences show up in the syntax rules: one message per paragraph, callouts excluded, and a model picker that queries each provider rather than shipping a fixed list.
Who is it for?
Tars suits someone who wants chat history to live in ordinary notes they can edit, diff, and search, and who is willing to follow its paragraph rules instead of fighting them.
Can I use it commercially?
Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
Is it still maintained?
Yes. The repository last received commits 36 days ago.
What is it written in?
Mainly TypeScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on October 1, 2026, and from our analysis. They are not legal advice.

Editorial analysis

The space after the tag is what fires the call

Triggering is not a button and not a slash command. You type `#`, use Obsidian's own tag completion to pick an assistant tag, then press space, and the generation runs. On mobile, where typing `#` is awkward, the alternative is to type the complete tag without the hash character.

There are three routes to the same action: pick a tag from the command palette, type `#` plus the tag plus a space, or type the complete tag on its own. The first version of this plugin used tag suggestions, and the trigger logic was later rebuilt around completion, which is why the hash has to be a real Obsidian tag rather than arbitrary text.

Tag commands act on the paragraph at the cursor, or on the paragraphs in a selection. A Markdown paragraph here means either several lines of plain text with no empty line between them, or a code block. So selecting three paragraphs and hitting an assistant tag sends all three.

One paragraph carries one message, so blank lines are structural

The conversation format follows how large language models expect turns to arrive: a system message first if you want one, then user and assistant alternating like a ping-pong match. Written out, the whole grammar is a handful of lines.

text
#User : 1+1=?(user message)
(blank line)
#Claude :(trigger)

The hard rule underneath it is that a paragraph cannot contain multiple messages, and messages are separated by blank lines. That is not stylistic advice, it is what the parser expects. You ask a question by typing `1+1=?` and then selecting `#User :` from the command list, which rewrites the line as `#User : 1+1=?`; then you select `#Claude :` and the assistant answers.

Two tags combine on one line. `#NewChat #User :` starts a fresh conversation and posts into it, and `#NewChat #System :` does the same for a system message. The `NewChat` tag is how you clear the running thread without touching the rest of the note.

Callout sections never reach the assistant

Anything inside an Obsidian callout is dropped before the prompt is built. You can write notes in a callout without them being sent to the configured assistant, which makes callouts the place for material that belongs to you and not to the model.

The reason is a syntax distinction: callout is not Markdown, it is an Obsidian extension, so the parser treats it as a container rather than as paragraph text. The practical upshot is that reasoning output formatted as a callout by several providers also stays out of the sent context.

That is more than a cosmetic feature. In a note that mixes private scratch work with a conversation, the callout convention is what keeps the two apart without any separate file or hidden block.

Azure wants a deployment name, Doubao wants a bot, Zhipu gets search

Sixteen providers are wired in, and the differences between them are specific enough to matter at configuration time.

On Azure OpenAI the model field holds the deployment name chosen in the portal rather than a model id, which is the first thing to get wrong on that row. Doubao goes through the bot API and carries a DeepSeek web search plugin and knowledge base plugin. Zhipu has a web search option, and its reasoning output for GLM-4.5, 4.6 and Z1 renders in callout format. DeepSeek's reasoning model prints its chain of thought in callout format too, and LongCat and MiniMax follow that convention. SiliconFlow carries many models including DeepSeek V3 and R1.

The last FAQ section of the documentation starts on the assistant type question, noting that LLM protocols differ significantly between OpenAI, Claude and the rest, and is cut off there. A provider you need that is absent from the list is handled by proposing a specific plan in the issue tracker, not by a generic request.

The model picker queries each provider, and the JSON override outranks it

Most providers are asked for their own model list, so the choices come from the API rather than from a list baked into the plugin. The consequence is that a model the provider no longer advertises will not be among the options, no matter how new it is.

The escape hatch is a setting called Override input parameters, where you supply JSON such as `{"model":"your-desired-model"}`. It takes precedence over whatever the picker had selected, so a typo here silently overrides a working choice.

There is a second fallback for a different failure. If the list cannot be read at all, which happens with an account still awaiting verification or with a relay that does not implement the listing, the row turns into a plain text field and the model name can be typed directly. So the row you see changes shape depending on whether the provider answered.

Only embedded files work for images and PDFs

Version 3.1 added multimodal handling on two fronts. GPT-Image-1 covers image generation and editing. Visual understanding is broader: Claude, OpenRouter and SiliconFlow can interpret an image, and Claude and OpenRouter also handle PDF document analysis.

The constraint is stated plainly: only embedded files are supported, written in Obsidian embed syntax as `![[example.jpg]]`. External URL links will not work. That is the practical difference between a provider that can read a PDF in principle and a plugin that can only read the copy living inside your vault.

The plugin works on both desktop and mobile, which is why the mobile trigger path exists as a separate instruction rather than an afterthought.

Retrying means deleting the answer first

There is no regenerate button in the conversation flow. To retry, you use the command Select the message at the cursor, select and delete the assistant's response content, modify your question, and trigger the assistant again.

The shorter path is to select the response content itself and fire a tag such as `#Claude :`. That deletes the previous response and generates a new one in place, which is what makes a re-roll a single tag rather than a sequence of edits.

Both routes work because the answer is ordinary note text under the syntax rules, so replacing it is text editing that the tag command performs for you.

Two runtime dependencies, with every provider SDK bundled at build time

The published plugin is small at runtime: `package.json` declares axios and handlebars as the only dependencies. Everything else is a dev dependency, including `@anthropic-ai/sdk`, `openai`, `@google/generative-ai`, `ollama` and `jose`, along with typescript, esbuild and `eslint-plugin-obsidianmd`. Provider calls are compiled into the bundle rather than resolved at runtime.

The build scripts follow that shape: `build` type checks with `tsc -noEmit -skipLibCheck` and then runs esbuild in production mode, `dev` runs esbuild directly, `smoke` runs a separate esbuild smoke script, and `lint` and `format` handle eslint and prettier. The entry point is `main.js`.

Release 3.6.0 shipped on 2026-08-28, one day after 3.5.3, and the last push on the repository is dated 2026-08-28 as well. The project is MIT licensed and not archived. Custom prompt templates exist too, loaded through a Load template file command on first use, and a status bar tracks character count, rounds and time spent.

Editorial conclusion

Tars suits someone who wants chat history to live in ordinary notes they can edit, diff, and search, and who is willing to follow its paragraph rules instead of fighting them. Check three things first: that every provider you need is on the list, since unlisted providers require a specific plan proposed in an issue tracker, that your model appears in the picker or that you are comfortable setting Override input parameters, and that you accept a plugin whose runtime dependencies are just axios and handlebars while each provider SDK ships as a dev dependency. Anyone wanting a structured transcript for training data should look at the JSONL export path before anything else.

Frequently asked questions

How do I trigger an assistant in Obsidian Tars?

Three ways: select a tag from the command palette, type # plus the tag plus a space, or type the complete tag without the hash character. The space after the tag is what fires the call. On mobile, typing the complete tag is the practical route since typing # is inconvenient there.

Why is my model missing from the settings picker in Obsidian Tars?

Most providers are asked for their own model list, so the options come from the API. Set the model under Override input parameters as JSON such as {"model":"your-desired-model"}, which takes precedence over the picker. If the list cannot be read, the row becomes a plain text field.

Can Obsidian Tars read a PDF from a URL?

No. Only embedded files are supported, written as ![[example.jpg]] style embeds, and external URL links will not work. Claude and OpenRouter are named for PDF document analysis on files inside the vault.

What do I put in the model field for Azure OpenAI in Obsidian Tars?

The deployment name you chose in the Azure portal, not a model id. This is the one provider row in the plugin that expects the deployment name rather than the model identifier.

Does Obsidian Tars export my conversations for fine-tuning?

Conversations can be exported to a JSONL dataset, and that format is stated to work with ms-swift, the Scalable lightWeight Infrastructure for Fine-Tuning project. Internal links are supported as well.

Official sources

  1. Issues
  2. License: MIT
  3. README
  4. Releases
  5. TarsLab/obsidian-tars on GitHub
Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/tarslab-obsidian-tars.svg)](https://hysenlabs.com/projects/tarslab-obsidian-tars)