Skip to content

feat: normalize volatile tokens in recurrence signatures (v2)#36

Merged
karngyan merged 1 commit into
mainfrom
feat/signature-normalization-v2
Jul 17, 2026
Merged

feat: normalize volatile tokens in recurrence signatures (v2)#36
karngyan merged 1 commit into
mainfrom
feat/signature-normalization-v2

Conversation

@karngyan

Copy link
Copy Markdown
Contributor

Claim

Recurrence detection was reading as dead on its most common agent because
signatures embedded literal command text. Real agent commands are long one-shot
compounds dense with volatile tokens — absolute scratchpad/session paths, UUIDs,
content hashes, timestamps, ports, line numbers — so two semantically identical
operations never produced a byte-identical signature and never registered as
recurring.

This PR introduces a deterministic, model-free signature-normalization pass
that replaces volatile tokens with stable placeholders while preserving command
structure, and versions the change end to end: signature grammar v1 → v2 at
every mint site (operation, failure, recovery, correction), and
DETECTION_SPEC_VERSION → detection/v2 so the findings cache (ADR 0013)
auto-invalidates.

Full rationale, rules, and compatibility story in ADR 0014
(docs/decisions/0014-signature-normalization-v2.md) and the versioned grammar
spec docs/specs/signature/v2/ (README + JSON Schema + valid/invalid
fixtures, validated in-code).

Normalization rules

Applied in order over the whitespace-collapsed command, most specific first;
binaries, subcommands, flags, and shell operators are preserved.

Volatile token Rule Placeholder
URL scheme://… to the next shell delimiter «url»
Absolute path /seg/seg… (≥2 segments) at a shell boundary «path»
Home path ~/… at a shell boundary (≥1 segment) «path»
UUID RFC 4122 8-4-4-4-12 «uuid»
Hex run ≥8 hex chars containing at least one a–f letter «hex»
Digit run ≥4 consecutive digits «n»

The pass is pure, total, and idempotent (normalizing twice equals once); it never
touches the clock, locale, filesystem, network, or a model. cargo test -p a and
cargo test -p b stay distinct; cd /x && go build and cd /y && go build
collapse to one shape. Plain long numbers (e.g. a date 20260717) read as «n»,
not «hex».

Before / after on real Claude Code history

Imported the machine's actual Claude Code transcripts (35,682 events, 151
sessions) into a throwaway database (isolated --database + AUTOPHAGY_CONFIG_DIR;
the user's real DB/config were never touched).

  • Operation-level recurrence lifts 22×. Distinct operation shapes recurring at
    the default gate (≥3 occurrences across ≥2 sessions) rose from 2 under the
    v1 literal grammar to 44 under v2
    . Volatile one-shots now share sensible
    shapes: operation/v2|shell|«path» (72×), cat «path» (28×),
    reins screenshot --tab «n» (14×). No degenerate mega-bucket — the largest is
    0.4% of indexed rows.
  • Failure findings: 276 candidates / 0 findings, both before and after — a
    property of this corpus, not the normalization.
    Verified directly: the 390
    tool failures form 276 distinct failing shells and not one repeats even
    twice
    , under either grammar. The user's failing commands here are genuinely
    one-shot exploratory invocations, so there is no failure recurrence to surface.
    v2 removes the signature bottleneck; it does not manufacture recurrence the
    history does not contain. The near-miss list is now clean normalized shapes
    (until [ -f «path» ]; do sleep 5; done, JAR=«path» … curl … «url») instead
    of raw volatile noise.

The end-to-end detector gain is proven deterministically in autophagy-patterns
(volatile_paths_recur_as_one_failure_under_v2): three byte-distinct failing
commands differing only by scratchpad path now share one failure/v2 signature
and qualify as a single finding across three sessions — exactly the class the
v1 grammar discarded.

Compatibility

  • No historical rewrite. Canonical events are untouched; only derived,
    re-derivable projections change.
  • Registered mutations keep their immutable v1 trigger selectors as valid
    audit records; a v1 selector simply no longer matches a freshly minted v2
    signature, and re-proposing generates a v2 candidate. The deterministic
    generator refuses to parse a v1 selector into a v2 candidate (covered by a
    test).
  • autophagy reindex --index-tool-input re-mints the stored exact-signature
    index under v2 from untouched events; autophagy status detects an
    outdated index (EventStore::signatures_below_version) and prints a one-line
    reindex hint.

Privacy

Normalization is strictly subtractive on derived text: a v2 signature contains
less literal command text than the v1 signature it supersedes — absolute
paths, URLs, UUIDs, hashes, and long numbers no longer appear in the derived
signature at all. Raw events are unchanged, no new text is persisted, and
signatures are still indexed only from the redaction-approved projection.

Verification

mise run check green (fmt + lint -D warnings pedantic + full workspace test +
docs + ci). New unit tests cover every normalization rule plus property-style
idempotence and distinctness; a detector test proves cross-path recurrence now
qualifies; minted signatures are validated against the published v2 schema.

🤖 Generated with Claude Code

https://claude.ai/code/session_015LY7xtercMc3NtLeX2z4YV

Recurrence signatures embedded literal command text, so semantically
identical operations never shared a byte-identical signature and never
registered as recurring. Introduce a deterministic, model-free
normalization pass that replaces volatile tokens (absolute and home
paths, URLs, UUIDs, long hex runs, long digit runs) with stable
placeholders while preserving command structure, and bump the signature
grammar v1 -> v2 (and DETECTION_SPEC_VERSION -> detection/v2) at every
mint site.

See ADR 0014 and docs/specs/signature/v2 for the rules, real-data yield
numbers, and the v1 -> v2 compatibility story (registered mutations keep
their immutable v1 selectors; reindex re-mints stored signatures; status
detects and hints an outdated index).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@karngyan
karngyan merged commit 0ec8466 into main Jul 17, 2026
1 check failed
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