This is an automated email from the ASF dual-hosted git repository.

wenjin272 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/flink-agents.git


The following commit(s) were added to refs/heads/main by this push:
     new 0ff883e69 [docs] Restructure the observability chapter (#1262)
0ff883e69 is described below

commit 0ff883e69a77481936d3bbd6347cff0be06686f0
Author: Sangkyoon Nam <[email protected]>
AuthorDate: Mon Oct 19 19:49:29 2026 +0900

    [docs] Restructure the observability chapter (#1262)
    
    Generated-by: Claude Code 2.1.302 (Claude Opus 5.5)
---
 docs/content/docs/operations/monitoring.md | 296 ++++++++++++++---------------
 1 file changed, 141 insertions(+), 155 deletions(-)

diff --git a/docs/content/docs/operations/monitoring.md 
b/docs/content/docs/operations/monitoring.md
index 08e4a590b..3e67f447b 100644
--- a/docs/content/docs/operations/monitoring.md
+++ b/docs/content/docs/operations/monitoring.md
@@ -22,107 +22,124 @@ specific language governing permissions and limitations
 under the License.
 -->
 
-## Metric
 
-### Built-in Metrics
+## Overview
 
-We offer data monitoring for built-in metrics, including input runs, events, 
actions, execution health, and token usage.
+A running agent produces three kinds of observability data, and each answers a 
different question.
 
-#### Event and Action Metrics
+- **Metrics** show how much and how fast: Event and Action throughput, 
input-run outcomes and latency, LLM, Tool and sub-agent calls, and token usage. 
They go through [Flink's metric 
system](https://nightlies.apache.org/flink/flink-docs-release-1.20/docs/ops/metrics/),
 so they reach the same reporters and dashboards as the rest of the job.
+- **Logs** carry what your Action code and the framework write through Flink's 
logging framework, such as warnings and stack traces.
+- **The [Trace Log](#trace-log)** records Events, Action executions and the 
calls made within Actions as structured JSON, so you can follow what one input 
did. By default it records only Events; configure `trace-log.targets` to 
include Actions and component calls.
 
-| Scope       | Metrics                                          | Description 
                                                                     | Type  |
-|-------------|--------------------------------------------------|----------------------------------------------------------------------------------|-------|
-| **Agent**   | numOfEventProcessed                              | The total 
number of Events this operator has processed.                          | Count |
-| **Agent** | numOfEventProcessedPerSec                        | The number of 
Events this operator has processed per second.                     | Meter |
-| **Agent** | numOfActionsExecuted                             | The total 
number of actions this operator has executed.                          | Count |
-| **Agent** | numOfActionsExecutedPerSec                       | The number of 
actions this operator has executed per second.                     | Meter |
-| **Agent** | numOfInputRunsSucceeded                          | The number of 
input runs that reached the run-completion boundary.                | Count |
-| **Agent** | numOfInputRunsFailed                             | The number of 
input runs terminated by an unhandled exception.                    | Count |
-| **Agent** | inputRunLatencyMs                                | End-to-end 
input-run latency from entering the agent operator to completion or failure, 
including time queued behind another input with the same key. | Histogram |
-| **Agent** | inputRunQueueLatencyMs                           | Time from 
entering the agent operator until the input run starts processing. | Histogram |
-| **Agent** | inputRunProcessingLatencyMs                      | Time from the 
input-run start boundary until completion or failure. | Histogram |
-| **Agent** | numOfPendingInputEvents                          | Current 
number of input Events buffered behind an active run with the same key. | Gauge 
|
-| **Agent** | numOfActiveInputRuns                             | Current 
number of logical input runs that are processing or waiting for asynchronous 
work. | Gauge |
-| **Action**  | action.\<action_name\>.numOfActionsExecuted | The total number 
of actions this operator has executed for a specific action name. | Count |
-| **Action**  | action.\<action_name\>.numOfActionsExecutedPerSec | The number 
of actions this operator has executed per second for a specific action name. | 
Meter |
-| **Action** | action.\<action_name\>.actionSchedulingLatencyMs | Time from 
enqueuing the initial Action task until it is selected for execution. | 
Histogram |
-| **Action** | action.\<action_name\>.actionExecutionLatencyMs | End-to-end 
latency of one logical Action execution, including asynchronous waits and 
continuations. | Histogram |
-| **Action** | action.\<action_name\>.numOfPendingActionTasks | Current number 
of physical Action task segments waiting to run, including continuations. | 
Gauge |
-| **Action** | action.\<action_name\>.numOfActiveActionExecutions | Current 
number of logical Action executions that have started but have not reached a 
terminal state. | Gauge |
-| **Action**  | action.\<action_name\>.routingDecisionLatencyMs | Wall-clock 
time, in milliseconds, spent resolving a model-routing decision when a 
`ChatRequestEvent` names a `MODEL_ROUTER`. Recorded once per routing decision, 
including replayed ones; see [Model Routing]({{< ref 
"docs/development/calling-llms/model_routing#observability" >}}). | Histogram |
-| **Agent**   | traceLogTruncatedRecords                          | Number of 
Trace Log records whose attributes were truncated at `STANDARD` level. 
Increments once per record, regardless of how many fields were truncated. Use 
this to adjust truncation limits or select entities at `VERBOSE`. | Count |
-| **Agent**   | traceLogWriteFailures                           | Number of 
Trace Log write attempts for which `append`, `flush`, or both failed. Trace Log 
writes are best-effort and do not fail the job. | Count |
-
-For a locally observed input run, `inputRunLatencyMs` is split into queueing 
and processing time at the input-run start boundary. `numOfPendingInputEvents` 
counts buffered inputs, while `numOfActiveInputRuns` counts logical runs; an 
asynchronous run remains active while it is waiting for its continuation.
-
-An Action execution can be active while one of its continuation tasks is 
pending, so `numOfActiveActionExecutions` and `numOfPendingActionTasks` are 
independent. Action scheduling latency is recorded only for the initial task; 
continuation queueing does not create another scheduling sample.
-
-Input-run outcomes and all latency samples are process-local. Runs or Action 
executions already in flight when a task is restored do not produce latency 
samples because their original timestamps are unavailable. An input Event 
restored from the pending queue can still produce an outcome and 
processing-latency sample after it starts in the new task attempt, but it does 
not produce queue or end-to-end latency. Current-count gauges are rebuilt from 
Flink state after restore.
+A typical investigation starts from a metric that looks wrong, such as a 
rising `numOfInputRunsFailed`, moves to the logs of the affected task for the 
error, and then to the Trace Log to see which Events and Actions led there.
 
-#### Execution Metrics
+## Metrics
 
-LLM, Tool, and sub-agent metrics describe calls observed by the framework 
within an Action. The framework reports each call's start and outcome directly 
to the metric collectors. These reports do not create Events for Action 
routing. Metrics are collected independently of Trace Log output: changing 
`trace-log.targets` or `level` does not disable them.
+Flink Agents registers its metrics with the job's Flink metric system. For the 
metric types used below (Counter, Meter, Histogram and Gauge), see [Metric 
types](https://nightlies.apache.org/flink/flink-docs-release-1.20/docs/ops/metrics/#metric-types).
 Latency metrics are in milliseconds, as their `Ms` suffix shows; 
`retryWaitSec` is in seconds.
 
-The `model_resource`, `tool`, `skill`, `mcp_server`, and `subagent` scopes are 
independent key-value scopes directly under an Action. The existing `model` 
scope remains dedicated to model usage metrics.
+### Accessing metrics
 
-| Scope | Metrics | Description | Type |
-|-------|---------|-------------|------|
-| **Model Resource** | 
action.\<action_name\>.model_resource.\<resource_name\>.numOfLlmCallsSucceeded 
| The number of framework-observed model invocations that returned 
successfully. | Count |
-| **Model Resource** | 
action.\<action_name\>.model_resource.\<resource_name\>.numOfLlmCallsFailed | 
The number of framework-observed model invocations that failed. | Count |
-| **Model Resource** | 
action.\<action_name\>.model_resource.\<resource_name\>.llmCallLatencyMs | 
Latency of each framework-observed model invocation, excluding 
structured-output parsing and retry wait time. | Histogram |
-| **Model Resource** | 
action.\<action_name\>.model_resource.\<resource_name\>.retryCount | The number 
of additional model invocations initiated when retries are enabled 
(`max-retries > 0`). Only recorded when at least one retry occurs. See 
[retry-wait-interval]({{< ref "docs/operations/configuration#core-options" 
>}}). | Count |
-| **Model Resource** | 
action.\<action_name\>.model_resource.\<resource_name\>.retryWaitSec | The 
total backoff time, in seconds, accumulated when retries are enabled 
(`max-retries > 0`). Only recorded when at least one retry occurs. | Count |
-| **Tool** | action.\<action_name\>.tool.\<tool_name\>.numOfToolCallsSucceeded 
| The number of successful calls to the Tool. | Count |
-| **Tool** | action.\<action_name\>.tool.\<tool_name\>.numOfToolCallsFailed | 
The number of failed calls to the Tool. | Count |
-| **Tool** | action.\<action_name\>.tool.\<tool_name\>.toolCallLatencyMs | 
Time spent invoking the individual Tool, excluding time waiting for other calls 
in the same parallel batch. | Histogram |
-| **Skill** | action.\<action_name\>.skill.\<skill_name\>.numOfSkillLoads | 
The number of terminal explicit `load_skill` calls attributed to the Skill, 
regardless of outcome. | Count |
-| **Skill** | action.\<action_name\>.skill.\<skill_name\>.skillLoadLatencyMs | 
Time spent invoking an explicit `load_skill` call. | Histogram |
-| **MCP Server** | 
action.\<action_name\>.mcp_server.\<server_name\>.numOfMcpToolCallsSucceeded | 
The number of successful Tool calls served by the MCP Server. | Count |
-| **MCP Server** | 
action.\<action_name\>.mcp_server.\<server_name\>.numOfMcpToolCallsFailed | The 
number of failed Tool calls served by the MCP Server. | Count |
-| **MCP Server** | 
action.\<action_name\>.mcp_server.\<server_name\>.mcpToolCallLatencyMs | 
Individual Tool invocation latency aggregated across the MCP Server. | 
Histogram |
-| **Subagent** | 
action.\<action_name\>.subagent.\<agent_name\>.numOfSubagentCallsSucceeded | 
The number of sub-agent delegations that returned a successful result. | Count |
-| **Subagent** | 
action.\<action_name\>.subagent.\<agent_name\>.numOfSubagentCallsFailed | The 
number of sub-agent delegations that failed, either while submitting or 
awaiting the delegation or because the sub-agent returned a failed result. | 
Count |
-| **Subagent** | 
action.\<action_name\>.subagent.\<agent_name\>.subagentCallLatencyMs | Time the 
Action observed for one sub-agent delegation, from handing the prompt to the 
sub-agent until its result is observed. | Histogram |
+Metrics are available wherever the job's other metrics are: through a 
configured [metric 
reporter](https://nightlies.apache.org/flink/flink-docs-release-1.20/docs/deployment/metric_reporters/),
 such as Prometheus, or in the Flink Web UI (see [Dashboard 
integration](https://nightlies.apache.org/flink/flink-docs-release-1.20/docs/ops/metrics/#dashboard-integration)).
 
-An LLM metric represents one framework invocation of `ChatModel`. A framework 
retry that calls the model again produces another LLM outcome and latency 
sample; retries hidden inside a provider or connection are not observed. Every 
named Tool execution emits Tool metrics. Skill metrics are emitted only for 
explicit `load_skill` calls; subsequent Tool calls are not inferred to belong 
to a Skill. MCP metrics aggregate only Tool executions carrying an explicit MCP 
Server resource name. A `lo [...]
+With Flink's default operator scope, metric identifiers start with 
`<host>.taskmanager.<tm_id>.<job_name>.<operator_name>.<subtask_index>`; 
reporters may format them differently. For an agent operator, `<operator_name>` 
is the agent name, or `action-execute-operator` when the agent has no name. In 
the Flink Web UI, the agent operator's metrics appear under the prefix 
`<subtask_index>.<operator_name>`.
 
-During fine-grained recovery, a cached durable child result contributes 
another call outcome to these metrics; cache reuse is not separately 
identified. A cached LLM result is reported as successful and may produce a 
near-zero latency sample. A cached Tool result retains its normalized outcome: 
`ToolResponse.error(...)` counts as failed, while a successful `ToolResponse` 
or an ordinary return value that can be converted to Tool content counts as 
successful. A cached Tool result produces  [...]
+{{< img src="/fig/operations/metricwebui.png" alt="Metric Web UI" >}}
 
-Tool names that are not registered runtime resources are aggregated under the 
fixed `tool=unknown` scope. Requested Skill names that do not resolve in the 
runtime registry are similarly aggregated under `skill=unknown`. A `_subagent_` 
callable name that does not resolve to a registered sub-agent is reported as a 
Tool and therefore aggregated under `tool=unknown`; the Subagent scope is keyed 
only by registered sub-agent names, so its cardinality is bounded by the Agent 
plan. When selected [...]
+### Scopes
 
-Tool outcomes follow the same two-layer contract in Java and Python. Resource 
preparation and invocation exceptions are failures. A returned 
`ToolResponse.error(...)` is also a failed Tool execution even though the 
durable invocation returned normally. Tools can return an explicit 
`ToolResponse` or an ordinary value that is converted to text: strings are used 
directly, and other values use JSON when possible, falling back to their string 
representation. A return value that still cannot b [...]
+The **Scope** column in the tables below identifies the metric group. The 
example names omit the operator prefix shown above; reporters may represent the 
groups as labels instead.
 
-`numOfSkillLoads` counts terminal calls rather than successful loads. In both 
runtimes, `load_skill` reports an unavailable manager, missing Skill, or 
missing resource through `ToolResponse.error(...)`; the Tool outcome is 
therefore failed while the Skill load counter still increments. MCP 
protocol-level errors follow the same failed Tool outcome contract.
+| Scope | Metric group | Example name |
+|-------|--------------|--------------|
+| **Agent** | the agent operator itself | `numOfEventProcessed` |
+| **Action** | `action.<action_name>` | 
`action.chat_model_action.numOfActionsExecuted` |
+| **Model** | `action.<action_name>.model.<model_name>` | 
`action.chat_model_action.model.qwen3:1.7b.promptTokens` |
+| **Model Resource**, **Tool**, **Skill**, **MCP Server**, **Subagent** | 
`action.<action_name>.<kind>.<name>`, where `<kind>` is `model_resource`, 
`tool`, `skill`, `mcp_server` or `subagent` | 
`action.tool_call_action.tool.get_weather.numOfToolCallsSucceeded` |
 
-Execution latency is measured from matching start and outcome observations of 
the same call in the same task attempt. Assigning a call identity before 
preparation or submission does not start the timer. Invoked Tools retain their 
own start and completion timestamps, even when the reports arrive after a 
durable call or parallel batch returns. Queueing and time spent waiting for 
other calls in the batch are excluded. Outcome counters still update when no 
matching start is available.
+Each group below the Agent scope is a key-value group: `<action_name>` is the 
Action's name, `<model_name>` the model a chat model reports usage for, and 
`<name>` the resource, Tool, Skill, MCP Server or sub-agent name. Reporters 
that support key-value groups, such as Prometheus, expose them as labels. The 
examples use `chat_model_action` and `tool_call_action`, [built-in Actions]({{< 
ref "docs/development/create-agent/events#built-in-events-and-actions" >}}) 
that call chat models and To [...]
 
-A timeout does not necessarily stop a running Tool. If the callable has not 
finished when the Action observes its durable result or failure, that 
observation time fixes the call's terminal timestamp. A later completion cannot 
extend the latency or add another outcome. This boundary excludes subsequent 
response processing and reporting delays, but may include a delay before the 
durable result reaches the Action. A callable that starts after this boundary 
has no reported start or latency s [...]
+### Built-in Metrics
 
-Available per-call outcomes are reported independently of response 
aggregation, so a later response-processing failure does not repeat or discard 
them. If a batch raises an ordinary Exception without returning per-call 
outcomes, known starts may still be observed, but no outcomes are inferred from 
timestamps or the batch exception. If a Java batch propagates an `Error`, 
directly or through a `CompletionException`, unresolved prepared calls are 
reported as failed and the original `Error`  [...]
+Flink Agents provides built-in metrics for input runs, Events, Actions, 
execution health, and token usage. Numbers in brackets refer to the notes below 
each table.
 
-In previous releases, `retryCount` and `retryWaitSec` used the 
`model.<connection_name>` scope. They now use `model_resource.<resource_name>` 
so retries are attributed to the configured ChatModel resource. Existing 
queries and dashboards for these two metrics must use the new scope.
+#### Event and Action Metrics
 
-#### Token Usage Metrics
+| Scope | Metrics | Description | Type |
+|-------|---------|-------------|------|
+| **Agent** | numOfEventProcessed | Events processed by this operator. | 
Counter |
+| **Agent** | numOfEventProcessedPerSec | Events processed per second. | Meter 
|
+| **Agent** | numOfActionsExecuted | Actions executed by this operator. | 
Counter |
+| **Agent** | numOfActionsExecutedPerSec | Actions executed per second. | 
Meter |
+| **Agent** | numOfInputRunsSucceeded | Input runs that completed. [1] | 
Counter |
+| **Agent** | numOfInputRunsFailed | Input runs terminated by an unhandled 
exception. [1] | Counter |
+| **Agent** | inputRunLatencyMs | Time from an input entering the operator 
until its run completes or fails, including time queued behind an earlier input 
with the same key. [1] | Histogram |
+| **Agent** | inputRunQueueLatencyMs | Time from an input entering the 
operator until its run starts. [1] | Histogram |
+| **Agent** | inputRunProcessingLatencyMs | Time from the start of an input 
run until it completes or fails. [1] | Histogram |
+| **Agent** | numOfPendingInputEvents | Inputs currently waiting behind an 
active run with the same key. [2] | Gauge |
+| **Agent** | numOfActiveInputRuns | Input runs currently processing or 
waiting for asynchronous work. [2] | Gauge |
+| **Action** | action.\<action_name\>.numOfActionsExecuted | Executions of 
this Action. | Counter |
+| **Action** | action.\<action_name\>.numOfActionsExecutedPerSec | Executions 
of this Action per second. | Meter |
+| **Action** | action.\<action_name\>.actionSchedulingLatencyMs | Time from 
scheduling an Action execution until it starts running. [3] | Histogram |
+| **Action** | action.\<action_name\>.actionExecutionLatencyMs | Time for one 
Action execution, including asynchronous waits. [1] | Histogram |
+| **Action** | action.\<action_name\>.numOfPendingActionTasks | Action tasks 
waiting to run, including continuations of asynchronous Actions. [3] | Gauge |
+| **Action** | action.\<action_name\>.numOfActiveActionExecutions | Action 
executions that have started and not yet finished. [3] | Gauge |
+| **Action** | action.\<action_name\>.routingDecisionLatencyMs | Time to 
resolve a routing decision when a `ChatRequestEvent` names a `MODEL_ROUTER`, 
including replayed decisions; see [Model Routing]({{< ref 
"docs/development/calling-llms/model_routing#observability" >}}). | Histogram |
+| **Agent** | traceLogTruncatedRecords | Trace Log records whose attributes 
were truncated at `STANDARD` level, counted once per record. | Counter |
+| **Agent** | traceLogWriteFailures | Failed Trace Log writes. Writes are best 
effort and do not fail the job. | Counter |
+
+1. Input-run outcomes and latency samples are local to a task attempt. Input 
runs already active when a task is restored produce neither outcome counts nor 
latency samples, and Action executions already active when the task is restored 
produce no latency sample. An input restored from the pending queue records its 
outcome and processing latency once it starts in the new attempt, but not its 
queue or end-to-end latency.
+2. `numOfPendingInputEvents` counts buffered inputs and `numOfActiveInputRuns` 
counts runs; a run waiting for an asynchronous continuation stays active. Both 
gauges are rebuilt from Flink state after a restore.
+3. An Action execution stays active while one of its continuation tasks is 
pending, so the two Action gauges move independently. Scheduling latency is 
recorded only for the first task of an execution.
 
-Token usage metrics are automatically recorded when chat models are invoked 
through `ChatModelConnection`. These metrics help track LLM API usage and costs.
+#### Execution Metrics
 
-| Scope     | Metrics                                                      | 
Description                                                                    
| Type  |
-|-----------|--------------------------------------------------------------|--------------------------------------------------------------------------------|-------|
-| **Model** | action.\<action_name\>.model.\<model_name\>.promptTokens     | 
The total number of prompt tokens consumed by the model within an action.      
| Count |
-| **Model** | action.\<action_name\>.model.\<model_name\>.completionTokens | 
The total number of completion tokens generated by the model within an action. 
| Count |
+These metrics describe LLM, Tool, Skill, MCP Server and sub-agent calls that 
the framework observes inside an Action. They are collected whatever 
`trace-log.targets` selects.
 
-### How to add custom metrics
+| Scope | Metrics | Description | Type |
+|-------|---------|-------------|------|
+| **Model Resource** | 
action.\<action_name\>.model_resource.\<resource_name\>.numOfLlmCallsSucceeded 
| Model invocations that returned successfully. [1] [6] | Counter |
+| **Model Resource** | 
action.\<action_name\>.model_resource.\<resource_name\>.numOfLlmCallsFailed | 
Model invocations that failed. [1] [6] | Counter |
+| **Model Resource** | 
action.\<action_name\>.model_resource.\<resource_name\>.llmCallLatencyMs | Time 
for one model invocation, excluding structured-output parsing and retry waits. 
[1] | Histogram |
+| **Model Resource** | 
action.\<action_name\>.model_resource.\<resource_name\>.retryCount | Additional 
invocations made by framework retries (`max-retries > 0`); recorded only when a 
retry occurs. See [retry-wait-interval]({{< ref 
"docs/operations/configuration#core-options" >}}). | Counter |
+| **Model Resource** | 
action.\<action_name\>.model_resource.\<resource_name\>.retryWaitSec | Backoff 
time accumulated by framework retries; recorded only when a retry occurs. | 
Counter |
+| **Tool** | action.\<action_name\>.tool.\<tool_name\>.numOfToolCallsSucceeded 
| Successful calls to the Tool. [2] [6] | Counter |
+| **Tool** | action.\<action_name\>.tool.\<tool_name\>.numOfToolCallsFailed | 
Failed calls to the Tool. [2] [6] | Counter |
+| **Tool** | action.\<action_name\>.tool.\<tool_name\>.toolCallLatencyMs | 
Time spent in one Tool call, excluding waits for other calls in the same 
parallel batch. [5] | Histogram |
+| **Skill** | action.\<action_name\>.skill.\<skill_name\>.numOfSkillLoads | 
Explicit `load_skill` calls for the Skill, whatever their outcome. [3] | 
Counter |
+| **Skill** | action.\<action_name\>.skill.\<skill_name\>.skillLoadLatencyMs | 
Time for one explicit `load_skill` call. | Histogram |
+| **MCP Server** | 
action.\<action_name\>.mcp_server.\<server_name\>.numOfMcpToolCallsSucceeded | 
Successful Tool calls served by the MCP Server. [3] | Counter |
+| **MCP Server** | 
action.\<action_name\>.mcp_server.\<server_name\>.numOfMcpToolCallsFailed | 
Failed Tool calls served by the MCP Server. [3] | Counter |
+| **MCP Server** | 
action.\<action_name\>.mcp_server.\<server_name\>.mcpToolCallLatencyMs | Time 
for one Tool call served by the MCP Server. | Histogram |
+| **Subagent** | 
action.\<action_name\>.subagent.\<agent_name\>.numOfSubagentCallsSucceeded | 
Sub-agent delegations that returned a successful result. [4] | Counter |
+| **Subagent** | 
action.\<action_name\>.subagent.\<agent_name\>.numOfSubagentCallsFailed | 
Sub-agent delegations that failed while submitting or awaiting, or returned a 
failed result. [4] | Counter |
+| **Subagent** | 
action.\<action_name\>.subagent.\<agent_name\>.subagentCallLatencyMs | Time 
from handing the prompt to the sub-agent until its result is observed. [4] | 
Histogram |
+
+1. One sample is one framework invocation of `ChatModel`. A framework retry 
produces another outcome and latency sample; retries inside a provider client 
are not observed. A response that is later rejected for its finish reason or 
structured-output parsing still counts as a successful call.
+2. A Tool call fails when preparing or invoking it raises an exception, when 
it returns `ToolResponse.error(...)`, or when its return value cannot be 
converted to text. Tool names that are not registered resources are counted 
under `tool=unknown`.
+3. A `load_skill` call or an MCP Tool call is also counted in its Tool scope. 
`numOfSkillLoads` increments even when the load fails, for example for a 
missing Skill. Skill names that do not resolve are counted under 
`skill=unknown`.
+4. Only delegations the model requests through the reserved `_subagent_` name 
are reported, under the Subagent scope keyed by the registered sub-agent name 
and not also under a Tool scope. A `_subagent_` name that does not resolve is 
counted under `tool=unknown`. For delegations dispatched together in one batch, 
the latency includes their overlap.
+5. A Tool call has a latency sample only when its callable started in the 
current task attempt.
+6. During recovery, a result replayed from durable state counts as another 
outcome, without implying another provider request or Tool invocation. A 
replayed LLM response keeps its outcome and may produce a near-zero latency 
sample; a replayed Tool result keeps its outcome and has no latency sample.
+
+#### Token Usage Metrics
 
-In Flink Agents, users implement their logic by defining custom Actions that 
respond to various Events throughout the Agent lifecycle. To support 
user-defined metrics, we introduce two new properties: `agent_metric_group` and 
`action_metric_group` in the RunnerContext. These properties allow users to 
create or update global metrics and independent metrics for actions. For an 
introduction to metric types, please refer to the [Metric types 
documentation](https://nightlies.apache.org/flink/ [...]
+Token usage metrics are recorded when chat models are invoked through 
`ChatModelConnection`, which helps track LLM API usage and cost. In the 
built-in chat flow, token usage metrics appear under 
`action.chat_model_action.model.<model_name>`, not under the Action that sends 
`ChatRequestEvent`.
 
-Metric names listed in the built-in tables above are reserved in their 
corresponding scopes. Custom metrics must use different names within the same 
scope.
+| Scope | Metrics | Description | Type |
+|-------|---------|-------------|------|
+| **Model** | action.\<action_name\>.model.\<model_name\>.promptTokens | 
Prompt tokens consumed by the model within an Action. | Counter |
+| **Model** | action.\<action_name\>.model.\<model_name\>.completionTokens | 
Completion tokens generated by the model within an Action. | Counter |
 
-Here is the user case example:
+### How to add custom metrics
+
+Actions can register their own metrics through the `RunnerContext`: 
`agent_metric_group` (Java `getAgentMetricGroup()`) for metrics of the whole 
operator, and `action_metric_group` (Java `getActionMetricGroup()`) for metrics 
of the current Action. Names used by the built-in metrics are reserved in their 
scope, so custom metrics need different names there.
 
 {{< tabs "Custom Metrics" >}}
 
 {{< tab "Python" >}}
 ```python
+import time
+
 class MyAgent(Agent):
     @action(EventType.InputEvent)
     @staticmethod
@@ -154,10 +171,9 @@ public class MyAgent extends Agent {
     public static void firstAction(Event event, RunnerContext ctx) throws 
Exception {
         InputEvent inputEvent = InputEvent.fromEvent(event);
         long startTime = System.currentTimeMillis();
-        
+
         // the action logic
-        ...
-        
+
         FlinkAgentsMetricGroup metrics = ctx.getAgentMetricGroup();
 
         metrics.getCounter("numInputEvent").inc();
@@ -174,34 +190,44 @@ public class MyAgent extends Agent {
 
 {{< /tabs >}}
 
+## Logs
 
-### How to check the metrics with Flink executor
-
-Flink agents enable the reporting of metrics to external systems by creating a 
metric identifier prefix in the format 
`<host>.taskmanager.<tm_id>.<job_name>.<operator_name>.<subtask_index>`. For an 
agent operator, `<operator_name>` is the agent name. If the Agent name is 
unavailable, the operator retains the previous `action-execute-operator` value 
as a fallback. This changes only the value of the existing `<operator_name>` 
scope; the Agent-specific metric hierarchy is unchanged. Queries [...]
+In a Java Action, log through SLF4J as in any Flink code; those logs follow 
the job's [Flink logging 
configuration](https://nightlies.apache.org/flink/flink-docs-release-1.20/docs/deployment/advanced/logging/).
 In a Python Action, use the standard `logging` module.
 
-Additionally, we can check the metric results in the Flink Job WebUI using the 
metric identifier prefix `<subtask_index>.<operator_name>`.
+{{< tabs "Logging in an Action" >}}
 
-{{< img src="/fig/operations/metricwebui.png" alt="Metric Web UI" >}}
-
-## Log
+{{< tab "Python" >}}
+```python
+import logging
 
-The Flink Agents' log system uses Flink's logging framework. For more details, 
please refer to the [Flink log system 
documentation](https://nightlies.apache.org/flink/flink-docs-master/docs/deployment/advanced/logging/).
+logger = logging.getLogger(__name__)
 
-### How to add log in Flink Agents
+class MyAgent(Agent):
+    @action(EventType.InputEvent)
+    @staticmethod
+    def process_input(event: Event, ctx: RunnerContext) -> None:
+        logger.info("Processing input: %s", InputEvent.from_event(event).input)
+        # the action logic
+```
+{{< /tab >}}
 
-For adding logs in Java code, you can refer to [Flink 
documentation](https://nightlies.apache.org/flink/flink-docs-master/docs/deployment/advanced/logging/#best-practices-for-developers).
 In Python, you can add logs using `logging`. Here is a specific example:
+{{< tab "Java" >}}
+```java
+public class MyAgent extends Agent {
+    private static final Logger LOG = LoggerFactory.getLogger(MyAgent.class);
 
-```python
-@action(EventType.InputEvent)
-@staticmethod
-def process_input(event: Event, ctx: RunnerContext) -> None:
-    logging.info("Processing input event: %s", event)
-    # the action logic
+    @Action(EventType.InputEvent)
+    public static void processInput(Event event, RunnerContext ctx) throws 
Exception {
+        LOG.info("Processing input: {}", 
InputEvent.fromEvent(event).getInput());
+        // the action logic
+    }
+}
 ```
+{{< /tab >}}
 
-### How to check the logs with Flink executor
+{{< /tabs >}}
 
-We can check the log result in the WebUI of Flink Job:
+You can read the logs of a running job in the Flink Web UI:
 
 {{< img src="/fig/operations/logwebui.png" alt="Log Web UI" >}}
 
@@ -209,12 +235,12 @@ We can check the log result in the WebUI of Flink Job:
 
 Trace Log records Events, Action executions, and component calls as structured 
JSON. By default, it records only Events at `STANDARD` level. Configure 
[`trace-log.targets`]({{< ref "docs/operations/configuration#trace-log-targets" 
>}}) to select other entities or change their level.
 
-The default output is **SLF4J Trace Log**. Select **File Trace Log** with 
`trace-log.output-type: FILE` or by setting a non-empty `trace-log.base-dir`. A 
configured base directory takes precedence over `output-type`.
-
-Trace Log writes are best-effort. An `append` or `flush` failure does not fail 
Event processing or the Flink job. The first failure is logged at `WARN`, 
subsequent failures at `DEBUG`, and every failed write attempt increments 
`traceLogWriteFailures`.
+Trace Log writes are best effort. A failed write does not fail Event 
processing or the job; the first failure is logged at `WARN`, later ones at 
`DEBUG`, and each one increments `traceLogWriteFailures`.
 
 ### Recording Targets
 
+A target's `scope` supports `EVENT_ONLY` (the default), which records only 
Events, and `ALL`, which records all entity types the framework observes. The 
example below uses `ALL`.
+
 To record Events, Actions, and component calls at `STANDARD`, with full 
attributes for the `search` Tool, configure:
 
 ```yaml
@@ -233,40 +259,32 @@ This example uses file output so the records can be read 
locally. Each target co
 
 See [Trace log targets]({{< ref 
"docs/operations/configuration#trace-log-targets" >}}) for the complete 
matching, inheritance, and validation rules. The [Recording 
Level](#recording-level) section below explains how the selected level affects 
the recorded data.
 
-### SLF4J Trace Log (Default)
-
-The SLF4J logger uses the category `org.apache.flink.agents.TraceLog`. In 
Flink's default log4j2 environment, where `log.file` is set, it automatically 
creates `FlinkAgentsTraceLogAppender` and writes JSON to 
`{log.file}.trace-log.log`. This file is visible in the Flink Web UI **Logs** 
tab.
+### Output
 
-An existing configuration for this logger is respected. If no dedicated logger 
is configured and `log.file` is absent, records go to the root logger. If 
automatic appender configuration fails, a warning is logged and the existing 
logging configuration handles output. For local use with the Trace Tree reader, 
select `FILE` or configure a dedicated SLF4J appender to emit JSON without log 
prefixes.
+The default output is **SLF4J**. Select **file** output with 
`trace-log.output-type: FILE` or by setting a non-empty `trace-log.base-dir`; a 
configured base directory takes precedence over `output-type`.
 
-All subtasks on a TaskManager share the default SLF4J destination. Each record 
therefore carries additional top-level `jobId`, `taskName`, and `subtaskId` 
fields.
+**SLF4J (default).** Records go to the logger category 
`org.apache.flink.agents.TraceLog`. In Flink's default log4j2 setup, where 
`log.file` is set, Flink Agents adds an appender that writes them as plain JSON 
to `{log.file}.trace-log.log`; in the Flink Web UI, the file is listed in the 
TaskManager's **Log List**, and it can be passed directly to the [Trace Tree 
reader](#trace-tree-reader). An existing configuration for this logger is 
respected. If no dedicated logger is configured and ` [...]
 
-### File Trace Log
-
-File output stores one file per operator subtask in `trace-log.base-dir`:
+**File.** File output writes one file per operator subtask in 
`trace-log.base-dir`, or under `java.io.tmpdir/flink-agents` when no base 
directory is set:
 
 ```text
 {trace-log.base-dir}/
-├── traces-{jobId}-{taskName}-{subtaskId}.log
-├── traces-{jobId}-{taskName}-{subtaskId}.log
-└── traces-{jobId}-{taskName}-{subtaskId}.log
+├── traces-{jobId}-{taskName}-0.log
+├── traces-{jobId}-{taskName}-1.log
+└── traces-{jobId}-{taskName}-2.log
 ```
 
-When `FILE` is selected without a base directory, files are stored under 
`java.io.tmpdir/flink-agents`.
-
-By default, each record occupies one line of JSONL. Setting 
`trace-log.pretty-print: true` writes multi-line JSON objects instead, so the 
file is no longer JSONL.
+Each record is one line of JSONL. Setting `trace-log.pretty-print: true` 
writes multi-line JSON objects instead, so the file is no longer JSONL.
 
 ### JSON Format
 
-Both outputs share the same observation fields: entity identity, 
relationships, timestamp, status, and attributes. Every record includes the 
top-level integer `schemaVersion`, currently `1`, identifying its JSON format. 
The logger adds `level` when writing JSON; it is output metadata, not a field 
of the `TraceRecord` model. SLF4J output also adds the job and subtask fields 
described above.
-
-For an Event, `attributes` contains its payload and `entityMetadata` contains 
its ID and source relationships. These relationships belong to the trace 
context and are not fields of the Event passed between Actions.
+Every record has the top-level integer `schemaVersion`, currently `1`. 
Incompatible changes to field names, types or meanings require a new schema 
version; added optional fields do not, and readers can ignore fields they do 
not use.
 
 | Field | Meaning |
 |-------|---------|
 | `schemaVersion` | JSON format version, currently `1`; independent of the 
Flink Agents release and persisted state formats. |
 | `timestamp` | Observation or occurrence time. |
-| `level` | Output metadata identifying the resolved `STANDARD` or `VERBOSE` 
level. |
+| `level` | The resolved `STANDARD` or `VERBOSE` level. |
 | `inputRunId`, `businessKey`, `agentName` | Available run identity and agent 
context. |
 | `entityType`, `entityName` | The observed entity's type and name. For an 
Event, its routing type is the entity name. |
 | `executionId`, `parentExecutionId` | Identity of an Action execution or 
component call, and its containing execution when available. These fields are 
absent for Events. |
@@ -274,11 +292,7 @@ For an Event, `attributes` contains its payload and 
`entityMetadata` contains it
 | `status`, `problemCategory` | Progress or outcome, and optional failure 
classification. These fields are absent for Events. |
 | `attributes` | Event payload or details of the observed execution or call. 
Only this object is subject to `STANDARD` truncation. |
 
-Incompatible changes to field names, types, or meanings require a new schema 
version. Adding optional fields does not require a version change; readers can 
ignore fields they do not use.
-
-When an Action is excluded by the logging configuration, its selected 
component calls still retain their `parentExecutionId`, even though the 
Action's own record is absent.
-
-For example, an output Event produced by an Action is recorded as:
+For an Event, `attributes` contains its payload and `entityMetadata` its ID 
and source relationships. For example, an output Event produced by an Action is 
recorded as:
 
 ```json
 {
@@ -300,7 +314,7 @@ For example, an output Event produced by an Action is 
recorded as:
 }
 ```
 
-`producerExecutionId` identifies the Action execution that directly produced 
the Event. `upstreamEventId` identifies the preceding Event in its flow, and 
`upstreamActionName` identifies the associated Action. A runtime-generated 
Event may have an upstream Event without an Action producer. These references 
remain in `entityMetadata` even when `STANDARD` truncates attributes.
+`producerExecutionId` identifies the Action execution that produced the Event, 
`upstreamEventId` the preceding Event in its flow, and `upstreamActionName` the 
associated Action. A runtime-generated Event may have an upstream Event without 
an Action producer. Action records carry `triggerEventId`, linking the 
execution to the Event that triggered it. When an Action is not selected, its 
selected component calls still keep their `parentExecutionId`.
 
 An LLM call uses its configured ChatModel Resource as `entityName`:
 
@@ -325,17 +339,9 @@ An LLM call uses its configured ChatModel Resource as 
`entityName`:
 }
 ```
 
-`entityMetadata.model` is the model or deployment configured on the Resource. 
It is the requested identifier, not a provider-confirmed model identity. Action 
metadata carries `triggerEventId`, linking the execution to the Event that 
triggered it.
-
-Successful LLM observations include `entityMetadata.usage` when the returned 
`ChatResult` contains token counts in `usage`. Trace Log records the available 
`TokenUsage` counts under `promptTokens` and `completionTokens`, independently 
of token metric collection. Each count is optional, and zero is retained. When 
neither count is available, `usage` is omitted. Availability depends on the 
ChatModel integration; the trace does not fetch additional provider data.
+`entityMetadata.model` is the model or deployment configured on the Resource, 
not a provider-confirmed model identity. A successful LLM record includes 
`entityMetadata.usage` when the model integration returns token counts; each 
count is optional, zero is kept, and `usage` is omitted when neither count is 
available. Each retry reports its own usage, and a response replayed from 
durable state reports its usage again.
 
-Each retry reports its own response usage without accumulating earlier 
attempts. A response rejected later because of its finish reason or 
structured-output parsing still has a successful LLM observation with its 
usage. Replaying a cached durable response reports that response's usage again; 
this does not establish that the provider received another request.
-
-One entity can have several observations sharing its identity. Progress and 
outcome statuses include `created`, `started`, `success`, `failed`, and 
`reused`; they are not separate Event routing types. Tool calls can be observed 
at creation before their callable starts. Some observations are published after 
a durable call or parallel batch returns, while retaining their occurrence 
timestamp. A missing observation therefore does not establish that the call 
never ran or completed. Use occur [...]
-
-For a new component invocation or retry attempt, the `created` and `started` 
reporting helpers, including timestamped starts, can allocate and return an 
`executionId`. The caller retains that ID and explicitly passes it to all 
subsequent reports for the call, including duplicate reports. The underlying 
`ExecutionReporter` API requires an explicit ID. Overlapping calls with 
identical type, name, and metadata have different IDs. Metadata may change 
between reports; correlate the observatio [...]
-
-An ordinary asynchronous wait retains the component call's ID. Failover or 
replay may create new component call IDs; Action execution IDs and lifecycle 
behavior retain their existing semantics. Trace output remains best effort and 
may contain missing or repeated records. An ID does not guarantee exactly-once 
output, deduplication, or a terminal observation for every call.
+An Action execution or component call can have several records sharing its 
`executionId`, one per status such as `created`, `started`, `success`, `failed` 
or `reused`; correlate them by `executionId` and use timestamps rather than 
file order for latency. Records are best effort: one can be missing or 
repeated, and failover or replay can give a component call a new ID, so a 
missing record does not prove that a call never ran.
 
 ### Recording Level
 
@@ -347,7 +353,7 @@ An ordinary asynchronous wait retains the component call's 
ID. Failover or repla
 
 `trace-log.standard.max-string-length`, 
`trace-log.standard.max-array-elements`, and `trace-log.standard.max-depth` 
control long strings, large arrays, and deep nesting respectively. Setting a 
threshold to `0` disables that truncation limit. Setting all three to `0` makes 
`STANDARD` retain the same content as `VERBOSE`, apart from the `level` label.
 
-**Media sanitization.** Both recording level settings (`STANDARD` and 
`VERBOSE`) omit inline Base64 media data from typed content blocks and remove 
userinfo, query strings, and fragments from media URLs. This includes blocks in 
`ChatMessage`, `ChatResult`, Tool results, and other Event attributes. Media 
metadata is retained. Message text is not automatically redacted.
+**Media sanitization.** Both `STANDARD` and `VERBOSE` omit inline Base64 media 
data from typed content blocks and remove userinfo, query strings, and 
fragments from media URLs. This includes blocks in `ChatMessage`, `ChatResult`, 
Tool results, and other Event attributes. Media metadata is retained. Message 
text is not automatically redacted.
 
 **Fields preserved in full.** Truncation never changes entity identity, 
relationships, entity metadata, timestamps, statuses, or problem categories. It 
applies only under `attributes`, so `STANDARD` retains the relationships needed 
to correlate the records that were selected.
 
@@ -363,13 +369,7 @@ A truncated field changes JSON type. Consumers requiring 
the full attribute sche
 
 ### Trace Tree Reader
 
-The `flink-agents-trace-tree` command is installed with the Python wheel. It 
reconstructs Event causal trees from the `TraceRecord` format described above. 
Pass a file directly or a directory containing `traces-*.log` or 
`*.trace-log.log` files. Both JSONL and pretty-printed JSON are supported.
-
-The reader accepts records with integer `schemaVersion: 1`. A missing, 
invalid, or unsupported version produces a warning and the record is skipped; 
other records continue to be read.
-
-The reader uses `entityMetadata.eventId`, `upstreamEventId`, and 
`upstreamActionName` to build an Event–Action–Event graph. An InputEvent with 
no upstream Event starts a tree; an InputEvent produced by an Action can be a 
descendant. An edge without an Action name produces a `MISSING_ACTION_NAME` 
warning and is not linked. The reader does not require Action observations, so 
the default `EVENT_ONLY` scope is sufficient. Other observed entities are 
skipped; component calls and their statuse [...]
-
-For example:
+The `flink-agents-trace-tree` command is installed with the Python wheel. It 
rebuilds Event causal trees from Trace Log records. Pass a file, or a directory 
containing `traces-*.log` or `*.trace-log.log` files; both JSONL and 
pretty-printed JSON are supported.
 
 ```bash
 flink-agents-trace-tree /path/to/trace-logs --format text
@@ -385,6 +385,8 @@ Trace Tree 1
       _output_event (output-1)
 ```
 
+The reader links Events through `entityMetadata.eventId`, `upstreamEventId` 
and `upstreamActionName`, so the default Event-only targets are enough; other 
records are skipped. It accepts `schemaVersion: 1` and skips records with a 
missing or unsupported version with a warning. An InputEvent with no upstream 
Event starts a tree. Shared descendants can appear under several branches, so 
the result may be a DAG rather than a strict tree, and sibling order does not 
indicate execution order. If [...]
+
 The JSON result exposes the same graph:
 
 | Field | Meaning |
@@ -395,20 +397,4 @@ The JSON result exposes the same graph:
 | `nodes[id].observationCount` | Number of log observations with this Event 
ID, including any conflicting observations reported in `warnings`. |
 | `nodes[id].upstreamEdges` | Distinct recorded upstream Event and Action 
references. Consult `warnings` for missing or invalid relationships. |
 | `nodes[id].actions` | Actions linked from this Event; each has a `name` and 
a `children` list of output Event IDs. |
-| `warnings` | Problems encountered while reading records or reconstructing 
relationships. |
-
-Observations with the same Event ID and type share one node and deduplicated 
edges. Shared descendants can appear under several branches, so the combined 
graph may be a DAG rather than a strict tree. Attribute differences, including 
`STANDARD` truncation, do not affect identity or relationships. Sibling order 
is only a presentation order and does not indicate execution order.
-
-Malformed records, conflicting Event identities, missing upstream Events, and 
cycles produce warnings. Recoverable records remain available in the JSON 
result, and cycle edges are removed before rendering. If recording targets 
filtered an upstream Event, the reader cannot reconstruct that missing part of 
the tree. Historical formats using `eventType`/`eventAttributes` or a nested 
`event` object are unsupported.
-
-### Compatibility Notes
-
-These notes compare Trace Log with the original Event Log, which recorded 
Events only.
-
-- **Configuration keys.** The old `event-log.*`, `eventLoggerType`, 
`baseLogDir`, and `prettyPrint` settings are rejected at startup. Configure 
Event selection and recording levels through `trace-log.targets`, truncation 
through `trace-log.standard.*`, and output through `trace-log.output-type`, 
`trace-log.base-dir`, and `trace-log.pretty-print`. See [Trace Log 
Configuration]({{< ref "docs/operations/configuration#trace-log-targets" >}}).
-- **Log format.** Records use the Trace Log JSON format with `schemaVersion: 
1`; the old nested `event` object is removed.
-- **Consumer field mappings.** Update parsers and queries from `logLevel` to 
`level`, from `eventType` to `entityName` for Events, from `event.id` to 
`entityMetadata.eventId`, and from `event.attributes` to `attributes`.
-- **Output names.** File names change from `events-*.log` to `traces-*.log`. 
The SLF4J category changes from `org.apache.flink.agents.EventLog` to 
`org.apache.flink.agents.TraceLog`, the appender from 
`FlinkAgentsEventLogAppender` to `FlinkAgentsTraceLogAppender`, and the file 
suffix from `.event-log.log` to `.trace-log.log`. Update log collection and 
custom log4j2 configuration accordingly.
-- **Custom loggers.** Custom `EventLogger` implementations and factory 
registrations must adapt to `TraceLogger` and `TraceLoggerFactory`. The append 
contract changes from `append(EventContext, Event)` to `append(TraceRecord, 
TraceLogLevel)`.
-- **Write failures and metrics.** An `append` or `flush` failure no longer 
propagates into Event processing or fails the job; it is logged and counted in 
`traceLogWriteFailures`. The truncation metric changes from 
`eventLogTruncatedEvents` to `traceLogTruncatedRecords` and covers all selected 
record types. Update dashboards and alert queries accordingly.
-- **Python Event identity.** Automatically generated Event IDs now use UUID4 
rather than a content-derived UUID. Changing an Event's payload no longer 
regenerates its ID, and `id` is immutable. Code that relies on equal payloads 
producing equal IDs or assigns to `event.id` must be updated.
+| `warnings` | Problems encountered while reading records or reconstructing 
relationships, such as malformed records, conflicting Event identities, missing 
upstream Events, edges without an Action name, and cycles. Cycle edges are 
removed before rendering. |

Reply via email to