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
| Parameter | Type | Required | What it does |
|---|---|---|---|
identifier | str | yes | Event name, e.g. "web_search" or "task.started". Positional. |
payload | dict | yes | Event body. Passed through redaction. Positional. |
channel_id | str | yes | Target channel UUID. Positional. |
agent_id | str | no | Which agent emitted this. |
event_type | EventType or str | no | Defaults to custom server-side. |
metadata | dict | no | Free-form map, redacted on the same rules as payload. |
trace_id | str | no | Trace UUID. One is created and installed on the current context when omitted. |
span_id | str | no | Span id. Generated from the trace context when omitted. |
parent_span_id | str | no | Parent span id, for span linking. |
parent_event_id | str | no | Parent event UUID, for building an event tree. |
prompt_id | str | no | Prompt registry id. Lands in metadata as gen_ai.prompt.id. |
prompt_version_id | str | no | Prompt version. Lands in metadata as gen_ai.prompt.version. |
environment | str | no | Per-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:
| Field | Type | Meaning |
|---|---|---|
event_id | str | Server-assigned event UUID. |
id | str | Alias of event_id. |
identifier | str | Echoed back. |
queued | bool | True once accepted for ingest. |
dedup_key | str | Server-generated idempotency key for this record. |
duplicate | bool | Set when the write collapsed onto an existing record. |
created_at | datetime | Acceptance timestamp. |
environment_id | str | Resolved 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)| Parameter | Type | What it does |
|---|---|---|
channel_id | str | Channel UUID. Positional and required. |
environment | str | Environment slug, resolved server-side. |
event_type | Sequence[str] | One or more event types to include. |
agent_id | str | Only events from this agent. |
trace_id | str | Only events in this trace. |
since / until | str | ISO 8601 bounds, since inclusive, until exclusive. |
cursor | str | Cursor from a previous meta.cursor. |
limit | int | Page size, 1–1000. Defaults to 100. |
payload_filter | str | Dotted 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
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:
| Parameter | Type | What it does |
|---|---|---|
app_id | str | Restrict to one app. |
channel_id | str | Restrict to one channel, optional here. |
source | str | Ingest source: app, sentry or otlp. |
query | str | Case-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.