microsoft / microsoft/agent-framework
Python: [Feature]: no signal when an `as_tool()` child agent has no agent-hooks bundle
@eavanvalkenburg is already working on this.
Since Sep 4, 2026.
- 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
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.