Condukt emits :telemetry events for the major phases of an agent run.
Attach handlers to feed your existing observability stack: Logger,
telemetry_metrics, Prometheus, OpenTelemetry, or anything else.
Events
| Event | Measurements | Metadata |
|---|---|---|
[:condukt, :agent, :start] | system_time | :agent, :session_id |
[:condukt, :agent, :stop] | duration | :agent, :session_id |
[:condukt, :tool_call, :start] | system_time | :tool, :tool_call_id, :args, :agent, :session_id |
[:condukt, :tool_call, :stop] | duration | :tool, :tool_call_id, :args, :agent, :session_id, :status, :result |
[:condukt, :subagent, :start] | system_time | :agent, :role, :child_agent, :input?, :output?, :parent_session_id |
[:condukt, :subagent, :stop] | duration | :agent, :role, :child_agent, :input?, :output?, :status, :error, :parent_session_id, :session_id |
[:condukt, :operation, :start] | system_time | :agent, :operation, :session_id |
[:condukt, :operation, :stop] | duration | :agent, :operation, :session_id |
[:condukt, :run, :start] | system_time | :structured?, :input?, :session_id |
[:condukt, :run, :stop] | duration | :structured?, :input?, :session_id |
[:condukt, :compact, :stop] | duration, before, after | :agent, :session_id |
[:condukt, :secrets, :resolve] | count | :agent, :names, :session_id |
[:condukt, :secrets, :access] | count | :agent, :tool, :tool_call_id, :names, :session_id |
The exact set may grow over time. Attach broadly with attach_many/4 so
new events surface in your handlers without code changes.
Tool call payloads
[:condukt, :tool_call, :start] and :stop events carry the parsed
arguments the model passed to the tool (:args) and a stable
:tool_call_id provided by the LLM. :stop also carries :status
(:ok or :error) and :result, which is the tool's return value
after session-secret redaction has been applied. This makes it possible
to persist a full audit trail of every tool invocation for an agentic
run.
:result is whatever the tool returned (string, map, list, error
tuple). Tools that produce large payloads should expect downstream
consumers to truncate or sample.
Session ids
Every event emitted from a Condukt.Session (or a runtime entry point that
spins up a transient one) carries a :session_id in metadata. Sessions
generate a UUIDv7 at startup unless the caller passes an explicit :id
option to Condukt.start_link/2 or Condukt.run/2. UUIDv7 ids are
time-ordered, so persisting them keeps storage and indexes aligned with
chronological order.
Use :session_id to group all events emitted by a single agentic run, for
example to persist a per-run audit trail. Condukt.run/2 and
Condukt.Operation.run/4 generate the id once and reuse it for both their
wrapping :run / :operation events and the inner agent and tool events.
Sub-agent delegation events expose both ids: :parent_session_id is the
session that called the subagent tool, and :session_id (on :stop) is
the child session created by the delegation. This lets observability tools
reconstruct full parent/child traces.
Secret events are value-free. :names contains environment variable names
such as ["GH_TOKEN"], never the resolved secret values. :tool_call_id is
present when the access comes from a provider-returned tool call.
Sub-agent events are value-free too. They identify the parent agent module,
the delegated role, the child agent module, whether structured input and output
contracts are configured, and whether delegation ended with :ok or :error.
The :error metadata is an atom such as :invalid_input, not the rejected
input or output payload.
Attaching a handler
:telemetry.attach_many(
"condukt-logger",
[
[:condukt, :agent, :start],
[:condukt, :agent, :stop],
[:condukt, :tool_call, :start],
[:condukt, :tool_call, :stop],
[:condukt, :subagent, :start],
[:condukt, :subagent, :stop],
[:condukt, :operation, :start],
[:condukt, :operation, :stop],
[:condukt, :run, :start],
[:condukt, :run, :stop],
[:condukt, :compact, :stop],
[:condukt, :secrets, :resolve],
[:condukt, :secrets, :access]
],
fn event, measurements, metadata, _config ->
Logger.info("#{inspect(event)} #{inspect(measurements)} #{inspect(metadata)}")
end,
nil
)Attach this once at application start.
With telemetry_metrics
def metrics do
[
summary("condukt.agent.stop.duration",
unit: {:native, :millisecond}
),
summary("condukt.tool_call.stop.duration",
tags: [:tool],
unit: {:native, :millisecond}
),
counter("condukt.tool_call.stop.count", tags: [:tool]),
summary("condukt.subagent.stop.duration", tags: [:agent, :role, :child_agent, :status]),
counter("condukt.subagent.stop.count", tags: [:agent, :role, :child_agent, :status]),
counter("condukt.secrets.access.count", tags: [:agent, :tool])
]
endTracing tool calls
Tool call start and stop events share an implicit span via the :telemetry
span helpers. With OpenTelemetry you can wrap them with a span processor
that turns each [:condukt, :tool_call, :*] pair into a span keyed by the
:tool metadata.