forked from phil65/agentpool
-
Notifications
You must be signed in to change notification settings - Fork 9
refactor(agents): Phase 2 — unify run_stream to RunExecutor #95
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Million-mo
merged 5 commits into
wolf1069b:refactor/thin-wrapper
from
Million-mo:refactor/phase2-run-stream
Jul 3, 2026
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
98975d7
fix(orchestrator): address 8 Gemini Code Assist review comments on PR…
2ef83a6
chore: limit pre-commit pytest to orchestrator subset (full suite in CI)
abf58ba
refactor(agents): Phase 2 — simplify run_stream producer/consumer to …
Million-mo f5e114c
fix(orchestrator): restore producer/consumer pattern + fix ruff lint
Million-mo a0b1890
test(agents): verify node-level Capability hooks on standalone run path
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,90 @@ | ||
| """Tests verifying pdai Capability hooks fire on standalone run path. | ||
|
|
||
| Phase 2 of thin-wrapper refactor: BaseAgent.run_stream() now delegates | ||
| directly to _run_stream_once() → _stream_events() → NativeTurn.execute() | ||
| which calls agent_run.next(node) explicitly, ensuring all pdai Capability | ||
| hooks fire on every run path. | ||
|
|
||
| This test verifies both run-level (`wrap_run`) and node-level | ||
| (`wrap_node_run`, `before_model_request`, `after_node_run`) hooks fire. | ||
| The node-level hooks are the core differentiator of Phase 2: they require | ||
| `agent_run.next(node)` to be called explicitly (rather than a bare | ||
| `async for` over the agent run, which would skip node-level hooks). | ||
| """ | ||
|
|
||
| from __future__ import annotations | ||
|
|
||
| from typing import Any | ||
|
|
||
| from pydantic_ai.capabilities import AbstractCapability | ||
| import pytest | ||
|
|
||
| from agentpool.agents.native_agent.agent import Agent | ||
| from agentpool.models.agents import NativeAgentConfig | ||
|
|
||
|
|
||
| class HookTrackerCapability(AbstractCapability[Any]): | ||
| """Capability that records when run- and node-level hooks are called.""" | ||
|
|
||
| def __init__(self) -> None: | ||
| super().__init__() | ||
| self.wrap_run_called = False | ||
| self.wrap_node_run_called = False | ||
| self.before_model_request_called = False | ||
| self.after_node_run_called = False | ||
|
|
||
| async def wrap_run(self, ctx: Any, *, handler: Any) -> Any: | ||
| self.wrap_run_called = True | ||
| return await handler() | ||
|
|
||
| async def wrap_node_run(self, ctx: Any, *, node: Any, handler: Any) -> Any: | ||
| self.wrap_node_run_called = True | ||
| return await handler(node) | ||
|
|
||
| async def before_model_request(self, ctx: Any, request_context: Any) -> Any: | ||
| self.before_model_request_called = True | ||
| return request_context | ||
|
|
||
| async def after_node_run(self, ctx: Any, *, node: Any, result: Any) -> Any: | ||
| self.after_node_run_called = True | ||
| return result | ||
|
|
||
|
|
||
| pytestmark = [pytest.mark.unit, pytest.mark.anyio] | ||
|
|
||
|
|
||
| async def test_capability_hooks_fire_on_standalone_run() -> None: | ||
| """All Capability hooks SHALL fire on standalone run_stream(). | ||
|
|
||
| Verifies the core Phase 2 invariant: `agent_run.next(node)` is invoked | ||
| on the standalone path, triggering node-level hooks. A bare | ||
| `async for` over the agent run would skip these hooks. | ||
| """ | ||
| tracker = HookTrackerCapability() | ||
| config = NativeAgentConfig( | ||
| name="test-agent", | ||
| model="test:test", | ||
| system_prompt="You are a test agent.", | ||
| capabilities=[tracker], | ||
| ) | ||
| agent: Agent[Any, Any] = Agent( | ||
| name="test-agent", | ||
| model="test:test", | ||
| agent_config=config, | ||
| ) | ||
|
|
||
| async with agent: | ||
| async for _event in agent.run_stream("Hello"): | ||
| pass | ||
|
|
||
| assert tracker.wrap_run_called, "wrap_run hook did not fire on standalone run_stream() path" | ||
| assert tracker.wrap_node_run_called, ( | ||
| "wrap_node_run hook did not fire on standalone run_stream() path — " | ||
| "agent_run.next(node) may not be called on this path" | ||
| ) | ||
| assert tracker.before_model_request_called, ( | ||
| "before_model_request hook did not fire on standalone run_stream() path" | ||
| ) | ||
| assert tracker.after_node_run_called, ( | ||
| "after_node_run hook did not fire on standalone run_stream() path" | ||
| ) | ||
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The test currently only implements and tracks the
wrap_runhook. However,wrap_runis a run-level hook that wraps the entire execution and is triggered byagentlet.iter(), meaning it likely would have fired even on the legacy path. The core objective of Phase 2 is to ensure that step/node-level hooks (wrap_node_run,before_model_request,after_node_run) are correctly fired on the standalone run path (which requires callingagent_run.next(node)instead of bareasync foriteration). To properly verify this, the mock capability should implement and track these node-level hooks as well.