Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions docs/hygiene-history/loop-tick-history.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 <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
Comment on lines +50 to +55
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
[`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
Expand Down
125 changes: 125 additions & 0 deletions docs/hygiene-history/session-snapshots.md
Original file line number Diff line number Diff line change
@@ -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.
Comment on lines +4 to +15
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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.

Copilot uses AI. Check for mistakes.

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: <session-boundary-slug> # 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: <git-sha>
branch: <current-branch>
repo: <owner>/<repo>
files:
claude_md_in_repo_sha: <sha>
claude_md_home_sha: <sha-or-empty>
agents_md_sha: <sha>
memory_index_sha: <sha>
memory_index_bytes: <int>
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
118 changes: 118 additions & 0 deletions tools/hygiene/capture-tick-snapshot.sh
Original file line number Diff line number Diff line change
@@ -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)
Comment on lines +4 to +10
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
#
# 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
Comment on lines +56 to +63
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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

Copilot uses AI. Check for mistakes.
}

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")
Comment on lines +67 to +70
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

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')
Copy link

Copilot AI Apr 24, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Suggested change
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')

Copilot uses AI. Check for mistakes.
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 <<JSON
{
"date_utc": "$date_utc",
"head_sha": "$head_sha",
"branch": "$branch",
"repo": "$repo_full",
"claude_cli_version": "$claude_version",
"claude_md_sha_in_repo": "$claude_md_sha",
"claude_md_sha_home": "$home_claude_md",
"agents_md_sha": "$agents_md_sha",
"memory_index_sha": "$memory_index_sha",
"memory_index_bytes": "$memory_index_bytes",
"model_snapshot": null,
"prompt_bundle_hash": null
}
JSON
else
cat <<YAML
# tick-snapshot captured $date_utc
snapshot:
date_utc: $date_utc
head_sha: $head_sha
branch: $branch
repo: $repo_full
claude_cli_version: $claude_version
files:
claude_md_in_repo_sha: $claude_md_sha
claude_md_home_sha: $home_claude_md
agents_md_sha: $agents_md_sha
memory_index_sha: $memory_index_sha
memory_index_bytes: $memory_index_bytes
# Agent fills these from session context:
model_snapshot: null # e.g., claude-opus-4-7
prompt_bundle_hash: null # null until a reconstruct-tool lands
YAML
fi
Loading