mirror of
https://github.com/alirezarezvani/claude-skills.git
synced 2026-08-28 04:24:58 +00:00
Turns DESIGN.md from a spec into a working plugin. Five stdlib scripts, three
hooks, agent, command, three references, plugin manifests.
The gates are the design:
L1 -> L2 >= 3 distinct sessions spanning >= 2 distinct calendar days
(`stated` = 2 sessions, day rule still applies; `verified` = 1
observation and is the only day-exempt path)
L2 -> L3 >= 2 distinct projects, >= 30 days, uncontested
Two gates refuse rather than guess. `redacted: true` blocks promotion on any
volume of evidence -- a durability-independent barrier, since a secret restated
across five sessions passes every recurrence gate; the flag firing means the
text was altered, a lexical filter finding one secret is not proof it found all
of them, and L2/L3 are committed to git. An open contradiction freezes both
claims, found by reverse join because the newer atom carries no flag.
All three hooks fail open: a broken memory system costs memory, never a session.
SessionEnd stages promotions to .memory/staged/ and never touches a CLAUDE.md;
only an explicit human adopt does, after backing both files up.
Verified, not asserted:
- all three pinned atom ids from DESIGN.md reproduce exactly
- both blocking gates demonstrated on sample input, named in the output
- end-to-end: two transcripts across two calendar days -> merged L1 atom ->
staged L2 promotion with the path prefix stripped
- reverse join blocks the unflagged newer atom
- cross-tier L2/L3 collision marked at injection time
- recall p50 29ms / p95 31ms / max 35ms spawn-to-exit, scoring itself 2-3ms
over 500 atoms -- interpreter cold start is the entire cost
- validate_examples.py 69 checks 0 failures; SKILL.md 6/6 PASS
- derive_counters --check, check_plugin_json --all, check_paths all clean
DESIGN.md 10.1's "+6" tool estimate corrected to +8 -- the delivered surface is
5 scripts + 3 hooks. README.md's deviations list is authoritative for that and
five other divergences from the pre-implementation spec.
Concept from TencentCloud/TencentDB-Agent-Memory (MIT). No upstream code.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EM5xmJ7AmTMg31rq68BCym
83 lines
2.9 KiB
Python
83 lines
2.9 KiB
Python
#!/usr/bin/env python3
|
|
"""SessionEnd -- capture L0 -> L1, detect contradictions, stage promotions.
|
|
|
|
DESIGN.md 5.3. Runs `async: true` so it can never delay session teardown.
|
|
|
|
The pipeline, in order, because the order is the safety property:
|
|
|
|
1. extract rule-based, high-precision markers only (9.2 option (a))
|
|
2. REDACT every atom, before anything is written (6 rule 1)
|
|
3. merge increment observations, extend sessions, raise confidence to
|
|
the max -- never lower (4.1.3)
|
|
4. detect 4.2.1's two rules; mark the OLDER atom contested
|
|
5. write atomic os.replace under a 5s-bounded lock (5.4); on
|
|
contention the atoms are DROPPED and the loss is logged to
|
|
.memory/errors.log -- not stderr, which for an async hook
|
|
goes nowhere a human reads
|
|
6. promote L1->L2->L3 on recurrence, staged to .memory/staged/
|
|
|
|
Step 6 NEVER writes CLAUDE.md. Adoption is a separate, explicit, human step
|
|
(`/cs:memory adopt`), which backs both CLAUDE.md files up first.
|
|
|
|
A missing .memory/atoms.jsonl is the normal first-run state, not an error.
|
|
Disable with AGENT_MEMORY_SESSIONEND=0.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
import os
|
|
import sys
|
|
|
|
sys.path.insert(0, os.path.join(
|
|
os.path.dirname(os.path.dirname(os.path.abspath(__file__))),
|
|
"skills", "agent-memory", "scripts"))
|
|
|
|
|
|
def main():
|
|
if os.environ.get("AGENT_MEMORY_SESSIONEND") == "0":
|
|
return 0
|
|
try:
|
|
raw = "" if sys.stdin.isatty() else sys.stdin.read()
|
|
payload = json.loads(raw) if raw.strip() else {}
|
|
except Exception:
|
|
return 0
|
|
|
|
transcript = payload.get("transcript_path")
|
|
if not transcript or not os.path.exists(transcript):
|
|
return 0
|
|
|
|
try:
|
|
import memory_core as core
|
|
import memory_extract as extract
|
|
import memory_promote as promote
|
|
|
|
cwd = payload.get("cwd") or os.getcwd()
|
|
project = os.path.basename(os.path.abspath(cwd))
|
|
session = payload.get("session_id") or "unknown-session"
|
|
store = core.AtomStore(os.path.join(cwd, ".memory"))
|
|
|
|
new_atoms = extract.extract(transcript, project, session)
|
|
if not new_atoms:
|
|
return 0
|
|
|
|
atoms, _n_new, _n_merged = extract.merge_into_store(store, new_atoms)
|
|
core.mark_contradictions(atoms)
|
|
|
|
if not store.write(atoms):
|
|
# 5.4 -- the one place data disappears. It is logged inside write().
|
|
return 0
|
|
|
|
l2, _blocked = promote.promote_l1_to_l2(atoms)
|
|
l3, notes = promote.promote_l2_to_l3(atoms + l2)
|
|
warnings = promote.apply_caps(atoms + l2 + l3)
|
|
if l2 or l3:
|
|
promote.stage(store, l2, l3, notes, warnings)
|
|
except Exception:
|
|
# Never blocks teardown. A lost capture costs one re-observation; L1 is
|
|
# the recoverable tier by construction (5.4).
|
|
return 0
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|