| name | Evening Recap |
| description | End-of-day operational summary — what Aeon shipped, what failed, what needs follow-up |
| var | |
| tags | ["meta"] |
${var} — Optional date override (YYYY-MM-DD). If empty, recaps today (UTC).
Read memory/MEMORY.md for context and memory/issues/INDEX.md for open issues.
Goal
The recap is not a log dump — the operator can read the log themselves. Its job is to deliver a verdict on the shape of the day and surface the calls that need a human. Lead with a one-sentence TL;DR; cap headlines; demand a URL on every shipped item; and never print empty sections.
Steps
-
Determine the date.
TODAY=${var:-$(date -u +%Y-%m-%d)}
-
Read today's activity log. Open memory/logs/${TODAY}.md.
- Treat both
## and ### as skill-entry headers (existing logs use both styles — ### autoresearch, ## Changelog Skill). Capture each heading text as the skill name and the body until the next heading.
- If the file is missing or whitespace-only, mark
log=missing and continue to step 3 — silent failures may still need reporting before exiting.
-
Cross-check memory/cron-state.json for silent failures. Load it as JSON. For each skill present:
consecutive_failures ≥ 1 and last_status != "success" → silent failure (force into Blockers regardless of log content).
last_success date == TODAY but no log entry for that skill → "ran without logging" (low-severity Blocker).
- If the file is missing or unparseable, record
cron-state=unavailable and skip the cross-check (do not abort).
-
Deduplicate repeat runs. If the same skill appears N>1 times in the log, fold into one entry labeled skill ×N. Keep the most informative run's headline (the one with a PR/URL or the longest body); collapse the rest to +K more.
-
Extract every artifact link. For each entry, capture every URL or file path in the body (PR link, run URL, articles/... path, apps/dashboard/outputs/... path, ISS-NNN reference). An entry with no concrete artifact is "talk, not ship" — demote it to the Notable tier.
-
Score and tier each entry on leverage. What matters for tomorrow's decisions:
- Headlines (top tier, cap 5): new PR opened, change merged, new article shipped, issue resolved or newly filed, new failure pattern.
- Notable (mid tier, cap 5): routine successful runs, repeat outputs, expected cron firings, talk-not-ship entries.
- Skip: pure noise (heartbeat OK with nothing flagged, dedup-only runs, "no new items" reports). Collapse to a count for the footer.
-
Identify decisions for tomorrow. Re-scan the day for items that need a human call:
- Failing skills past their retry budget (cron-state
consecutive_failures ≥ 2).
- PRs awaiting merge for >24h (use
gh pr list --state open --json number,title,url,createdAt if gh is available; skip if not).
- Open issues from
memory/issues/INDEX.md mentioned in today's log without resolution.
- Conflicting outputs across skills.
List as concrete asks naming the target ("merge PR #N", "decide whether ISS-007 is wontfix"). If none, omit the section.
-
Write the TL;DR last. After steps 2–7, write one sentence that takes a stance on the shape of the day. Examples:
- "heavy ship day — 5 evolution PRs filed and 0 failures"
- "quiet — only crons fired, nothing shipped"
- "two regressions opened, one resolved; net negative"
- "first failure of
fetch-tweets in a week — investigate before tomorrow's run"
No hedging, no "today saw...", no "various activity occurred".
-
Compose and send the recap via ./notify.
*Evening Recap — ${TODAY}*
_TL;DR: <one-sentence verdict from step 8>_
*Headlines:*
- [skill] — [one-line outcome] · <URL>
- ...
*Notable:* (omit section if empty)
- [skill ×N] — [one-line]
- ...
*Decisions for tomorrow:* (omit if empty)
- [specific ask, named target]
*Blockers:* (omit if empty)
- [skill] — [error in ≤8 words] · <run URL if available>
_+M routine runs collapsed · sources: log=[ok|missing|empty] cron-state=[ok|unavailable]_
Hard rules:
- ≤2000 chars total.
- Every Headline bullet must include a URL. No URL → demote to Notable.
- TL;DR is mandatory and must take a stance.
- Never print "none" or "clean" — omit the section instead.
- Always include the source-health footer line so future-you can debug "why was this recap empty".
- Lead with shipped artifacts, not skills attempted.
- Empty-day exit: if
log=missing AND no silent failures AND no decisions, send a single line *Evening Recap — ${TODAY}*: quiet day, no activity recorded · sources: log=missing cron-state=ok and stop.
-
Log to memory. Append to memory/logs/${TODAY}.md (create the file if it didn't exist):
## Evening Recap
- Sent for ${TODAY}: H headlines, N notable, B blockers, D decisions queued, M collapsed
- TL;DR: <copy the one-sentence verdict>
- Sources: log=X cron-state=Y
Sandbox note
All inputs are local file reads (logs, issues index, cron-state). gh pr list runs through the GitHub CLI and is sandbox-friendly — if it fails, treat the source as unavailable and skip the PR-staleness check. ./notify writes to .pending-notify/ when outbound HTTP is blocked, so delivery is reliable.