RFC: Optional tool lifecycle hooks for auditable execution receipts

Author: jsonwisdomCreated Aug 7, 2026Updated Sep 9, 2026

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:

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

python
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 policy

Argument 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 graph

QV_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.