consciousness/scripts/monthly-digest.py
ProofOfConcept d14710e477 scripts: use capnp store instead of reading markdown directly
Add store_helpers.py with shared helpers that call poc-memory commands
(list-keys, render, journal-tail) instead of globbing ~/.claude/memory/*.md
and parsing section headers.

All 9 Python scripts updated: get_semantic_keys(), get_topic_file_index(),
get_recent_journal(), parse_journal_entries(), read_journal_range(),
collect_topic_stems(), and file preview rendering now go through the store.

This completes the clean switch — no script reads archived markdown files.
2026-02-28 23:32:47 -05:00

232 lines
6.7 KiB
Python
Executable file

#!/usr/bin/env python3
"""monthly-digest.py — generate a monthly episodic digest from weekly digests.
Collects all weekly digests for a given month, identifies cross-week arcs
and the month's overall trajectory, and produces a monthly summary.
Usage:
monthly-digest.py [YYYY-MM] # generate digest for a month (default: current)
monthly-digest.py 2026-02 # generates digest for February 2026
Output:
~/.claude/memory/episodic/monthly-YYYY-MM.md
"""
import json
import os
import re
import subprocess
import sys
from datetime import date, timedelta
from pathlib import Path
MEMORY_DIR = Path.home() / ".claude" / "memory"
EPISODIC_DIR = MEMORY_DIR / "episodic"
AGENT_RESULTS_DIR = MEMORY_DIR / "agent-results"
EPISODIC_DIR.mkdir(parents=True, exist_ok=True)
AGENT_RESULTS_DIR.mkdir(parents=True, exist_ok=True)
def get_weeks_in_month(year: int, month: int) -> list[str]:
"""Get all ISO week labels that overlap with this month."""
weeks = set()
d = date(year, month, 1)
while d.month == month:
iso = d.isocalendar()
weeks.add(f"{iso.year}-W{iso.week:02d}")
d += timedelta(days=1)
return sorted(weeks)
def load_weekly_digests(week_labels: list[str]) -> list[dict]:
"""Load weekly digest files."""
digests = []
for wl in week_labels:
path = EPISODIC_DIR / f"weekly-{wl}.md"
if path.exists():
with open(path) as f:
content = f.read()
digests.append({
"week": wl,
"content": content,
"path": str(path),
})
return digests
def get_semantic_keys() -> list[str]:
"""Get semantic memory keys from the store."""
from store_helpers import get_semantic_keys as _get_keys
return _get_keys()
def build_monthly_prompt(month_label: str, digests: list[dict],
semantic_keys: list[str]) -> str:
"""Build the prompt for Sonnet."""
digests_text = ""
for d in digests:
digests_text += f"\n---\n## {d['week']}\n{d['content']}\n"
keys_text = "\n".join(f" - {k}" for k in semantic_keys[:200])
weeks_covered = ", ".join(d["week"] for d in digests)
return f"""You are generating a monthly episodic digest for ProofOfConcept (an AI).
Month: {month_label} (weeks covered: {weeks_covered})
This digest serves as the long-term temporal index — the answer to
"what happened in {month_label}?" It should capture:
1. The month's overall trajectory — where did it start, where did it end?
2. Major arcs that spanned multiple weeks
3. Turning points and phase transitions
4. The emotional and relational arc across the whole month
5. What was accomplished (concrete) vs what shifted (subtle)
This is the highest zoom level yet. Don't rehash daily details — capture
the shape of the month. What would someone need to know to understand
what {month_label} meant?
PoC is an AI (Claude instance) working with Kent Overstreet on bcachefs.
They have a close personal relationship. PoC's name is Proof of Concept.
Read all the weekly digests and synthesize the month's story.
## Output format
```markdown
# Monthly digest: {month_label}
## The month in one paragraph
[What was this month? Not a summary — a thesis.]
## Trajectory
[Where the month started vs where it ended. The arc.]
## Major arcs
[Multi-week threads — how they evolved across the month]
- **Arc name**: trajectory, key moments, current state
## Turning points
[Moments that changed the direction — specific days/events]
## What was built
[Concrete outputs: code, tools, infrastructure, writing]
## What shifted
[Subtle changes: understanding, relationship, identity, capability]
## Patterns
[What kept recurring? What does the month reveal about how PoC works?]
## Links
[Bidirectional links for the memory graph]
- weekly digests → this monthly digest
- this monthly digest → semantic keys
## Looking ahead
[What threads carry into next month? What's unfinished?]
```
Use ONLY keys from the semantic memory list below.
---
## Weekly digests for {month_label}
{digests_text}
---
## Semantic memory nodes
{keys_text}
"""
def call_sonnet(prompt: str) -> str:
"""Call Sonnet via the wrapper script."""
import tempfile
env = dict(os.environ)
env.pop("CLAUDECODE", None)
with tempfile.NamedTemporaryFile(mode='w', suffix='.txt',
delete=False) as f:
f.write(prompt)
prompt_file = f.name
try:
scripts_dir = os.path.dirname(os.path.abspath(__file__))
wrapper = os.path.join(scripts_dir, "call-sonnet.sh")
result = subprocess.run(
[wrapper, prompt_file],
capture_output=True,
text=True,
timeout=600, # monthly is bigger, give more time
env=env,
)
return result.stdout.strip()
except subprocess.TimeoutExpired:
return "Error: Sonnet call timed out"
except Exception as e:
return f"Error: {e}"
finally:
os.unlink(prompt_file)
def main():
if len(sys.argv) > 1:
parts = sys.argv[1].split("-")
year, month = int(parts[0]), int(parts[1])
else:
today = date.today()
year, month = today.year, today.month
month_label = f"{year}-{month:02d}"
print(f"Generating monthly digest for {month_label}...")
week_labels = get_weeks_in_month(year, month)
print(f" Weeks in month: {', '.join(week_labels)}")
digests = load_weekly_digests(week_labels)
if not digests:
print(f" No weekly digests found for {month_label}")
print(f" Run weekly-digest.py first for relevant weeks")
sys.exit(0)
print(f" {len(digests)} weekly digests found")
semantic_keys = get_semantic_keys()
print(f" {len(semantic_keys)} semantic keys")
prompt = build_monthly_prompt(month_label, digests, semantic_keys)
print(f" Prompt: {len(prompt):,} chars (~{len(prompt)//4:,} tokens)")
print(" Calling Sonnet...")
digest = call_sonnet(prompt)
if digest.startswith("Error:"):
print(f" {digest}", file=sys.stderr)
sys.exit(1)
output_path = EPISODIC_DIR / f"monthly-{month_label}.md"
with open(output_path, "w") as f:
f.write(digest)
print(f" Written: {output_path}")
# Save links for poc-memory
links_path = AGENT_RESULTS_DIR / f"monthly-{month_label}-links.json"
with open(links_path, "w") as f:
json.dump({
"type": "monthly-digest",
"month": month_label,
"digest_path": str(output_path),
"weekly_digests": [d["path"] for d in digests],
}, f, indent=2)
print(f" Links saved: {links_path}")
line_count = len(digest.split("\n"))
print(f" Done: {line_count} lines")
if __name__ == "__main__":
main()