axonpush
Python SDK

Events

Publish an event to a channel, then list or search what landed, every parameter, and the shapes that come back.

Publish

publish takes three positional arguments, the identifier, the payload, and the channel UUID, and everything else by keyword.

from axonpush import AxonPush, EventType

with AxonPush() as client:
  event = client.events.publish(
      "web_search",
      {"query": "AI agent frameworks"},
      "b0f1c2d3-4e5f-6071-8293-a4b5c6d7e8f9",
      agent_id="researcher",
      event_type=EventType.AGENT_TOOL_CALL_START,
      metadata={"tool_name": "web_search"},
  )
  print(event.event_id, event.queued)

The identifier is the event’s name, and the backend uses it as part of its de-duplication key. It does not have to be unique across events, many log records sharing a logger name all persist as separate rows.

Parameters

ParameterTypeRequiredWhat it does
identifierstryesEvent name, e.g. "web_search" or "task.started". Positional.
payloaddictyesEvent body. Passed through redaction. Positional.
channel_idstryesTarget channel UUID. Positional.
agent_idstrnoWhich agent emitted this.
event_typeEventType or strnoDefaults to custom server-side.
metadatadictnoFree-form map, redacted on the same rules as payload.
trace_idstrnoTrace UUID. One is created and installed on the current context when omitted.
span_idstrnoSpan id. Generated from the trace context when omitted.
parent_span_idstrnoParent span id, for span linking.
parent_event_idstrnoParent event UUID, for building an event tree.
prompt_idstrnoPrompt registry id. Lands in metadata as gen_ai.prompt.id.
prompt_version_idstrnoPrompt version. Lands in metadata as gen_ai.prompt.version.
environmentstrnoPer-call override of the client-level environment slug.

What comes back

Ingest is asynchronous, so the response describes the accepted write, not the stored row:

FieldTypeMeaning
event_idstrServer-assigned event UUID.
idstrAlias of event_id.
identifierstrEchoed back.
queuedboolTrue once accepted for ingest.
dedup_keystrServer-generated idempotency key for this record.
duplicateboolSet when the write collapsed onto an existing record.
created_atdatetimeAcceptance timestamp.
environment_idstrResolved environment, when one applied.

The call returns None instead, rather than raising, when the API is unreachable and fail_open is left on. See fail-open.

Event types

from axonpush import EventType

EventType.AGENT_START            # "agent.start"
EventType.AGENT_END              # "agent.end"
EventType.AGENT_MESSAGE          # "agent.message"
EventType.AGENT_TOOL_CALL_START  # "agent.tool_call.start"
EventType.AGENT_TOOL_CALL_END    # "agent.tool_call.end"
EventType.AGENT_ERROR            # "agent.error"
EventType.AGENT_HANDOFF          # "agent.handoff"
EventType.AGENT_LLM_TOKEN        # "agent.llm.token"
EventType.AGENT_LOG              # "agent.log"
EventType.APP_LOG                # "app.log"
EventType.APP_SPAN               # "app.span"
EventType.CUSTOM                 # "custom"

EventType is a str enum, so the plain string works everywhere the enum does: event_type="agent.start".

app.log and agent.log are what the logging integrations emit; app.span is what the OpenTelemetry exporter emits.

List

list reads one channel, newest first, and pages with an opaque cursor.

listing = client.events.list(channel_id, limit=20)

for ev in listing.data:
    print(ev.event_type, ev.identifier, ev.created_at)

if listing.meta.has_more:
    next_page = client.events.list(channel_id, limit=20, cursor=listing.meta.cursor)
ParameterTypeWhat it does
channel_idstrChannel UUID. Positional and required.
environmentstrEnvironment slug, resolved server-side.
event_typeSequence[str]One or more event types to include.
agent_idstrOnly events from this agent.
trace_idstrOnly events in this trace.
since / untilstrISO 8601 bounds, since inclusive, until exclusive.
cursorstrCursor from a previous meta.cursor.
limitintPage size, 1–1000. Defaults to 100.
payload_filterstrDotted filter evaluated against the event payload.

The response is an EventListResponseDto: data is a list of event records, meta carries has_more and cursor.

search spans the whole organisation rather than one channel, and takes every filter by keyword. All filters are AND-ed; omit one to match everything.

results = client.events.search(
    event_type=["agent.error"],
    since="2026-08-01T00:00:00Z",
    query="timeout",
    limit=50,
)

It accepts the same filters as list, plus:

ParameterTypeWhat it does
app_idstrRestrict to one app.
channel_idstrRestrict to one channel, optional here.
sourcestrIngest source: app, sentry or otlp.
querystrCase-insensitive free-text match over event fields and retained payload.

The Lucene q= parameter was removed in v0.1.0. Use the typed filters above, with query= for free text.

The stored event

Records returned by list, search and traces_v2.events() are richer than the publish acknowledgement. Beyond event_id, identifier, payload, metadata, agent_id, trace_id, span_id, parent_event_id, parent_span_id, event_type, channel_id, app_id, org_id, environment_id and created_at, the backend fills in what it could derive:

  • Model attribution, request_model, response_model, provider_name, operation_name, finish_reason, semantic_kind.
  • Token and cost accounting, input_tokens, output_tokens, total_tokens, reasoning_tokens, cache_read_tokens, cache_write_tokens, cost_usd.
  • Timing, duration_ms, time_to_first_token_ms, start_time_unix_nano, end_time_unix_nano, occurred_at.
  • Provenance, source, service_name, service_version, tool_name, status.

These are optional: a hand-published custom event leaves most of them unset.