Skip to content

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

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

# 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

Activity

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

Metadata

Metadata

Labels

agentsUsage: [Issues, PRs], Target: Single agentmiddlewareUsage: [Issues, PRs], Target: middleware related featuresobservabilityUsage: [Issues, PRs], Target: observability related featurespythonUsage: [Issues, PRs], Target: Python

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions