Skip to content

About

A Claude Code skill for persistent session memory via Apple Notes — multi-agent, three-tier architecture

Resources

Stars

1 star

Watchers

0 watching

Forks

Repository files navigation

claude-session-handoff

A Claude Code skill that gives your AI persistent memory across sessions using plain Markdown files — Obsidian-vault friendly.

Requires Python 3 (stdlib only). Any writable directory works as the store; an iCloud-synced Obsidian vault is what the author uses. Legacy Apple Notes migration tools (macOS only) are included.

繁體中文版 README

The Problem

Claude Code sessions are stateless. Every time a session ends, all context vanishes. The next session starts from zero — no memory of what was done, what's in progress, or what decisions were made.

The Solution

This skill writes structured Markdown handoff shards to a filesystem store before each session ends, and reads them back at the start of the next session via a SessionStart hook. It's like leaving yourself a sticky note, except the AI does it automatically.

v2.0 storage cutover — the backend switched from Apple Notes to plain Markdown files. If you used v1.x, see Migrating from Apple Notes.

Architecture

Session ends
    ↓
┌───────────────────────────────────────────────┐
│ [Private] {root}/Active/{AgentID}.md          │  ← per-agent working state
│ [Shared]  {root}/Shared/{AgentID}.md          │  ← cross-agent sync (per-agent shard)
└───────────────────────────────────────────────┘
    ↓ old content archived
┌───────────────────────────────────────────────┐
│ [Archive] {root}/Archive/{YYYY}/….md          │  ← one file per session
└───────────────────────────────────────────────┘
    ↓ periodic consolidation
┌───────────────────────────────────────────────┐
│ [Long-term] MEMORY.md / episodic memory files │  ← distilled knowledge
└───────────────────────────────────────────────┘

Every file carries YAML frontmatter (schema_version / kind / agent / updated_at), generated and validated by the bundled scripts. Malformed files fail loudly instead of being silently mangled.

Three-Tier Memory

Tier Storage Lifecycle
Active Private + Shared shards Overwritten each session
Archive Archive/{YYYY}/ files Rolling, keeps last 5 entries
Long-term MEMORY.md / episodic files Permanent, distilled patterns

Multi-Agent Support

If you run Claude Code on multiple machines (e.g., a laptop for interactive dev + a server for unattended tasks), each agent writes its own private shard and its own shared shard (Shared/{AgentID}.md) — no agent ever touches another agent's files. At session start, each agent's hook merges every Shared/*.md.

Single-agent mode is also supported — just skip the shared shard.

Installation

1. Install the skill

npx skills add fredchu/claude-session-handoff

2. Pick a handoff root

Any writable directory. Two common choices:

  • ~/.agents/handoff — local only, zero dependencies
  • A folder inside an Obsidian vault on iCloud/Syncthing — synced across machines, browsable in Obsidian
mkdir -p ~/.agents/handoff

3. Configure the SessionStart hook

macOS / Linux

Copy the example hook and set your agent name and root:

cp hooks/session-start.sh ~/.claude/hooks/session-start.sh
chmod +x ~/.claude/hooks/session-start.sh
# Edit AGENT_ID and HANDOFF_ROOT in the script

Add to .claude/settings.json:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "~/.claude/hooks/session-start.sh"
          }
        ]
      }
    ]
  }
}

chmod is a Unix-only step.

Windows

Requires Python 3.9+ on PATH or available through the py launcher.

In File Explorer, copy hooks/session-start.ps1 to %USERPROFILE%\.claude\hooks\session-start.ps1, then edit $AgentId and $HandoffRoot in the copied script.

Add this to .claude/settings.json:

{
  "hooks": {
    "SessionStart": [
      {
        "matcher": "",
        "hooks": [
          {
            "type": "command",
            "command": "powershell.exe -NoProfile -ExecutionPolicy Bypass -File \"C:/Users/<you>/.claude/hooks/session-start.ps1\""
          }
        ]
      }
    ]
  }
}

Replace <you> with your Windows username. Use forward slashes in this command. Agent IDs may contain letters, digits, spaces, and hyphens. Avoid < > : " / \ | ? *, trailing dots or spaces, and reserved device names (CON, PRN, AUX, NUL, COM1–COM9, LPT1–LPT9).

The platform hook calls handoff_cli.py session-start, which prints the private shard plus every agent's shared shard (with a ⚠️ stale marker on shards older than --stale-days, default 14).

4. Add config and trigger rules to CLAUDE.md

Add to your user-level CLAUDE.md (~/.claude/CLAUDE.md):

## Session Handoff Config
- Agent ID: Main
- Handoff root: ~/.agents/handoff
- Episodic dir: ~/.agents/memory/episodic
- Other Agents: (leave empty for single-agent mode)
- Private budget: 1500 chars
- Shared budget: 1000 chars

## Session Handoff Rules
- When user says "bye", "done", "handoff", "收工", or "結束" → run `/session-handoff`
- Do NOT skip even if "nothing was done" this session

Usage

Just say "bye" or "handoff" at the end of your session. The skill will:

  1. Archive the previous handoff content (one frontmatter-tagged file per session)
  2. Reconcile a decision list first: every decision, deliverable, open item and pitfall from the session gets one line, and after writing the shards each line must appear in them or be explicitly dropped (running summaries lose facts most easily)
  3. Write new private + shared shards via handoff_cli.py; a section that existed in the previous shard and is missing from the new one is refused unless you pass --drop '<heading>'
  4. Consolidate automatically when the Archive reaches 5+ entries (old entries distilled into episodic memory)
  5. Extract lessons worth saving to long-term memory

How It Works

All writes go through one CLI

The executing LLM never writes shard files by hand. Every write (Active / Shared / Archive) goes through scripts/handoff_cli.py, which handles frontmatter generation, schema validation, atomic writes, and path-escape protection:

python3 scripts/handoff_cli.py write   --root "$ROOT" --kind active --agent "Main" --body-file /tmp/private.md
python3 scripts/handoff_cli.py write   --root "$ROOT" --kind shared --agent "Main" --body-file /tmp/shared.md
python3 scripts/handoff_cli.py archive --root "$ROOT" --agent "Main" --session-id "20260726-topic" --slug "topic" --body-file /tmp/old.md

Reads are just file reads. This design came from hard experience: letting the LLM improvise storage writes (the v1.x Apple Notes era) produced duplicate notes and mangled content; deterministic scripts fixed it at the source. Atomic writes also keep cloud sync from uploading half-written files.

Content Routing

This session's output
    ↓
Does another agent need to know?
    ├── Yes → Shared shard
    └── No  → Private shard
  • Private: feature branches, environment-specific issues, this machine only
  • Shared: cross-agent project state, user decisions, environment sync status

Character Budget

Shards are kept compact to minimize token usage when injected at session start:

Shard Budget
Private 1500 chars
Shared 1000 chars
Total injected ~2500 chars

Why plain Markdown?

  • Git-diffable, greppable, no vendor lock-in
  • Works with any sync layer (iCloud, Syncthing, git) and browsable in Obsidian
  • Schema-validated frontmatter — corruption fails loudly
  • No AppleScript flakiness, no HTML round-trip mangling (Apple Notes used to shred CJK bold headers into fragments)
  • Cross-platform: the storage scripts are stdlib-only Python

Caveats

Archive creation uses hardlinks (os.link), which can fail on OneDrive placeholder files or non-NTFS volumes. Atomic replacement (os.replace) can raise PermissionError while a sync client locks the target, so OneDrive is conditionally supported. On Windows, setting PYTHONUTF8=1 system-wide is recommended. The 2>> diagnostic log redirect uses different encodings in Windows PowerShell 5.1 and PowerShell 7; the log is diagnostic-only.

Migrating from Apple Notes (v1.x)

The repo keeps the legacy tooling for a one-time export:

python3 scripts/export_notes_to_markdown.py --root ~/.agents/handoff --agent "Main" \
  --folder "Claude Workspace"

This converts your existing Private / Shared / Archive notes into the Markdown store. After exporting, stop writing to Apple Notes entirely — the old notes become a frozen archive.

License

MIT

About

A Claude Code skill for persistent session memory via Apple Notes — multi-agent, three-tier architecture

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages