Skip to content

Commit 5d5b110

Browse files
authored
Merge pull request #8 from aliirz/master
fix(claude-code): replace hardcoded PostToolUse hook with rich episodic logging
2 parents 6328fbb + 6008332 commit 5d5b110

9 files changed

Lines changed: 1247 additions & 43 deletions

File tree

.agent/harness/hooks/claude_code_post_tool.py

Lines changed: 482 additions & 0 deletions
Large diffs are not rendered by default.

.agent/harness/hooks/on_failure.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,16 @@ def _count_recent_failures(skill_name):
3333

3434
def on_failure(skill_name, action, error, context="", confidence=0.9,
3535
evidence_ids=None):
36+
# Format reflection without the noisy `type(error).__name__:` prefix
37+
# when the caller passes a pre-formatted string (the common case for
38+
# hook callers). Only include the type name for actual Exception objects
39+
# where it carries diagnostic value.
40+
if isinstance(error, Exception):
41+
_refl = (f"FAILURE in {skill_name}: {type(error).__name__}: "
42+
f"{str(error)[:200]}")
43+
else:
44+
_refl = f"FAILURE in {skill_name}: {str(error)[:200]}"
45+
3646
entry = {
3747
"timestamp": datetime.datetime.now().isoformat(),
3848
"skill": skill_name,
@@ -41,8 +51,7 @@ def on_failure(skill_name, action, error, context="", confidence=0.9,
4151
"detail": str(error)[:500],
4252
"pain_score": 8,
4353
"importance": 7,
44-
"reflection": f"FAILURE in {skill_name}: {type(error).__name__}: "
45-
f"{str(error)[:200]}",
54+
"reflection": _refl,
4655
"context": context[:300],
4756
"confidence": confidence,
4857
"source": build_source(skill_name),

.agent/harness/hooks/post_execution.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,24 @@
77

88

99
def log_execution(skill_name, action, result, success, reflection="",
10-
importance=5, confidence=0.5, evidence_ids=None):
10+
importance=5, confidence=0.5, evidence_ids=None,
11+
pain_score=None):
12+
"""Log a structured episodic entry.
13+
14+
pain_score: override the default (2 for success, 7 for failure). Pass
15+
a higher value (e.g. 5) for high-importance successful operations so
16+
recurring patterns cross the dream-cycle promotion threshold (7.0).
17+
"""
1118
os.makedirs(os.path.dirname(EPISODIC), exist_ok=True)
19+
if pain_score is None:
20+
pain_score = 2 if success else 7
1221
entry = {
1322
"timestamp": datetime.datetime.now().isoformat(),
1423
"skill": skill_name,
1524
"action": action[:200],
1625
"result": "success" if success else "failure",
1726
"detail": str(result)[:500],
18-
"pain_score": 2 if success else 7,
27+
"pain_score": pain_score,
1928
"importance": importance,
2029
"reflection": reflection,
2130
"confidence": confidence,
Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
{
2+
"_comment": [
3+
"Extra patterns for the PostToolUse hook importance scorer.",
4+
"",
5+
"high_stakes -> importance=9 (pain_score=5 for successes, 8+ for failures)",
6+
"medium_stakes -> importance=6",
7+
"",
8+
"HOW IMPORTANCE IS DECIDED:",
9+
" The hook matches the OPERATION, not the service brand.",
10+
" 'vercel deploy' is high-stakes because of 'deploy', not 'vercel'.",
11+
" 'supabase db push' is medium because 'push' -- add 'supabase' to",
12+
" high_stakes here if you want every supabase command scored as 9.",
13+
"",
14+
"ALREADY HIGH-STAKES (built-in, no config needed):",
15+
" deploy, release, rollback, migration, migrate, schema,",
16+
" alter/drop/create table, truncate, production, staging,",
17+
" force-push, push --force, secret, credential.",
18+
"",
19+
"ALREADY MEDIUM-STAKES (built-in):",
20+
" commit, push, merge, rebase, test, spec, build, bundle,",
21+
" compile, install, upgrade, delete, remove, chmod, cron.",
22+
"",
23+
"Add service names or domain-specific terms your project uses.",
24+
"Values are word-boundary regex fragments (case-insensitive).",
25+
"Restart Claude Code after editing -- patterns load at hook startup.",
26+
"",
27+
"Copy entries from _examples into high_stakes / medium_stakes to activate."
28+
],
29+
"high_stakes": [],
30+
"medium_stakes": [],
31+
"_examples": {
32+
"_comment": "Copy entries from here into high_stakes / medium_stakes above.",
33+
"high_stakes": [
34+
"supabase",
35+
"vercel",
36+
"railway",
37+
"fly\\.io",
38+
"render\\.com",
39+
"aws\\s+",
40+
"gcloud\\s+",
41+
"kubectl",
42+
"heroku",
43+
"docker\\s+push",
44+
"npm\\s+publish",
45+
"pip\\s+publish",
46+
"stripe",
47+
"twilio",
48+
"sendgrid",
49+
"resend",
50+
"planetscale",
51+
"neon",
52+
"turso",
53+
"upstash",
54+
"cloudflare\\s+",
55+
"firebase"
56+
],
57+
"medium_stakes": [
58+
"jest",
59+
"pytest",
60+
"vitest",
61+
"mocha",
62+
"cypress",
63+
"playwright"
64+
]
65+
}
66+
}

.agent/tools/memory_reflect.py

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,14 @@
77

88

99
def reflect(skill_name, action, outcome, success=True, importance=5,
10-
reflection="", error=None, confidence=None, evidence_ids=None):
10+
reflection="", error=None, confidence=None, evidence_ids=None,
11+
pain_score=None):
1112
if success:
1213
return log_execution(skill_name, action, outcome, True,
1314
reflection=reflection, importance=importance,
1415
confidence=0.5 if confidence is None else confidence,
15-
evidence_ids=evidence_ids)
16+
evidence_ids=evidence_ids,
17+
pain_score=pain_score)
1618
return on_failure(skill_name, action, error or outcome,
1719
context=reflection,
1820
confidence=0.9 if confidence is None else confidence,
@@ -31,8 +33,11 @@ def reflect(skill_name, action, outcome, success=True, importance=5,
3133
p.add_argument("--confidence", type=float, default=None)
3234
p.add_argument("--evidence", nargs="*", default=None,
3335
help="Space-separated episode/lesson IDs this entry builds on.")
36+
p.add_argument("--pain", type=int, default=None,
37+
help="Override pain_score (2=routine, 5=significant success, "
38+
"8=failure, 10=incident). Default: 2 for success, 7 for --fail.")
3439
args = p.parse_args()
3540
print(reflect(args.skill, args.action, args.outcome,
3641
success=not args.fail, importance=args.importance,
3742
reflection=args.note, confidence=args.confidence,
38-
evidence_ids=args.evidence))
43+
evidence_ids=args.evidence, pain_score=args.pain))

adapters/claude-code/CLAUDE.md

Lines changed: 92 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -3,41 +3,106 @@
33
This project uses the **agentic-stack** portable brain. All memory, skills,
44
and protocols live in `.agent/`.
55

6-
## Before doing anything
7-
1. Read `.agent/AGENTS.md` — it's the map.
8-
2. Read `.agent/memory/personal/PREFERENCES.md` — how the user works.
9-
3. Read `.agent/memory/semantic/LESSONS.md` — what we've learned.
10-
4. Read `.agent/protocols/permissions.md` — what you can and cannot do.
6+
## Session start — read in this order
7+
1. `.agent/AGENTS.md` — the map of the whole brain
8+
2. `.agent/memory/personal/PREFERENCES.md` — how the user works
9+
3. `.agent/memory/working/REVIEW_QUEUE.md` — pending lessons awaiting review
10+
4. `.agent/memory/semantic/LESSONS.md` — what we've already learned
11+
5. `.agent/protocols/permissions.md` — hard constraints, read before any tool call
12+
13+
## Before every non-trivial action — recall first
1114

12-
## Before every non-trivial task — recall first
1315
For any task involving **deploy**, **ship**, **release**, **migration**,
14-
**schema change**, **timestamp** / **timezone** / **date**, **failing test**,
15-
**debug**, **investigate**, or **refactor**, run recall FIRST and present
16-
the surfaced lessons to yourself before acting:
16+
**schema change**, **supabase**, **edge function**, **timestamp** /
17+
**timezone** / **date**, **failing test**, **debug**, **investigate**, or
18+
**refactor**, run recall FIRST and present the results before acting:
1719

1820
```bash
1921
python3 .agent/tools/recall.py "<one-line description of what you're about to do>"
2022
```
2123

22-
If the output contains a "Consulted lessons for intent:" block with one or
23-
more results, show them to the user in a `Consulted lessons before acting:`
24-
block and adjust your plan to respect them. If a surfaced lesson would be
25-
violated by your intended action, stop and explain.
26-
27-
This is how graduated lessons actually change behavior across harnesses.
28-
Skip it and the system is just files on disk.
24+
Show the output in a `Consulted lessons before acting:` block. If a surfaced
25+
lesson would be violated by your intended action, stop and explain why.
2926

3027
## While working
31-
- Consult `.agent/skills/_index.md` and load the full `SKILL.md` for any
32-
skill whose triggers match the task.
33-
- Update `.agent/memory/working/WORKSPACE.md` as the task evolves.
34-
- Log significant actions to `.agent/memory/episodic/AGENT_LEARNINGS.jsonl`
35-
via `.agent/tools/memory_reflect.py`.
36-
- Quick state check any time: `python3 .agent/tools/show.py`.
37-
- Teach the agent a new rule in one shot:
38-
`python3 .agent/tools/learn.py "<the rule>" --rationale "<why>"`.
39-
40-
## Rules that override defaults
28+
29+
### Skills
30+
Read `.agent/skills/_index.md` and load the full `SKILL.md` for any skill
31+
whose triggers match the task. Don't skip this — skills carry constraints
32+
the permissions file doesn't cover.
33+
34+
### Workspace
35+
Update `.agent/memory/working/WORKSPACE.md` when:
36+
- You start a new task (write the goal and first step)
37+
- Your hypothesis changes
38+
- You complete or abandon a task (clear it so the next session is clean)
39+
40+
### Brain state
41+
Quick overview any time:
42+
```bash
43+
python3 .agent/tools/show.py
44+
```
45+
46+
### Teaching the agent a new rule
47+
When you discover something that should never happen again:
48+
```bash
49+
python3 .agent/tools/learn.py "<the rule, phrased as a principle>" \
50+
--rationale "<why — include the incident that taught you this>"
51+
```
52+
53+
## Manual memory logging — when and how
54+
55+
The PostToolUse hook captures every tool call automatically, but its
56+
reflections are mechanical. For **significant events** you must call
57+
`memory_reflect.py` explicitly with a rich `--note`. These are the entries
58+
the dream cycle promotes into lessons.
59+
60+
### When to log manually
61+
- After completing a major feature or fixing a bug that took real investigation
62+
- After any rollback, incident, or unexpected failure
63+
- After any architectural decision (why you chose approach A over B)
64+
- After discovering a project-specific constraint (e.g. "this table has a
65+
trigger that fires on every insert — don't bulk insert")
66+
- After a Supabase migration, RLS policy change, or edge function deploy
67+
- Any time you think "I wish I had known this an hour ago"
68+
69+
### How to write a good entry
70+
71+
```bash
72+
# Good: specific, domain-rich, future-oriented
73+
python3 .agent/tools/memory_reflect.py \
74+
"supabase-migration" \
75+
"applied add_user_tier_column migration" \
76+
"migration succeeded; 847 rows backfilled to tier=free" \
77+
--importance 8 \
78+
--note "RLS policy on user_profiles must be updated whenever a new column is added that affects row visibility. Missed this, caused 401s in staging for 20 minutes."
79+
80+
# Good: failure with root cause
81+
python3 .agent/tools/memory_reflect.py \
82+
"edge-function" \
83+
"deployed notify-on-signup" \
84+
"deploy failed: missing RESEND_API_KEY in production env" \
85+
--fail \
86+
--importance 9 \
87+
--note "Production env vars for edge functions must be set in supabase secrets, not .env. The .env file is ignored at deploy time."
88+
89+
# Bad: vague, no content words for clustering
90+
python3 .agent/tools/memory_reflect.py \
91+
"claude-code" "did stuff" "ok" --importance 3
92+
```
93+
94+
### Importance guide
95+
| Value | When |
96+
|---|---|
97+
| 9–10 | Production incident, data migration, rollback, security issue |
98+
| 7–8 | Deploy, schema change, architectural decision, non-obvious constraint |
99+
| 5–6 | Refactor, significant bug fix, API contract change |
100+
| 3–4 | Routine edit, file creation, test run |
101+
102+
## Rules that override all defaults
41103
- Never force push to `main`, `production`, or `staging`.
42104
- Never delete episodic or semantic memory entries — archive them.
43-
- Never modify `.agent/protocols/permissions.md`.
105+
- Never modify `.agent/protocols/permissions.md` — only humans edit it.
106+
- Never hand-edit `.agent/memory/semantic/LESSONS.md` — use `graduate.py`.
107+
- If `REVIEW_QUEUE.md` shows pending > 10 or oldest > 7 days, review
108+
candidates before starting substantive work.

adapters/claude-code/README.md

Lines changed: 76 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,81 @@ Or let the top-level install script do it:
1616
```
1717

1818
## What it wires up
19-
- `CLAUDE.md` tells Claude Code to read `.agent/` before acting.
20-
- `.claude/settings.json` adds:
21-
- A **PostToolUse** hook that logs every Bash/Edit/Write call to episodic memory.
22-
- A **Stop** hook that runs the dream cycle when a session ends.
23-
- Permission denies for the most destructive operations (force push, `rm -rf /`).
19+
20+
- **`CLAUDE.md`** — boot instructions at project root. Claude Code reads this
21+
before every session. Tells the model to read the brain in the correct order,
22+
run `recall.py` before high-stakes operations, and call `memory_reflect.py`
23+
manually for significant events.
24+
25+
- **`.claude/settings.json`** — two hooks + permission denies:
26+
27+
| Hook | Trigger | Script |
28+
|---|---|---|
29+
| `PostToolUse` | `Bash\|Edit\|MultiEdit\|Write\|Task\|TodoWrite` | `.agent/harness/hooks/claude_code_post_tool.py` |
30+
| `Stop` | `*` (session end) | `.agent/memory/auto_dream.py` |
31+
32+
### Why `claude_code_post_tool.py` and not `memory_reflect.py`
33+
34+
The old hook called `memory_reflect.py claude-code post-tool ok` — every
35+
entry was identical (action="post-tool", detail="ok", reflection=""). The
36+
dream cycle clusters on the `reflection` field; an empty reflection means
37+
zero candidates staged regardless of how many tool calls fire.
38+
39+
`claude_code_post_tool.py` reads the JSON payload Claude Code sends via
40+
**stdin** on every PostToolUse event:
41+
42+
```json
43+
{
44+
"tool_name": "Bash",
45+
"tool_input": {"command": "supabase db push --db-url $DATABASE_URL"},
46+
"tool_response": {"output": "Applied 1 migration.", "exit_code": 0}
47+
}
48+
```
49+
50+
It then:
51+
- Maps `tool_name` + `tool_input` to a meaningful action label
52+
- Scores `importance` by domain (deploy/migrate/supabase/edge-function = 9)
53+
- Detects failures from `exit_code`, `error` stream, and `is_error`
54+
- Generates a non-empty `reflection` the dream cycle can cluster on
55+
- Sets `pain_score=5` for high-importance successes so recurring patterns
56+
cross the promotion threshold (7.0); routine ops stay at `pain_score=2`
2457

2558
## Verify
26-
Open Claude Code in the project and ask: "What's in my lessons file?"
27-
If it reads `.agent/memory/semantic/LESSONS.md`, the wiring works.
59+
60+
1. Open Claude Code in your project.
61+
2. Run one Bash command.
62+
3. Check the last line of `.agent/memory/episodic/AGENT_LEARNINGS.jsonl`:
63+
- `action` should describe the actual command, not `"post-tool"`
64+
- `reflection` should be non-empty
65+
- `importance` should be 9 for deploy/supabase ops, 3 for `git status`
66+
67+
```bash
68+
tail -1 .agent/memory/episodic/AGENT_LEARNINGS.jsonl | python3 -m json.tool
69+
```
70+
71+
4. Check brain state:
72+
```bash
73+
python3 .agent/tools/show.py
74+
```
75+
76+
## Troubleshooting
77+
78+
- **Hook doesn't fire at all:** run `claude settings` and confirm your
79+
`.claude/settings.json` appears in the merged config. Claude Code merges
80+
project-level settings with global `~/.claude/settings.json`.
81+
82+
- **`stdin` is empty / payload is `{}`:** older Claude Code versions may not
83+
pass the JSON payload. The hook falls back to `CLAUDE_TOOL_NAME` /
84+
`CLAUDE_TOOL_INPUT` env vars. The action label will still be correct; the
85+
detail and output capture will be empty. Upgrade Claude Code to get full
86+
stdin payloads.
87+
88+
- **`python3` not found:** add `AGENT_PYTHON=python` to your shell profile
89+
and edit the hook commands in `.claude/settings.json` accordingly.
90+
91+
- **Dream cycle stages nothing:** after a session, run
92+
`python3 .agent/memory/auto_dream.py` manually and check the output line.
93+
If `patterns=0`, the episodic log is either empty or all entries have
94+
empty reflections (old hook). If `patterns=N staged=0`, salience is too
95+
low — check that `importance` and `pain_score` are non-trivial in your
96+
entries.

adapters/claude-code/settings.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,11 @@
33
"hooks": {
44
"PostToolUse": [
55
{
6-
"matcher": "Bash|Edit|Write",
6+
"matcher": "Bash|Edit|MultiEdit|Write|Task|TodoWrite",
77
"hooks": [
88
{
99
"type": "command",
10-
"command": "python3 .agent/tools/memory_reflect.py claude-code post-tool ok"
10+
"command": "python3 .agent/harness/hooks/claude_code_post_tool.py"
1111
}
1212
]
1313
}

0 commit comments

Comments
 (0)