Skip to main content

Publishers

When to use this

Use a publisher when you need to observe graph execution in real time — for monitoring dashboards, analytics pipelines, or external stream processors. Publishers receive structured EventModel objects at every node transition, tool call, and streaming chunk.

Import paths

from agentflow.runtime.publisher import BasePublisher, ConsolePublisher
from agentflow.runtime.publisher.events import Event, EventType, ContentType, EventModel

# Optional backends
from agentflow.runtime.publisher import RedisPublisher # pip install redis
from agentflow.runtime.publisher import KafkaPublisher # pip install aiokafka
from agentflow.runtime.publisher import RabbitMQPublisher # pip install aio-pika

# Fan-out
from agentflow.runtime.publisher import CompositePublisher

# Tracing backends
from agentflow.runtime.publisher import (
LangsmithPublisher,
LogfirePublisher,
ObservabilityLevel,
OtelPublisher,
setup_langsmith,
setup_logfire,
setup_observability,
setup_tracing,
)

EventModel

The unit of data published to a publisher. Every significant moment in graph execution emits one.

from agentflow.runtime.publisher.events import EventModel
FieldTypeDescription
event_idstrUUID identifying this event.
eventEventSource of the event (graph, node, tool, streaming).
event_typeEventTypePhase of the event (start, progress, result, end, error…).
content_typeContentTypeSemantic type of the payload (text, tool_call, state…).
node_namestr | NoneGraph node that emitted the event.
dataAnyThe event payload.
contentContentBlock | NoneContent block if relevant.
thread_idstr | NoneThread for this execution.
run_idstr | NoneRun for this execution.
timestampdatetimeWhen the event was emitted.
metadatadictAdditional context.

Event — source enum

from agentflow.runtime.publisher.events import Event
ValueDescription
GRAPH_EXECUTIONEmitted by the graph runner (start/end of full execution).
NODE_EXECUTIONEmitted at the start and end of each node.
LLM_CALLEmitted for individual LLM API calls within a node.
TOOL_EXECUTIONEmitted before and after each tool call.
STREAMINGEmitted for each incremental streaming chunk from the LLM.
REALTIMEEmitted by realtime audio-to-audio sessions.

EventType — phase enum

from agentflow.runtime.publisher.events import EventType
ValueWhen emitted
STARTExecution begins.
PROGRESSIntermediate update during streaming.
RESULTA result is ready (tool result, LLM completion).
ENDExecution ends normally.
UPDATEState or data updated.
ERRORAn error occurred.
INTERRUPTEDExecution paused at an interrupt point.

ContentType — payload type enum

from agentflow.runtime.publisher.events import ContentType
ValueWhen used
TEXTPlain text output.
MESSAGEFull message object.
REASONINGExtended thinking trace.
TOOL_CALLTool invocation request.
TOOL_RESULTTool execution result.
IMAGEImage content.
AUDIOAudio content.
TRANSCRIPTText transcript of audio content (realtime sessions).
VIDEOVideo content.
DOCUMENTDocument content.
DATABinary/structured data.
STATEGraph state snapshot.
UPDATEIncremental update.
ERRORError payload.

BasePublisher

Abstract class. All publishers implement this interface.

from agentflow.runtime.publisher import BasePublisher

Abstract methods

MethodSignatureDescription
publishasync (event: EventModel) -> AnyPublish one event. Raises RuntimeError if the publisher is closed.
closeasync () -> NoneRelease connections and resources. Idempotent.
sync_close() -> NoneSynchronous close for use in non-async shutdown handlers.

Context manager

async with ConsolePublisher() as publisher:
app = graph.compile(publisher=publisher)
await app.ainvoke(...)
# publisher is automatically closed

ConsolePublisher

A development and debugging publisher. It is opt-in and not wired up by default. For production, use a real transport such as RedisPublisher, KafkaPublisher, or RabbitMQPublisher.

By default events are written to stdout via print. In server contexts where stdout output is undesirable, set use_logger=True to route events through the agentflow.publisher logger at INFO level instead.

from agentflow.runtime.publisher import ConsolePublisher

# Default — writes to stdout
publisher = ConsolePublisher()

# Route through the logging system instead of stdout
publisher = ConsolePublisher(config={"use_logger": True})

app = graph.compile(publisher=publisher)
Config keyDefaultDescription
format"json"Output format.
include_timestampTrueInclude timestamp in output.
indent2JSON indentation.
use_loggerFalseWhen True, emit via the agentflow.publisher logger at INFO level instead of print.

RedisPublisher

Publishes events to a Redis Pub/Sub channel or Redis Stream.

Optional dependency
pip install 10xscale-agentflow[redis]
# or: pip install redis>=4.2
from agentflow.runtime.publisher import RedisPublisher

publisher = RedisPublisher(config={
"url": "redis://localhost:6379/0",
"mode": "pubsub", # or "stream"
"channel": "agentflow.events",
"stream": "agentflow.events",
"maxlen": 10000, # max stream length (stream mode only)
"max_connections": 10,
"socket_timeout": 5.0,
"health_check_interval": 30,
})

app = graph.compile(publisher=publisher)
Config keyDefaultDescription
urlredis://localhost:6379/0Redis connection URL.
modepubsub"pubsub" for Pub/Sub or "stream" for Redis Streams.
channelagentflow.eventsPub/Sub channel name.
streamagentflow.eventsStream name for "stream" mode.
maxlenNoneMaximum stream entries (stream mode). Set for bounded streams.
max_connections10Connection pool size.
socket_timeout5.0Socket timeout in seconds.
socket_connect_timeout5.0Connection timeout in seconds.
socket_keepaliveTrueTCP keepalive.
health_check_interval30Seconds between pool health checks.

KafkaPublisher

Publishes events to an Apache Kafka topic.

Optional dependency
pip install aiokafka
from agentflow.runtime.publisher import KafkaPublisher

publisher = KafkaPublisher(config={
"bootstrap_servers": "localhost:9092",
"topic": "agentflow-events",
"compression_type": "gzip",
})

app = graph.compile(publisher=publisher)

RabbitMQPublisher

Publishes events to a RabbitMQ exchange.

Optional dependency
pip install aio-pika
from agentflow.runtime.publisher import RabbitMQPublisher

publisher = RabbitMQPublisher(config={
"url": "amqp://guest:guest@localhost:5672/",
"exchange": "agentflow",
"routing_key": "events",
})

app = graph.compile(publisher=publisher)

CompositePublisher

Broadcasts every event to a list of publishers concurrently. A failure in one publisher is logged and does not stop the others.

from agentflow.runtime.publisher import CompositePublisher, ConsolePublisher, RedisPublisher

publisher = CompositePublisher([ConsolePublisher(), RedisPublisher({"url": "redis://localhost:6379"})])
app = graph.compile(publisher=publisher)

add_publisher(publisher) and remove_publisher(publisher) mutate the list after construction.


Tracing publishers

These publishers map EventModel events onto OpenTelemetry spans instead of onto a message bus. Unlike the bus publishers they must be attached before graph.compile(), because the tracer has to be bound in the DI container at compile time.

ObservabilityLevel

Controls how much data ends up on the spans.

ValueEmits
SPANSTiming and structure only. No I/O data.
STANDARDAdds token counts, model name, and request parameters when available. Default.
FULLAdds model input and output messages, tool I/O, and the system prompt. May contain PII; use only in controlled environments.

OtelPublisher

from agentflow.runtime.publisher import ObservabilityLevel, OtelPublisher, setup_tracing

# Explicit
graph._publisher = OtelPublisher(tracer=my_tracer, level=ObservabilityLevel.STANDARD)

# Or via the helper, which builds and attaches one for you
setup_tracing(graph, level=ObservabilityLevel.STANDARD)

app = graph.compile()
ParameterTypeDefaultDescription
tracerTracer | NoneNoneExplicit OTEL tracer. Uses the global TracerProvider when omitted.
levelObservabilityLevelSTANDARDHow much data lands on the spans.

setup_tracing(graph, tracer=None, level=STANDARD) registers an OtelPublisher on the graph and returns it. It raises ImportError when opentelemetry-api is not installed (pip install "10xscale-agentflow[otel]").

LogfirePublisher

An OtelPublisher that calls logfire.configure() during construction, so the Logfire-managed TracerProvider is global before any span is created.

from agentflow.runtime.publisher import setup_logfire

setup_logfire(graph, service_name="my-agent", send_to_logfire=True)
app = graph.compile()
ParameterTypeDefaultDescription
tokenstr | NoneNoneLogfire write token. Falls back to LOGFIRE_TOKEN.
service_namestr | NoneNoneService name shown in the Logfire UI.
send_to_logfireboolTrueWhether to export spans to logfire.dev.
consoleAnyNoneFalse to suppress console output, or a logfire.ConsoleOptions. None uses env-var defaults.
levelObservabilityLevelSTANDARDSpan detail level.
additional_span_processorslist | NoneNoneExtra SpanProcessor instances to attach alongside the Logfire processor.
**configure_kwargsanyForwarded verbatim to logfire.configure().

Requires pip install "10xscale-agentflow[logfire]".

LangsmithPublisher

An OtelPublisher that builds an OTLP HTTP span processor pointed at LangSmith and attaches it to a supplied or freshly created TracerProvider.

from agentflow.runtime.publisher import setup_langsmith

setup_langsmith(graph, project="my-project")
app = graph.compile()
ParameterTypeDefaultDescription
api_keystr | NoneNoneLangSmith API key. Falls back to LANGSMITH_API_KEY. Raises ValueError when neither is set.
projectstr | NoneNoneSent as the Langsmith-Project request header.
endpointstrhttps://api.smith.langchain.com/otelBase OTEL endpoint. /v1/traces is appended automatically. Override for regional deployments.
levelObservabilityLevelSTANDARDSpan detail level.
tracer_providerAnyNoneExisting TracerProvider to attach to. A new global one is created when omitted.

Requires pip install "10xscale-agentflow[langsmith]".

setup_observability

One entry point driven by the observability block of agentflow.json. Enables Logfire and/or LangSmith and makes them share a single TracerProvider when both are active.

from agentflow.runtime.publisher import setup_observability

setup_observability(graph, {
"level": "standard",
"logfire": {"enabled": True, "service_name": "my-agent", "console": False},
"langsmith": {"enabled": True, "project": "my-project"},
})

Pass graph=None to configure providers and exporters only, without binding a publisher onto a graph. Secrets (LOGFIRE_TOKEN, LANGSMITH_API_KEY) must come from the environment, never from the config dict. An unrecognised level falls back to STANDARD.

See Send traces to Logfire or LangSmith for the end-to-end setup.


Writing a custom publisher

from agentflow.runtime.publisher import BasePublisher
from agentflow.runtime.publisher.events import EventModel
import httpx

class WebhookPublisher(BasePublisher):

def __init__(self, webhook_url: str, config: dict | None = None):
super().__init__(config or {})
self.webhook_url = webhook_url
self._client: httpx.AsyncClient | None = None

async def publish(self, event: EventModel) -> None:
if self._is_closed:
raise RuntimeError("Publisher is closed")
if self._client is None:
self._client = httpx.AsyncClient()
await self._client.post(self.webhook_url, json=event.model_dump())

async def close(self):
if not self._is_closed:
if self._client:
await self._client.aclose()
self._is_closed = True

def sync_close(self):
import asyncio
asyncio.run(self.close())

Common errors

ErrorCauseFix
RuntimeError: Cannot publish to closed publisherpublish() called after close().Do not call ainvoke after aclose(). Use the async context manager.
ImportError: redisRedisPublisher used without redis installed.pip install redis.
ImportError: aiokafkaKafkaPublisher used without aiokafka.pip install aiokafka.
ImportError: aio-pikaRabbitMQPublisher used without aio-pika.pip install aio-pika.
Events missing from channelPublisher not passed to graph.compile().Add publisher=my_publisher to compile().
ImportError: OpenTelemetry is required for tracingOtelPublisher / setup_tracing used without OTEL.pip install "10xscale-agentflow[otel]".
No spans from a tracing publisherAttached after graph.compile().Call setup_tracing / setup_logfire / setup_langsmith before compile().