Skip to content

fix(hook): bound soft nudges per agent (companion of Graphify-Labs/graphify#3893) - #12

Open
andrebrait wants to merge 2 commits into
v8from
fix/per-agent-hook-nudges
Open

andrebrait wants to merge 2 commits into
v8from
fix/per-agent-hook-nudges

Conversation

@andrebrait

Copy link
Copy Markdown
Member

Companion of Graphify-Labs#3893 for CI and bot review; same head (fix/per-agent-hook-nudges) and base (v8, synced to upstream d6eaa8a). Review discussion that affects the change is mirrored upstream.

What does this PR do?

Carries Graphify-Labs#3443 by @L4XB onto the current v8 base, preserving its commit authorship and message, then adds one follow-up commit that closes three gaps found while running it downstream. Refs Graphify-Labs#3435, Graphify-Labs#3756, Graphify-Labs#2984.

Relationship to Graphify-Labs#3443

The follow-up

  1. Subagents get their own budget. Claude Code subagent tool calls carry the parent's session_id and are distinguished only by agent_id (hooks reference). Keying the counter on session_id alone lets the parent exhaust the budget before a subagent, which starts with an empty context, sees a nudge. The marker is now keyed on session_id plus a 16-character SHA-256 prefix of agent_id when present, so long ids cannot truncate two agents into one key.
  2. The query stamp no longer silences agents that never queried. last_query_stamp is project-wide and cannot say which agent ran query/explain/path. Each agent's first nudge now fires regardless of the stamp; the stamp silences the later ones. Calls without a session_id keep the stamp-only behavior.
  3. The stale-graph notice is bounded. _READ_NUDGE_STALE fired on every read of a file edited after the last build. It now draws on the same per-agent budget.

_nudge_allowed takes the hook payload instead of a session id so it can read agent_id. The strict-mode deny is unchanged.

Type of change

  • Bug fix

Verification & Invariants

Invariants: each agent (session, or subagent within it) sees its first nudge; after that, nudges stop while the query stamp is fresh and after GRAPHIFY_HOOK_NUDGE_CAP per agent; the strict deny still fires at most once per session; any marker error fails open.

test_fresh_query_stamp_suppresses_deny changes accordingly: with a fresh stamp there is still no deny, the session's first nudge fires, and the second read is silent. New tests cover per-agent budgets (including 64-character session ids), a subagent's first nudge after the parent queried, the stamp without a session_id, and the stale notice sharing the budget. They were run red against Graphify-Labs#3443 alone before the follow-up.

Known limitation (inherited from Graphify-Labs#3443): the counter is an unlocked read-modify-write, so concurrent hook processes for the same agent can exceed the cap slightly. They cannot suppress an agent's first nudge.

  • Read the CONTRIBUTING.md guide.
  • Reproduced the issue and identified the invariant.
  • Made the smallest fix necessary.
  • Added a regression test (if bug fix) or isolated boundary test.
  • Kept the PR description synchronized with the final implementation.
  • Documented any limitations / unsupported cases explicitly.

How was this tested?

uv run pytest tests/test_hook_guard.py tests/test_hook_strict.py tests/test_hook_guard_token_match.py tests/test_hook_out_of_project_paths.py tests/test_install_strings.py
  165 passed, 4 skipped
uv run pytest -q
  24 failed, 5961 passed, 100 skipped (all 24 also fail on unmodified v8 here: optional
  Erlang/R/Solidity/VB.NET extractors and Ollama backend not installed locally)
uv run ruff check .
  All checks passed!

Also deployed downstream and checked live under Oh My Pi: after graphify query, the parent session's next grep was silent, and a freshly spawned subagent still received its first nudge, with separate markers under graphify-out/cache/hook_sessions/.

Graphify-specific checklist

  • I updated generated skill artifacts (uv run python -m tools.skillgen --bless) when changing their source fragments. (Not applicable.)
  • I confirmed that AST/structural extraction remains deterministic (no ambient state dependencies like ENV variables).
  • I reviewed changes for security implications (no unsafe interpolation into shell/Python).
  • I confirmed no API keys or local-only graph data are included.

@github-actions

Copy link
Copy Markdown

Thanks for the pull request, @andrebrait. A maintainer will review it soon.

Want to talk it through while it is in review? Come join us on our Discord server. For longer-form discussion there is also GitHub Discussions.

A couple of things that speed up review: make sure the test suite passes on Python 3.10 and 3.13, and that the change keeps extraction deterministic.

@andrebrait
andrebrait requested a lite review from Copilot September 28, 2026 11:08

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot was unable to review this pull request because the user who requested the review has reached their quota limit.

L4XB and others added 2 commits September 29, 2026 12:01
The PreToolUse guard re-injected the MANDATORY read/search nudge on every
qualifying Read/Glob/Grep/Bash call with no cap and no check whether the
agent had just queried the graph. Only the opt-in strict deny was capped.
One project measured 8,348 injections (~651k tokens) for 339 graphify
calls, several times the cost of every query they were asking for.

Skip the soft nudge while the query stamp is fresh (the agent is already
oriented) and cap it at GRAPHIFY_HOOK_NUDGE_CAP nudges per session
(default 5, 0 disables), counted in a per-session marker next to the
strict-mode ones. Calls without a session id are uncounted, the guard
still fails open, and the shared marker GC now runs on both paths.

Fixes Graphify-Labs#3435
Subagents start with an empty context, but Claude Code gives them the
parent's session_id; keying the nudge budget on session_id alone let a
parent exhaust a subagent's budget. Key it on session_id plus a hash of
agent_id when present.

The query stamp is project-wide and cannot say which agent queried, so it
no longer silences an agent's first nudge; it silences later ones. The
stale-graph notice now draws on the same budget instead of firing on every
read of an edited file.
@andrebrait
andrebrait force-pushed the fix/per-agent-hook-nudges branch from 17a2822 to 158f035 Compare September 29, 2026 12:01

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants