microsoft / microsoft/agent-framework

Python: [Feature]: no signal when an `as_tool()` child agent has no agent-hooks bundle

Open
#8,050 1 comment 0 reactions 1 assignee View on GitHub

@eavanvalkenburg is already working on this.

Since Sep 4, 2026.

agents middleware observability python
Dominant language
Python
Stars
13.6k
Forks
2.3k
Avg merge
2d 45m
Merged PRs (30d)
358

Description

### Description

`Agent.as_tool()` lets an agent delegate to a child agent. agent-hooks enforcement is
installed per agent, so the child needs its own bundle — the caller's bundle does not
reach into the child's agent loop. `docs/INTEROP.md` already states the principle for
agent-to-agent delegation: "The remote agent's own loop — if it also implements
agent-hooks — is a separate session with its own `agent_startup`, records, and
identities."

For an in-process `as_tool()` child this is easy to get wrong by accident, and there is
no signal either way. The caller's records are complete and correct for what the caller
did — they show the delegation — so the run looks the same whether or not the child is
enforced.

A smaller case has the same shape: a bundle that is not first in `middleware`. The
`_agent_hooks` module docstring is explicit that middleware listed before the bundle runs
outside the enforcement boundary ("outer position is outer trust"), but nothing reports
it.

**What this is not.** This is not a vulnerability report and not a bypass of any
documented guarantee. agent-hooks does not claim complete mediation — spec §1.4 states
that normatively, and SECURITY.md adds that the CTK cannot detect bypass paths. Per-agent
installation is already documented in the `_agent_hooks` module docstring. Both
behaviours above are consistent with all of that.

I tested the documented enforcing configuration first and it behaved correctly: with one
bundle installed first in `enforce` mode, every side effect I observed out-of-process had
a causally preceding `pre_tool_call` permitting exactly that operation, including under
`transform`, checked against the framework's own `enforced_identity`.

The request is only that a documented configuration requirement be made harder to violate
accidentally.

**Expected behaviour.** Some signal when the enforcement a user installed does not extend
to a child agent they delegate to. Nothing that raises, refuses, or changes what is
enforced.

**Possible implementations** — entirely your call, and "no change" is a legitimate
answer:

- a log notice at run start for an unbundled `as_tool()` child, and optionally for a
non-first bundle;
- an explicit validator or helper a host can call itself;
- documentation at `Agent.as_tool()` and in the agent-hooks docstring;
- some other internal marker.

If an automatic notice is the direction, one caveat belongs in its own docs: **silence is
not proof of coverage.** It would report the configurations it knows about and nothing
else — direct tool execution, plugin hooks, background tasks and service-side tool
execution stay outside every interception point regardless. A signal that got read as a
completeness guarantee would be worse than no signal.

One detail relevant to any implementation: on `main` today nothing distinguishes a
`FunctionTool` produced by `as_tool()` from an ordinary one, so a marker of some kind
would be needed first. #5300 (closed unmerged) put related metadata in
`additional_properties` and drew review concern about override conflicts with nested
sub-agents, so a private attribute may be the safer shape.

**Prior work.** Related but distinct: #4158 and #6062 (.NET) ask for middleware/approval
propagation *into* nested tools, which is a larger and different feature. I did not find
an existing issue for detecting or reporting the missing installation.

I have a small working implementation of the log-notice option against `main` at
`02e68f93`, with tests for both configurations and their corrected counterparts. Happy to
open a PR if that shape is useful, or to drop it if you would rather solve it another way.

### Code Sample

```markdown
# Child agent: no bundle. Its own model chooses the arguments to the real tool.
worker = Agent(client=worker_client, name="worker", tools=[transfer_funds])
delegate = worker.as_tool(name="delegate", description="delegate a task to the worker")

# Caller: enforced, bundle first.
supervisor = Agent(
client=supervisor_client,
name="supervisor",
tools=[delegate],
middleware=[create_agent_hooks_middleware([guard], mode="enforce")],
)
await supervisor.run("delegate this")

On `main` at `02e68f93` (`agent-framework-core` 1.17.0, `agent-hooks-sdk` 0.1.0a5,
Python 3.12), with the model scripted and the tool real:

guard sees at pre_tool_call : delegate{'task': 'please help the user'}
tool that actually ran : transfer_funds(target='mallory', amount='999999')
pre_tool_call for transfer_funds : 0

The caller's session is a clean ten-point trace with every verdict `allow`.

Adding a bundle to `worker` resolves it: the child's own `pre_tool_call` fires carrying
exactly `transfer_funds(target='mallory', amount='999999')`.
```

### Language/SDK

Python

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.