AgentStack
SKILL verified MIT Self-run

Recall

skill-abhattacherjee-claude-code-skills-recall · by abhattacherjee

Pure read-only context resume — summarizes unsummarized notes and surfaces last-session context. Use `/check-items` to triage open items. Use when: (1) /recall command, (2) /recall <project-name>, (3) resuming work on a project and wanting prior context.

No reviews yet
0 installs
1 views
0.0% view→install

Install

$ agentstack add skill-abhattacherjee-claude-code-skills-recall

✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.

Security review

✓ Passed

No issues found. Passed automated security review. · v0.1.0 How review works →

  • Prompt-injection patterns
  • Secret / credential exfiltration
  • Dangerous shell & filesystem operations
  • Untrusted network calls
  • Known-malicious package signatures

What it can access

  • Network access No
  • Filesystem access Used
  • Shell / process execution Used
  • Environment & secrets No
  • Dynamic code execution No

From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.

Are you the author of Recall? Claim this listing to set pricing, connect Stripe payouts, and keep 70% of every sale.
Sign up to claim

About

Recall — Load Project Context from Obsidian Vault

Searches the Obsidian vault for session notes and insights matching the current project, upgrades any unsummarized notes with AI summaries, and presents a concise context brief.

Tools needed: Bash, Grep, Read, Write

Procedure

Follow these steps exactly. Do not skip steps or reorder them.

Step 1 — Load config and derive project

Run a single call that loads config and derives the project name (saves one parent round):

cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
python3 -c '
import sys, os
import glob; sys.path.insert(0, max(glob.glob(os.path.expanduser("~/.claude/plugins/cache/*/obsidian-brain/*/hooks")), default="hooks"))
from obsidian_utils import load_config
c = load_config()
if not c.get("vault_path"):
    print("ERROR: vault_path not configured", file=sys.stderr)
    sys.exit(1)
project = os.path.basename(os.getcwd()).lower().replace(" ", "-")
print("VAULT=" + c["vault_path"])
print("SESS=" + c.get("sessions_folder", "claude-sessions"))
print("INS=" + c.get("insights_folder", "claude-insights"))
print("PROJECT=" + project)
'

Parse each output line as KEY=VALUE, splitting on the first =.

If the user passed a project name argument (e.g. /recall my-project), override PROJECT with that value.

If the output is empty or errors, tell the user:

> Config not found. Run /obsidian-setup first to configure your Obsidian vault.

Stop here if config is missing.

Create the task manifest for the full /recall flow:

TaskCreate: subject="Find unsummarized notes", activeForm="Searching for unsummarized notes"
TaskCreate: subject="Summarize unsummarized notes", activeForm="Summarizing notes"
TaskCreate: subject="Present read-only context brief", activeForm="Building and presenting context brief"

Track the returned task IDs — you will update them as each step completes. Immediately set task #1 to in_progress via TaskUpdate.

Step 2 — Summarize unsummarized notes (deferred summarization, truncation-aware)

> ⚠️ THIS STEP IS MANDATORY. DO NOT SKIP IT. > > If Grep finds any file matching both status: auto-logged AND project: $PROJECT, you must produce an upgraded summary for every such file before proceeding to Step 3. "Skipping to save context" or "the other session covers it" is a bug, not an optimization — the user ran /recall specifically to get current-session context, and stale unsummarized notes are exactly what they asked you to fix. > > Visibility requirement: Before Step 3, emit a one-line status: Step 2: processing N unsummarized note(s) for $PROJECT (or Step 2: no unsummarized notes for $PROJECT if the intersection is empty). This makes the decision auditable in the tool trace.

Find unsummarized notes for this project in a single Python call (replaces multiple Grep rounds):

cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
python3 -c '
import sys, os
import glob; sys.path.insert(0, max(glob.glob(os.path.expanduser("~/.claude/plugins/cache/*/obsidian-brain/*/hooks")), default="hooks"))
from obsidian_utils import find_unsummarized_notes
print(find_unsummarized_notes(sys.argv[1], sys.argv[2], sys.argv[3]))
' "$VAULT_PATH" "$SESSIONS_FOLDER" "$PROJECT"

Parse the JSON output: {"unsummarized": ["/path/to/note1.md", ...], "auto_fixed": N}.

The function handles project filtering, defense-in-depth (skips notes with real ## Summary but stale auto-logged status, auto-fixes them), and returns only genuinely unsummarized note paths.

If auto_fixed > 0, report: Auto-fixed N note(s) with stale status.

Store the length of unsummarized as N.

Update task #1 to completed. Update task #2 subject to Summarize N unsummarized note(s) and set to in_progress.

Path A: N=0 (no unsummarized notes)

Update task #2 subject to No unsummarized notes found and set to completed. Skip to Step 3.

Path B: N>=1 (parallel Haiku pipelines with sub-agent fallback)

Task management threshold: If N 5, skip per-note sub-tasks — use a single progress update on task #2 instead. This saves ~15-20s of parent round-trip overhead at large N.

Phase 1 — Parallel Haiku upgrades (single batch call)

If N ", activeForm "Upgrading via Haiku"`).

Single Bash tool callupgrade_batch() fans out N Haiku invocations in parallel inside one Python process via concurrent.futures.ThreadPoolExecutor. This sidesteps the Claude Code harness's serialization of parallel Bash tool calls for subprocess-blocking work:

cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
printf '%s' "$UNSUMMARIZED_PATHS_JSON" | python3 -c '
import sys, os, json
import glob; sys.path.insert(0, max(glob.glob(os.path.expanduser("~/.claude/plugins/cache/*/obsidian-brain/*/hooks")), default="hooks"))
from obsidian_utils import upgrade_batch
paths = json.loads(sys.stdin.read())
results = upgrade_batch(paths, sys.argv[1], sys.argv[2], sys.argv[3])
print(json.dumps([{"path": p, "status": s} for p, s in results]))
' "$VAULT_PATH" "$SESSIONS_FOLDER" "$PROJECT"

Parse the returned JSON array. For each entry:

  • status starts with Upgraded → mark as succeeded
  • anything else (including Failed: ..., empty, or unexpected prefix) → add to the Phase 2 fallback list

If N ). If N > 5: update task #2 subject to Upgrade N notes: M succeeded, F pending fallback`.

> Why a single Bash call, not N parallel calls? The Claude Code harness serializes parallel Bash tool calls through a limited shell pool when each subprocess blocks on I/O (e.g., claude -p --model haiku taking 5-30s). Dispatching 10 Bash calls in one message still executes them one at a time — wall time ≈ Σ per-call. Pushing fan-out into a single Python process with ThreadPoolExecutor gives true concurrency (the GIL releases during subprocess waits), so wall time ≈ max per-call. See claude-insights/2026-04-21-recall-parallel-bash-dispatch-runs-sequentially-fbee-error.md and GH #69.

Phase 2 — Sub-agent fallback (only for failed notes)

If no failures, skip this phase entirely.

For each failed note, spawn a sub-agent. If multiple notes failed, spawn all sub-agents in a single message turn:

Agent({
  description: "Summarize session note ",
  prompt: "Read the session note at . Produce a structured summary with these exact markdown sections:\n\n## Summary\n1-3 sentence overview of what was accomplished.\n\n## Key Decisions\n- Bullet list of important technical decisions. Write \"None noted.\" if none.\n\n## Changes Made\n- Bullet list of files modified/created with brief description. Write \"None noted.\" if none.\n\n## Errors Encountered\n- Bullet list of errors and how resolved. Write \"None.\" if none.\n\n## Open Questions / Next Steps\n- [ ] Checkbox list of unresolved items. Write \"None.\" if none.\n\nWrite the summary to ~/.claude/obsidian-brain/summary-.md using the Write tool. After the summary sections, add a final line:\nIMPORTANCE: N\nwhere N is 1-10. 1-3: trivial (config, interrupted). 4-6: standard work. 7-8: key decisions or error resolutions. 9-10: major releases or security audits.\n\nReturn ONLY the single line: WRITTEN:~/.claude/obsidian-brain/summary-.md"
})

When sub-agents return, for each:

  1. If the sub-agent returned WRITTEN:, extract the path after WRITTEN: and replace the leading ~ with $HOME to get an absolute path. Store this as SUMMARY_TEMP_PATH. Verify the file exists: test -f "$SUMMARY_TEMP_PATH" && echo "EXISTS" || echo "MISSING".
  2. If EXISTS, apply it via Python:

``bash cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)" python3 -c ' import sys, os import glob; sys.path.insert(0, max(glob.glob(os.path.expanduser("~/.claude/plugins/cache/*/obsidian-brain/*/hooks")), default="hooks")) from obsidian_utils import upgrade_note_with_summary with open(os.path.expanduser(sys.argv[6]), "r") as f: summary = f.read() status = upgrade_note_with_summary(sys.argv[1], summary, sys.argv[2], sys.argv[3], sys.argv[4], sys.argv[5]) print(status) ' "$NOTE_PATH" "$VAULT_PATH" "$SESSIONS_FOLDER" "$PROJECT" "sub-agent" "$SUMMARY_TEMP_PATH" ``

If the write-back status starts with Failed:, count this note as permanently failed — do NOT count it as upgraded. If N `.

If the write-back succeeds, and N `.

  1. If MISSING or sub-agent didn't return WRITTEN: → note stays unsummarized for next /recall. If N `.

Always clean up temp files from Phase 2 after all write-backs complete, regardless of outcome. Use the actual SUMMARY_TEMP_PATH values collected from each sub-agent's WRITTEN: response (not placeholder names):

rm -f "$SUMMARY_TEMP_PATH_1" "$SUMMARY_TEMP_PATH_2" ...

If N > 5: update task #2 subject to reflect final Phase 2 results (e.g. Upgrade N notes: M Haiku + F fallback succeeded, K failed).

Completion

Mark task #2 as completed. Report results:

  • How many upgraded via Haiku pipeline (Phase 1 successes)
  • How many upgraded via sub-agent fallback (Phase 2 write-back successes)
  • How many permanently failed (notes where both Phase 1 Haiku AND Phase 2 sub-agent fallback failed or were skipped — these stay unsummarized for next /recall)

For failed notes: "Note ` could not be summarized. It will be retried on the next /recall`."

Step 3 — Build context brief (Python)

Update task #3 to in_progress.

Run a single Python call that reads all session and insight files and composes the brief — no sub-agent needed:

cd "$(git rev-parse --show-toplevel 2>/dev/null || pwd)"
python3 -c '
import sys, os
import glob; sys.path.insert(0, max(glob.glob(os.path.expanduser("~/.claude/plugins/cache/*/obsidian-brain/*/hooks")), default="hooks"))
from obsidian_utils import build_context_brief, check_hook_status
hs = check_hook_status()
status_line = ("[OK] " if hs["ok"] else "[WARN] ") + hs["message"]
print(build_context_brief(sys.argv[1], sys.argv[2], sys.argv[3], sys.argv[4], hook_status_line=status_line))
' "$VAULT_PATH" "$SESSIONS_FOLDER" "$INSIGHTS_FOLDER" "$PROJECT"

The first line of the emitted CONTEXT_BRIEF is always the hook-status line. If it starts with [OK], omit it from the displayed output — the user doesn't need to see "session logging active" every time. If it starts with [WARN], display it verbatim so the user knows to take action (e.g., run /obsidian-setup).

If the command fails (non-zero exit code), print the error and stop — do not fall back to in-context reads.

Parse the output. Split on section labels:

  1. Extract >> — everything between this delimiter and >>. This is the brief to display.
  2. Extract >> — parse full_session_title, full_session_date, full_session_path, summary_session_title, summary_session_date, insight_count, snapshot_count (optional), and all snapshot: lines (there may be zero or more, each followed by optional 2-space-indented key_context bullets).
  3. Extract >> — either NO_CANDIDATES, NO_ITEMS, or a JSON array. Count the number of - [ ] items across all scanned session notes. Store as open_items_total.

Present the brief immediately (same turn — saves one parent round):

> Here's what I found from your Obsidian vault for $PROJECT:

Then output the CONTEXT_BRIEF section. For the session history table, paraphrase each session's Title column into a concise one-line summary (under ~80 characters) that captures the key accomplishment. Keep all other columns (date, duration, branch) verbatim.

Snapshots appear in the brief as nested indented rows beneath their parent session (rows starting with ↳ HH:MM:SS). Render them verbatim — do not paraphrase snapshot titles (they're already one-line summaries). Display the snapshot: lines from LOAD_MANIFEST as bullet points under the most-recent session in the "Loaded into this conversation" output.

If unsummarized notes were upgraded in Step 2, also mention:

> Upgraded N session note(s) with AI summaries.

Step 4 — Show read-only context brief footer

Append to the brief written in Step 3:

> N open items in this project — run /check-items to triage.

Where N is the count of - [ ] items found while scanning sessions in Step 3. Use the open_items_total value already computed by the Python block in Step 3 (if not present, count by re-scanning the same notes). This step is read-only: do NOT prompt the user about individual items, do NOT compute candidate matches, and do NOT cite any session note.

If N == 0, omit the footer entirely.

Mark task #3 (the renamed final task) as completed and end.

Edge Cases

  • No sessions found: Tell the user no session history was found for this project. Suggest they start a session and it will be logged automatically.
  • No insights found: Omit the "Curated Insights" section. Mention: "No curated insights yet for this project."
  • Very large vault (50+ sessions): Only grep, never glob the entire folder. Limit reads to the most recent 5 sessions + all insights.
  • Config exists but vault path is invalid: Warn the user and suggest running /obsidian-setup again.
  • Open items exist: Do not attempt to check them off. Append the footer nudge pointing to /check-items instead.

Source & license

This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.

Install and usage instructions live in the source repository linked above.

Reviews

No reviews yet — be the first.

Versions

  • v0.1.0 Imported from the upstream source.