Python: agent-framework-agent-hooks — middleware adapter for the AGENT-HOOKS-0.1 control contract - #7444
Conversation
Survey of how the eight interception points of the agent-hooks control contract (github.com/responsibleai/agent-hooks) land on the Python middleware pipeline: agent/chat/function middleware cover six points cleanly; the run brackets are synthesized with a session-per-run scope; streaming post-action points and session-scoped brackets are the two seams that would need upstream support. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
agent-framework-agent-hooks implements AGENT-HOOKS-0.1 on the middleware pipeline: the middleware trio emits the eight interception points (session-per-run scope), block verdicts terminate via MiddlewareTermination with the post-action result discarded, transforms write back through the context so execution uses the approved value, and composition/approval/identity/record semantics come from the published agent-hooks-sdk package. Tests cover allow/deny/transform flows and no-op behavior outside a bracketed run. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
Adopts the deny constructor introduced in 0.1.0a4 in place of wire dicts; transform and allow verdicts use the typed constructors. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
A raising tool invocation still emits post_tool_call with tool_result.is_error before the exception propagates, and tool results are projected to JSON-safe values before marshalling so an exotic result type cannot crash the record path. Covered by a new test. Signed-off-by: MohammadHaroonAbuomar <40180927+MohammadHaroonAbuomar@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Adds a new Python integration package (agent-framework-agent-hooks) that adapts Agent Framework’s middleware seams to the AGENT-HOOKS-0.1 control contract, enabling allow/deny/transform enforcement (fail-closed) across agent/chat/tool execution points.
Changes:
- Added a new workspace package implementing agent-hooks interception via Agent/Chat/Function middleware (
agent_hooks_middlewarefactory). - Added mapping and usage documentation plus MIT license for the new package.
- Added a pytest suite validating point sequencing, deny behavior, transform write-back, and no-op behavior outside a bracketed run.
Show a summary per file
| File | Description |
|---|---|
| python/pyproject.toml | Registers agent-framework-agent-hooks as a workspace source for uv resolution. |
| python/packages/agent-hooks/pyproject.toml | Defines the new distributable package metadata and dependencies. |
| python/packages/agent-hooks/agent_framework_agent_hooks/_middleware.py | Implements the AGENT-HOOKS-0.1 adapter across Agent/Chat/Function middleware tiers. |
| python/packages/agent-hooks/agent_framework_agent_hooks/init.py | Exports the public middleware types and factory. |
| python/packages/agent-hooks/agent_framework_agent_hooks/py.typed | Marks the package as typed for type checkers. |
| python/packages/agent-hooks/tests/test_agent_hooks_middleware.py | Adds unit tests covering allow/deny/transform behavior and sequencing. |
| python/packages/agent-hooks/README.md | Documents installation, usage, and the trust model. |
| python/packages/agent-hooks/MAPPING.md | Documents seam-by-seam mapping and known gaps (streaming/session/tool-call extraction). |
| python/packages/agent-hooks/LICENSE | Adds MIT license text for the new package. |
Review details
Comments suppressed due to low confidence (1)
python/packages/agent-hooks/agent_framework_agent_hooks/_middleware.py:198
- If output emission fails with an exception other than InterceptionBlocked, shutdown_reason is left as "completed", which can mislabel the agent_shutdown record. Mark shutdown_reason="error" for any exception during output emission before re-raising.
try:
await emitter.emit(builder.output(content=_result_content(context.result)))
except InterceptionBlocked as exc:
context.result = None
shutdown_reason = "error"
raise _terminate(exc) from exc
- Files reviewed: 8/9 changed files
- Comments generated: 4
- Review effort level: Lite
| finally: | ||
| # Shutdown blocks are record-only per the spec; nothing to halt. | ||
| with contextlib.suppress(InterceptionBlocked): | ||
| await emitter.emit(builder.agent_shutdown(reason=shutdown_reason)) | ||
| _RUN.reset(token) |
| try: | ||
| await emitter.emit(builder.agent_startup(tools_registered=tools)) | ||
| await emitter.emit(builder.input(content=_messages_to_wire(context.messages))) | ||
| except InterceptionBlocked as exc: | ||
| shutdown_reason = "error" | ||
| raise _terminate(exc) from exc | ||
|
|
| async def _run( | ||
| middlewares: list[Any], | ||
| fn_ctx: FunctionInvocationContext, | ||
| tool: Callable[[], Awaitable[None]] | None = None, | ||
| ) -> list[Any]: | ||
| """Drive the agent middleware bracket around one function invocation.""" | ||
| agent_mw, _, fn_mw = middlewares | ||
| records: list[Any] = [] | ||
|
|
||
| async def inner() -> None: | ||
| async def call_fn() -> None: | ||
| fn_ctx.result = "ok" | ||
|
|
||
| await fn_mw.process(fn_ctx, tool or call_fn) | ||
|
|
||
| agent_ctx = _agent_context() | ||
| await agent_mw.process(agent_ctx, inner) | ||
| return records | ||
|
|
| from agent_hooks import Decision, Verdict | ||
|
|
||
|
|
||
| class EgressGuard: | ||
| def intercept(self, context): | ||
| if context["interception_point"] != "pre_tool_call": | ||
| return {"decision": "allow"} | ||
| if "confidential" in str(context["target"]): | ||
| return {"decision": "deny", "reason": "egress_blocked"} | ||
| return {"decision": "allow"} |
|
Hi @MohammadHaroonAbuomar, this is something that needs to stem from an issue. The team must triage/prioritize it, and see if we even want to take it forward. Please start from that process first. |
|
Superseded by #7515, which implements the first-class core design requested in maintainer feedback (single |
Problem
Controls for agentic workloads (policy engines, approval flows, information-flow checks, audit pipelines) are rebuilt per framework, and each framework answers differently whether a control can stop an action, what happens when a control crashes, and what evidence exists afterwards. AGENT-HOOKS-0.1 (https://github.com/responsibleai/agent-hooks) is a framework-neutral control contract: eight interception points, a three-verdict model (allow / deny with optional human-approval lift / transform), normative fail-closed host obligations, and a conformance kit that makes "supported" checkable (https://responsibleai.github.io/agent-hooks/).
What this adds
A self-contained workspace package,
agent-framework-agent-hooks, following the layout of the existing integration packages. One factory wires the contract into the middleware pipeline:Middleware mapping (full analysis in MAPPING.md)
AgentMiddlewareinput/output(plus synthesizedagent_startup/agent_shutdownper run)ChatMiddlewarepre_model_call/post_model_callFunctionMiddlewarepre_tool_call/post_tool_call(transform writes back into executed arguments)All three tiers have native control semantics (
call_next,MiddlewareTermination, result override), so blocking and transforming compose with the framework rather than fighting it. Deny anywhere raisesMiddlewareTermination; adapter errors terminate the run (fail closed), never fall through.Limitations (deliberate, documented in MAPPING.md)
agent_startup/agent_shutdownbracket one agent run (a session-scoped seam is the concrete upstream ask).stream_result_hooksintegration (natural follow-up).post_model_calltool-call extraction is best-effort across client result shapes.Test evidence
5/5 pytest green against published
agent-hooks-sdk 0.1.0a4: run-point sequencing, deny-blocks-tool with post-point suppression and session-trail closure, transform write-back proving execution sees the approved value, and no-op behavior outside a bracketed run. Ruff clean under the repository configuration.Conformance
The contract ships a 47-vector conformance kit and a public claims registry; the reference policy runtime (https://github.com/responsibleai/agent-control-spec) passes 46/47 as an interceptor-side consumer. An Agent Framework host conformance claim would follow once the streaming and session seams land.