LLM Tasks

unlisted

by John

Dispatch lines of text as tasks to LLM agents directly from your notes.

Updated 1mo agoMIT
View on GitHub

LLM Tasks

An Obsidian plugin that dispatches lines of text to LLM agents. Cursor on a line, hotkey, done. The agent runs in the background. Your note updates when it finishes.

No streaming UI, no chat panels, no embedded AI. Just a line of text → a background process → a status update.

LLM Tasks Demo

Platform: macOS and Linux only (desktop). This plugin requires a Unix shell and command-line AI agents (such as Claude Code or Pi) to be installed on your machine. Windows and mobile (iOS/Android) are not supported.

How It Works

Write a task on any line in a note:

Refactor the auth module to use dependency injection

Hit your hotkey. The line becomes:

- ⏳ Refactor the auth module to use dependency injection <!-- llm:2026-04-14_143022_refactor-the-auth-module session:a1b2c3d4-... -->

The <!-- ... --> comment is invisible in Obsidian reading mode. When the agent finishes:

- ✅ Refactor the auth module to use dependency injection <!-- llm:2026-04-14_143022_refactor-the-auth-module session:a1b2c3d4-... -->

Or if it failed: - ❌

Continuing a Conversation

Indent a follow-up line under a completed task and dispatch it:

- ✅ Refactor the auth module <!-- llm:... session:a1b2c3d4 -->
  - Now add tests for it

The plugin detects this is a continuation, finds the previous session ID, and resumes the conversation. The parent task's marker reflects the latest run:

- ⏳ Refactor the auth module <!-- llm:... session:a1b2c3d4 -->
  - Now add tests for it <!-- llm:... session:e5f6g7h8 -->

Multiple follow-ups stay at the same indent level — no deeper nesting:

- ✅ Refactor the auth module <!-- llm:... session:a1b2c3d4 -->
  - Now add tests for it <!-- llm:... session:e5f6g7h8 -->
  - Fix the failing test <!-- llm:... session:i9j0k1l2 -->

Supported Agents

The plugin works with any CLI agent that supports non-interactive mode. It ships with presets for Claude Code and Pi:

Claude Code

Default preset. Uses claude -p --dangerously-skip-permissions.

Why --dangerously-skip-permissions? In non-interactive (-p) mode, Claude can't prompt you for file write/edit permissions. Without this flag, tool calls are silently denied and the agent fails. Only use this in vaults/directories you trust.

Sessions are tracked via --session-id and continued with --resume.

Pi

Uses pi -p. Sessions are saved to pi's default session directory (~/.pi/agent/sessions/) using pi's native filename format. Resume finds the session file automatically by UUID.

Custom Agents

Select "Custom" from the agent preset dropdown and configure:

  • Agent command — The full command prefix (e.g. aider --message). The rendered prompt is appended as the final argument.
  • Session template — Args to set session identity on fresh runs. Use {sessionId} placeholder. Leave empty for pi (uses default session dir) or if your agent doesn't support sessions.
  • Resume template — Args to resume a session on continuations. Use {sessionId} placeholder. Leave empty for pi (resume is handled automatically).

Installation

From source

git clone https://github.com/jacrify/llm-tasks.git
cd llm-tasks
npm install
npm run build

Symlink into your vault:

mkdir -p /path/to/vault/.obsidian/plugins/llm-tasks
ln -sf $(pwd)/main.js /path/to/vault/.obsidian/plugins/llm-tasks/main.js
ln -sf $(pwd)/manifest.json /path/to/vault/.obsidian/plugins/llm-tasks/manifest.json
ln -sf $(pwd)/styles.css /path/to/vault/.obsidian/plugins/llm-tasks/styles.css

Enable LLM Tasks in Settings → Community Plugins. Assign a hotkey for "LLM Tasks: Dispatch task" in Settings → Hotkeys.

Shell Environment

The plugin spawns agents through your login shell (/bin/zsh -l -i -c '...') so they inherit your full environment — PATH, API keys, everything.

Your agent must work from a non-interactive login shell. Test it:

/bin/zsh -l -i -c 'claude -p "say hello"'
/bin/zsh -l -i -c 'pi -p "say hello"'

If that works, the plugin will work. If not, check that your shell profile (.zshrc, .zprofile) exports the necessary environment variables and adds agent binaries to PATH.

Commands

CommandDescription
Dispatch taskSend current line to the agent
Cancel taskKill the agent process for the current line
Cancel all tasksKill every running agent

No default hotkeys — assign them in Settings → Hotkeys.

Settings

Agent

SettingDescription
Agent presetClaude Code, Pi, or Custom. Sets sensible defaults for all fields below.
Agent commandFull command prefix. The prompt is appended as the final argument.
Session templateArgs for fresh sessions. {sessionId} is replaced with a generated UUID. Leave empty for pi (uses default session dir).
Resume templateArgs for continuations. {sessionId} is replaced with the previous session's UUID. Leave empty for pi (handled automatically).

Prompt

SettingDescription
Prompt templateSystem prompt sent to the agent. Supports {{task}}, {{sourceNoteName}}, {{noteContext}}, {{vaultPath}}, {{timestamp}} placeholders.
Include note contextPass the full source note content via {{noteContext}}.
Context limitMax characters of note context to include.

Shell

SettingDescription
Shell pathShell used to run agent commands (default: /bin/zsh).
Extra PATH entriesColon-separated paths prepended to PATH. Needed because Obsidian GUI apps have a minimal PATH.

General

SettingDescription
Poll intervalSeconds between checking for task completion (default: 5).
Max concurrent tasksLimit simultaneous agents. 0 = unlimited.
Notify on completionShow an Obsidian notice when a task finishes.

Task Markers

SettingDefaultDescription
Pending markerShown while the agent is running
Done markerShown when the agent exits successfully
Failed markerShown when the agent exits with an error

Agent Output

Agent stdout and stderr are captured to log files in your system's temp directory (/tmp/llm-tasks/ or equivalent). These are useful for debugging but are not surfaced in the UI.

How It Works (Technical)

  1. Dispatch: The plugin spawns the agent as a detached child process via your login shell. Output is piped to a log file. The process PID is tracked.

  2. Polling: A timer checks if each tracked PID is still alive. When a process exits, the plugin reads the exit code and updates the note.

  3. Continuation: When dispatching an indented line, the plugin scans upward for sibling/parent lines with session: tags. If found, it uses resumeTemplate instead of sessionTemplate to resume the previous session. For pi, the plugin finds the session file by UUID in pi's default session directory.

  4. Session IDs: A UUID is generated at dispatch time and passed to the agent via sessionTemplate (or used to build a pi-native session path). This UUID is stored in the note's HTML comment and used for future continuations.

Disclosure

This plugin:

  • Spawns external processes: Tasks are executed by spawning LLM agent CLI tools (e.g. claude, pi) as child processes on your machine. These agents may make network requests to external APIs (e.g. Anthropic) depending on the agent you use.
  • May access files outside your vault: The spawned agents operate as general-purpose coding agents. Depending on the task you give them, they may read or modify files anywhere on your system that your user account has access to.

No data is sent to any service by the plugin itself — all network activity originates from the agent process you configure.

Development

npm install
npm run build    # build main.js
npm test         # run tests

Tests use vitest with a mock of the Obsidian API. Core logic is unit-tested without Obsidian. Integration tests spawn real processes.

Releasing

Once the plugin is listed in the Obsidian community plugin directory, users receive updates automatically when you publish a new GitHub release. Here's the process for each release:

1. Bump the version

Update the version field in both files to the new semver version:

  • manifest.json
  • package.json
# Example: bumping to 1.1.0
sed -i '' 's/"version": ".*"/"version": "1.1.0"/' manifest.json package.json

Also update minAppVersion in manifest.json if the plugin now requires a newer Obsidian version.

2. Build and test

npm run build
npm test

Verify that main.js is freshly built and reflects your changes.

3. Commit and tag

git add manifest.json package.json main.js
git commit -m "Release 1.1.0"
git tag 1.1.0
git push origin main --tags

The tag must exactly match the version in manifest.json (e.g. 1.1.0, not v1.1.0).

4. Create the GitHub release

Go to Releases → Draft a new release on GitHub (or use the gh CLI):

gh release create 1.1.0 \
  main.js \
  manifest.json \
  styles.css \
  --title "1.1.0" \
  --notes "Release notes here"

The release must have these three files attached as assets:

AssetDescription
main.jsCompiled plugin code
manifest.jsonPlugin metadata (version must match the tag)
styles.cssPlugin styles

Obsidian's plugin infrastructure fetches these assets from the GitHub release, so all three are required even if styles.css is minimal.

5. Verify

  • Check the release page on GitHub — confirm the tag and all three assets are present.
  • In Obsidian, go to Settings → Community Plugins → Check for updates to confirm the new version appears.

Notes

  • No PR needed for updates. The initial community-plugins.json PR was a one-time submission. Subsequent releases are picked up automatically from GitHub releases.
  • Tag format matters. Obsidian expects bare semver tags (1.1.0), not prefixed ones (v1.1.0).
  • versions.json is optional. If you need to declare different minAppVersion values for different plugin versions, create a versions.json mapping versions to minimum Obsidian versions. Not needed if minAppVersion stays the same.

For plugin developers

Search results and similarity scores are powered by semantic analysis of your plugin's README. If your plugin isn't appearing for searches you'd expect, try updating your README to clearly describe your plugin's purpose, features, and use cases.