diff --git a/docs/hygiene-history/loop-tick-history.md b/docs/hygiene-history/loop-tick-history.md index b779ed8e..4513692a 100644 --- a/docs/hygiene-history/loop-tick-history.md +++ b/docs/hygiene-history/loop-tick-history.md @@ -44,6 +44,23 @@ numbers. When auditing, rely on the UTC timestamp (the first column) as the canonical ordering key; `auto-loop-N` is a within-session sequence tag only. +### On snapshot pinning + +Per Amara's 4th-ferry absorb (PR #221) and Otto-70 +scaffolding (PR ): when a tick's action is +proxy-significant or settings-changing, the `notes` +column can include a brief snapshot fingerprint +(CLAUDE.md SHA, model snapshot). For session-level +state — model swap, compaction boundary, significant +memory migration — use the dedicated sidecar file +[`session-snapshots.md`](./session-snapshots.md) instead +of inline. Capture helper at +[`tools/hygiene/capture-tick-snapshot.sh`](../../tools/hygiene/capture-tick-snapshot.sh) +prints a YAML fragment. Snapshot pinning in tick-history +rows is **optional** — don't slow the autonomous-loop +tick-close for every fire; pin when the action warrants +audit. + ## Why this exists Aaron 2026-04-22: *"you might as well right a history record diff --git a/docs/hygiene-history/session-snapshots.md b/docs/hygiene-history/session-snapshots.md new file mode 100644 index 00000000..6e555a22 --- /dev/null +++ b/docs/hygiene-history/session-snapshots.md @@ -0,0 +1,125 @@ +# Session snapshots — Claude state pins + +Durable record of Claude session state at session-open or at +per-decision pin-time. Addresses Amara's 4th-ferry concern +(PR #221) that "Claude is not a single stable operator unless +the actual snapshot, system-prompt bundle, and loaded memory +surfaces are all pinned and recorded." + +## Why this file + +Across Claude model versions (3.5 → 3.7 → 4 → 4.x), the +system-prompt bundle + knowledge cutoff + memory-retention +language shift materially. When a future session, external +reviewer, or tuning pipeline asks *"what did Kenji actually +know when this decision was made?"* this file answers. + +Complements: + +- [`loop-tick-history.md`](./loop-tick-history.md) — what + happened each tick (action-summary) +- [`docs/decision-proxy-evidence/`](../decision-proxy-evidence/) + — per-decision evidence records with their own `model` + block (PR #222) + +This file is **session-level + daily**, not per-tick. A new +row lands on session-open and on significant session +events (mid-session model swap, major memory migration, +compaction boundary). Per-tick snapshots live inline in +tick-history row `notes` if load-bearing for that tick. + +## Capture helper + +[`tools/hygiene/capture-tick-snapshot.sh`](../../tools/hygiene/capture-tick-snapshot.sh) +prints a YAML fragment covering what's mechanically +accessible. Agent fills in `model_snapshot` + (eventually) +`prompt_bundle_hash` from session context. + +```bash +tools/hygiene/capture-tick-snapshot.sh # YAML +tools/hygiene/capture-tick-snapshot.sh --json +``` + +## Row format + +```yaml +- session_id: # e.g., "2026-04-23-otto-long-session" + captured_utc: 2026-04-24T00:00:00Z + event: session-open | mid-session-pin | session-close | compaction + agent: Otto # persona hat active; may change mid-session + model_snapshot: claude-opus-4-7 + claude_cli_version: "2.1.118 (Claude Code)" + head_sha: + branch: + repo: / + files: + claude_md_in_repo_sha: + claude_md_home_sha: + agents_md_sha: + memory_index_sha: + memory_index_bytes: + notes: > + Free-form context: session-boundary reason, model swap + rationale, compaction trigger, etc. + prompt_bundle_hash: null # populate once a reconstruct-tool lands +``` + +Append-only. Rows stay forever. + +## Seed entries + +- session_id: 2026-04-23-otto-long-session-start + captured_utc: 2026-04-24T00:28:28Z + event: mid-session-pin + agent: Otto + model_snapshot: claude-opus-4-7 + claude_cli_version: "2.1.118 (Claude Code)" + head_sha: 9752e475c2bb2624aaa1e8b85f1d917f23e21a9f + branch: stabilize/snapshot-pinning-tick-history-amara-action-2 + repo: Lucent-Financial-Group/Zeta + files: + claude_md_in_repo_sha: d774531bf284437bbc0bf68133651bf72e300e44 + claude_md_home_sha: "" + agents_md_sha: ea94fa680373715526ebb0d6ecdfbd31e25794ff + memory_index_sha: f2799a35808f79ccb924641aaa1a04db73163be3 + memory_index_bytes: 58842 + notes: > + First entry — captured at Otto-70 when snapshot-pinning + scaffolding landed. This session has been running long + (~70 Otto ticks); the actual session-open state is + earlier and was not captured at that time. Memory index + is currently 58842 bytes (over the FACTORY-HYGIENE row + #11 24976-byte cap — known separately-tracked drift). + prompt_bundle_hash: null + +## What this file is NOT + +- **Not per-tick** — the tick-history file covers that; + this file is session-level + significant-event. +- **Not retroactive for prior sessions** — the record + starts from when the capture tool landed. Prior sessions + are unreachable for this fidelity. +- **Not CI-gated** — append on discipline; enforcement + waits until the format stabilizes (Determinize-stage + per Amara roadmap). +- **Not complete** — `model_snapshot` + `prompt_bundle_hash` + are agent-filled / future-tool-filled respectively. + What's captured is the floor, not the ceiling. +- **Not a replacement for `memory/CURRENT-*.md`** — those + are running distillations; this file is point-in-time + pins for audit. + +## Composes with + +- FACTORY-HYGIENE row #44 (cadence-history-tracking) — + this file is the autonomous-loop's session-level + fire-log, sibling to the tick-level `loop-tick-history.md` +- `docs/hygiene-history/loop-tick-history.md` — per-tick + action-summary; this file is per-session-state +- `docs/decision-proxy-evidence/` — per-decision evidence + (PR #222); can cite rows here via `model.loaded_memory_ + files` + snapshot refs +- `docs/aurora/2026-04-23-amara-memory-drift-alignment- + claude-to-memories-drift.md` (PR #221) — Amara ferry + that named snapshot-pinning as Stabilize-stage action +- `tools/hygiene/capture-tick-snapshot.sh` — capture helper diff --git a/tools/hygiene/capture-tick-snapshot.sh b/tools/hygiene/capture-tick-snapshot.sh new file mode 100755 index 00000000..d953318c --- /dev/null +++ b/tools/hygiene/capture-tick-snapshot.sh @@ -0,0 +1,118 @@ +#!/usr/bin/env bash +# tools/hygiene/capture-tick-snapshot.sh +# +# Captures a snapshot pin of factory state at tick-open / +# tick-close time. Prints a YAML fragment that can be pasted +# into: +# - docs/hygiene-history/session-snapshots.md (session-level) +# - docs/decision-proxy-evidence/DP-NNN.yaml `model` block +# (decision-level) +# - a tick-history row's `notes` column (tick-level) +# +# Addresses Amara's 4th-ferry (PR #221 absorb) snapshot-pinning +# concern: "Claude is not a single stable operator unless the +# actual snapshot, system-prompt bundle, and loaded memory +# surfaces are all pinned and recorded". The pin is the +# mechanism that makes Claude's behavior reproducible after +# prompt / model updates ship. +# +# What the snapshot captures (mechanically accessible): +# +# - Claude Code CLI version (`claude --version`) +# - CLAUDE.md content SHA (in-repo + per-user home if present) +# - AGENTS.md content SHA +# - memory/MEMORY.md content SHA + byte count +# - Current git HEAD SHA + branch + repo name +# - Date UTC +# +# What the snapshot does NOT capture (agent must fill in): +# +# - Claude model snapshot (e.g., claude-opus-4-7) — known to +# the agent from session context, not exposed by CLI +# - Prompt bundle hash — not currently computable from +# session; placeholder null until a tool that reconstructs +# the system prompt bundle lands +# - Active permission / skill set — session-specific +# +# Usage: +# tools/hygiene/capture-tick-snapshot.sh # print YAML fragment +# tools/hygiene/capture-tick-snapshot.sh --json # print JSON +# +# Part of Amara Stabilize-stage (PR #221 roadmap); FACTORY- +# HYGIENE row for cadenced capture is a follow-up after +# format stabilizes. + +set -euo pipefail + +format="yaml" +if [[ "${1:-}" == "--json" ]]; then + format="json" +fi + +# Helpers — each returns empty string on failure rather than +# aborting under `set -euo pipefail`. +safe_sha() { + if [[ -f "$1" ]]; then + git hash-object "$1" 2>/dev/null || printf '' + fi +} + +safe_bytes() { + if [[ -f "$1" ]]; then + wc -c < "$1" 2>/dev/null | tr -d ' ' || printf '' + fi +} + +claude_version=$(claude --version 2>/dev/null | head -1 || printf 'unknown') +claude_md_sha=$(safe_sha "./CLAUDE.md") +agents_md_sha=$(safe_sha "./AGENTS.md") +memory_index_sha=$(safe_sha "memory/MEMORY.md") +memory_index_bytes=$(safe_bytes "memory/MEMORY.md") +head_sha=$(git rev-parse HEAD 2>/dev/null || printf 'unknown') +branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null || printf 'unknown') +repo_full=$(git config --get remote.origin.url 2>/dev/null | sed -E 's|.*[:/]([^/]+/[^/]+)(\.git)?$|\1|' || printf 'unknown') +date_utc=$(date -u '+%Y-%m-%dT%H:%M:%SZ') + +# Per-user CLAUDE.md (if present; path is harness-specific) +home_claude_md="" +if [[ -f "$HOME/.claude/CLAUDE.md" ]]; then + home_claude_md=$(git hash-object "$HOME/.claude/CLAUDE.md" 2>/dev/null || printf '') +fi + +if [[ "$format" == "json" ]]; then + cat <