Run AI, TypeScript, and shell workflow cells inside Markdown notes, with per-note permissions, code cells in containers, and cron scheduling.
VaultOre runs workflows written as Markdown notes: a note holds TypeScript, shell and AI cells, and VaultOre runs them in order, each code cell in its own container, and writes every result back under the cell. Workflows can read and write your notes, call OpenAI or Anthropic with your own key, and run on a cron schedule while Obsidian is open.
[!WARNING] VaultOre runs code from your notes. A workflow note is executable content: it starts containers through your container engine and can, if it declares the permissions, reach the network and change files in your vault. A permission a note sets to
allowapplies without asking. Only run workflows you have read and trust, including notes that arrive through sync.
Create a note with this content:
---
ore: true
name: Hello World
---
```ore:ts id=hello
const message = "Hello from VaultOre!";
message;
```
```ore:ts id=shout depends=[hello]
cell("hello").toUpperCase();
```
Open the command palette and run VaultOre: Run all cells. Each cell runs in a container,
and a callout with its result appears under it; the full output is stored under _vaultore/.
This example needs a container engine and no API key.
| Obsidian 1.11.4 or later, desktop only | VaultOre starts programs and reads the vault from disk; it does not load on mobile |
| A container engine, installed and running | Docker, Podman, Colima (macOS), or Apple container (macOS 26+, Apple silicon). Every run needs it, even a workflow with only AI cells |
| An OpenAI or Anthropic API key (optional) | Only for ore:ai cells |
Colima users: see Known limitations for the file-sharing mode to use. Windows has not been tested; there, VaultOre looks for Docker Desktop or Podman.
Network use.
api.openai.com or api.anthropic.com, with your API
key, when a workflow with an ore:ai cell runs and is granted network. The prompt can
contain note contents ({{note:…}}) and other cells' outputs.oven/bun:1-alpine, from Docker Hub). VaultOre
does not contact registries itself.network: allow, and can then contact any host.Accounts and payment. AI cells need an account with OpenAI or Anthropic that has paid API access, and your own API key; the provider bills you for each request. Everything else (TypeScript and shell cells, scheduling, the CLI) works without an account.
Runs external programs. VaultOre starts your container engine's command-line tool
(docker, podman, colima or container) to detect it and to run each code cell, and the
cell runs the code written in the note. See the warning above.
Files outside the vault. VaultOre reads and writes files only inside the vault. Outside it,
it looks for the engine's command-line tool in the directories on your PATH and in fixed
install locations (macOS: /usr/local/bin, /opt/homebrew/bin,
/Applications/Docker.app/Contents/Resources/bin/docker; Linux: /usr/bin; Windows: the
Docker Desktop and Podman folders under C:\Program Files), and starts it. The engine mounts
the vault folder into containers as the workflow's permissions allow; what the engine itself
reads, such as its own configuration and image store, is the engine's business.
From the community plugin directory: Settings → Community plugins → Browse, search for "VaultOre", install and enable (listing).
From a release: download main.js, manifest.json and styles.css from a
release into
<your vault>/.obsidian/plugins/vaultore/, then enable VaultOre under Settings →
Community plugins.
From source:
git clone https://github.com/cronai-labs/vaultore.git
cd vaultore
bun install
bun run build
bun run obsidian:package # writes packages/obsidian/package/vaultore/
Copy packages/obsidian/package/vaultore/ into <your vault>/.obsidian/plugins/ and enable
the plugin.
Then open Settings → VaultOre, pick your container engine, and, for AI cells, select the secret that holds your API key.
Offered only on workflow notes (frontmatter ore: true). No hotkeys are set; assign them under
Settings → Hotkeys.
| Command | What it does |
|---|---|
| Run all cells | Runs every cell of the note, in dependency order |
| Run cell | Runs the cell under the cursor and the cells it depends on |
| Run cell only (skip dependencies) | Runs the cell under the cursor, with its dependencies' last outputs |
| Cell | Runs | Notes |
|---|---|---|
```ore:ts |
TypeScript on Bun, in a container | The last expression is the output. cell("id") reads another cell's output; vault.read, vault.write, vault.exists, vault.mkdirp work on notes; vault.write is applied after the cell, only if the notes it read are unchanged |
```ore:shell |
sh in a container |
stdout is the output |
```ore:ai |
One request to your AI provider | {{cellId}} inserts an output, {{note:Path/To/Note.md}} a note. Needs network: allow |
Both container cells use the workflow's runtime.image (default oven/bun:1-alpine). The
workflow note spec has the full format, including cell
attributes (depends, timeout, model, temperature, maxTokens) and the output files.
| Setting | Default | |
|---|---|---|
| Default provider | OpenAI | OpenAI or Anthropic, for every ore:ai cell |
| Default model | gpt-5-mini |
Used when a cell sets no model=. Clearing the field sets the current provider's default (claude-haiku-5-5 for Anthropic) |
| Temperature | provider default | |
| Max tokens | 800 | |
| AI request timeout | 120 seconds | |
| Default runtime engine | Docker | A workflow's runtime.engine overrides it |
| Output folder | _vaultore |
Where run outputs are written |
| OpenAI API key, Anthropic API key | none | The name of a secret in Obsidian's secret storage |
| Run scheduled workflows | off | See Scheduling |
---
ore: true
name: Daily Summary
schedule: "0 18 * * *" # every day at 18:00, local time
---
Nothing runs on a schedule until you opt in. Settings → VaultOre → Scheduled workflows lists every note with a schedule, with its next run or why the schedule is invalid. Turn on Run scheduled workflows, then approve each note you want to run. An approval covers that note's path and exact schedule: a changed schedule, a renamed note, or a note that arrives through sync is listed but does not run until you approve it. Scheduled runs happen only while Obsidian is open.
Permissions are declared per workflow in frontmatter, each allow, deny or ask.
The defaults (permissions spec, the single source):
| Permission | Default | Controls |
|---|---|---|
network |
deny |
Network for container cells; required by AI cells |
vaultRead |
allow |
Whether the vault is mounted into containers; {{note:}} |
vaultWrite |
deny |
Whether that mount is writable |
buildNetwork |
deny |
Nothing yet (reserved for planned Python and Go cells) |
The mount enforces them. A container cell sees the whole vault read-only, the whole vault
read-write, only its own run directory, or nothing, depending on vaultRead and vaultWrite.
.obsidian/, which holds the plugin and its settings, and .vaultore/, which holds the CLI's
settings and stored permission decisions, are hidden from every cell.
Containers are hardened on every engine: read-only root filesystem, a noexec /tmp, no
Linux capabilities, a non-root user, no network unless granted, and memory and CPU limits.
Docker, Podman and Colima also get no-new-privileges and a limit of 256 processes, which
Apple's container cannot express (it runs each container in its own VM). Exact flags per
engine: sandbox spec and permissions spec §4.
ask is asked once per content. The decision is stored for that note and bound to a hash
of its frontmatter and cells; when either changes, including through sync, you are asked
again. A permission set to allow or deny in the note is applied without a prompt.
Scheduling needs consent, off by default, per note and schedule (above).
API keys live in Obsidian's secret storage. VaultOre's settings hold only the name of the secret you selected, never the key, and the plugin never writes to the secret storage. Keys are used for the provider request only, not passed to containers.
vault.write cannot save such a copy: the host applies it only if the note is still what
the cell read, and otherwise fails the cell and writes nothing, so run it again. A cell that
only reads still sees the stale copy, and shell cells and raw writes to /workspace
(Bun.write, node:fs) get no such check. Use virtiofs:
colima start --vm-type vz --mount-type virtiofs.exit 1.vault.write, {{note:}}, output files, the workflow note), and a link as the last
component of a path is refused wherever it points. A workflow in a folder linked from outside
the vault therefore cannot run..obsidian/ and .vaultore/ are hidden from cells. Obsidian's "Override config
folder" setting is not followed.container engine is covered by unit tests only, not yet by runs against a
real installation.@vaultore/cli runs the same workflows without Obsidian, on any folder of Markdown. It is not
on npm yet; build it from source (bun run build) and run node packages/cli/dist/cli.js.
vaultore run <workflow.md> [--vault <dir>] [--cell <id>] [--skip-deps] [--yes] [--quiet]
vaultore list [--vault <dir>]
vaultore agent [--vault <dir>] [--yes] [--rescan <seconds>] [--quiet]
vaultore schedules export [--vault <dir>] [--out <file>]
<vault>/.vaultore/config.json (defaultProvider, defaultModel,
runtimeEngine, outputRoot, aiTimeoutSeconds, …), API keys from the environment
(VAULTORE_OPENAI_APIKEY or OPENAI_API_KEY, VAULTORE_ANTHROPIC_APIKEY or
ANTHROPIC_API_KEY).ask are denied unless you pass --yes, and the decision is stored in the
config file. A deny stored that way sticks until the note changes
(#28).vaultore agent is a scheduler daemon. It runs every workflow in the vault that has a
schedule; it has no approval step.vaultore schedules export writes the manifest that hands scheduled workflows to CronAI
(handover spec).More in docs/interoperability.md.
See CONTRIBUTING.md for setup, conventions and the release process, and the plugin development quickstart for running a dev build.
MIT © CronAI UG