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
482 changes: 482 additions & 0 deletions .agent/harness/hooks/claude_code_post_tool.py

Large diffs are not rendered by default.

13 changes: 11 additions & 2 deletions .agent/harness/hooks/on_failure.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,16 @@ def _count_recent_failures(skill_name):

def on_failure(skill_name, action, error, context="", confidence=0.9,
evidence_ids=None):
# Format reflection without the noisy `type(error).__name__:` prefix
# when the caller passes a pre-formatted string (the common case for
# hook callers). Only include the type name for actual Exception objects
# where it carries diagnostic value.
if isinstance(error, Exception):
_refl = (f"FAILURE in {skill_name}: {type(error).__name__}: "
f"{str(error)[:200]}")
else:
_refl = f"FAILURE in {skill_name}: {str(error)[:200]}"

entry = {
"timestamp": datetime.datetime.now().isoformat(),
"skill": skill_name,
Expand All @@ -41,8 +51,7 @@ def on_failure(skill_name, action, error, context="", confidence=0.9,
"detail": str(error)[:500],
"pain_score": 8,
"importance": 7,
"reflection": f"FAILURE in {skill_name}: {type(error).__name__}: "
f"{str(error)[:200]}",
"reflection": _refl,
"context": context[:300],
"confidence": confidence,
"source": build_source(skill_name),
Expand Down
13 changes: 11 additions & 2 deletions .agent/harness/hooks/post_execution.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,24 @@


def log_execution(skill_name, action, result, success, reflection="",
importance=5, confidence=0.5, evidence_ids=None):
importance=5, confidence=0.5, evidence_ids=None,
pain_score=None):
"""Log a structured episodic entry.

pain_score: override the default (2 for success, 7 for failure). Pass
a higher value (e.g. 5) for high-importance successful operations so
recurring patterns cross the dream-cycle promotion threshold (7.0).
"""
os.makedirs(os.path.dirname(EPISODIC), exist_ok=True)
if pain_score is None:
pain_score = 2 if success else 7
entry = {
"timestamp": datetime.datetime.now().isoformat(),
"skill": skill_name,
"action": action[:200],
"result": "success" if success else "failure",
"detail": str(result)[:500],
"pain_score": 2 if success else 7,
"pain_score": pain_score,
"importance": importance,
"reflection": reflection,
"confidence": confidence,
Expand Down
66 changes: 66 additions & 0 deletions .agent/protocols/hook_patterns.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
{
"_comment": [
"Extra patterns for the PostToolUse hook importance scorer.",
"",
"high_stakes -> importance=9 (pain_score=5 for successes, 8+ for failures)",
"medium_stakes -> importance=6",
"",
"HOW IMPORTANCE IS DECIDED:",
" The hook matches the OPERATION, not the service brand.",
" 'vercel deploy' is high-stakes because of 'deploy', not 'vercel'.",
" 'supabase db push' is medium because 'push' -- add 'supabase' to",
" high_stakes here if you want every supabase command scored as 9.",
"",
"ALREADY HIGH-STAKES (built-in, no config needed):",
" deploy, release, rollback, migration, migrate, schema,",
" alter/drop/create table, truncate, production, staging,",
" force-push, push --force, secret, credential.",
"",
"ALREADY MEDIUM-STAKES (built-in):",
" commit, push, merge, rebase, test, spec, build, bundle,",
" compile, install, upgrade, delete, remove, chmod, cron.",
"",
"Add service names or domain-specific terms your project uses.",
"Values are word-boundary regex fragments (case-insensitive).",
"Restart Claude Code after editing -- patterns load at hook startup.",
"",
"Copy entries from _examples into high_stakes / medium_stakes to activate."
],
"high_stakes": [],
"medium_stakes": [],
"_examples": {
"_comment": "Copy entries from here into high_stakes / medium_stakes above.",
"high_stakes": [
"supabase",
"vercel",
"railway",
"fly\\.io",
"render\\.com",
"aws\\s+",
"gcloud\\s+",
"kubectl",
"heroku",
"docker\\s+push",
"npm\\s+publish",
"pip\\s+publish",
"stripe",
"twilio",
"sendgrid",
"resend",
"planetscale",
"neon",
"turso",
"upstash",
"cloudflare\\s+",
"firebase"
],
"medium_stakes": [
"jest",
"pytest",
"vitest",
"mocha",
"cypress",
"playwright"
]
}
}
11 changes: 8 additions & 3 deletions .agent/tools/memory_reflect.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@


def reflect(skill_name, action, outcome, success=True, importance=5,
reflection="", error=None, confidence=None, evidence_ids=None):
reflection="", error=None, confidence=None, evidence_ids=None,
pain_score=None):
if success:
return log_execution(skill_name, action, outcome, True,
reflection=reflection, importance=importance,
confidence=0.5 if confidence is None else confidence,
evidence_ids=evidence_ids)
evidence_ids=evidence_ids,
pain_score=pain_score)
return on_failure(skill_name, action, error or outcome,
context=reflection,
confidence=0.9 if confidence is None else confidence,
Expand All @@ -31,8 +33,11 @@ def reflect(skill_name, action, outcome, success=True, importance=5,
p.add_argument("--confidence", type=float, default=None)
p.add_argument("--evidence", nargs="*", default=None,
help="Space-separated episode/lesson IDs this entry builds on.")
p.add_argument("--pain", type=int, default=None,
help="Override pain_score (2=routine, 5=significant success, "
"8=failure, 10=incident). Default: 2 for success, 7 for --fail.")
args = p.parse_args()
print(reflect(args.skill, args.action, args.outcome,
success=not args.fail, importance=args.importance,
reflection=args.note, confidence=args.confidence,
evidence_ids=args.evidence))
evidence_ids=args.evidence, pain_score=args.pain))
119 changes: 92 additions & 27 deletions adapters/claude-code/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,41 +3,106 @@
This project uses the **agentic-stack** portable brain. All memory, skills,
and protocols live in `.agent/`.

## Before doing anything
1. Read `.agent/AGENTS.md` — it's the map.
2. Read `.agent/memory/personal/PREFERENCES.md` — how the user works.
3. Read `.agent/memory/semantic/LESSONS.md` — what we've learned.
4. Read `.agent/protocols/permissions.md` — what you can and cannot do.
## Session start — read in this order
1. `.agent/AGENTS.md` — the map of the whole brain
2. `.agent/memory/personal/PREFERENCES.md` — how the user works
3. `.agent/memory/working/REVIEW_QUEUE.md` — pending lessons awaiting review
4. `.agent/memory/semantic/LESSONS.md` — what we've already learned
5. `.agent/protocols/permissions.md` — hard constraints, read before any tool call

## Before every non-trivial action — recall first

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

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

If the output contains a "Consulted lessons for intent:" block with one or
more results, show them to the user in a `Consulted lessons before acting:`
block and adjust your plan to respect them. If a surfaced lesson would be
violated by your intended action, stop and explain.

This is how graduated lessons actually change behavior across harnesses.
Skip it and the system is just files on disk.
Show the output in a `Consulted lessons before acting:` block. If a surfaced
lesson would be violated by your intended action, stop and explain why.

## While working
- Consult `.agent/skills/_index.md` and load the full `SKILL.md` for any
skill whose triggers match the task.
- Update `.agent/memory/working/WORKSPACE.md` as the task evolves.
- Log significant actions to `.agent/memory/episodic/AGENT_LEARNINGS.jsonl`
via `.agent/tools/memory_reflect.py`.
- Quick state check any time: `python3 .agent/tools/show.py`.
- Teach the agent a new rule in one shot:
`python3 .agent/tools/learn.py "<the rule>" --rationale "<why>"`.

## Rules that override defaults

### Skills
Read `.agent/skills/_index.md` and load the full `SKILL.md` for any skill
whose triggers match the task. Don't skip this — skills carry constraints
the permissions file doesn't cover.

### Workspace
Update `.agent/memory/working/WORKSPACE.md` when:
- You start a new task (write the goal and first step)
- Your hypothesis changes
- You complete or abandon a task (clear it so the next session is clean)

### Brain state
Quick overview any time:
```bash
python3 .agent/tools/show.py
```

### Teaching the agent a new rule
When you discover something that should never happen again:
```bash
python3 .agent/tools/learn.py "<the rule, phrased as a principle>" \
--rationale "<why — include the incident that taught you this>"
```

## Manual memory logging — when and how

The PostToolUse hook captures every tool call automatically, but its
reflections are mechanical. For **significant events** you must call
`memory_reflect.py` explicitly with a rich `--note`. These are the entries
the dream cycle promotes into lessons.

### When to log manually
- After completing a major feature or fixing a bug that took real investigation
- After any rollback, incident, or unexpected failure
- After any architectural decision (why you chose approach A over B)
- After discovering a project-specific constraint (e.g. "this table has a
trigger that fires on every insert — don't bulk insert")
- After a Supabase migration, RLS policy change, or edge function deploy
- Any time you think "I wish I had known this an hour ago"

### How to write a good entry

```bash
# Good: specific, domain-rich, future-oriented
python3 .agent/tools/memory_reflect.py \
"supabase-migration" \
"applied add_user_tier_column migration" \
"migration succeeded; 847 rows backfilled to tier=free" \
--importance 8 \
--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."

# Good: failure with root cause
python3 .agent/tools/memory_reflect.py \
"edge-function" \
"deployed notify-on-signup" \
"deploy failed: missing RESEND_API_KEY in production env" \
--fail \
--importance 9 \
--note "Production env vars for edge functions must be set in supabase secrets, not .env. The .env file is ignored at deploy time."

# Bad: vague, no content words for clustering
python3 .agent/tools/memory_reflect.py \
"claude-code" "did stuff" "ok" --importance 3
```

### Importance guide
| Value | When |
|---|---|
| 9–10 | Production incident, data migration, rollback, security issue |
| 7–8 | Deploy, schema change, architectural decision, non-obvious constraint |
| 5–6 | Refactor, significant bug fix, API contract change |
| 3–4 | Routine edit, file creation, test run |

## Rules that override all defaults
- Never force push to `main`, `production`, or `staging`.
- Never delete episodic or semantic memory entries — archive them.
- Never modify `.agent/protocols/permissions.md`.
- Never modify `.agent/protocols/permissions.md` — only humans edit it.
- Never hand-edit `.agent/memory/semantic/LESSONS.md` — use `graduate.py`.
- If `REVIEW_QUEUE.md` shows pending > 10 or oldest > 7 days, review
candidates before starting substantive work.
83 changes: 76 additions & 7 deletions adapters/claude-code/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,81 @@ Or let the top-level install script do it:
```

## What it wires up
- `CLAUDE.md` tells Claude Code to read `.agent/` before acting.
- `.claude/settings.json` adds:
- A **PostToolUse** hook that logs every Bash/Edit/Write call to episodic memory.
- A **Stop** hook that runs the dream cycle when a session ends.
- Permission denies for the most destructive operations (force push, `rm -rf /`).

- **`CLAUDE.md`** — boot instructions at project root. Claude Code reads this
before every session. Tells the model to read the brain in the correct order,
run `recall.py` before high-stakes operations, and call `memory_reflect.py`
manually for significant events.

- **`.claude/settings.json`** — two hooks + permission denies:

| Hook | Trigger | Script |
|---|---|---|
| `PostToolUse` | `Bash\|Edit\|MultiEdit\|Write\|Task\|TodoWrite` | `.agent/harness/hooks/claude_code_post_tool.py` |
| `Stop` | `*` (session end) | `.agent/memory/auto_dream.py` |

### Why `claude_code_post_tool.py` and not `memory_reflect.py`

The old hook called `memory_reflect.py claude-code post-tool ok` — every
entry was identical (action="post-tool", detail="ok", reflection=""). The
dream cycle clusters on the `reflection` field; an empty reflection means
zero candidates staged regardless of how many tool calls fire.

`claude_code_post_tool.py` reads the JSON payload Claude Code sends via
**stdin** on every PostToolUse event:

```json
{
"tool_name": "Bash",
"tool_input": {"command": "supabase db push --db-url $DATABASE_URL"},
"tool_response": {"output": "Applied 1 migration.", "exit_code": 0}
}
```

It then:
- Maps `tool_name` + `tool_input` to a meaningful action label
- Scores `importance` by domain (deploy/migrate/supabase/edge-function = 9)
- Detects failures from `exit_code`, `error` stream, and `is_error`
- Generates a non-empty `reflection` the dream cycle can cluster on
- Sets `pain_score=5` for high-importance successes so recurring patterns
cross the promotion threshold (7.0); routine ops stay at `pain_score=2`

## Verify
Open Claude Code in the project and ask: "What's in my lessons file?"
If it reads `.agent/memory/semantic/LESSONS.md`, the wiring works.

1. Open Claude Code in your project.
2. Run one Bash command.
3. Check the last line of `.agent/memory/episodic/AGENT_LEARNINGS.jsonl`:
- `action` should describe the actual command, not `"post-tool"`
- `reflection` should be non-empty
- `importance` should be 9 for deploy/supabase ops, 3 for `git status`

```bash
tail -1 .agent/memory/episodic/AGENT_LEARNINGS.jsonl | python3 -m json.tool
```

4. Check brain state:
```bash
python3 .agent/tools/show.py
```

## Troubleshooting

- **Hook doesn't fire at all:** run `claude settings` and confirm your
`.claude/settings.json` appears in the merged config. Claude Code merges
project-level settings with global `~/.claude/settings.json`.

- **`stdin` is empty / payload is `{}`:** older Claude Code versions may not
pass the JSON payload. The hook falls back to `CLAUDE_TOOL_NAME` /
`CLAUDE_TOOL_INPUT` env vars. The action label will still be correct; the
detail and output capture will be empty. Upgrade Claude Code to get full
stdin payloads.

- **`python3` not found:** add `AGENT_PYTHON=python` to your shell profile
and edit the hook commands in `.claude/settings.json` accordingly.

- **Dream cycle stages nothing:** after a session, run
`python3 .agent/memory/auto_dream.py` manually and check the output line.
If `patterns=0`, the episodic log is either empty or all entries have
empty reflections (old hook). If `patterns=N staged=0`, salience is too
low — check that `importance` and `pain_score` are non-trivial in your
entries.
4 changes: 2 additions & 2 deletions adapters/claude-code/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
"hooks": {
"PostToolUse": [
{
"matcher": "Bash|Edit|Write",
"matcher": "Bash|Edit|MultiEdit|Write|Task|TodoWrite",
"hooks": [
{
"type": "command",
"command": "python3 .agent/tools/memory_reflect.py claude-code post-tool ok"
"command": "python3 .agent/harness/hooks/claude_code_post_tool.py"
}
]
}
Expand Down
Loading