Repository navigation
claude-hooks v1.1.0
[1.1.0] — 2026-05-08
MINOR bump for several new opt-in subsystems landed since v1.0.3:
the /get-advice LLM-to-LLM advisor skill (multi-turn second
opinions via local Ollama), the shared agent_loop.runner that
backs both caliber and the advisor, the stop_guard stall check, a
full pgvector backup + canary stack, and the v1.1 of the
/consultants agentic engine — full per-role LLM message-history
persistence so a session closed and reopened from disk produces
identical follow-up answers to a warm one, plus multi-model
researcher (xmedium / xhigh) and multi-critic + meta-critic
(xmax) fan-out tiers for hard architectural questions where
diverse cloud-model perspectives matter. Ten phases on dev
(9f71c9d..cdea074) plus the planning commit (5cb6738).
Added
-
/consultants v1.1 — multi-model x-tiers (xmedium / xhigh / xmax)
— three new effort tiers that fan out fan-outable roles across
multiple Ollama models per plan-item lane, so the synthesizer
(or meta-critic at xmax) sees diverse perspectives from
different model trainings on the same evidence. Configured via
per-roleextra_models = [...]in the role's TOML block;
silently ignored at every base tier (a benchmark labeled
highis never accidentally 3× the cost — opting into x-tiers
requires the explicit tier name). xmedium / xhigh only fan
out the researcher; xmax additionally fans out the critic and
adds a meta-critic node that synthesizes the C parallel-critic
verdicts into one consolidated decision (anonymized as
Critic 1/Critic 2/ ... in the prompt to avoid biasing
toward a model the meta-critic "knows" performs better; the
recorder's per-rowmodelcolumn is the audit map). Cost-of-
fan-out warning fires once at consultation start with the
expected token-cost multiplier. Skill (/consultants--config)
gains a "Manage extra models" sub-action under researcher /
critic; CLI gainsset-role <role> --add-model X --remove-model Y --clear-extras. Live-verified on solidpc — xmax with 2
researcher models and 3 critic models produces 3 distinct
critic verdicts (one per model) that the meta-critic
consolidates. Ten phases ondev(9f71c9d..cdea074). -
/consultants v1.1 — full message-history persistence — every
consultation now produces a SQLitetranscript.dbsidecar at
<cwd>/.claude-hooks/consultants/<sid>/transcript.dbalongside
the existingsummary.md,transcript.md, andmetadata.json.
The recorder writes one row per LLM call, tool execution, and
node enter/exit boundary in WAL mode (concurrent fan-out lanes
write through per-thread connections). When a session is
reopened from disk after engine restart or eviction, the
per-role LLM message threads are reconstructed via SQL —
follow-ups against disk-reopened parents now extend those
threads with the new question instead of rebuilding prompts
from scratch. Live-verified: a follow-up against a closed
parent issued 0 tool calls vs. the parent's 5 because the
model could lean on prior tool results in context (the explicit
v1.1-is-done criterion from the plan). SQLite was chosen over
JSONL for opacity to text indexers (claudemem reindex,
ripgrep, RAG ingestors) sincetranscript.dbcarries full LLM
payloads. Schema documented at
docs/consultants-transcript-db-schema.md;
inspect a session viaclaude-consultants show --raw <sid>with
optional--filter role=researcher --filter kind=tool_call --limit N. Backward-compatible: v1.0 sessions without a.db
reopen via the existing turn-content fallback. The legacy v1.0
JSONL trace at~/.claude/consultants-traces/<sid>.jsonlis
decommissioned;CONSULTANTS_TRACEand the--trace/
--no-traceCLI flags are now no-ops with a one-shot
deprecation warning (will be removed in v1.2). Plan and
pre-implementation log:docs/PLAN-consultants-v1.1-message-history.md. -
/get-advice — LLM-to-LLM advisor skill — Claude Code can now consult
a configured Ollama model (defaultqwen3.5:cloud) for a multi-turn
second opinion via the/get-advice <query>skill. Three helper
skills (/get-advice--model,/get-advice--effort,
/get-advice--tools) configure model + ctx, effort tier (low=1
session / medium=3 / high=5 / max=25), and the per-tool gate
(CSV /all/none) without editing JSON. Settings persist to
~/.claude/get-advice-config.json. New CLIbin/claude-advisor
drives the conversation:turn,reset,cleanup, get/set
subcommands. Per-turn JSON exposesprompt_eval_count/
eval_countso Claude knows when to summarize and reset before the
advisor's context fills (default threshold 85%). Reuses caliber-proxy
grounding (project anchors + structure map) and the same six tools
(read_file,grep,glob,list_files,survey_project,
recall_memory) when enabled. -
agent_loop.runner — shared tool-use loop — extracted the agent
loop fromclaude_hooks.caliber_proxy.server.run_agent_loopinto a
reusableclaude_hooks.agent_loop.runner.run_loopfunction with a
LoopConfigdataclass. Both the caliber grounding proxy and the new
/get-adviceadvisor drive their conversations through this single
loop, so every gemma4-era quirk (force-first-tool-call,
force-answer-after, tool-call burst dedup + cap, preseed survey)
benefits both consumers consistently. The runner is transport-
agnostic: callers pass their ownchat_fnandtool_executor.
caliber_proxy/server.py:run_agent_loopis now a thin shim that
reads env vars, builds theLoopConfig, prepends grounding, calls
the runner, and applies the caliber-specific
sanitize_assistant_jsonpost-processor. Behavior unchanged — the
full caliber-proxy test suite (91 tests acrossTestAgentLoop/
TestPreseedSurvey/ etc.) passes against the refactored path.
v1.1 added optionalon_iter/on_toolcallbacks so consumers
(notably the consultantsMessageRecorder) can observe every
chat round and tool execution without sub-classing the runner. -
stop_guard: stall-after-commitment check — catches a new failure
mode observed onclaude-opus-4-7(1M context): the model writes a
paragraph ending with an action-commitment phrase ("Diving in now",
"Writing the script now", "On it.") and then ends the turn WITHOUT
calling any tool. The user has to nudge the session to unstall it.
Three independent conditions stack so false-positive risk is low:
(1)stop_reason=end_turn, (2) zerotool_useblocks in the
message content, (3) one of the commitment phrases appears in the
last ~250 chars of the message text. The Stop hook returns
decision=blockwith a correction asking the model to either
execute the action it described or ask a specific question. Honours
the same user-wrap-up bypass as the prose-pattern guard so an
"All done. On it." closing after the user said "wrap up" doesn't
trigger. Default on when stop_guard itself is enabled; opt out via
hooks.stop_guard.stall_check_enabled = false. New module entry
points:claude_hooks.stop_guard.check_stall_after_commitment,
COMMITMENT_PATTERNS,STALL_CORRECTION. 15 unit tests in
tests/test_stop_guard.py::StallAfterCommitmentTestsplus an
end-to-end smoke through_run_stop_guard. -
pgvector backup-validity canary — new
claude-hooks-pgvector-backup-check.{service,timer}runs every
Monday at 02:43 local and walks each retention tier
(daily/weekly/monthly), validating the most recent dump in two
layers: (1)pg_restore -lfor the TOC + metadata, (2)
pg_restore -f /dev/nullfor a full byte-read of the archive
(catches mid-file corruption that the TOC scan misses). Both
layers run inside themcp-pgvectorcontainer so the
pg_restore version always matches whatever wrote the dump.
Exits non-zero on any failure → wireable intoOnFailure=.
New script:scripts/pgvector_backup_check.sh. -
pgvector daily backup timer — new
claude-hooks-pgvector-backup.{service,timer}runs
pg_dump -Fcinside themcp-pgvectorcontainer at 01:17 local
every day and writes to/shared/config/mcp-pgvector/backups/
with three retention tiers: 7 daily, 4 weekly (promoted on
Sunday by hardlink), 3 monthly (promoted on day 1 by hardlink).
pg_dumptakes onlyACCESS SHARElocks so reads + writes are
not blocked during the backup. New scripts:
scripts/pgvector_backup.sh(the worker) and
scripts/pgvector_restore.sh(interactive restore helper with
latest_daily/latest_weekly/latest_monthlyshortcuts).
Wired intoinstall.py— installed whenproviders.pgvector.enabled
is true. Tunables:CONTAINER,PG_USER,PG_DB,BACKUP_DIR,
KEEP_DAILY,KEEP_WEEKLY,KEEP_MONTHLY,WEEKLY_DOW.
Fixed
-
/consultants xmax — critic-fanout 6× cost overshoot — the
Phase 10 multi-critic dispatcher wired its conditional fan-out
edge directly toresearcher, which is itself Send-multiplexed
by the Phase 9 researcher fan-out (N×M parallel invocations at
x-tiers). LangGraph'sadd_conditional_edgesfrom a
Send-multiplexed source fires PER UPSTREAM SEND INVOCATION,
not per-barrier-merge — so 6 researcher lanes spawned 6 ×
C critic invocations instead of C. Caught on the first live
xmax smoke (csl-2026-05-07-1707-2a8f): 18 critic LLM calls
against an intended 3. Correctness wasn't affected — every
critic still saw the same merged research and meta-critic
consolidated correctly — but token cost was 6× the design.
Fix inserts a single-invocation pass-throughresearch_barrier
node between researcher and the critic-fanout dispatcher.
Unconditional edges from Send-multiplexed sources DO barrier-
merge (this is how the legacy single-critic edge always worked),
so routing through the barrier node forces the conditional
fan-out to fire exactly once. Same question post-fix: 3 critic
invocations, wall time 374s → 174s (54% faster). Test pins the
count invariant: regardless of how many researcher lanes fan
out, the critic fires exactly1 + len(extra_models)times. -
axon-host crash-loop after host restart — two compounding
issues that put the unit into a 5sRestart=on-failureloop
forever. (1)/root/.axonhad been deleted between installs;
the unit'sReadWritePaths=/root/.axondirective failed
systemd's namespace bind-mount withstatus=226/NAMESPACE
("Failed to set up mount namespacing"). (2) Theclaude-hooks
conda env had drifted —uvicorn,httpx-sse,
pydantic-settings, andsse-starlettewere silently dropped
(likely from a partial reinstall during another env's build),
and once the namespace bug was fixed axon crashed on import
withModuleNotFoundError: No module named 'uvicorn'. The
loop just moved one step deeper. install.py now does two
pre-flight checks before enabling the unit:_ensure_axon_ registry_dirmkdir's~/.axon/repos/so the bind-mount has a
target, and_ensure_axon_depsprobes the env's import
surface and pip-installsrequirements-axon.txt(new file
pinning the runtime deps) when anything is missing. Refuses to
enable the unit when either pre-flight fails, so future drift
becomes a clearinstall.pyre-run rather than a silent
service-loop. -
PreCompact: stop emitting hookSpecificOutput — Claude Code's
PreCompact event schema does NOT accepthookSpecificOutput
(only the universalcontinue/stopReason/suppressOutput
envelope). Returning the wrap-up markdown as
hookSpecificOutput.additionalContextfailed CC's JSON validator
with(root): Invalid input— the disk write succeeded but the
hook was reported as failed every time the user resumed a session
that had auto-compacted. The wrap-up file on disk is the sole
delivery channel;wrapup_recoveryalready surfaces the pointer
on the next post-compactionUserPromptSubmit, so dropping the
inline context loses nothing. Handler now returnsNoneon
success. Existingtest_pre_compact.pyupdated to pin the new
contract.
Added
-
/get-advice — LLM-to-LLM advisor skill — Claude Code can now consult
a configured Ollama model (defaultqwen3.5:cloud) for a multi-turn
second opinion via the/get-advice <query>skill. Three helper
skills (/get-advice--model,/get-advice--effort,
/get-advice--tools) configure model + ctx, effort tier (low=1
session / medium=3 / high=5 / max=25), and the per-tool gate
(CSV /all/none) without editing JSON. Settings persist to
~/.claude/get-advice-config.json. New CLIbin/claude-advisor
drives the conversation:turn,reset,cleanup, get/set
subcommands. Per-turn JSON exposesprompt_eval_count/
eval_countso Claude knows when to summarize and reset before the
advisor's context fills (default threshold 85%). Reuses caliber-proxy
grounding (project anchors + structure map) and the same six tools
(read_file,grep,glob,list_files,survey_project,
recall_memory) when enabled. -
agent_loop.runner — shared tool-use loop — extracted the agent
loop fromclaude_hooks.caliber_proxy.server.run_agent_loopinto a
reusableclaude_hooks.agent_loop.runner.run_loopfunction with a
LoopConfigdataclass. Both the caliber grounding proxy and the new
/get-adviceadvisor drive their conversations through this single
loop, so every gemma4-era quirk (force-first-tool-call,
force-answer-after, tool-call burst dedup + cap, preseed survey)
benefits both consumers consistently. The runner is transport-
agnostic: callers pass their ownchat_fnandtool_executor.
caliber_proxy/server.py:run_agent_loopis now a thin shim that
reads env vars, builds theLoopConfig, prepends grounding, calls
the runner, and applies the caliber-specific
sanitize_assistant_jsonpost-processor. Behavior unchanged — the
full caliber-proxy test suite (91 tests acrossTestAgentLoop/
TestPreseedSurvey/ etc.) passes against the refactored path. -
stop_guard: stall-after-commitment check — catches a new failure
mode observed onclaude-opus-4-7(1M context): the model writes a
paragraph ending with an action-commitment phrase ("Diving in now",
"Writing the script now", "On it.") and then ends the turn WITHOUT
calling any tool. The user has to nudge the session to unstall it.
Three independent conditions stack so false-positive risk is low:
(1)stop_reason=end_turn, (2) zerotool_useblocks in the
message content, (3) one of the commitment phrases appears in the
last ~250 chars of the message text. The Stop hook returns
decision=blockwith a correction asking the model to either
execute the action it described or ask a specific question. Honours
the same user-wrap-up bypass as the prose-pattern guard so an
"All done. On it." closing after the user said "wrap up" doesn't
trigger. Default on when stop_guard itself is enabled; opt out via
hooks.stop_guard.stall_check_enabled = false. New module entry
points:claude_hooks.stop_guard.check_stall_after_commitment,
COMMITMENT_PATTERNS,STALL_CORRECTION. 15 unit tests in
tests/test_stop_guard.py::StallAfterCommitmentTestsplus an
end-to-end smoke through_run_stop_guard. -
pgvector backup-validity canary — new
claude-hooks-pgvector-backup-check.{service,timer}runs every
Monday at 02:43 local and walks each retention tier
(daily/weekly/monthly), validating the most recent dump in two
layers: (1)pg_restore -lfor the TOC + metadata, (2)
pg_restore -f /dev/nullfor a full byte-read of the archive
(catches mid-file corruption that the TOC scan misses). Both
layers run inside themcp-pgvectorcontainer so the
pg_restore version always matches whatever wrote the dump.
Exits non-zero on any failure → wireable intoOnFailure=.
New script:scripts/pgvector_backup_check.sh. -
pgvector daily backup timer — new
claude-hooks-pgvector-backup.{service,timer}runs
pg_dump -Fcinside themcp-pgvectorcontainer at 01:17 local
every day and writes to/shared/config/mcp-pgvector/backups/
with three retention tiers: 7 daily, 4 weekly (promoted on
Sunday by hardlink), 3 monthly (promoted on day 1 by hardlink).
pg_dumptakes onlyACCESS SHARElocks so reads + writes are
not blocked during the backup. New scripts:
scripts/pgvector_backup.sh(the worker) and
scripts/pgvector_restore.sh(interactive restore helper with
latest_daily/latest_weekly/latest_monthlyshortcuts).
Wired intoinstall.py— installed whenproviders.pgvector.enabled
is true. Tunables:CONTAINER,PG_USER,PG_DB,BACKUP_DIR,
KEEP_DAILY,KEEP_WEEKLY,KEEP_MONTHLY,WEEKLY_DOW.
Late additions (post-2026-05-07 cut-prep work, landed 2026-05-08)
The 2026-05-07 batch above was complete but uncut — pyproject.toml
was bumped to 1.1.0 with a "prep for tag, not yet cut" commit
(f984d73). The day before the actual cut produced four more sets
of changes that landed under the same MINOR version because they're
all extensions of the v1.1 work above (cloud-flap recovery for the
new /consultants engine, install.py glue so the new skill CLIs
resolve on every platform, and a documentation pass for the v1.1
surface).
Added (2026-05-08)
-
/consultants — synthesizer fallback chain on persistent
failure — when the primary synthesizer model exhausts its
ChatClient retry budget on a cloud flap (HTTP 500 / 502 / 503 /
504 / 408 / 429), the engine now walkssynthesizer.extra_models
in order before declaring the consultation failed. Same
chat_client(so the same proxy + connection pool); only the
modelfield of the payload changes per attempt. First success
wins. Each attempt records anllm_callevent with the actual
model used, so post-hoc audit via/consultants--show <sid> --raw
reveals which model produced the final answer. Configure with
claude-consultants config set-role synthesizer --add-model <tag>. Active at every effort tier (not gated by the x-prefix —
cloud flaps don't care about effort). -
/consultants — degraded-answer composer on synthesizer
failure — when every model in the fallback chain fails, the
council now writes asummary.mdwhosefinal_answerfield
surfaces the researcher's full reports + the critic's verdict
rather than(consultation incomplete: synthesizer error: ...).
Researcher reports often run 3-5k tokens of analysis at xhigh
effort, and the critic verdict adds another 1k of structured
decision text — that's the most expensive work in a consultation
and now survives the synthesizer's failure to the user. The
banner explains it's a degraded answer (not a synthesized one)
and points the user atclaude-consultants follow-up <THIS_SID> --message "compose a final answer..."to recover cheaply (the
next synthesizer attempt inherits research + critic warm and
costs one more call, not a full re-run). -
/consultants--followup — failed-session-aware parent picker
— the skill now defaults to the most recent session of any
status (was: most recentcompletedonly). When the most recent
isfailed, AskUserQuestion offers two paths: (1) chain off the
failed sid (cheapest — researcher + critic threads inherit from
disk and only the synthesizer re-runs) or (2) chain off the
failed sid'sparent_sid(start over from a known-good thread).
Pairs with the engine-side fallback chain + degraded answer above
to make recovery from a cloud flap a one-step user action. -
/consultants--followup — dedicated sub-skill — the new fifth
member of the/consultantsskill family, exposes
claude-consultants follow-updirectly. Previously only
reachable via the underlying CLI or by asking Claude to dispatch
it manually; now/consultants--followup [<sid>] <question>is
a first-class skill with its own SKILL.md + activation guard +
failed-session handling. -
Cloud-model evaluation suite — full grading pass — every
label in the 2026-05-07 sweep now carries Claude-graded per-query- per-role grades + a verdict (PROD-READY / EVALUATED-ONLY) per
thedocs/benchmarks/EVALUATION.md
rubric. Three labels are PROD-READY at single-run with
P:A R:A C:A S:A:kimi-k2.6-cloud,gemma4-31b-cloud,
glm-5-1-cloud. Three are EVALUATED-ONLY usable in mixes for
specific roles where the per-role grade is A:
minimax-m2-7-cloud(strong critic),qwen3-5-397b-cloud
(strong planner + critic),qwen3-5-cloud(cheap sibling).
Headline matrix lives at the top ofdocs/benchmarks/index.md.
EVALUATION.md §3.5 was updated to clarify the grader is Claude
reading transcripts, not the human (the original "grader is the
human" wording contradicted the LLM-to-LLM workflow).
- per-role grades + a verdict (PROD-READY / EVALUATED-ONLY) per
-
User-facing v1.1 documentation pass — three new top-level
user runbooks landed:docs/get-advice.md
(351 lines: when to use, prereqs, the four sub-skills, model
picking, effort tiers, tools, common workflows, troubleshooting),
docs/consultants.md(639 lines: the
four-role council, x-tier multi-model fan-out semantics, service
modes, follow-ups + chaining + failed-session recovery, the
three-layer cloud-flap recovery story, configuration via
/consultants--config, picking models with explicit benchmark
links, troubleshooting), anddocs/whats-new.md
(276 lines: human-readable v1.1 highlights with the full benchmark
verdict matrix). README.md grew from 8 to 16 slash-command rows
with a new "Since" column flagging v1.1 additions, and a CLI
block per skill family. Install section grew from 6 to 8
numbered steps to cover the new bin/* PATH wrappers and the
opt-in /consultants conda env.
Changed (2026-05-08)
- ChatClient retry budget bumped from 8 attempts / ~136 s to 15
attempts / ~905 s (~15 min) —DEFAULT_MAX_RETRIES8 → 15 and
DEFAULT_RETRY_MAX_DELAY_S30 → 90 in
claude_hooks/get_advice/chat_client.py. The motivating session
(csl-2026-05-07-2158-7c75, xhigh effort) burned the whole
pre-bump budget on a 2+ minute Ollama Cloud 500 window and lost
the synthesizer outright; with the new budget a flap of that
shape is absorbed by the retry loop and the consultation
completes. A 5-15 minute consultation can now tolerate up to ~15
minutes of cloud unavailability without failing — the trade-off
being that an actual permanent outage takes longer to surface as
a user-visible error. Affects both/consultants(synthesizer- every other role's ChatClient) and
/get-advice(the advisor
itself). Override viaChatClient(..., max_retries=N, retry_max_delay_s=S)per call site if a cheaper budget is
desirable.
- every other role's ChatClient) and
Fixed (2026-05-08)
-
install.py — bin/ shim PATH wrappers (cross-platform)* —
skill CLIs (claude-consultants,claude-advisor, …) invoked by
bare name from a/consultants--configor/get-adviceskill
failed withcommand not foundbecause Claude Code's bash
subprocess does not include the repo'sbin/on PATH on any
platform. Symlinks don't fix it either: the shims resolveREPO
viadirname "$0", which through a symlink points at the symlink
dir (e.g./usr/local/bin/..) and the helper sourcing breaks.
Installer now drops thin exec-wrappers in a known PATH-friendly
location for all 11 shims (claude-hook,claude-consultants,
claude-advisor,claude-hooks-daemon,claude-hooks-daemon-ctl,
claude-hooks-proxy,claude-hooks-dashboard,
claude-hooks-rollup,caliber-grounding-proxy,caliber-smart,
claude-hook-pgvector-mcp):- POSIX (Linux + macOS):
~/.local/bin/<shim>— POSIX sh
wrapper thatexecs the absolute repo path. - Windows:
%LOCALAPPDATA%\claude-hooks\bin\<shim>(POSIX sh
for the MSYS bash that Claude Code uses) plus a.cmdsibling
for native cmd / PowerShell users.
Wrappers carry an install-time tag in their first comment line,
sopython install.pyis fully idempotent and won't clobber a
hand-rolled wrapper.python install.py --uninstallremoves only
tagged wrappers. Same root cause as the 2026-05-02 ruff PATH fix
— once the wrappers land, every bare-name invocation from a skill
resolves on every platform.
- POSIX (Linux + macOS):
-
install.py — Windows User PATH auto-prepend via
reg add—
for/consultantsand/get-adviceskills to actually resolve
on Windows the wrapper directory needs to be on User PATH that
Claude Code's bash subprocess inherits. Installer now prepends
%LOCALAPPDATA%\claude-hooks\bintoHKCU\Environment\PATH
usingreg add(NOTsetx—setxsilently truncates User
PATH to 1024 chars, which is destructive on any developer
machine), then broadcastsWM_SETTINGCHANGEso new processes
pick it up without a logoff. Defensive 16 KB ceiling on the
resulting PATH.