| name | obsidian-enrich-daily |
| description | Enrich daily notes by harvesting activity from git repos, Apple Photos, and other sources. Automatically catches up missed days using a 7-day lookback window and per-host run history. Use when the user says "enrich daily", "enrich today", "harvest my day", "what did I do today", "fill in my daily", "daily roundup", or asks to populate their daily note with real activity data. Also trigger for "enrich yesterday", "catch up on yesterday", or any request to backfill daily notes with activity data.
|
Obsidian Enrich Daily
You enrich the user's Obsidian daily notes by harvesting evidence of their actual activity from local sources. By default you cover today plus any missed days in a 7-day lookback window, tracked per-host so multi-machine setups stay in sync. Each date's activity goes into its respective daily note.
Execution Architecture
This skill uses delegated processing to protect the main conversation's context window. The main agent handles lightweight orchestration; a subagent does all heavy data processing.
Step 1: Determine Dates (main agent)
Follow the "Date Handling" section below to compute which dates need enrichment:
- Read
zz_config/skill_history/enrich-daily.md for run history
- Apply the 7-day lookback logic (or use explicit dates if the user specified them)
- Collect the list of dates to process
Step 2: Run Harvest Script (main agent)
Run the wrapper script, which captures the full harvest to a vault-local file and prints only a summary:
bash .claude/skills/obsidian-enrich-daily/run-harvest.sh YYYY-MM-DD [YYYY-MM-DD ...]
The wrapper outputs three lines: HOST:, LINES:, and OUTPUT: (the path to the harvest file). Do not read the full harvest file — the subagent handles that.
Step 3: Launch Processing Subagent (main agent)
Use the Agent tool with subagent_type="vault-operator" (pinned to Sonnet — this is mechanical harvesting + formatting, no Opus reasoning needed) and this prompt (fill in the bracketed values):
You are processing an enrich-daily harvest for an Obsidian vault.
**Harvest output**: .claude/skills/obsidian-enrich-daily/.harvest-output.txt
**Dates to process**: [DATES from step 1]
**Host**: [HOST from step 2]
**Run history**: zz_config/skill_history/enrich-daily.md
**Daily note path pattern**: 0.periodic/YYYY/Daily/MM/YYYY-MM-DD.md
Read the skill file at .claude/skills/obsidian-enrich-daily/SKILL.md — the "Processing Reference" section and everything below it contains all formatting rules, merge logic, source interpretation, journal handling, and output specifications.
Your job:
1. Read the harvest output file
2. For each date with activity: read the daily note, synthesize Breadcrumbs, insert/merge using Edit tool
3. Handle journal entries (see "Repo Journal Notes" section)
4. Update the run history file
5. Return a brief summary: which dates were enriched, which repos appeared, what was inserted
Use the `obsidian` CLI where specified in the skill file (daily:read, search:context, recents, tasks daily).
Do NOT re-run the harvest script — it has already been run.
Step 4: Report Results (main agent)
Summarize what the subagent did for the user. Keep it brief — the subagent's return message has the details.
Processing Reference
Note: Everything below this line is reference material for the processing subagent. The main agent does not need to read past this point.
Sources
1. Git Activity and Apple Photos — Harvesting Script
The harvest has already been run by the main agent (Step 2). The output is at .claude/skills/obsidian-enrich-daily/.harvest-output.txt. Do not re-run the script.
The script discovers all git repos (including worktrees), scans for the user's commits, and queries Apple Photos. It outputs:
HOST: line — the machine hostname
- Per-date sections delimited by
DATE: headers
- Per-repo:
--- REPO: path (branch: name) --- with commit onelines + DIFFSTAT: summary (files changed, insertions, deletions)
- Photos query results
- Agent environment changes
- Journal entries
Interpreting the output
Git: Deduplicate by commit hash — if both a main repo and its worktree are scanned, the same commit may appear. Group output by repo name (the parent project, e.g., "compass"), noting which branch/worktree the work was on.
Photos: From the results:
- Count total photos
- Time spread — earliest and latest photo timestamps show the active window
- Location clusters — group by GPS coordinates (round to ~3 decimal places). Use reverse geocoding knowledge if coordinates are recognizable (e.g., 44.9/-123.0 = Salem, OR area)
- Gaps — long gaps between photo clusters suggest transitions or different activities
- Note:
-180.0/-180.0 means no GPS data (screenshot, saved image, etc.) — skip these for location analysis
3. Dev Journal Entries
The script scans all discovered repos for journal entries in two locations: .journal/docs/journal/*.md (the current worktree-based layout) and docs/journal/*.md (legacy). If a repo has the .journal worktree, that takes precedence. These are structured journal entries created by the /journal skill, each with YAML frontmatter:
---
date: 2026-03-07
generated: 2026-03-12
branch: feature-auth
host: m4MBPro
commits: 3
---
The script outputs all journal entries it finds in a === JOURNAL ENTRIES === section, tagged by repo name and filename. See "Repo Journal Notes" below for how to process these into vault notes.
4. Claude Code Sessions (if available)
Check if there are any CLAUDE.md or session artifacts in repos that had git activity today.
5. Existing Daily Note Content
Use obsidian daily:read to get today's daily note. Don't duplicate anything already there (tasks, journal entries, inbox observations). Build around what exists.
6. Recently Opened Notes
Run obsidian recents to see which notes were recently opened or edited. This adds context about what the user was working on beyond code.
7. Completed Tasks
Run obsidian tasks daily to surface any completed tasks from today's daily note. These are useful context for the breadcrumbs synthesis.
8. Agent Environment Changes
The script scans agent config directories for files modified on the target date:
~/.claude/ — plugins, MCP server configs, settings, skills
~/.config/opencode/ — opencode configuration
These changes are often invisible (not tracked in any git repo) but represent real work — installing a plugin, tweaking an MCP server, adding a skill. Surface them in the Breadcrumbs under a ### Tooling subsection when present:
### Tooling · m4MBPro
- Installed Claude Code plugin `mcp-obsidian` (~/.claude/plugins/mcp-obsidian/)
- Updated opencode config (~/.config/opencode/config.json)
Synthesize the file paths into human-readable descriptions of what changed. Don't list every file — group by action (installed, updated, configured).
Output Section
Insert a new section called ## Breadcrumbs into today's daily note.
Insertion point: After ## Journal (and its subsections like ### Habits) but before ## Inbox Processing Observations or # WebClips — whichever comes first.
Use obsidian daily:read to get the full note, find the insertion point, then use the Edit tool to insert the Breadcrumbs section. (The CLI's daily:append only appends to the end, so Edit is needed for mid-file insertion.)
Use obsidian search:context query="## Breadcrumbs" to check if Breadcrumbs already exist before inserting.
Dependency: Vault operations use the obsidian binary (Obsidian's official CLI, v1.12+). It communicates with the running Obsidian desktop app via IPC. The obsidian-cli skill documents all available commands — refer to it for syntax and flags. Commands used here: obsidian daily:path, obsidian daily:read, obsidian recents, obsidian tasks daily, obsidian search:context.
Format
The script outputs a HOST: line — use this as the host tag in entries below.
## Breadcrumbs
### Code
- **compass** (`feature-auth` · macbook): Refactored auth middleware (3 commits)
- **api-server** (`main` · workstation): Added rate limiting endpoints (2 commits)
### Photos · macbook (12 photos)
- 7:45–8:15am — Near Killarney (3 photos)
- 2:00–3:30pm — Cork city center (9 photos)
### Movement
_Strava integration coming soon_
Each Code entry is: - **repo-name** (\branch` · host): synthesis (N commits)Each Photos subsection is tagged:### Photos · host (N photos)`
Formatting rules:
- Code section: synthesize, don't list every commit. Key by repo name. Lead with what not how.
- Photos section: tell a story from the metadata. "7:45–8:15am — out near Killarney, Ireland (3 photos)" is better than listing filenames.
- Omit any section that has no data (e.g., skip Photos if none taken today)
- Keep it tight — this is a breadcrumb trail, not a journal entry. The user can expand later.
Merge / Upsert Rules
The skill may run multiple times per day, or on different hosts that sync via Obsidian. When ## Breadcrumbs already exists in the daily note, merge — never duplicate or overwrite blindly.
Code entries — match on **repo-name**:
- Same repo, same host: Replace the existing line with updated data (more commits since last run).
- Same repo, different host: Keep both lines — the user worked on the same repo from two machines. This is rare but valid.
- New repo: Append to the
### Code section.
Photos — match on ### Photos · host:
- Same host: Replace the entire Photos subsection (the newer run has the complete picture).
- Different host: Add a second
### Photos · host subsection. Each host has its own photo library.
Other sections (Movement, etc.): Replace by host if tagged, otherwise leave unchanged.
Run History
The skill tracks successful runs in zz_config/skill_history/enrich-daily.md, a Markdown file with two JSON code blocks that Obsidian syncs across hosts.
Enriched Dates (runs) — which dates have been checked, by which host:
{
"runs": {
"2026-03-10": ["m4MBPro"],
"2026-03-11": ["m4MBPro", "workstation"]
}
}
Each key is a date, each value is the list of hosts that have enriched that date. A date appears here even if the harvest found nothing — it means "this host checked and there was nothing to add." Never prune this block.
Processing Log (log) — when each enrichment actually ran (append-only):
{
"log": [
{"date": "2026-03-11", "host": "m4MBPro", "enriched": ["2026-03-10", "2026-03-11"]}
]
}
Each entry records the calendar date the skill ran, which host ran it, and which dates were enriched in that invocation.
Reading the history
- Read
zz_config/skill_history/enrich-daily.md
- The file has two JSON code blocks — extract the first one (under
## Enriched Dates) for the runs object
- Parse it to determine which dates have been enriched by which hosts
Updating the history
After successfully processing dates (whether Breadcrumbs were inserted or the day was empty):
- Read the current history file
- In the
runs block: add the current host to each processed date's array (skip if already present)
- In the
log block: append a new entry with today's date, the host, and the list of dates enriched
- Write the updated file using the Edit tool (replace both JSON code blocks)
Date Handling
Default: 7-day lookback
When invoked with no specific date arguments:
- Read the run history
- Get the current hostname from the script's
HOST: output
- Compute the last 7 days (today through 6 days ago)
- Identify missing dates — dates where the current host is NOT in the runs list
- Always include today (even if already run — allows upsert with new activity)
- Always include the most recent enriched date for this host (the latest date in the
runs list where this host appears). This covers the "same-day staleness" case: you run the skill, keep working, then don't run again for days — the last enriched date gets re-harvested and upserted with any new activity that happened after the previous run.
- Pass all missing dates + today + most-recent-enriched-date (deduplicated) to the harvest script
This means: run the skill once and it automatically catches up any missed days in the past week, plus revisits the last day it touched to pick up anything that happened after the previous run.
Explicit dates
If the user specifies dates ("just today", "enrich 2026-03-08", "last 4 days"), use those dates directly — skip the lookback logic.
Script invocation
The main agent handles script invocation in Step 2 of the Execution Architecture. The output is at .claude/skills/obsidian-enrich-daily/.harvest-output.txt, with data for each date separated by DATE: headers.
Daily note path
The path follows 0.periodic/YYYY/Daily/MM/YYYY-MM-DD.md. Note: obsidian daily:path only returns today's path regardless of date argument — compute past dates manually.
Missing daily notes
If a daily note doesn't exist for a target date, create it using the obsidian CLI before inserting Breadcrumbs:
obsidian create path="0.periodic/YYYY/Daily/MM/YYYY-MM-DD.md" template="Daily Template"
This uses Obsidian's Templater integration to render the template with proper date navigation, frontmatter, and task query blocks. After creating, read the new note with obsidian read path="..." to get its contents for insertion point detection.
Important: Check that the file doesn't already exist before calling create — the CLI appends " 1" to the filename instead of erroring on duplicates.
Empty days
If the harvest script returns no git activity, no photos, and no agent env changes for a date, still record it in the run history (the host checked and found nothing) but skip inserting Breadcrumbs into the daily note. This prevents re-checking the same empty day on every invocation.
Repo Journal Notes
After processing Breadcrumbs for all dates, handle any journal entries found by the script. This is a separate phase from Breadcrumbs — journal entries become their own vault notes, not inline daily note content.
Interpreting script output
The script outputs journal entries in a === JOURNAL ENTRIES === section:
--- JOURNAL: repo-name (2026-03-07.md) ---
---
date: 2026-03-07
generated: 2026-03-12
branch: feature-auth
host: m4MBPro
commits: 3
---
## 2026-03-07 - Refactored auth middleware
...
---END---
Each entry has YAML frontmatter with a generated date (when the entry was written, which may differ from the date it covers).
Deciding what's new
Compare each entry's generated date against the last journal harvest timestamp from the run history (see "Journal Harvest History" below). Process entries where generated is newer than or equal to the last harvest date. This ensures that entries regenerated on the same day (e.g., running /journal twice) get re-upserted with updated content. The write logic is idempotent — same-date entries are replaced, not duplicated.
If no harvest history exists yet, process all entries found.
Writing vault journal notes
For each new journal entry, upsert into 3r/repos/journal-{repo-name}.md:
- If the file doesn't exist, create it using the obsidian CLI, then populate with Edit:
obsidian create path="3r/repos/journal-{repo-name}.md"
Then use obsidian property:set to set the frontmatter fields:
obsidian property:set name="fileClass" value="repoJournal" path="3r/repos/journal-{repo-name}.md"
obsidian property:set name="repo" value="[[repo-name]]" path="3r/repos/journal-{repo-name}.md"
obsidian property:set name="latest_entry" value="2026-03-07" path="3r/repos/journal-{repo-name}.md"
obsidian property:set name="entry_count" value="1" path="3r/repos/journal-{repo-name}.md"
Then use Edit to insert the first entry body after the frontmatter:
## 2026-03-07 - Short Theme
**Branch:** `feature-auth` (3 commits)
[full entry body from the repo journal file, without the YAML frontmatter]
-
If the file exists, use Edit to insert the new entry after the frontmatter closing --- (before existing entries, so newest is first). Update latest_entry and entry_count via obsidian property:set.
-
If an entry for the same date already exists in the vault note, replace it with Edit (the repo-side entry may have been regenerated with better content). Match on the ## YYYY-MM-DD heading.
Updating repo note metadata
When a journal entry is processed, the corresponding repo likely has new commits. Keep the repo note's repo_last_commit in sync so /repo-scan doesn't report drift:
- Check if
3r/repos/{repo-name}.md exists (the repo's vault note, not the journal note).
- If it exists, read its
repo_last_commit property.
- If the journal entry's
date is newer than repo_last_commit (or repo_last_commit is empty), update it:
obsidian property:set name="repo_last_commit" value="YYYY-MM-DD" path="3r/repos/{repo-name}.md"
- Set
repo_journal to link the repo note to its journal (idempotent, always set regardless of whether the journal note exists yet — ghost links are good Obsidian practice):
obsidian property:set name="repo_journal" value="[[journal-{repo-name}]]" path="3r/repos/{repo-name}.md"
-
If the repo note doesn't exist, skip — the user hasn't done intake for that repo yet.
-
Verify: After setting properties, confirm with obsidian property:read name="repo_journal" path="3r/repos/{repo-name}.md" and obsidian property:read name="repo_last_commit" path="3r/repos/{repo-name}.md". Log any mismatches in the subagent's return summary.
This is lightweight and idempotent. The journal entry's date field is used (the day the work covers), not generated (when the entry was written), because repo_last_commit tracks when actual work happened.
Breadcrumbs annotation
When writing ### Code entries in Breadcrumbs, if a repo has a journal entry covering that date, append a wikilink: (journal: [[journal-repo-name]]). This connects the breadcrumb trail to the richer narrative.
Example:
- **compass** (`feature-auth` · m4MBPro): Refactored auth middleware (3 commits) (journal: [[journal-compass]])
Retroactive annotation
Journal entries often arrive after Breadcrumbs were already written (e.g., journaling days after the work happened, or journaling after enrich-daily already ran today). After processing all journal entries, retroactively patch any daily note Breadcrumbs that are missing the journal annotation:
- For each journal entry that was just processed, note the
date and repo-name.
- Compute the daily note path for that date (
0.periodic/YYYY/Daily/MM/YYYY-MM-DD.md).
- Read the daily note. If it has a
### Code section with a line matching **repo-name** but no (journal: [[journal-repo-name]]) annotation, append the annotation to that line.
- If the daily note doesn't exist or has no Breadcrumbs or no matching Code entry, skip — there's nothing to annotate.
This is idempotent: if the annotation is already present, no change is made. It closes the gap where backfilled journal entries (covering days that were already enriched) would otherwise leave Breadcrumbs unlinked to the richer narrative.
Journal Harvest History
Track journal harvesting in the same zz_config/skill_history/enrich-daily.md file, using a third JSON code block:
{
"journal_harvest": {
"last_harvest": "2026-03-12",
"repos_harvested": {
"compass": "2026-03-12",
"api-server": "2026-03-10"
}
}
}
last_harvest: The most recent generated date processed across all repos
repos_harvested: Per-repo, the most recent generated date processed
After processing journal entries, update this block. If the block doesn't exist yet, create it.
Guidelines
- Synthesize, don't dump — raw git logs and photo filenames are not useful. Interpret them.
- Respect existing content — never overwrite. Insert the new section at the right spot.
- Handle empty days gracefully — if no git activity and no photos, say so briefly: "Quiet day on the digital front." and skip the section.
- GPS privacy — only use coordinates for context in the daily note (neighborhood/city level). Don't write raw lat/long into the note.
- Time zones — Photos timestamps are local time. Git timestamps honor the repo's configured timezone.