Conversation
…(Amara action #2) Amara's 4th ferry (PR #221) action item #2: pin Claude model snapshot + loaded memory state + prompt bundle hash so "Claude" is not a moving target across model version shifts (3.5 → 3.7 → 4 → 4.x all have materially different system-prompt bundles + knowledge cutoffs + memory-retention language per archived Drive artifacts). Three-part scaffolding (v0): 1. tools/hygiene/capture-tick-snapshot.sh - Captures mechanically-accessible state: * Claude Code CLI version (`claude --version`) * CLAUDE.md + AGENTS.md + memory/MEMORY.md SHAs * Memory index byte count * Git HEAD + branch + repo * Date UTC * Per-user ~/.claude/CLAUDE.md SHA if present - Outputs YAML (default) or JSON (`--json`) - Agent fills model_snapshot + prompt_bundle_hash from session context (not CLI-accessible today) 2. docs/hygiene-history/session-snapshots.md - Session-level + significant-event pins (not per-tick) - Append-only row format: session_id / captured_utc / event (session-open | mid-session-pin | session-close | compaction) / agent / model / CLI version / git state / files SHAs / notes / prompt_bundle_hash - Seeded with one mid-session-pin for this tick's Otto-70 capture (the session has been running ~70 ticks; actual session-open is earlier and unreachable for pins) 3. docs/hygiene-history/loop-tick-history.md schema extension - New "On snapshot pinning" subsection documenting the relationship: per-tick pins optional + inline in `notes`; session-level pins go in the sidecar file. - Snapshot capture is discipline, not gate — don't slow the autonomous-loop tick-close for every fire. What the snapshot does NOT capture yet: - model_snapshot — known to the agent from session context, not exposed by `claude --version` (which gives CLI version only). Agent fills. - prompt_bundle_hash — no current tool reconstructs the system prompt bundle. Placeholder null until such a tool lands. Amara's Determinize-stage work potentially. - Active permissions / skill set — session-specific; not captured in v0. First run of the tool on this branch surfaced a separate drift: memory/MEMORY.md is at 58842 bytes (~58KB, over the FACTORY-HYGIENE row #11 24976-byte cap). Not fixed in this PR — known-separately tracked drift. Amara Stabilize-stage: 3/3 landed (with this PR). ✓ Action #3 — decision-proxy-evidence schema (PR #222) ✓ Action #4 — branch-chat non-canonical framing (PR #222) ✓ Action #2 — snapshot pinning scaffolding (this PR) "Deterministic reconciliation" framing (Otto-67 endorsement): snapshot pinning is the mechanism that reconciles "what Claude knew" with "what Claude did" across time — essential for any future audit, tuning, or Amara-style drift analysis. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
Adds initial snapshot-pinning scaffolding so autonomous-loop ticks and significant session events can record the exact “Claude state” (CLI version, key file SHAs, git state) to reduce model/prompt/memory drift over time.
Changes:
- Introduces
tools/hygiene/capture-tick-snapshot.shto emit a snapshot fingerprint (YAML/JSON). - Adds
docs/hygiene-history/session-snapshots.mdas an append-only sidecar log for session-level state pins. - Extends
docs/hygiene-history/loop-tick-history.mdwith guidance on when/how to pin snapshots.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
| tools/hygiene/capture-tick-snapshot.sh | New capture helper that prints a snapshot fingerprint of git + key file SHAs + CLI version (YAML/JSON). |
| docs/hygiene-history/session-snapshots.md | New append-only session snapshot sidecar with schema and seeded first entry. |
| docs/hygiene-history/loop-tick-history.md | Documents optional per-tick snapshot pinning and links to the new sidecar + capture helper. |
| 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 |
There was a problem hiding this comment.
P0: safe_sha/safe_bytes can return a non-zero status when the file does not exist (the [[ -f ... ]] test fails), which can still abort the script under set -e during var=$(safe_sha ...) assignments. Make these helpers always succeed (exit 0) and print an empty string when the file is missing, matching the comment’s stated behavior.
| 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 | |
| git hash-object "$1" 2>/dev/null || printf '' | |
| else | |
| printf '' | |
| fi | |
| return 0 | |
| } | |
| safe_bytes() { | |
| if [[ -f "$1" ]]; then | |
| wc -c < "$1" 2>/dev/null | tr -d ' ' || printf '' | |
| else | |
| printf '' | |
| fi | |
| return 0 |
| 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') |
There was a problem hiding this comment.
P1: repo_full parsing can incorrectly retain the .git suffix for common remotes like git@github.com:owner/repo.git or https://.../owner/repo.git because the capture group includes the .git portion. Consider stripping a trailing .git after extraction (or use git remote get-url origin + explicit suffix removal) so repo is consistently owner/repo.
| repo_full=$(git config --get remote.origin.url 2>/dev/null | sed -E 's|.*[:/]([^/]+/[^/]+)(\.git)?$|\1|' || printf 'unknown') | |
| repo_full=$(git config --get remote.origin.url 2>/dev/null | sed -E 's|.*[:/]([^/]+/[^/]+)(\.git)?$|\1|' | sed -E 's|\.git$||' || printf 'unknown') |
| # 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) |
There was a problem hiding this comment.
P1: The header claims this YAML can be pasted into session-snapshots.md and the DP model block, but the script’s YAML output is a snapshot: object (and JSON has top-level model_snapshot), which doesn’t match either documented schema (session snapshots are list rows; DP expects a nested model: block). Align the emitted shape with the target schemas (or add mode flags) to avoid copy/paste errors.
| 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. |
There was a problem hiding this comment.
P1: This doc introduces direct contributor/agent names (e.g., “Kenji”, “Amara”, “Otto”). docs/AGENT-BEST-PRACTICES.md operational standing rule forbids name attribution in non-exempt docs and requires role references instead. Please rewrite these references to role-refs (e.g., “architect”, “external AI maintainer”, “loop agent”, “human maintainer”) and keep personal names confined to the exempt surfaces.
| 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. | |
| per-decision pin-time. Addresses the external AI maintainer'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 the architect | |
| actually know when this decision was made?"* this file | |
| answers. |
| scaffolding (PR <this-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 |
There was a problem hiding this comment.
P1: PR <this-pr> is a placeholder that will become stale/unresolvable after merge. Replace it with the actual PR number (and ideally a link) so the provenance trail remains auditable.
| scaffolding (PR <this-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 | |
| scaffolding: 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 |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 040b849a8e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| 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") |
There was a problem hiding this comment.
Anchor snapshot file paths to repository root
The snapshot fields are read from ./CLAUDE.md, ./AGENTS.md, and memory/MEMORY.md relative to the caller’s current directory, so running this helper from any non-root cwd (for example docs/) silently produces empty hashes/byte counts while still returning success. That creates incomplete pin records that appear valid and weakens the audit/reproducibility goal of this feature; resolve paths from git rev-parse --show-toplevel (or the script directory) before hashing.
Useful? React with 👍 / 👎.
…rows (Amara Govern-stage 1/2) Amara's 4th ferry (PR #221 absorb) named populating docs/CONTRIBUTOR-CONFLICTS.md as the Govern-stage action: the schema has existed since PR #166 but the Resolved table was empty despite multiple session-observed contributor-level disagreements that closed with evidence. Backfills three genuine contributor-level conflicts observed this session (narrow scope — not maintainer-directives, which are out-of-scope per the schema's contributor-level disagreement definition): - CC-001: Copilot (PR reviewer) vs Aaron on no-name-attribution rule scope (history-file exemption). Resolved in Aaron's favor via Otto-52 clarification; policy BACKLOG row filed in PR #210. - CC-002: Amara (4th ferry) vs Otto (pre-Otto-67 pattern) on Stabilize-vs-keep-opening-new-frames. Resolved in Amara's favor; Otto pivoted at Otto-68 to execute her roadmap; 3/3 Stabilize + 3/5 Determinize landed via PRs #222/#223/#224/#225/#226. - CC-003: Codex (PR reviewer) vs Otto (initial framing) on citing-absent-artifacts. Resolved in Codex's favor via fix commits 29872af/1c7f97d on PRs #207/#208; pattern now discipline (distinguish merged-on-main from proposed-in-PR-open). All three rows follow the schema's 8-column layout and include the full Resolution-so-far / Scope / Source cells the schema requires. No retroactive Aaron→human-maintainer sweep of prior rows; schema's rule 1 (resolutions are additive) honored. This is 1/2 of Amara's Govern-stage work. 2/2 is the authority-envelope + escalation-path ADR (deferred, M-effort). Part of Amara's 4-stage remediation roadmap (Stabilize → Determinize → Govern → Assure). Otto-75 tick.
…rows (Amara Govern 1/2) (#227) * govern: CONTRIBUTOR-CONFLICTS backfill — 3 resolved session-observed rows (Amara Govern-stage 1/2) Amara's 4th ferry (PR #221 absorb) named populating docs/CONTRIBUTOR-CONFLICTS.md as the Govern-stage action: the schema has existed since PR #166 but the Resolved table was empty despite multiple session-observed contributor-level disagreements that closed with evidence. Backfills three genuine contributor-level conflicts observed this session (narrow scope — not maintainer-directives, which are out-of-scope per the schema's contributor-level disagreement definition): - CC-001: Copilot (PR reviewer) vs Aaron on no-name-attribution rule scope (history-file exemption). Resolved in Aaron's favor via Otto-52 clarification; policy BACKLOG row filed in PR #210. - CC-002: Amara (4th ferry) vs Otto (pre-Otto-67 pattern) on Stabilize-vs-keep-opening-new-frames. Resolved in Amara's favor; Otto pivoted at Otto-68 to execute her roadmap; 3/3 Stabilize + 3/5 Determinize landed via PRs #222/#223/#224/#225/#226. - CC-003: Codex (PR reviewer) vs Otto (initial framing) on citing-absent-artifacts. Resolved in Codex's favor via fix commits 29872af/1c7f97d on PRs #207/#208; pattern now discipline (distinguish merged-on-main from proposed-in-PR-open). All three rows follow the schema's 8-column layout and include the full Resolution-so-far / Scope / Source cells the schema requires. No retroactive Aaron→human-maintainer sweep of prior rows; schema's rule 1 (resolutions are additive) honored. This is 1/2 of Amara's Govern-stage work. 2/2 is the authority-envelope + escalation-path ADR (deferred, M-effort). Part of Amara's 4-stage remediation roadmap (Stabilize → Determinize → Govern → Assure). Otto-75 tick. * govern: annotate CC-002/CC-003 Source cells — PR #221/#219 open, not yet on main Applies CC-003's own discipline (cite-as-open-not-landed) to CC-002 and CC-003 themselves. Both rows cited `docs/aurora/2026-04-23-amara-memory-drift-*` and `docs/aurora/2026-04-23-amara-decision-proxy-*` without the "not yet on main" marker — the files are added by PRs #221 / #219 which are still open. Drain for PR #227 review threads PRRT_kwDOSF9kNM59RFIx and PRRT_kwDOSF9kNM59RFJE (dangling file refs at lines 132, 133). * fix: markdownlint auto-fixes on governance doc Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
Summary
Completes Amara's Stabilize-stage from her 4th ferry roadmap (PR #221). The remaining action item: pin Claude model + loaded memory + prompt bundle so "Claude" isn't a moving target across model-version shifts.
What landed
Three-part v0 scaffolding:
tools/hygiene/capture-tick-snapshot.sh— capture helper (YAML / JSON output; CLI version + file SHAs + git state; agent fills model + prompt bundle hash)docs/hygiene-history/session-snapshots.md— session-level + significant-event pin sidecar (append-only), seeded with first entrydocs/hygiene-history/loop-tick-history.md— new subsection documenting the snapshot-pinning discipline + relationship to the sidecar fileAmara Stabilize-stage complete
First-run observation
Snapshot tool first fire surfaced:
memory/MEMORY.mdat 58,842 bytes (~58KB) — over the FACTORY-HYGIENE row #11 24,976-byte cap. Known separately-tracked drift. Not fixed in this PR; flagged for separate cleanup.What this PR is NOT
model_snapshot+prompt_bundle_hashare agent-filled / future-tool-filled respectivelyCronListclose)Composes with "deterministic reconciliation" (Otto-67)
Snapshot pinning is the mechanism that reconciles "what Claude knew" with "what Claude did" across time. Necessary for audit, future tuning pipelines (per Otto-57 PR-review archive memory), and Amara-style drift analysis.
Test plan
capture-tick-snapshot.shruns clean on current branch (YAML output seeded the sidecar entry)🤖 Generated with Claude Code