Skip to content

feat(autogen-core): add gen_ai.agent.action_ref to trace_tool_span for cross-producer audit correlation #7850

Description

@giskard09

Summary

Propose adding gen_ai.agent.action_ref — a deterministic, recomputable SHA-256 handle — as an optional span attribute in trace_tool_span() inside autogen_core/_telemetry/_genai.py.

Motivation

trace_tool_span() today emits gen_ai.tool.name, gen_ai.tool.call.id, and gen_ai.tool.description. These are producer-scoped: a consumer comparing spans across two independent producers, or auditing a span without trusting the emitting runtime, has no recomputable correlation key.

This gap was raised in #7405 (GuardrailProvider) by multiple contributors, including @rpelevin:

"canonical action_ref: stable identity of the action class and actor, with JCS-normalized consequential input."

The eight attributes proposed in open-telemetry/semantic-conventions-genai#291 address the same producer-scoped gap. A ninth attribute — gen_ai.agent.action_ref — closes the cross-producer case.

Proposed Change

Three additions to autogen_core/_telemetry/_genai.py:

1. Constant:

GEN_AI_AGENT_ACTION_REF = "gen_ai.agent.action_ref"

2. Derivation helper:

def derive_action_ref(
    agent_id: str,
    action_type: str,
    scope: str,
    timestamp_ms: int,
) -> str:
    """Derive action_ref per action-ref-v1.

    Canonical derivation: SHA-256(RFC 8785 JCS({agent_id, action_type, scope, timestamp_ms})).

    For production use the `jcs` package (PyPI) or any RFC 8785-conformant serializer.
    The snippet below is correct for the canonical preimage fields (string/int only —
    no floats, no unicode escapes) and is included for readability:

        import hashlib, json
        preimage = json.dumps(
            {"action_type": action_type, "agent_id": agent_id,
             "scope": scope, "timestamp_ms": timestamp_ms},
            sort_keys=True, separators=(",", ":"),
        )
        return hashlib.sha256(preimage.encode()).hexdigest()

    Byte-identity across runtimes is verified by the conformance suites listed below.
    """

3. Optional parameter on trace_tool_span():

@contextmanager
def trace_tool_span(
    tool_name: str,
    *,
    tracer: Optional[trace.Tracer] = None,
    parent: Optional[Span] = None,
    tool_description: Optional[str] = None,
    tool_call_id: Optional[str] = None,
    action_ref: Optional[str] = None,   # <-- new
) -> Generator[Span, Any, None]:
    ...
    if action_ref is not None:
        span_attributes[GEN_AI_AGENT_ACTION_REF] = action_ref

No new dependencies for the attribute constant and parameter. The derivation helper uses only stdlib (hashlib, json) for the reference implementation; jcs is recommended for production. Zero breaking changes — the parameter is optional.

Properties

  • Recomputable without a service call: any implementation following the derivation produces the same 32-byte hex from the same four fields. A verifier does not need to call any external service — the correlation is independently verifiable from the preimage fields alone.
  • Cross-producer correlation: two independent runtimes emitting spans for the same agent action produce the same action_ref, enabling cross-framework audit without shared state.
  • Additive alongside gen_ai.tool.call.id: call_id is the runtime's session-scoped handle; action_ref is the verifier's recomputable handle. They serve different consumers.

Normative Reference

Derivation spec: draft-giskard-aeoess-action-ref-00

Three independent production implementations with byte-identical conformance suites:

  • examples/conformance/presidio/ — vstantch/presidio-hardened-x402 (arXiv 2604.11430) → argentum-core
  • examples/conformance/aps/ — AEOESS/agent-passport-system (co-author, I-D) → argentum-core
  • conformance/agentgraph/ — kenneives/AgentGraph (CTEF v0.4 normative) → draft-giskard-aeoess-action-ref

Relation to #7405

GuardrailProvider is the policy enforcement layer. action_ref is the correlation key that binds the pre-execution guardrail decision to the post-execution span — without either layer trusting the other. They compose; neither depends on the other being merged first.

Happy to open a PR directly if useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions