Johannes Kaindl71 downloadsRun autonomous local LLM agent teams (crews) on your vault via LM Studio
Run autonomous local LLM agent teams ("crews") on your Obsidian vault, powered by a local LLM model (LM Studio or Ollama) — with a deterministic, orchestrator-led pipeline and a snapshot safety net under every run.
Local models are treated as weak, unreliable executors. The orchestrator decides flow, paths and writes; the model only ever decides content, inside narrow, schema-validated contracts. Every output is constrained, then verified, before it ever touches your vault.
collector (deterministic context
gathering), llm (one chat completion against a schema-validated contract), and
actions (deterministic application of a validated action list to the vault). The
model never controls flow and never touches the vault directly.run.md
(frontmatter + per-task detail, Bases-compatible) and a machine-readable
state.json, plus a shipped runs.base dashboard.state.json still marked running are
detected on the next plugin load, with one recommended action: finish the run
(keep the partial changes — they remain undoable via the write-ahead snapshot).isDesktopOnly: true — the plugin is built around a locally-hosted
LLM served over HTTP, a desktop workflow).1234) or
Ollama (default port 11434), serving an OpenAI-compatible API.
The endpoint is configurable in the plugin settings; just enter the URL (e.g.,
http://localhost:1234/v1 for LM Studio or http://localhost:11434/v1 for Ollama).
You can list several endpoints (one per line), and the plugin uses the first
reachable one at each preflight. No provider selection needed — the plugin
auto-detects context length and capabilities.XMLHttpRequest
from inside Obsidian's renderer process (requestUrl cannot stream). LM Studio:
Settings → Developer → Enable CORS. Ollama: set the environment variable
OLLAMA_ORIGINS=<your-obsidian-app-url> (optional; without it, the plugin falls back
to non-streaming mode, and results still arrive).From Community plugins (once listed): open Settings → Community plugins → Browse, search for Vault Crews, install and enable it.
Before it is listed — via BRAT (Beta Reviewers Auto-update Tool):
https://codeberg.org/jkaindl/vault-crews).After enabling, run the command "Install example crews" to seed _crews/
(default root, configurable in settings) with the Task-Triage and Daily-Briefing
example teams, their agents, and the runs.base dashboard. Installed files are never
overwritten by a second run — edit them freely afterwards.
write_scope whitelist, per team, plus a fixed denylist that always wins. Every
team declares the vault-relative globs it may write to. A fixed denylist —
.obsidian/**, .git/**, _crews/**, _vaultrag/**, dotfiles — overrides any
whitelist unconditionally; crews can never read or write their own configuration
(no self-triggering, no prompt-injection path into plugin control)..obsidian/plugins/vault-crews/undo/). Even a failed or aborted run with partial
writes stays fully undoable. Undo last run restores changed notes and trashes
run-created ones, showing exactly what it will undo (team, time, files) before you
confirm — and warns if a note was edited after the run rather than silently
overwriting it. Run-created notes go to the Obsidian trash, never a hard delete.max_writes per run (team-configurable, capped by
a plugin-wide maximum), a hard per-note size cap, an LLM call budget, and a
wall-clock watchdog (default 10 minutes) that aborts a runaway run (leaving its
partial writes snapshotted and undoable) rather than running forever.output-validator) validates the raw
model JSON against a built-in schema and binds every path/enum value to the
material actually collected for that task. Stage 2 (action-executor) re-checks
every action against the path whitelist/denylist, allowed action types, allowed
frontmatter keys, and a content-hash staleness guard (if you edited the file since
it was collected, that single action is skipped, never silently overwritten) —
immediately before the write, independent of stage 1.http://localhost:1234/v1 or Ollama http://localhost:11434/v1 by default,
user-configurable). No other host is ever contacted, no telemetry, no analytics, no
update-check pings.child_process or
node:fs.Documented rather than silently missing:
failed with
its partial writes snapshotted; the plugin does not attempt to reconnect or re-resolve the
endpoint within a run. This is deliberately deferred — a failed run is always
safe (undo snapshot + full log) and cheap to re-run: section.replace is idempotent
and note.create/patch semantics refuse to double-apply, so simply re-running the
same crew after restarting LM Studio is the supported recovery path.state.json still running) is designed for "Obsidian crashed on this machine
mid-run". A vault synced across two concurrently-running Obsidian desktops (e.g.
via iCloud/Syncthing while both are open) is explicitly out of scope for V1 — see
design risk #8.runs/<id>/artifacts/.
Whenever a task's output fails schema/source-binding validation, the raw model
response is written to artifacts/<taskId>-1.txt; if the one repair attempt also
fails validation, its raw response is written to artifacts/<taskId>-2.txt. This
feeds the test-fixture corpus of real broken model outputs and is written under the
run directory — it is never counted as a vault write, never touches max_writes, and
is not snapshotted for undo. Successful runs write no artifacts at all.verboseLogging (Settings → Advanced) is reserved, not yet wired. The setting
exists and persists, but nothing currently reads it; full raw-output recording of
every call (success or failure) — beyond the failure-case artifacts/ capture
above — is not implemented.run.md at the top, not scrolled to the failed task.
"View failure" opens the run's log file via workspace.getLeaf().openFile() with
no ephemeral scroll state — you land at the top of the note and scroll to the
relevant ## section yourself.onload() to construct the LlmClient; changing
the endpoint or timeout values in Settings does not affect an already-running
plugin instance. Disable and re-enable the plugin (or restart Obsidian) after
changing these settings for them to take effect.status: aborted with partial writes (undoable). With a
fast local model a whole run can finish in 1–2 s, so a click can land after the last
checkpoint and the run completes normally — that is correct (the work was already
done), not a lost click. The panel reflects this truthfully: while aborting it shows
"Abort requested…", and if the run finished first it states "the run finished before
the abort took effect — nothing was aborted" rather than freezing on a spinner. There
is deliberately no mechanism to throw away already-completed work.Eine Crew ist Markdown im Vault: ein Team (crew-kind: team) als Pipeline aus
collector → llm → actions, plus Agent-Notes (crew-kind: agent, System-Prompt
im Body). Die mitgelieferten Crews (Command „Install example crews") sind editierbare
Beispiele — kopiere und passe sie an.
output:)Ein llm-Task legt sein Ausgabeformat über einen output:-Block fest:
frontmatter.set — das Modell schlägt Frontmatter-Werte für Quell-Notizen vor.
allowed_keys beschränkt, welche Felder gesetzt werden dürfen. Pfade sind an das
Quellmaterial gebunden (keine Halluzination); die strukturelle Enum-Erzwingung
bindet Werte an die im Vault vorhandenen Ist-Werte, greift aber erst, wenn das Feld
im Ordner bereits belegte Werte hat — beim allerersten Lauf (noch keine Werte, keine
Wertetabelle) kommt eine Wertebeschränkung nur aus Instruktion und Agent-Prompt.output:
family: frontmatter.set
allowed_keys: [tags, kategorie]
section.write — das Modell schreibt Markdown-Text, der per section.replace
in das target des nachgelagerten actions-Tasks geschrieben wird. Optional
max_chars (Default 16000).output:
family: section.write
Die älteren Namen output_schema: triage-v1 / briefing-v1 bleiben als Kurzform gültig.
include_content)Standardmäßig liefert tasknotes.query nur Frontmatter. Für Crews, die den
Notiz-Text brauchen (Tagger, Zusammenfasser), setze include_content: true:
collector: tasknotes.query
params:
folder: Notizen
where_missing: [tags]
include_content: true
write_scope)write_scope ist eine Glob-Allowlist: eine Crew darf nur dort schreiben. Setze sie
so eng wie möglich — und lass collector-folder und write_scope auf denselben
Ordner zeigen, sonst werden Vorschläge außerhalb der Schreibfreigabe verworfen. Das
Plugin-Limit „Max. Schreibvorgänge pro Lauf" deckelt zusätzlich jeden max_writes-Wert.
AGPL-3.0-or-later — see LICENSE for the full text.