Reflex makes AI coding agents reach for the right tool — by default, not by luck.
What & Why · Philosophy · Measurement · Quickstart · The Four Layers · Layout
AI coding agents don't have a capability problem — they have a habit problem.
The agent knows jq exists. It still pipes JSON through grep. It knows crudini
can edit an INI file surgically. It still reaches for sed -i and corrupts the next key.
It knows dwdiff exists, and still drowns you in line-noise from raw diff. The model has
read the man pages — but at decision time, the muscle memory is grep/sed/awk and a
fistful of Edit calls.
(These are the failure modes the tools are designed to prevent. Whether they net-reduce tokens or errors in practice is a hypothesis we are still measuring — see Measurement.)
Reflex fixes the habit, not just the toolbox. It does two things:
- Installs a tight, curated core of agent-friendly CLI tools (12 Homebrew/pipx
packages, 13 commands — run
tools/toolcount.sh) — headless, scriptable, deterministic, machine-parseable. The exact traits that made them awkward for a human at a live terminal are the traits an agent wants. No kitchen sink; these are the ones that earn their place. - Wires the agent (Claude Code) to actually lean on them — through a deterministic hook, an always-in-context preference table, a recalled memory, and an on-demand skill.
That's the whole product: a curated core plus the wiring that makes the agent use it. These were chosen from a much larger survey of candidate tools — only the ones that earned their place on an agent's PATH are installed here.
Installing a tool changes nothing if the defaults win.
Behavior change doesn't come from what's on the machine. It comes from what's in the agent's context at the moment it decides what to type. A tool the agent never recalls is a tool that doesn't exist.
So Reflex treats the deterministic hook as the backbone — it fires on every command,
whether or not the model "remembered" — and treats the table, the memory, and the skill as
reinforcement that compounds. Layered, the reflex changes. Tools without wiring is a wish;
wiring without tools is a nag. You need both.
These are aids, not laws. When the boring tool is genuinely clearer for a one-off, the agent still uses it. The goal is better defaults, not new dogma.
Reflex's mechanism is verified: the hook deterministically blocks the named wrong idioms and
re-routes the agent (see tests/test_hook.py). Its efficiency — fewer tokens, fewer tool
calls, higher first-try correctness — is a hypothesis, not a measured result. We ship
local telemetry (~/.reflex/events.jsonl, opt-out) so a with/without benchmark can produce
and audit the number before it is claimed. Until that benchmark is published, treat efficiency
claims as unproven.
Rule for future copy: treat every comparative claim (faster / cheaper / more correct / beats / inferior) as an unproven hypothesis until a published benchmark cites a number.
git clone https://github.com/campbellcharlie/reflex.git ~/src/reflex
cd ~/src/reflex
./install.sh # installs tools + wires Claude Code config
# then in Claude Code: open /hooks once (or restart) to load the hookPiecemeal:
make tools # just install the CLI tools (brew + pipx)
make claude # just wire the Claude Code layers
make verify # show which toolkit commands are on PATHTarget a non-default config dir: CLAUDE_HOME=/path/to/.claude make claude.
What gets installed
jc · jq · gron · dwdiff · sem (GNU parallel) · augtool (augeas) · hxselect
(html-xml-utils) · unbuffer/expect · gstdbuf (coreutils) · crudini (pipx) ·
datamash · tmux, plus system lsof/ed. All verified on macOS 27. See
tools/install-tools.sh. The tool list and its count
(12 packages / 13 commands) are generated from tools/manifest.tsv
— run tools/toolcount.sh to reproduce every number here.
Telemetry (local-only, opt-out): the hook appends one JSON line per Bash decision to
~/.reflex/events.jsonl — only the decision class (block/advise/raw/allow), the
suggested tool, and whether it was on PATH. No command text, arguments, cwd, or env is ever
recorded, so the file is safe to share. It is never transmitted anywhere. Opt out with
REFLEX_TELEMETRY=0; relocate the directory with REFLEX_HOME=/path.
jq is the keystone — jc and gron both pipe into it — so the installer ensures it rather
than assuming it's present.
tmux is the one mainstream pick — it's installed because send-keys + capture-pane -p
is the best persistent-session driver an agent has. It's not a macOS default; macOS ships
/usr/bin/screen as a weaker zero-install fallback.
Deliberately not auto-installed (collisions / unpackaged):
- real
pup— brewpupis the Datadog CLI; usehxselectorgo install github.com/ericchiang/pup flock/lockf— usesemspiff— archival sourcesam/ssam— needsplan9port
Installing tools changes nothing if defaults win — so the change comes from four reinforcing layers, with the deterministic hook as the backbone.
| Layer | File | What it does |
|---|---|---|
| 1 · PreToolUse hook (deterministic) | claude/hooks/toolkit-nudge.py |
On every Bash call, classifies the command. Tier A blocks the high-confidence wrong idioms (grep/sed/awk on a .json file, sed -i on a config file) via permissionDecision:deny, feeding the right tool back so the agent re-routes. Tier B advises the fuzzier ones (ps|awk, curl|grep HTML). Append # raw to override a block. |
| 2 · CLAUDE.md table (always in context) | claude/CLAUDE.snippet.md |
A "Preferred Tooling" reach-for-A → use-B table appended to ~/.claude/CLAUDE.md. |
| 3 · Memory entry (recalled) | claude/memory/agent-cli-toolkit.md |
Persistent note of the toolkit + preference, dropped into your auto-memory dir. |
4 · toolkit skill (depth on demand) |
claude/skills/toolkit/SKILL.md |
The full pairing map, auto-loaded when relevant work starts. |
claude/install-claude.sh wires layers 1, 2, and 4 idempotently (re-running re-applies snippet
updates, not just appends-once). merge-settings.py merges the hook into settings.json
without clobbering existing hooks and backs it up to settings.json.bak. Layer 3 (the
memory note) is not auto-installed — copy claude/memory/agent-cli-toolkit.md into your
auto-memory directory yourself if you use auto-memory.
Hook tuning
The hook is two-tier. A small BLOCK list — grep/sed/awk on a .json file, sed -i on a
config file — denies the command (permissionDecision: "deny") with a message naming the right
tool, so the agent re-routes instead of running the wrong thing. A larger ADVISE list
(ps|awk, HTML scraping) only injects a tip; the command still runs. Three guards keep blocking
safe: patterns are anchored to a command position (start, or after |/;/&&) to avoid false
positives; a block never fires toward a tool that isn't installed; and appending # raw to a
command bypasses the block for genuine exceptions (malformed JSON, raw context). To loosen,
move a pattern from BLOCK to ADVISE in toolkit-nudge.py; to tighten, do the reverse.
(Note: blocking is done with the stdout permissionDecision: "deny" form, not a bare exit 2 —
the registered hook command wraps the script in || true, and this Claude Code surface acts on
the JSON decision, not the exit code.)
Uninstall
- Remove the
PreToolUse"Bash" entry from~/.claude/settings.json(restore~/.claude/settings.json.bak). - Delete
~/.claude/hooks/toolkit-nudge.pyand~/.claude/skills/toolkit/. - Remove the "Preferred Tooling" section from
~/.claude/CLAUDE.md.
install.sh # tools + claude
tools/install-tools.sh # brew + pipx, idempotent
claude/
install-claude.sh # wire the 4 layers
merge-settings.py # idempotent settings.json hook merge
merge-claudemd.py # idempotent CLAUDE.md section insert/replace
hooks/toolkit-nudge.py
skills/toolkit/SKILL.md
CLAUDE.snippet.md
memory/agent-cli-toolkit.md
tests/test_hook.py # hook allow/deny/advise suite (make test)
MIT © 2026 Charlie Campbell