Hermes Kanban Obsidian Integration
Skill by ara.so — Hermes Skills collection.
Overview
Hermes Kanban Bridge is an Obsidian plugin that exposes a REST API (default port 27124) allowing Hermes agents to manage Kanban boards inside your Obsidian vault. Boards are stored as Markdown files with YAML frontmatter, rendered visually by the obsidian-kanban community plugin.
Architecture:
- Hermes Agent → HTTP REST API → hermes-kanban-bridge plugin → Obsidian Vault API → Markdown files → obsidian-kanban visual renderer
Key Features:
- Break goals into structured Kanban boards via API
- Move cards, update metadata, query state in real-time
- Daily standups and weekly review rituals
- Fully local, no cloud dependencies
- Visual board rendering in Obsidian
Installation
Automated Install (Recommended)
# Clone the repository
git clone https://github.com/GumbyEnder/hermes-kanban.git
cd hermes-kanban
# Edit install script to set your vault path
export VAULT_PATH="/path/to/your/ObsidianVault"
# Run automated installer
bash hermes-kanban-install.sh
The script installs:
- hermes-kanban-bridge plugin (REST API server)
- obsidian-kanban plugin (visual renderer)
- Registers both in vault's community-plugins.json
Manual Installation
Build the plugin:
cd plugin
npm install
npm run build
Copy to vault:
VAULT="/path/to/your/vault"
mkdir -p "$VAULT/.obsidian/plugins/hermes-kanban-bridge"
cp main.js manifest.json "$VAULT/.obsidian/plugins/hermes-kanban-bridge/"
Install obsidian-kanban renderer:
KANBAN_DIR="$VAULT/.obsidian/plugins/obsidian-kanban"
mkdir -p "$KANBAN_DIR"
TAG="2.0.51"
for f in main.js manifest.json styles.css; do
curl -sL "https://github.com/obsidian-community/obsidian-kanban/releases/download/$TAG/$f" -o "$KANBAN_DIR/$f"
done
Enable plugins in Obsidian:
- Reload Obsidian
- Settings → Community Plugins → disable Safe Mode
- Enable "Hermes Kanban Bridge" and "Kanban"
- Verify notice: "Hermes Kanban Bridge started on port 27124"
Install Hermes skills:
mkdir -p ~/.hermes/profiles/$HERMES_PROFILE/skills/productivity
cp skills/*.md ~/.hermes/profiles/$HERMES_PROFILE/skills/productivity/
Configuration
Plugin Settings (Obsidian → Settings → Hermes Kanban Bridge):
| Setting | Default | Description |
|---|---|---|
| Port | 27124 | Local REST API port |
| Board folder | Kanban | Vault folder for boards |
| Trust mode | confirm | confirm = approval modal, auto = silent |
| Enable server | on | Toggle REST API |
Network Configuration for Remote Access:
If Hermes runs on a different machine, use Tailscale/LAN IP instead of localhost.
Windows firewall rule:
netsh advfirewall firewall add rule name="Hermes Kanban Bridge" dir=in action=allow protocol=TCP localport=27124
Verify listening on all interfaces:
netstat -ano | findstr 27124
# Should show: TCP 0.0.0.0:27124 LISTENING
REST API Reference
Base URL: http://localhost:27124 (or remote IP)
Health Check
curl http://localhost:27124/health
# {"ok":true,"status":"running","port":27124,"version":"1.0.0"}
Create Board
curl -X POST http://localhost:27124/boards \
-H "Content-Type: application/json" \
-d '{
"name": "Q3 Launch",
"columns": ["Backlog", "In Progress", "Review", "Done"]
}'
# {"success":true,"board_id":"Q3-Launch","path":"Kanban/Q3-Launch.md"}
Add Card
curl -X POST http://localhost:27124/boards/Q3-Launch/cards \
-H "Content-Type: application/json" \
-d '{
"title": "Design landing page",
"column": "Backlog",
"metadata": {
"priority": "high",
"assignee": "alice"
}
}'
# {"success":true,"card_id":"card-001"}
Move Card
curl -X PATCH http://localhost:27124/boards/Q3-Launch/cards/card-001 \
-H "Content-Type: application/json" \
-d '{
"column": "In Progress"
}'
# {"success":true}
Query Board State
# Get all cards
curl http://localhost:27124/boards/Q3-Launch/cards
# {"cards":[{"id":"card-001","title":"Design landing page","column":"In Progress",...}]}
# Filter by column
curl http://localhost:27124/boards/Q3-Launch/cards?column=Backlog
# Filter by metadata
curl http://localhost:27124/boards/Q3-Launch/cards?assignee=alice
Daily Standup
curl -X POST http://localhost:27124/rituals/standup \
-H "Content-Type: application/json" \
-d '{
"board_id": "Q3-Launch"
}'
# {"summary":"3 cards in progress, 2 blocked, 5 completed yesterday"}
Weekly Review
curl -X POST http://localhost:27124/rituals/weekly-review \
-H "Content-Type: application/json" \
-d '{
"board_id": "Q3-Launch"
}'
# {"summary":"12 cards completed this week, velocity: 2.4 cards/day"}
Python Client Usage
import requests
import os
BASE_URL = os.getenv("HERMES_KANBAN_URL", "http://localhost:27124")
class KanbanClient:
def __init__(self, base_url=BASE_URL):
self.base = base_url
def health_check(self):
"""Verify server is running"""
r = requests.get(f"{self.base}/health")
return r.json()
def create_board(self, name, columns):
"""Create new Kanban board"""
r = requests.post(
f"{self.base}/boards",
json={"name": name, "columns": columns}
)
return r.json()
def add_card(self, board_id, title, column, metadata=None):
"""Add card to board"""
payload = {"title": title, "column": column}
if metadata:
payload["metadata"] = metadata
r = requests.post(
f"{self.base}/boards/{board_id}/cards",
json=payload
)
return r.json()
def move_card(self, board_id, card_id, new_column):
"""Move card between columns"""
r = requests.patch(
f"{self.base}/boards/{board_id}/cards/{card_id}",
json={"column": new_column}
)
return r.json()
def query_cards(self, board_id, filters=None):
"""Query cards with optional filters"""
params = filters or {}
r = requests.get(
f"{self.base}/boards/{board_id}/cards",
params=params
)
return r.json()
def daily_standup(self, board_id):
"""Run daily standup ritual"""
r = requests.post(
f"{self.base}/rituals/standup",
json={"board_id": board_id}
)
return r.json()
# Example usage
client = KanbanClient()
# Verify connection
print(client.health_check())
# Create board
board = client.create_board(
name="Product Roadmap",
columns=["Ideas", "Planned", "In Development", "Shipped"]
)
board_id = board["board_id"]
# Add cards
client.add_card(
board_id,
"Implement dark mode",
"Planned",
metadata={"priority": "medium", "estimate": "3d"}
)
# Query blocked items
blocked = client.query_cards(board_id, {"status": "blocked"})
print(f"Blocked cards: {len(blocked['cards'])}")
# Run standup
standup = client.daily_standup(board_id)
print(standup["summary"])
Board Markdown Format
Boards are stored as Markdown with YAML frontmatter:
---
kanban-plugin: board
---
## Backlog
- [ ] Design landing page #high @alice
- [ ] Set up CI/CD pipeline #medium @bob
## In Progress
- [ ] Implement authentication #high @alice
- Started: 2026-05-15
- Blocked: waiting on API keys
## Review
- [ ] Write user documentation #low @carol
## Done
- [x] Initialize project repository
- Completed: 2026-05-14
Important: The kanban-plugin: board frontmatter triggers visual rendering. Without it, boards display as plain Markdown.
Common Patterns
Breaking Down a Project
def break_down_project(client, project_name, tasks):
"""Create board and populate with tasks"""
board = client.create_board(
name=project_name,
columns=["Backlog", "In Progress", "Review", "Done"]
)
board_id = board["board_id"]
for task in tasks:
client.add_card(
board_id,
task["title"],
"Backlog",
metadata=task.get("metadata", {})
)
return board_id
# Usage
tasks = [
{"title": "Research competitors", "metadata": {"priority": "high"}},
{"title": "Draft wireframes", "metadata": {"priority": "medium"}},
{"title": "Set up analytics", "metadata": {"priority": "low"}}
]
board_id = break_down_project(client, "Market Analysis", tasks)
Automated Workflow Progression
def progress_workflow(client, board_id):
"""Move cards through workflow automatically"""
# Get all cards in Review
cards = client.query_cards(board_id, {"column": "Review"})
for card in cards["cards"]:
# Check if ready to move (example: all subtasks done)
if card_is_complete(card):
client.move_card(board_id, card["id"], "Done")
print(f"Completed: {card['title']}")
def card_is_complete(card):
"""Example validation logic"""
metadata = card.get("metadata", {})
return metadata.get("reviewed", False) and metadata.get("tests_passing", False)
Daily Ritual Integration
import schedule
import time
def daily_standup_job():
"""Run standup for all active boards"""
client = KanbanClient()
# Get all boards
boards = requests.get(f"{client.base}/boards").json()
for board in boards["boards"]:
if board.get("active", True):
result = client.daily_standup(board["id"])
send_notification(result["summary"])
# Schedule daily at 9 AM
schedule.every().day.at("09:00").do(daily_standup_job)
while True:
schedule.run_pending()
time.sleep(60)
Testing
The project includes 105 tests (59 unit, 46 E2E):
# All tests
pytest tests/
# E2E only
pytest tests/e2e/
# Skip slow TUI tests
pytest tests/e2e/ -k "not tui"
# Mock API server (no Obsidian required)
python tests/e2e/mock_api_server.py
Mock API for development:
# tests/e2e/mock_api_server.py implements all REST endpoints
# Use for testing without running Obsidian
import requests
# Start mock server on port 27125
response = requests.get("http://localhost:27125/health")
assert response.json()["ok"] == True
Troubleshooting
Connection Refused
Symptoms: curl: Failed to connect to localhost port 27124
Solutions:
- Verify plugin is enabled: Settings → Community Plugins → Hermes Kanban Bridge = ON
- Look for notice "Hermes Kanban Bridge started on port 27124"
- Toggle plugin off/on to restart server
- Check port not in use:
lsof -i :27124(Unix) ornetstat -ano | findstr 27124(Windows)
Board Shows as Markdown List
Symptoms: Board opens as plain text instead of visual kanban
Solution: Add YAML frontmatter to top of file:
---
kanban-plugin: board
---
All API-created boards include this automatically. Manually created boards need it added.
Remote Access Not Working
Symptoms: Can't connect from Hermes on different machine
Solutions:
- Use Obsidian machine's Tailscale/LAN IP instead of
localhost - Verify server binds to
0.0.0.0not127.0.0.1:netstat -ano | findstr 27124 # Should show: TCP 0.0.0.0:27124 LISTENING - Add firewall rule (Windows):
netsh advfirewall firewall add rule name="Hermes Kanban Bridge" dir=in action=allow protocol=TCP localport=27124 - macOS: System Settings → Network → Firewall → allow Obsidian
Plugin Not Appearing
Symptoms: Plugin not in Community Plugins list
Solution: Quit Obsidian completely (not just reload), then reopen. Obsidian requires full restart to detect new plugin folders.
Port Conflict
Symptoms: Server fails to start, port already in use
Solution:
- Change port: Settings → Hermes Kanban Bridge → Port → set to 27125 (or other)
- Toggle plugin off/on to apply
- Update Hermes skill configs and
HERMES_KANBAN_URLto new port
Environment Variables
# REST API base URL (default: http://localhost:27124)
export HERMES_KANBAN_URL="http://192.168.1.100:27124"
# Hermes profile for skills installation
export HERMES_PROFILE="frodo"
# Obsidian vault path for scripts
export VAULT_PATH="/Users/alice/Documents/ObsidianVault"
Integration with Hermes Agent
Once skills are installed (~/.hermes/profiles/*/skills/productivity/), interact naturally:
Voice Commands:
- "Break down the Q3 launch into a Kanban board"
- "Run my daily standup"
- "What cards are blocked?"
- "Move the authentication task to In Progress"
- "Give me a weekly review"
Hermes uses the installed skills to translate commands into REST API calls automatically.
Resources
- API Documentation:
docs/API.mdin repository - Demo Board:
docs/demo/Q3-Launch.md- sample board for testing - Test Suite:
tests/- 105 tests, 55% coverage - obsidian-kanban plugin: https://github.com/obsidian-community/obsidian-kanban