Cadence Integration¶
Cadence is typed event infrastructure for ambient AI agency -- agents that observe, notice, and act without being asked. Most AI agents are request-response: you ask, they answer. Cadence closes that gap by enabling agents that watch file changes, webhook events, and scheduled triggers, then act autonomously based on what they see.
In the sandbox, Cadence runs as a systemd service that watches your Obsidian vault, extracts insights from journal entries, and delivers digests to Telegram.
What Cadence Does¶
Obsidian Watcher --> Insight Extractor (LLM) --> Insight Digest (Batching) --> Telegram Delivery
- Watches your Obsidian vault for new or modified journal entries
- Extracts insights using an LLM (Claude by default) from entries tagged with
::publish - Batches insights into digests based on content pillars and schedule
- Delivers digests to Telegram (or Discord, or a log file)
Under the hood, Cadence uses a pluggable architecture (Transport, Store, Executor) with type-safe signals. The sandbox integration uses the built-in file watcher source pointed at your Obsidian vault mount.
Prerequisites¶
Cadence requires:
- An Obsidian vault mounted via
--vault - A Telegram bot token (for Telegram delivery)
- An Anthropic API key (for LLM extraction)
Setup¶
1. Bootstrap with vault¶
# Using the Bilrost CLI (recommended); configure vault path during `bilrost init`
bilrost up
# Using bootstrap.sh directly
./bootstrap.sh --openclaw ~/Projects/openclaw \
--vault ~/Documents/Vaults/main \
--secrets ~/.openclaw-secrets.env
Ensure your secrets file includes:
ANTHROPIC_API_KEY=sk-ant-xxx
TELEGRAM_BOT_TOKEN=123456:ABC-xxx
2. Configure cadence.json¶
The Ansible role creates a default cadence.json from a template. Edit it to set your Telegram chat ID and enable the pipeline:
limactl shell openclaw-sandbox -- nano ~/.openclaw/cadence.json
{
"enabled": true,
"vaultPath": "/workspace-obsidian",
"delivery": {
"channel": "telegram",
"telegramChatId": "YOUR_CHAT_ID",
"fileLogPath": "~/.openclaw/cadence/signals.jsonl"
},
"pillars": [
{ "id": "tech", "name": "Technology", "keywords": ["code", "software", "ai", "engineering"] },
{ "id": "business", "name": "Business", "keywords": ["startup", "strategy", "growth"] },
{ "id": "life", "name": "Life", "keywords": ["reflection", "learning", "health"] }
],
"llm": {
"provider": "anthropic",
"model": "claude-3-5-haiku-latest"
},
"extraction": {
"publishTag": "::publish"
},
"digest": {
"minToFlush": 3,
"maxHoursBetween": 24,
"cooldownHours": 2,
"quietHoursStart": "22:00",
"quietHoursEnd": "07:00"
},
"schedule": {
"enabled": true,
"nightlyDigest": "21:00",
"morningStandup": "08:00",
"timezone": "America/New_York"
}
}
Getting your Telegram chat ID
Message your bot, then check: curl https://api.telegram.org/bot<TOKEN>/getUpdates | jq '.result[0].message.chat.id'
3. Restart the cadence service¶
limactl shell openclaw-sandbox -- sudo systemctl restart openclaw-cadence
cadence.json Reference¶
| Field | Default | Description |
|---|---|---|
enabled |
false |
Master switch for the pipeline |
vaultPath |
/workspace-obsidian |
Path to Obsidian vault inside VM (merged overlay mount) |
delivery.channel |
telegram |
Delivery channel: telegram, discord, log |
delivery.telegramChatId |
"" |
Telegram chat ID for delivery |
delivery.fileLogPath |
~/.openclaw/cadence/signals.jsonl |
JSONL log for container signal bridging |
pillars |
3 default pillars | Content categories for insight classification |
llm.provider |
anthropic |
LLM provider for extraction |
llm.model |
claude-3-5-haiku-latest |
LLM model |
extraction.publishTag |
::publish |
Tag that marks entries for extraction |
digest.minToFlush |
3 |
Minimum insights before sending a digest |
digest.maxHoursBetween |
24 |
Maximum hours between digests |
digest.cooldownHours |
2 |
Cooldown between digest deliveries |
digest.quietHoursStart |
22:00 |
Start of quiet hours (no delivery) |
digest.quietHoursEnd |
07:00 |
End of quiet hours |
schedule.enabled |
true |
Enable scheduled digests |
schedule.nightlyDigest |
21:00 |
Time for nightly digest |
schedule.morningStandup |
08:00 |
Time for morning standup |
schedule.timezone |
America/New_York |
Timezone for schedule |
The ::publish Marker¶
Write journal entries in your Obsidian vault with ::publish on the second line (after the title) to mark them for insight extraction:
# My Daily Journal Entry
::publish
Today I learned about OverlayFS and how it handles whiteout files...
Only entries with the ::publish tag are processed by the extraction pipeline. Other entries are ignored.
Vault Sync and the inotify Gotcha¶
Cadence watches the vault at /workspace-obsidian using chokidar (inotify under the hood). This is the merged overlay mount, not the raw upper directory.
Always write to the merged mount
Writing directly to the overlay upper dir (/var/lib/openclaw/overlay/obsidian/upper/) bypasses inotify on the merged mount. Cadence will never see those writes. Always write to /workspace-obsidian/ so that file watchers detect the changes.
Host-Side Vault Sync¶
If your Obsidian vault is synced via iCloud, virtiofs cannot read iCloud-locked files directly. The sync-vault.sh script uses rsync to copy vault contents to the VM:
# Manual sync
./scripts/sync-vault.sh
# Automated via launchd (see below)
The rsync uses --exclude=.obsidian/ to avoid ESTALE errors on virtiofs lower-layer config files that iCloud may lock.
Signal Bridging via fileLogPath¶
The delivery.fileLogPath field in cadence.json enables JSONL-based signal bridging. When set, cadence writes every signal event to this file in addition to the primary delivery channel:
{
"delivery": {
"channel": "telegram",
"fileLogPath": "~/.openclaw/cadence/signals.jsonl"
}
}
This allows other processes (e.g., sandbox containers, buildlog, qortex) to read the signal log and react to cadence events without needing direct Telegram access.
Custom Skills Mount¶
The --skills flag mounts a custom skills directory into the VM at /mnt/skills-custom (read-only):
# Using bootstrap.sh
./bootstrap.sh --openclaw ~/Projects/openclaw \
--vault ~/Documents/Vaults/main \
--skills ~/Projects/skills/skills/custom
# The skills directory is available at /mnt/skills-custom inside the VM
Cadence can load custom skill definitions from this mount for use in the signal bus pipeline.
Host-Side Scheduling (launchd)¶
Several launchd plist files in scripts/ provide host-side scheduling for macOS. These are manual installs. Bootstrap and Ansible provision the VM but do not modify your macOS LaunchAgents.
Vault Sync (every 5 minutes)¶
scripts/com.openclaw.vault-sync.plist: runs sync-vault.sh on a timer to keep the VM vault current with host-side iCloud changes.
Cadence Host Process¶
scripts/com.openclaw.cadence.plist: runs the cadence host-side coordinator.
Dashboard Sync (every 10 minutes)¶
scripts/com.openclaw.dashboard-sync.plist: runs dashboard-sync.sh to pull GitHub issues into Obsidian kanban boards. See Dashboard Sync for full configuration.
Installing the plists¶
# Copy whichever plists you want to automate
cp scripts/com.openclaw.vault-sync.plist ~/Library/LaunchAgents/
cp scripts/com.openclaw.cadence.plist ~/Library/LaunchAgents/
cp scripts/com.openclaw.dashboard-sync.plist ~/Library/LaunchAgents/
# Load them (starts immediately)
launchctl load ~/Library/LaunchAgents/com.openclaw.vault-sync.plist
launchctl load ~/Library/LaunchAgents/com.openclaw.cadence.plist
launchctl load ~/Library/LaunchAgents/com.openclaw.dashboard-sync.plist
Full Disk Access required
launchd agents cannot access ~/Documents/ without Full Disk Access (FDA) for /bin/bash. Grant FDA to /bin/bash in System Settings > Privacy & Security > Full Disk Access. Manual script runs from Terminal work fine because Terminal typically has FDA.
Not handled by bootstrap
All host-side launchd plists are manual installs. Bootstrap provisions the VM via Ansible; it does not install macOS LaunchAgents. This is by design: host-side scheduling is a user choice, not a provisioning step.
Ansible Variables¶
These variables can be set at bootstrap time with -e:
| Variable | Default | Description |
|---|---|---|
cadence_enabled |
false |
Enable the cadence pipeline |
cadence_vault_path |
/workspace-obsidian |
Vault path inside VM (merged overlay mount) |
cadence_delivery_channel |
telegram |
Delivery channel |
cadence_telegram_chat_id |
"" |
Telegram chat ID |
cadence_llm_provider |
anthropic |
LLM provider |
cadence_llm_model |
claude-3-5-haiku-latest |
LLM model |
cadence_schedule_enabled |
true |
Enable scheduled digests |
cadence_nightly_digest |
21:00 |
Nightly digest time |
cadence_morning_standup |
08:00 |
Morning standup time |
cadence_timezone |
America/New_York |
Schedule timezone |
Example:
./bootstrap.sh --openclaw ~/Projects/openclaw \
--vault ~/Documents/Vaults/main \
-e "cadence_enabled=true" \
-e "cadence_telegram_chat_id=123456789"
LLM Authentication¶
Cadence uses OpenClaw's auth-profiles.json for LLM access. During provisioning, if ANTHROPIC_API_KEY is found in /etc/openclaw/secrets.env, the cadence role creates:
~/.openclaw/agents/main/agent/auth-profiles.json
This file contains the API key reference for the LLM provider. The cadence service also loads secrets via EnvironmentFile=-/etc/openclaw/secrets.env.
No API key = no extraction
Without ANTHROPIC_API_KEY in your secrets, the LLM extraction step will not work. Cadence will still watch for file changes, but it cannot generate insights.
Service Management¶
# Start cadence
limactl shell openclaw-sandbox -- sudo systemctl start openclaw-cadence
# Stop cadence
limactl shell openclaw-sandbox -- sudo systemctl stop openclaw-cadence
# Restart cadence (after config changes)
limactl shell openclaw-sandbox -- sudo systemctl restart openclaw-cadence
# Check status
limactl shell openclaw-sandbox -- sudo systemctl status openclaw-cadence
# View logs
limactl shell openclaw-sandbox -- sudo journalctl -u openclaw-cadence -f
# Manual digest trigger
limactl shell openclaw-sandbox -- bun /workspace/scripts/cadence.ts digest
# Run interactively (foreground, for debugging)
limactl shell openclaw-sandbox -- bun /workspace/scripts/cadence.ts start
Systemd Service¶
The cadence service unit is deployed at /etc/systemd/system/openclaw-cadence.service:
- Runs as the Ansible user (not root)
- Loads secrets via
EnvironmentFile=-/etc/openclaw/secrets.env - Depends on
openclaw-gateway.serviceandworkspace.mount(if overlay is active) - Restarts on failure with a 10-second delay
Troubleshooting¶
Cadence not starting¶
- Check prerequisites: vault mounted, config exists,
enabled: true - Check the service:
sudo systemctl status openclaw-cadence - Check logs:
sudo journalctl -u openclaw-cadence --no-pager -n 50
No insights being extracted¶
- Verify entries have
::publishon line 2 - Check
ANTHROPIC_API_KEYis in secrets:sudo grep ANTHROPIC_API_KEY /etc/openclaw/secrets.env - Verify
auth-profiles.jsonexists:ls ~/.openclaw/agents/main/agent/auth-profiles.json
Digests not delivering to Telegram¶
- Check
telegramChatIdincadence.json - Verify
TELEGRAM_BOT_TOKENis in secrets - Check that
minToFlushthreshold is met (default: 3 insights before sending) - Check quiet hours -- no delivery between
22:00and07:00by default