axonpush
Python SDKIntegrations

OpenAI Agents

Lifecycle hooks that trace agent runs, tool calls and handoffs from the OpenAI Agents SDK.

pip install "axonpush[openai-agents]"

Tested against openai-agents>=0.1.0,<2.0.

Attach the hooks

The OpenAI Agents SDK is async-only, so this integration takes an AsyncAxonPush.

from agents import Agent, Runner
from axonpush import AsyncAxonPush
from axonpush.integrations.openai_agents import AxonPushRunHooks

client = AsyncAxonPush()
hooks = AxonPushRunHooks(client, channel_id)

agent = Agent(name="research-agent", instructions="You are a research assistant.")
result = await Runner.run(agent, "Find papers on multi-agent systems", hooks=hooks)

await hooks.flush(timeout=2.0)

client and channel_id are positional. AxonPushRunHooks subclasses the SDK’s RunHooks, so it goes wherever a hooks= argument is accepted.

Pass an AsyncAxonPush. There is no sync variant, and mode="sync" drops every event with a warning on the axonpush logger, the hooks publish through an async background queue.

Constructor

AxonPushRunHooks(
    client,             # AsyncAxonPush - positional
    channel_id,         # str UUID - positional
    *,
    agent_id=None,      # fallback when the agent has no name
    trace_id=None,
    mode=None,          # "background" (default) | "sync"
    max_pending=1000,
)

agent_id is only a fallback. Each event is tagged with the agent’s own name when the SDK provides one, so a multi-agent run attributes its events correctly without configuration.

What gets published

IdentifierEvent typePayload
agent.run.startagent.startagent_name, model
agent.run.endagent.endagent_name, output_length
tool.<name>.startagent.tool_call.starttool_name, agent_name
tool.<name>.endagent.tool_call.endtool_name, result_length
agent.handoffagent.handofffrom_agent, to_agent

Every event carries framework: "openai-agents" in metadata, and shares the trace id the hooks were constructed with, pass trace_id= to join a run to a trace you already own, or leave it out to adopt the trace on the current context.

Payloads are deliberately thin: lengths rather than contents. Nothing the model produced leaves your process through this integration.

Clean up

await hooks.flush(timeout=2.0)   # drain the queue
await hooks.close()              # drain and stop the worker
await client.close()

Or let the context manager own the client:

async with AsyncAxonPush() as client:
    hooks = AxonPushRunHooks(client, channel_id)
    result = await Runner.run(agent, "…", hooks=hooks)
    await hooks.flush(timeout=2.0)

Both flush and close are coroutines here, unlike the LangChain handler’s sync pair.