RFC: Optional tool lifecycle hooks for auditable execution receipts
Summary
This RFC proposes optional lifecycle hooks around tool execution so users can attach audit logging, replay verification, and authorization policies without modifying model behavior or agent planning.
A working Phase 0 reference implementation is available here:
- Reference tree: https://github.com/jsonwisdom/AL/tree/build/qwen-replay-adapter-v0.1/Qwen/qwen-replay-adapter
- Reference PR: https://github.com/jsonwisdom/AL/pull/414
- Verified head SHA:
343a2c894ba74da6493e470fd864992680601be8 - GitHub Actions run: https://github.com/jsonwisdom/AL/actions/runs/31145821790
- Fixture manifest SHA-256:
fa738e8f5efb405d9917683be35c6c9d1ea3460a1fae5d659eefb4239619d117
The dedicated Qwen Replay Adapter CI workflow completed successfully. Both pytest -q and python scripts/verify_manifest.py passed on the verified head SHA.
Problem
Qwen-Agent users who need execution records currently have to compose wrappers around individual tools. Phase 0 proves this works at the BaseTool.call() boundary.
A minimal native lifecycle interface could remove the wrapper requirement while preserving existing semantics.
Proposed minimal interface
from typing import Protocol
class ToolLifecycleHook(Protocol):
def before_tool_call(
self,
*,
tool_name: str,
tool_args: str | dict,
context: dict,
) -> dict | None:
...
def after_tool_call(
self,
*,
tool_name: str,
tool_args: str | dict,
result: object,
context: dict,
metadata: dict | None,
) -> None:
...
def on_tool_error(
self,
*,
tool_name: str,
tool_args: str | dict,
error: Exception,
context: dict,
metadata: dict | None,
) -> None:
...Would this contract support current audit, replay, middleware, and approval-gate use cases? Would maintainers prefer the interception point at BaseTool.call(), the agent _call_tool() boundary, or both?
Behavioral boundaries
The reference design does not change:
- model output;
- planning logic;
- message formatting;
- streaming behavior;
- tool arguments or results.
It supports two explicit modes:
MODE_OBSERVE = behavior-preserving audit collection
MODE_ENFORCE = optional pre-execution denial by user policyArgument and result mutation are prohibited by design. Receipts contain both requested_arguments_hash and executed_arguments_hash; a mismatch without mutation_source fails verification.
Phase 0 evidence
The reference implementation verifies:
QV_001 normal tool execution
QV_002 canonical argument ordering
QV_003 explicit policy denial
QV_004 unchanged exception propagation
QV_005 deterministic nested call graphQV_005 proves nested relationship semantics only. Real parallel execution ordering is intentionally deferred to QV_006.
Related issues
- #854 proposes cryptographic signing for MCP tool-call receipts. This RFC addresses the execution-layer lifecycle and receipt boundary that signing could cover.
- #901 requests an optional approval/deny gate before Python execution. This RFC proposes a general pre-execution lifecycle hook that could support that class of control.
- #670 asks about callback behavior for MCP tools. This RFC asks whether a common tool lifecycle contract could serve those callback needs more generally.
Non-goals
This RFC does not propose:
- model-weight changes;
- changes to agent reasoning;
- mandatory policy enforcement;
- replacement of existing logging;
- a Qwen-specific compliance framework.
The goal is an optional lifecycle surface so users can add their own observability, replay, or authorization layers.
Contribution offer
The composition adapter can be used today without upstream changes. If maintainers are interested in a native lifecycle contract, we are prepared to contribute a minimal implementation, tests, and documentation, then migrate the adapter to consume the native hooks.
Feedback is especially welcome on the preferred interception boundary and whether message-level hooks are also required.
Source: QwenLM/Qwen-Agent