Skip to content

Python: agent-framework-agent-hooks — middleware adapter for the AGENT-HOOKS-0.1 control contract - #7444

Closed
MohammadHaroonAbuomar wants to merge 5 commits into
microsoft:mainfrom
MohammadHaroonAbuomar:feature/agent-hooks-contract
Closed

Python: agent-framework-agent-hooks — middleware adapter for the AGENT-HOOKS-0.1 control contract#7444
MohammadHaroonAbuomar wants to merge 5 commits into
microsoft:mainfrom
MohammadHaroonAbuomar:feature/agent-hooks-contract

Conversation

@MohammadHaroonAbuomar

Copy link
Copy Markdown

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:

agent = Agent(..., middleware=agent_hooks_middleware(interceptors=[policy], resolver=approvals))

Middleware mapping (full analysis in MAPPING.md)

Agent Framework seam Interception point
AgentMiddleware input / output (plus synthesized agent_startup / agent_shutdown per run)
ChatMiddleware pre_model_call / post_model_call
FunctionMiddleware pre_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 raises MiddlewareTermination; adapter errors terminate the run (fail closed), never fall through.

Limitations (deliberate, documented in MAPPING.md)

  • No framework lifecycle seam for session boundaries: agent_startup/agent_shutdown bracket one agent run (a session-scoped seam is the concrete upstream ask).
  • Streaming runs enforce pre-action points only; post/output enforcement over streams needs a stream_result_hooks integration (natural follow-up).
  • post_model_call tool-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.

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>
Copilot AI review requested due to automatic review settings July 30, 2026 21:39
@agent-framework-automation agent-framework-automation Bot added documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python labels Jul 30, 2026

Copilot AI 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.

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_middleware factory).
  • 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

Comment on lines +201 to +205
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)
Comment on lines +179 to +185
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

Comment on lines +62 to +80
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

Comment on lines +22 to +31
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"}
@moonbox3

Copy link
Copy Markdown
Contributor

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.

@MohammadHaroonAbuomar

Copy link
Copy Markdown
Author

Superseded by #7515, which implements the first-class core design requested in maintainer feedback (single agent_hooks_middleware() factory, private middleware trio, opt-in agent-hooks extra, transform write-back into native contexts, Content-preserving conversion, fully buffered fail-closed streaming). Closing this adapter-based draft in its favor.

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

Labels

documentation Usage: [Issues, PRs], Target: documentation in the code base and learn docs python Usage: [Issues, PRs], Target: Python

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants