ai

An AI journal that reads its own logs

This post is the first entry in the journal, and it exists because a skill I wrote scanned my own session logs and suggested itself as a candidate. I want to note the setup briefly, because the move — treating session logs as a first-class artefact the agent can mine — has turned out to be more useful than I expected.

The idea

I’ve been running Claude Code and Codex heavily for months. Every session leaves a .jsonl transcript on disk: ~/.claude/projects/<slug>/<session-id>.jsonl for Claude, ~/.codex/sessions/YYYY/MM/DD/rollout-*.jsonl for Codex. I already download these locally with agent-log so I can grep through them. The thought was: if I’m going to keep a public diary about AI, the diary shouldn’t be a blank page I stare at once a week. It should start from what I actually did.

So I wrote a Claude skill — /ai-journal — that:

  1. Reads a “last run” timestamp from a small state file.
  2. Scans both log trees for sessions modified since that cutoff.
  3. Reads the promising ones in full, groups related sessions, and returns a ranked list of candidate write-ups.
  4. Asks me which to draft, writes drafts into the site as org source files, and bumps the cutoff.

The first scan surfaced eight candidates from about 50 sessions. One of them was this skill. The loop closed neatly.

What made it work

A few small choices mattered more than I expected.

Separate mtime filtering from content timestamps. The scan script filters session files by file mtime (so it only opens files touched since the last run) but reports each session’s start time from the first user message in the transcript. This matters because Claude touches a lot of cached subagent files on every session startup, and those files contain old timestamps. If you filter only by mtime you get thousands of “sessions” from January; if you filter only by content timestamp you miss the ones that are actually active. Both filters together give you the right set.

Delegate triage to a subagent. Fifty sessions is already too many to read in the main context without drowning it. The skill spawns a subagent whose only job is to skim the TSV of session excerpts, sample the promising .jsonl files with offset / limit reads, and return 3–10 candidates in a fixed format. The main conversation never sees the raw transcripts; it only sees the shortlist.

Ground every claim in the transcript. The skill’s instructions are explicit that posts must not invent details. If I need something that isn’t in the session, the skill asks rather than fills in plausible-sounding text. This constraint is the difference between a diary and a content farm.

What I’d do differently

A few things I already want to revise after one run:

  • The first draft of the skill told the agent to write markdown directly into content/ai/. That was wrong: my site is ox-hugo-driven, content/ is gitignored, and the canonical source for every page is an org file under ~/My Drive/notes/. So the skill now writes posts as org files under ~/My Drive/notes/ai-journal/ with :EXPORT_HUGO_SECTION: ai set on the heading, and the normal notes-export pipeline picks them up. Worth remembering: when you build a skill that ends in “commit a file somewhere,” take one pass through the destination’s actual publishing pipeline before you write the instructions.
  • The scan script returns file mtime-matched sessions whose content timestamps are ancient (thousands of them, mostly subagent warmup stubs). The candidate triage subagent has to ignore them. A second-pass filter that drops sessions whose content start is more than, say, 24h before the cutoff would trim the list at source.

The meta-observation

What I notice from building this: local session logs are crucial because they turn “find the interesting thing I did last week” into an AI task. Without them, I’d be relying on memory, and the interesting things are precisely the ones I wouldn’t have the context to remember.