Skip to content

journal: record what we ran, what came out, and what we concluded - #41

Merged
taddyb merged 1 commit into
masterfrom
research-journal
Sep 11, 2026
Merged

taddyb merged 1 commit into
masterfrom
research-journal

Conversation

@taddyb

@taddyb taddyb commented Sep 11, 2026

Copy link
Copy Markdown
Owner

Adds an automatic research journal at docs/journal/. Independent of #40 — no Rust, no build, no CI gate, src/ and the invariants untouched.

The problem

89 runs and ~20 experiment bundles exist under .ddrs/. The reasoning behind almost all of them lives only in conversation history, which gets compacted away. CLAUDE.md already requires committing knowledge from a completed run; nothing enforced it and nothing made it cheap, so the repo has findings docs for the campaigns someone remembered to write up and nothing for the other 200 runs.

The design

One file per month, append-only, two parts:

  • Ledger — one row per completed run or experiment, smoke tests and shards included, so no run is lost. Machine-written.
  • Entries — Question / What we did / Result / Conclusion. Human-written.

The split is the whole design. scripts/journal.py scrapes everything a manifest knows and writes _TODO_ for everything it cannot:

 ddrs run / ddrs experiment finishes
            |
      scripts/journal.py
            |
   smoke test or shard? ──yes──> ledger row only
            |no
   ledger row + entry stub with 4 _TODO_ fields
            |
   docs/journal/2026-09.md   (git-tracked, append-only)

Three hooks in .claude/settings.json: PostToolUse on ddrs run/ddrs experiment writes the record; Stop blocks only on stubs the current session opened; SessionStart reports runs finished outside a session and writes nothing.

It never invents judgement

This is the load-bearing constraint, and it shaped three decisions:

  • --mode backfill writes ledger rows only. 103 of the 224 historical records would otherwise have opened a full stub, and nobody can reconstruct the hypothesis behind a run from two months ago. History gets provenance, not guesses.
  • Stop is scoped to the originating session. A session with no context on a run would be pressured into inventing a hypothesis.
  • The skill says so explicitly: an entry for a run whose purpose is unrecoverable writes unknown — run predates this entry. One confident guess and the record stops being evidence.

What is already in it

224 historical records backfilled, 121 correctly classified as smoke tests or shards:

Month Ledger rows
2026-06 19
2026-07 25
2026-08 13
2026-09 167

Plus one hand-written entry for the gridded DDM30 port, so the format ships with real content rather than a placeholder.

Safety

The hook runs on every Bash call in the repo, so it is built not to matter when it does not apply:

Property How
Cannot fail the command it observes Top-level except exits 0
Cheap when irrelevant Regex-gated; ~30 ms and silent otherwise
Cannot perturb a worktree Stdlib only, no git subprocess
Cannot loop stop_hook_active guard
Killable DDRS_JOURNAL_OFF=1

scripts/test_journal.py (stdlib, no deps, all passing) covers idempotency — three backfills leave the table unchanged — smoke classification, that judgement is never auto-written, that the Stop nag is session-scoped and cannot loop, and that a corrupt manifest does not take down the observed command. Also verified live against the real .ddrs, then cleaned up.

Known gaps, stated up front

  1. A hook only sees commands Claude runs. ddrs run typed in your own terminal produces no PostToolUse; it is caught at the next session start instead. Capture is eventual, not immediate.
  2. Long runs are backgrounded, so PostToolUse fires before the manifest exists. Handled by scanning for any finished-and-unjournaled run rather than only the one that command started.
  3. Experiment ledger rows are thin (N arm(s), status) because experiment manifests carry no scalar result; theirs live in figures/.

Review notes

🤖 Generated with Claude Code

https://claude.ai/code/session_01EqnrtmU91yzuVpGnADnXLP

89 runs and ~20 experiment bundles exist under .ddrs/, and the reasoning
behind almost all of them lived only in conversation history, which gets
compacted away. CLAUDE.md already required committing that knowledge; nothing
enforced it and nothing made it cheap.

docs/journal/YYYY-MM.md is append-only and has two parts: a ledger with one
row per completed run or experiment (smoke tests and shards included, so no
run is lost), and entries carrying four judgement fields -- Question, What we
did, Result, Conclusion.

The split is the design. scripts/journal.py scrapes everything a manifest
knows (metrics, git sha, dirty flag, epochs, gauge count, wall time) and
writes _TODO_ for everything it cannot know. It never invents a hypothesis,
and --mode backfill writes ledger rows only: the reasoning behind a run from
two months ago is not recoverable, and one confident guess would stop the
record being evidence. 224 historical records were backfilled that way.

Hooks in .claude/settings.json: PostToolUse on ddrs run / ddrs experiment
writes the record, Stop blocks only on stubs the current session opened (a
session with no context would be pressured into inventing one), SessionStart
reports runs finished outside a session and writes nothing. The script is
stdlib-only with no git subprocess, is regex-gated so unrelated Bash calls
return in ~30 ms, exits 0 on any internal error so it can never fail the
command it observes, and DDRS_JOURNAL_OFF=1 disables it.

No Rust, no build, no CI gate: src/, Cargo.toml and the invariants are
untouched. scripts/test_journal.py covers idempotency, smoke classification,
that judgement is never auto-written, and that the Stop nag is session-scoped
and cannot loop.

The one hand-written entry describes the gridded DDM30 port and links to a
spec that lands with PR #40; that link resolves when #40 merges.

Design, rejected alternatives, blast radius and known gaps:
docs/superpowers/specs/2026-09-10-research-journal-design.md

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EqnrtmU91yzuVpGnADnXLP
@taddyb
taddyb merged commit 98a3461 into master Sep 11, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant