Skip to content

feat: add BEFORE_TOOL_DEFINITIONS hook to transform advertised tool defs#2477

Merged
MervinPraison merged 3 commits into
mainfrom
claude/issue-2473-20260630-0846
Jun 30, 2026
Merged

feat: add BEFORE_TOOL_DEFINITIONS hook to transform advertised tool defs#2477
MervinPraison merged 3 commits into
mainfrom
claude/issue-2473-20260630-0846

Conversation

@praisonai-triage-agent

@praisonai-triage-agent praisonai-triage-agent Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Fixes #2473

Summary

Adds a first-class, additive extension point that lets a hook/plugin inspect or rewrite the tool definitions (descriptions and parameter schemas) an agent advertises to the model for a given request, before they reach the LLM. This mirrors how BEFORE_LLM already lets a hook mutate messages in place, and is fully backward compatible (no hook registered → identical behaviour).

Changes

  • Core hook surface (praisonaiagents/hooks/):
    • New HookEvent.BEFORE_TOOL_DEFINITIONS enum value (types.py)
    • New BeforeToolDefinitionsInput dataclass carrying tool_definitions, model, plus read context (agent name, session id) (events.py)
    • Registered in __init__.py (__all__ + lazy group)
  • Runtime wiring (agent/chat_mixin.py):
    • New _apply_before_tool_definitions_hook() helper that fires the event after formatted_tools is assembled and adopts mutations
    • Wired into both sync and async dispatch paths; no-op when no hook runner or no tools
  • Plugin consumer surface (plugins/):
    • New before_tool_definitions() method on HookPluginProtocol and base Plugin
  • Tests (tests/unit/hooks/test_before_tool_definitions.py):
    • Event value + input serialization
    • A registered hook can drop a tool and edit a description; change is reflected
    • No-op when nothing registered
    • Agent integration adopts mutations via the helper

Layer placement

Primary layer is core (the tool payload is assembled in the agent runtime and only core can expose it at the right point). Plugins are the consumer via the new protocol method.

Test plan

  • New unit tests pass (6/6)
  • Existing hooks/plugin unit tests pass (1 pre-existing, unrelated failure: test_agent_execute_decorated_tool re: tool retry max_attempts, present on main)

Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Added a new hook point that lets plugins adjust tool definitions right before they are sent to the AI model.
    • Tool-definition hooks now work in both synchronous and asynchronous chat flows.
    • Expanded the plugin interface to support modifying advertised tools before a response is generated.
  • Tests

    • Added coverage for the new hook event, input payload, mutation behavior, and agent integration.

…efs (fixes #2473)

Adds a first-class, additive extension point that receives the assembled
tool definitions for a request and may modify or filter them before they
reach the LLM, mirroring how BEFORE_LLM lets a hook mutate messages.

- New HookEvent.BEFORE_TOOL_DEFINITIONS + BeforeToolDefinitionsInput dataclass
- Fired in chat_mixin.py (sync + async paths) right after formatted_tools is
  assembled; mutations are adopted, no-op when nothing registered
- New before_tool_definitions plugin protocol/base method for plugin consumers
- Unit tests for event, runner mutation, and agent integration

Co-authored-by: MervinPraison <MervinPraison@users.noreply.github.com>
@MervinPraison

Copy link
Copy Markdown
Owner

@coderabbitai review

@MervinPraison

Copy link
Copy Markdown
Owner

/review

@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@MervinPraison MervinPraison added pipeline/blocked:ci Blocked: CI not green on HEAD pipeline/blocked:no-final Blocked: no FINAL @claude trigger yet pipeline/final-claude-pending Reviews done; waiting for FINAL @claude labels Jun 30, 2026
@greptile-apps

greptile-apps Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a hook point for changing tool definitions before they are sent to the model. The main changes are:

  • A new BEFORE_TOOL_DEFINITIONS hook event and input payload.
  • Sync and async chat wiring for hook-registry based mutations.
  • A plugin API method for tool-definition changes.
  • Unit coverage for event data, in-place mutation, async execution, and cache isolation.

Confidence Score: 4/5

This is close, but the plugin-facing path should be fixed before merging.

  • Hook-registry based sync and async flows now apply the new event.
  • Plugin subclasses can define the new method, but the chat path does not call the plugin manager for this event.
  • Plugin-based tool filtering can be skipped while the model receives the original tools.

src/praisonai-agents/praisonaiagents/plugins/plugin.py and the plugin dispatch wiring

Important Files Changed

Filename Overview
src/praisonai-agents/praisonaiagents/agent/chat_mixin.py Adds sync and async helpers that run the hook-registry event after tool formatting.
src/praisonai-agents/praisonaiagents/hooks/events.py Adds the input payload for tool-definition hook data.
src/praisonai-agents/praisonaiagents/hooks/types.py Adds the new hook event name.
src/praisonai-agents/praisonaiagents/plugins/plugin.py Adds the plugin method for tool-definition changes, but the runtime path does not invoke plugin manager hooks for this event.
src/praisonai-agents/tests/unit/hooks/test_before_tool_definitions.py Adds tests for hook-registry behavior, async execution, and cache isolation.

Reviews (3): Last reviewed commit: "fix: address reviewer findings on BEFORE..." | Re-trigger Greptile

Comment thread src/praisonai-agents/praisonaiagents/agent/chat_mixin.py Outdated
Comment thread src/praisonai-agents/praisonaiagents/plugins/plugin.py
Comment thread src/praisonai-agents/praisonaiagents/hooks/events.py Outdated
@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: ee4939a5-4ba7-4c53-9dd1-4820e43e98a9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds a BEFORE_TOOL_DEFINITIONS hook event that fires after tool definitions are assembled and before the LLM call. Introduces HookEvent.BEFORE_TOOL_DEFINITIONS, BeforeToolDefinitionsInput dataclass, before_tool_definitions on Plugin and HookPluginProtocol, a ChatMixin._apply_before_tool_definitions_hook helper inserted in both sync and async chat paths, and unit tests.

Changes

BEFORE_TOOL_DEFINITIONS hook

Layer / File(s) Summary
Hook event, input dataclass, and plugin protocol
praisonaiagents/hooks/types.py, praisonaiagents/hooks/events.py, praisonaiagents/hooks/__init__.py, praisonaiagents/plugins/plugin.py, praisonaiagents/plugins/protocols.py
Adds HookEvent.BEFORE_TOOL_DEFINITIONS, BeforeToolDefinitionsInput dataclass (fields: tool_definitions, model; to_dict() emits count and model), exports it from the hooks package, and adds before_tool_definitions(tool_definitions) to both Plugin base class and HookPluginProtocol.
ChatMixin integration
praisonaiagents/agent/chat_mixin.py
Adds _apply_before_tool_definitions_hook method that builds BeforeToolDefinitionsInput, runs the hook runner, and returns the possibly-mutated list; inserts the call in both sync _chat_completion and async _achat_impl immediately after _format_tools_for_completion.
Unit tests
tests/unit/hooks/test_before_tool_definitions.py
Tests verify the event string value, to_dict() fields, hook-driven tool mutation (drop and description edit), no-op behavior with no registered hook, and Agent._apply_before_tool_definitions_hook end-to-end.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

A rabbit hops in before the LLM's glance,
reshaping the tools in a last-minute dance.
Drop one, rename another, the list is reborn—
no schemas shall pass without a hook-worn horn.
🐇✨ The definitions are yours to transform!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 43.48% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly describes the new BEFORE_TOOL_DEFINITIONS hook and its purpose.
Linked Issues check ✅ Passed The PR adds the requested tool-definition hook, wires it into chat flow, exposes the plugin method, and includes mutation tests.
Out of Scope Changes check ✅ Passed The changes stay focused on the new tool-definition hook and related tests, with no obvious unrelated additions.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/issue-2473-20260630-0846

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MervinPraison

Copy link
Copy Markdown
Owner

@copilot Do a thorough review of this PR. Read ALL existing reviewer comments above from Qodo, Coderabbit, and Gemini first — incorporate their findings.

Review areas:

  1. Bloat check: Are changes minimal and focused? Any unnecessary code or scope creep?
  2. Security: Any hardcoded secrets, unsafe eval/exec, missing input validation?
  3. Performance: Any module-level heavy imports? Hot-path regressions?
  4. Tests: Are tests included? Do they cover the changes adequately?
  5. Backward compat: Any public API changes without deprecation?
  6. Code quality: DRY violations, naming conventions, error handling?
  7. Address reviewer feedback: If Qodo, Coderabbit, or Gemini flagged valid issues, include them in your review
  8. Suggest specific improvements with code examples where possible

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
src/praisonai-agents/tests/unit/hooks/test_before_tool_definitions.py (1)

105-127: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider a regression test for cache reuse.

The integration tests call _apply_before_tool_definitions_hook once. Adding a test that invokes the agent's tool-formatting + hook path twice and asserts the second call still sees the full tool set would have caught the cache-mutation issue flagged in chat_mixin.py. Optional, but valuable as a guard.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai-agents/tests/unit/hooks/test_before_tool_definitions.py` around
lines 105 - 127, Add a regression test around Agent’s tool-formatting and
BEFORE_TOOL_DEFINITIONS hook path to catch cache reuse mutations. In the
TestAgentIntegration suite, use Agent with HookRegistry and a hook like
drop_delete, then invoke the same formatting/hook flow twice and assert the
second call still starts from the full tool list rather than the mutated result
from the first call. Reuse the existing _apply_before_tool_definitions_hook and
_sample_tools helpers to keep the test focused on the cache-mutation behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/praisonai-agents/praisonaiagents/agent/chat_mixin.py`:
- Around line 543-554: The BEFORE_TOOL_DEFINITIONS hook in `ChatMixin` is
receiving the cached `formatted_tools` list by reference, so in-place hook
mutations are leaking back into `_format_tools_for_completion`’s cache and
affecting later calls. Update the `BeforeToolDefinitionsInput` construction to
pass a defensive copy of `formatted_tools` (and its nested tool dicts if needed)
before calling `self._hook_runner.execute_sync`, while keeping the returned
`_inp.tool_definitions` as the adopted per-request value.

---

Nitpick comments:
In `@src/praisonai-agents/tests/unit/hooks/test_before_tool_definitions.py`:
- Around line 105-127: Add a regression test around Agent’s tool-formatting and
BEFORE_TOOL_DEFINITIONS hook path to catch cache reuse mutations. In the
TestAgentIntegration suite, use Agent with HookRegistry and a hook like
drop_delete, then invoke the same formatting/hook flow twice and assert the
second call still starts from the full tool list rather than the mutated result
from the first call. Reuse the existing _apply_before_tool_definitions_hook and
_sample_tools helpers to keep the test focused on the cache-mutation behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: fd31fb63-ee1d-4e99-87a0-08c7e0520732

📥 Commits

Reviewing files that changed from the base of the PR and between 29ece53 and 7fe7163.

📒 Files selected for processing (7)
  • src/praisonai-agents/praisonaiagents/agent/chat_mixin.py
  • src/praisonai-agents/praisonaiagents/hooks/__init__.py
  • src/praisonai-agents/praisonaiagents/hooks/events.py
  • src/praisonai-agents/praisonaiagents/hooks/types.py
  • src/praisonai-agents/praisonaiagents/plugins/plugin.py
  • src/praisonai-agents/praisonaiagents/plugins/protocols.py
  • src/praisonai-agents/tests/unit/hooks/test_before_tool_definitions.py

Comment on lines +543 to +554
_inp = BeforeToolDefinitionsInput(
session_id=getattr(self, '_session_id', 'default'),
cwd=os.getcwd(),
event_name=HookEvent.BEFORE_TOOL_DEFINITIONS,
timestamp=str(time.time()),
agent_name=self.name,
model=self.llm if isinstance(self.llm, str) else str(self.llm),
tool_definitions=formatted_tools,
)
self._hook_runner.execute_sync(HookEvent.BEFORE_TOOL_DEFINITIONS, _inp)
# Adopt mutations, mirroring how BEFORE_LLM adopts messages.
return _inp.tool_definitions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Hook mutations poison the formatted-tools cache.

_format_tools_for_completion caches and returns the same cleaned_tools list object it stores (Lines 524-529). Here you pass that exact object as tool_definitions, and registered hooks mutate it in place (e.g. data.tool_definitions[:] = [...] or t["function"]["description"] += ... as in the tests). Because the hook mutates the cached list/dicts, the changes persist into the cache and bleed into every subsequent request sharing the cache key — dropped tools stay dropped and appended description text accumulates each call. This breaks the intended per-request semantics.

Pass a copy into the hook so the cache is never mutated:

🛡️ Proposed fix
         if not formatted_tools or not getattr(self, '_hook_runner', None):
             return formatted_tools
         try:
-            from ..hooks import HookEvent, BeforeToolDefinitionsInput
+            import copy
+            from ..hooks import HookEvent, BeforeToolDefinitionsInput
             _inp = BeforeToolDefinitionsInput(
                 session_id=getattr(self, '_session_id', 'default'),
                 cwd=os.getcwd(),
                 event_name=HookEvent.BEFORE_TOOL_DEFINITIONS,
                 timestamp=str(time.time()),
                 agent_name=self.name,
                 model=self.llm if isinstance(self.llm, str) else str(self.llm),
-                tool_definitions=formatted_tools,
+                tool_definitions=copy.deepcopy(formatted_tools),
             )
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_inp = BeforeToolDefinitionsInput(
session_id=getattr(self, '_session_id', 'default'),
cwd=os.getcwd(),
event_name=HookEvent.BEFORE_TOOL_DEFINITIONS,
timestamp=str(time.time()),
agent_name=self.name,
model=self.llm if isinstance(self.llm, str) else str(self.llm),
tool_definitions=formatted_tools,
)
self._hook_runner.execute_sync(HookEvent.BEFORE_TOOL_DEFINITIONS, _inp)
# Adopt mutations, mirroring how BEFORE_LLM adopts messages.
return _inp.tool_definitions
import copy
from ..hooks import HookEvent, BeforeToolDefinitionsInput
_inp = BeforeToolDefinitionsInput(
session_id=getattr(self, '_session_id', 'default'),
cwd=os.getcwd(),
event_name=HookEvent.BEFORE_TOOL_DEFINITIONS,
timestamp=str(time.time()),
agent_name=self.name,
model=self.llm if isinstance(self.llm, str) else str(self.llm),
tool_definitions=copy.deepcopy(formatted_tools),
)
self._hook_runner.execute_sync(HookEvent.BEFORE_TOOL_DEFINITIONS, _inp)
# Adopt mutations, mirroring how BEFORE_LLM adopts messages.
return _inp.tool_definitions
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/praisonai-agents/praisonaiagents/agent/chat_mixin.py` around lines 543 -
554, The BEFORE_TOOL_DEFINITIONS hook in `ChatMixin` is receiving the cached
`formatted_tools` list by reference, so in-place hook mutations are leaking back
into `_format_tools_for_completion`’s cache and affecting later calls. Update
the `BeforeToolDefinitionsInput` construction to pass a defensive copy of
`formatted_tools` (and its nested tool dicts if needed) before calling
`self._hook_runner.execute_sync`, while keeping the returned
`_inp.tool_definitions` as the adopted per-request value.

@MervinPraison

Copy link
Copy Markdown
Owner

@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.

Phase 1: Review per AGENTS.md

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.

Phase 2: FIX Valid Issues
5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
6. Push all code fixes directly to THIS branch (do NOT create a new PR)
7. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
8. If all issues are resolved, approve the PR / close the Issue
9. If blocking issues remain, request changes / leave clear action items

@MervinPraison MervinPraison added pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI pipeline/blocked:cooldown Blocked: post-push or @claude cooldown and removed pipeline/final-claude-pending Reviews done; waiting for FINAL @claude pipeline/blocked:no-final Blocked: no FINAL @claude trigger yet labels Jun 30, 2026
@praisonai-triage-agent

praisonai-triage-agent Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #2477 (BEFORE_TOOL_DEFINITIONS hook)

Verdict: ✅ Approved — fixes pushed to claude/issue-2473-20260630-0846 (commit 7632cf9b). All 176 hooks + plugins unit tests pass.

Phase 1 — Architecture (per AGENTS.md)

  • Protocol-driven: ✅ Hook surface lives in core; no heavy impl added. Mirrors existing BEFORE_LLM in-place mutation pattern.
  • SDK value: ✅ Genuine, additive extension point (per-request tool redaction / schema constraint / description annotation). No new Agent params, no agent.py bloat (logic is in chat_mixin).
  • Backward compatible: ⚠️→✅ Found & fixed a regression (see below). No hook = identical behaviour.
  • Performance: ✅ Fast-path no-ops when no hook runner / no tools. deepcopy only runs when a hook is actually registered.

Phase 2 — Valid issues fixed

# Reviewer Issue Fix
1 CodeRabbit (Major) Hook mutated the cached formatted_tools list in place → poisoned cache across requests _build_before_tool_definitions_input now deepcopys tool defs before the hook; cache stays pristine
2 Greptile (P1) Async path called execute_sync from a running loop → raised & silently sent unchanged tools Added _aapply_before_tool_definitions_hook using await self._hook_runner.execute(...); wired into _achat_impl
3 Greptile (P2) events.py docstring promised modified_input return path that the default (parallel) dispatch never applies Docstring corrected to document in-place mutation only (matches BEFORE_LLM)
4 (found in review) Adding before_tool_definitions as a required member of runtime-checkable HookPluginProtocol broke isinstance() for existing plugins → test_protocols.py regression Reverted the protocol addition; base Plugin.before_tool_definitions() (additive default) retained

New regression tests added:

  • test_apply_before_tool_definitions_hook_does_not_mutate_input (cache-poisoning guard, per CodeRabbit nitpick)
  • test_aapply_before_tool_definitions_hook_runs_in_event_loop (async path actually runs the hook)

Skipped (with reasoning)

  • Greptile P1 "Plugin method unreachable": The agent runtime does not invoke PluginManager.execute_hook for any lifecycle event (before_llm, before_tool, etc. are all equally un-wired today). Bridging the entire PluginManager into the agent dispatch is a pre-existing, separate concern and would be scope creep for this PR. The Plugin base method remains available for the existing plugin-manager dispatch path; no new false promise is introduced.

Files modified

  • praisonaiagents/agent/chat_mixin.py — deepcopy helper + async hook variant + async call-site
  • praisonaiagents/hooks/events.py — docstring correction
  • praisonaiagents/plugins/protocols.py — reverted protocol method (backward-compat)
  • tests/unit/hooks/test_before_tool_definitions.py — 2 regression tests

Out of scope honored: no changes to praisonai-rust / praisonai-ts.

--- · branch claude/issue-2473-20260630-0846

- deep-copy tool defs before hook to stop cache poisoning (CodeRabbit)
- add async helper using await execute so async chat runs the hook (Greptile P1)
- correct events.py docstring to only promise in-place mutation (Greptile P2)
- revert protocol method addition that broke HookPluginProtocol isinstance (backward-compat)
- add cache-reuse and async regression tests

Co-authored-by: Mervin Praison <MervinPraison@users.noreply.github.com>
@MervinPraison

Copy link
Copy Markdown
Owner

@claude You are the FINAL architecture reviewer. If the branch is under MervinPraison/PraisonAI (not a fork), you are able to make modifications to this branch and push directly. SCOPE: Focus ONLY on Python packages (praisonaiagents, praisonai). Do NOT modify praisonai-rust or praisonai-ts. Read ALL comments above from Gemini, Qodo, CodeRabbit, and Copilot carefully before responding.

Phase 1: Review per AGENTS.md

  1. Protocol-driven: check heavy implementations vs core SDK
  2. Backward compatible: ensure zero feature regressions
  3. Performance: no hot-path regressions
  4. SDK value: review in depth whether the change genuinely adds value to the SDK — never add features for the sake of adding them. It must strengthen the SDK (simpler, more user-friendly, robust, world-class, secure). If it does not clearly add value, request changes or recommend rejecting/closing rather than merging scope creep
  5. Do not bloat the Agent class with additional params — only if absolutely required; we already support many params.

Phase 2: FIX Valid Issues
5. For any VALID bugs or architectural flaws found by Gemini, CodeRabbit, Qodo, Copilot, or any other reviewer: implement the fix
6. Push all code fixes directly to THIS branch (do NOT create a new PR)
7. Comment a summary of exact files modified and what you skipped

Phase 3: Final Verdict
8. If all issues are resolved, approve the PR / close the Issue
9. If blocking issues remain, request changes / leave clear action items

@praisonai-triage-agent

praisonai-triage-agent Bot commented Jun 30, 2026

Copy link
Copy Markdown
Contributor Author

Claude finished @MervinPraison's task —— View job


Final Architecture Review — PR #2477 (BEFORE_TOOL_DEFINITIONS hook)

Verdict: ✅ Approved — no further code changes required. The fixes from commit 7632cf9b are all present and correct; all 8 unit tests pass (verified in this run).

  • Read AGENTS.md guidelines
  • Verify previous fixes (commit 7632cf9b) are present
  • Review current code for remaining valid issues
  • Run tests (8/8 pass)
  • Push fixes if needed — none required
  • Final verdict

Phase 1 — Architecture (per AGENTS.md)

  • Protocol-driven: ✅ Hook surface lives in core (hooks/); no heavy implementation added. Mirrors the existing BEFORE_LLM in-place mutation pattern.
  • No Agent bloat: ✅ Zero new Agent params. Logic lives entirely in chat_mixin.py helpers, not agent.py.
  • Backward compatible: ✅ No hook registered → identical behaviour. The runtime-checkable HookPluginProtocol was not extended (would have broken isinstance() for existing plugins), so existing plugins keep working.
  • Performance: ✅ Fast-path no-op when no hook runner / no tools. deepcopy only runs when a hook is actually registered — no hot-path regression for the common (no-hook) case.
  • SDK value: ✅ Genuine additive extension point (per-request tool redaction / schema constraint / description annotation) that fills a real gap symmetric to BEFORE_LLM.

Phase 2 — Reviewer issues (all verified resolved)

# Reviewer Issue Status
1 CodeRabbit (Major) Hook mutated the cached formatted_tools in place → cache poisoning ✅ Fixed — _build_before_tool_definitions_input deepcopys before the hook (chat_mixin.py:538); regression test test_apply_before_tool_definitions_hook_does_not_mutate_input guards it
2 Greptile (P1) Async path called execute_sync from a running loop → raised, tools sent unchanged ✅ Fixed — _aapply_before_tool_definitions_hook awaits execute() (chat_mixin.py:571), wired at chat_mixin.py:2930; test test_aapply_before_tool_definitions_hook_runs_in_event_loop runs via real asyncio.run (not skipped)
3 Greptile (P2) events.py docstring promised a modified_input return path the default dispatch never applies ✅ Fixed — docstring now documents in-place mutation only (events.py:50-60)
4 (found in prior review) Adding the method to runtime-checkable HookPluginProtocol broke isinstance() for existing plugins ✅ Fixed — protocol addition reverted; protocols.py unchanged

Skipped (with reasoning)

  • Greptile P1 "Plugin method unreachable" — Verified the agent runtime invokes no Plugin lifecycle method (before_llm, before_tool, etc.) directly; they are all dispatched only through the separate PluginManager. So this applies equally to every pre-existing plugin method and is an existing architectural concern — bridging PluginManager into agent dispatch would be scope creep. The base Plugin.before_tool_definitions() simply preserves symmetry with the existing plugin surface. Correctly out of scope.

Test results (this run)

8 passed in 0.18s

All hook + agent-integration tests pass, including both the cache-poisoning guard and the async-loop regression test (confirmed executing, not skipped).

Conclusion

Minimal, focused, backward-compatible, and well-tested. No bloat, no hot-path regression, no Agent params added. Approved — ready to merge.
· branch claude/issue-2473-20260630-0846

Comment on lines +109 to +114
def before_tool_definitions(
self, tool_definitions: List[Dict[str, Any]]
) -> List[Dict[str, Any]]:
"""Called after advertised tool definitions are assembled and before
they reach the LLM. Can inspect, filter, or rewrite them."""
return tool_definitions

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Plugin Hook Unreachable

This method is still disconnected from the runtime path that sends tool definitions to the model. The chat flow fires BEFORE_TOOL_DEFINITIONS through the hook runner and hook registry, while plugin subclasses are invoked through the plugin manager. A plugin can implement before_tool_definitions() and declare the hook, but its edits are never called before the LLM receives the tools. Plugin-based schema filtering or redaction will be silently ignored unless this event is bridged into the plugin dispatch path.

@MervinPraison MervinPraison removed the pipeline/blocked:cooldown Blocked: post-push or @claude cooldown label Jun 30, 2026
@MervinPraison MervinPraison merged commit 6a41e64 into main Jun 30, 2026
43 of 44 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

pipeline/awaiting-merge-gate FINAL done; waiting for merge gate / CI pipeline/blocked:ci Blocked: CI not green on HEAD

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add an extension point to transform advertised tool definitions before the LLM call

1 participant