↑ subaud · local-first · MIT

Format

The casebook repository, file by file.

casebook-data is a private git repository written only by the casebook binary. History is linear: no merge commits, no branches. This is format version 2, the version casebook 0.2.0 and later write.

the root

Top-level files.

pathcontent
casebook.tomlformat_version = 2. A binary refuses a repo with a newer version, and upgrades an older one in place with one commit.
policy.tomlAttention thresholds in days, shared by every machine: outgoing_pr_stale_days (14), incoming_no_reply_days (7), unpushed_days (3), repo_dormant_days (365). Unknown keys are errors.
README.md, CONTRIBUTIONS.md, MACHINES.mdViews rendered at each sync. On a conflict the upstream copy wins and the next sync renders them again.

The policies are: an open outgoing pull request with no activity past its threshold (outgoing-stale); an undecided incoming pull request or issue whose latest activity is not your reply (incoming-no-reply); an undecided, unarchived repo not pushed for a year (dormant); and a branch or worktree with commits on no remote (unpushed). An item decided ignore is never flagged.

decisions

items/: one file per decision.

Owner and name are lower case; branch names and paths keep their case and are path-escaped in the file name.

kindkeyfile
reporepo:<owner>/<name>items/repo/<owner>/<name>.toml
prpr:<owner>/<name>#<n>items/pr/<owner>/<name>/<n>.toml
issueissue:<owner>/<name>#<n>items/issue/<owner>/<name>/<n>.toml
branchbranch:<owner>/<name>@<branch>items/branch/<owner>/<name>/<escaped branch>.toml
worktreeworktree:<machine>:<abs path>items/worktree/<machine>/<escaped path>.toml
disposition = "watch"                                  # keep archive close delete merge wait watch ignore
note        = "retire the fork when this lands"        # optional
until       = "merged(pr:elidickinson/pi-claude-bridge#97)"  # optional; required for wait and watch
decided_by  = "you"                                    # your login, claude:<session>, pi:<session>
decided_at  = 2026-09-24T10:12:00Z                     # UTC, second precision
proposed_by = "pi:<session>"                           # optional: who proposed it (a session or rule:<id>)
rule        = "<rule id>"                              # optional: the standing rule that proposed it

[conflict]                                             # only after a decision race
disposition = "archive"
decided_by  = "you"
decided_at  = 2026-09-24T10:11:00Z

Each decision is one commit, decide <key> → <disposition>. casebook validate checks every file. The dispositions each kind allows, and the until forms, are on the rules page.

Races. When two machines decide the same item between syncs, the later decided_at wins. The other is kept under [conflict], and the item's status is conflict until someone decides again.

journal

journal/<machine>/<YYYY>/<MM-DD>.jsonl

One JSON object per line, for every git and gh action the hooks and harnesses saw. Each machine writes only its own directory, so journals never conflict.

  • v, ts, and src: git-hook, claude or pi.
  • For git hooks: hook, args and stdin (whitespace-split lines, at most 200, with truncated when cut).
  • cwd, git_dir, repo, machine, exit_code.
  • claude_id and agent_id, the raw harness session ids, and child when the process belongs to the agent session.
  • actions: tool, verb, dir, repo, number, refs, flags.

Never stored: command lines, commit or tag messages, titles, bodies, field values, header values, query strings, or credentials in URLs.

snapshots

machines/<machine>.json

This machine's clones under its roots. The file is deterministic and holds no timestamps of its own, so an unchanged machine makes no commit.

  • Per clone: its path, its GitHub repo, its remotes (non-GitHub URLs redacted), whether it is bare or dirty, its stash count, its local core.hooksPath, and whether casebook's shims are adopted there.
  • Per branch: upstream, ahead count, unpushed count and the oldest unpushed commit's time, tip and tip time, the upstream's tip, and the landed verdict: landed_state (yes, no, unknown), with landed (in <default> or merged #<n>), landed_tip and landed_how when it is yes.
  • Per linked worktree: path, branch, head, detached, dirty.

rules and restores

rules/ and restores/

rules/<id>.toml holds one standing rule each; the rules page documents the file. Commits read rule <id> → active by <who>, rule <id> edited by <who> and rule <id> deactivated by <who>.

restores/<YYYY-MM-DD>.tsv is appended one line at a time, immediately before each destructive step, under the header key, action, before, restore-command, separated by tabs. Each line is its own commit, restore record for <key> (<action>). A tab or newline in any field is refused, and the step fails before it checks anything. The apply page lists the restore commands.

not in the repo

What stays on the machine.

The GitHub observation cache (github.json), the drift record (seen.json), the event spool and the hook shims are local to each machine. So is casebook serve's working state in ~/.local/state/casebook/casebook.db: agent sessions, threads, messages and deliveries, proposals, evidence, progress, jobs and the live event log.

That database records collaboration, never intent. A proposal becomes intent only when you accept it, as a decision file with proposed_by.

Versions

  • 1 (casebook 0.1.x): decisions, journal, snapshots, views.
  • 2 (casebook 0.2.0): decisions gain the optional proposed_by and rule. rules/ and restores/ are new directories that older binaries ignore. The upgrade rewrites only casebook.toml.