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 47daff57b [docs] Improve consistency and configuration guidance (#1284)
47daff57b is described below

commit 47daff57b5c4189c80dd7037660f2a7832270879
Author: Yu Tang <[email protected]>
AuthorDate: Tue Oct 20 22:30:35 2026 +0800

    [docs] Improve consistency and configuration guidance (#1284)
    
    Generated-by: Codex CLI 0.163.0-alpha.10.2 (GPT-6)
    Co-Authored-By: Codex <[email protected]>
---
 docs/content/_index.md                             |   2 +-
 docs/content/docs/development/calling-llms/chat.md |   5 +-
 .../docs/development/calling-llms/model_routing.md |   4 +-
 .../docs/development/calling-llms/tool_use.md      |   4 +-
 .../docs/development/create-agent/actions.md       |  30 +-
 .../docs/development/memory/memory_events.md       |   2 +-
 .../docs/development/multi-agent/subagents.md      |   8 +-
 docs/content/docs/get-started/overview.md          |   2 +-
 docs/content/docs/get-started/setup.md             |   5 +-
 docs/content/docs/operations/configuration.md      | 404 ++++++++++-----------
 docs/content/docs/operations/consistency.md        | 356 ++++++++++++++++++
 docs/content/docs/operations/deployment.md         |  61 +---
 docs/content/docs/operations/monitoring.md         | 123 ++++++-
 13 files changed, 692 insertions(+), 314 deletions(-)

diff --git a/docs/content/_index.md b/docs/content/_index.md
index 06d239970..5e8d316a1 100644
--- a/docs/content/_index.md
+++ b/docs/content/_index.md
@@ -45,7 +45,7 @@ It provides a step by step introduction on how to install 
Flink Agents and build
 
 ### How to Operate an Agent
 
-* [Deployment]({{< ref "docs/operations/deployment" >}}), [Configuration]({{< 
ref "docs/operations/configuration" >}}), and [Monitoring]({{< ref 
"docs/operations/monitoring" >}}) - Deploy, configure, and monitor agents
+* [Deployment]({{< ref "docs/operations/deployment" >}}), [Consistency]({{< 
ref "docs/operations/consistency" >}}), [Configuration]({{< ref 
"docs/operations/configuration" >}}), and [Monitoring]({{< ref 
"docs/operations/monitoring" >}}) - Deploy, configure, recover, and monitor 
agents
 
 <--->
 
diff --git a/docs/content/docs/development/calling-llms/chat.md 
b/docs/content/docs/development/calling-llms/chat.md
index e86388fff..102278405 100644
--- a/docs/content/docs/development/calling-llms/chat.md
+++ b/docs/content/docs/development/calling-llms/chat.md
@@ -259,13 +259,12 @@ the failure.
 <span id="failures-and-recovery"></span>
 
 Model-call failures are reported after configured retries and routing fallbacks
-are exhausted. See [Core Options]({{< ref 
"docs/operations/configuration#core-options" >}})
+are exhausted. See [Model calls, tools, and retries]({{< ref 
"docs/operations/configuration#model-calls-tools-and-retries" >}})
 for retry settings and [Model Routing]({{< ref 
"docs/development/calling-llms/model_routing" >}})
 for fallbacks.
 
 For how calls behave after a failure and recovery, see
-[Recovery Behavior]({{< ref 
"docs/development/create-agent/actions#recovery-behavior" >}})
-in Actions.
+[Recovery flow]({{< ref "docs/operations/consistency#recovery-flow" >}}).
 
 ## Multimodal Messages {#message-content-blocks}
 
diff --git a/docs/content/docs/development/calling-llms/model_routing.md 
b/docs/content/docs/development/calling-llms/model_routing.md
index 58db5d44b..c59efbc4f 100644
--- a/docs/content/docs/development/calling-llms/model_routing.md
+++ b/docs/content/docs/development/calling-llms/model_routing.md
@@ -281,7 +281,7 @@ behaviors:
 
 For candidates `[small, medium, big]`, selecting `big` with fallback enabled 
tries
 `big`, then `small`, then `medium`. Model-call retries run before fallback; see
-[Core Options]({{< ref "docs/operations/configuration#core-options" >}}).
+[Model calls, tools, and retries]({{< ref 
"docs/operations/configuration#model-calls-tools-and-retries" >}}).
 Failures before the call, such as an unresolved model resource, can proceed
 directly to the next candidate without retries.
 
@@ -354,7 +354,7 @@ metadata when distinguishing their usage.
 With durable execution enabled, completed routing decisions and model calls can
 be reused after recovery. Without it, a request may be routed again and select 
a
 different model. See
-[Recovery Behavior]({{< ref 
"docs/development/create-agent/actions#recovery-behavior" >}})
+[Consistency]({{< ref 
"docs/operations/consistency#recovery-guarantees-and-user-responsibilities" >}})
 for the persistence and replay guarantees.
 
 ## Examples
diff --git a/docs/content/docs/development/calling-llms/tool_use.md 
b/docs/content/docs/development/calling-llms/tool_use.md
index ce33ef845..dde007f38 100644
--- a/docs/content/docs/development/calling-llms/tool_use.md
+++ b/docs/content/docs/development/calling-llms/tool_use.md
@@ -437,9 +437,9 @@ inside each tool, such as an HTTP client read timeout.
 A batch timeout does not undo external effects or guarantee that an already
 running tool stops. Tools that change external systems should be idempotent,
 because a call may run again after recovery. See
-[Configuration]({{< ref "docs/operations/configuration" >}}) for the full 
async batch
+[Configuration]({{< ref 
"docs/operations/configuration#runtime-and-async-execution" >}}) for the full 
async batch
 and execution-capacity options, and
-[Durable Execution]({{< ref 
"docs/development/create-agent/actions#durable-execution" >}})
+[Consistency]({{< ref 
"docs/operations/consistency#recovery-guarantees-and-user-responsibilities" >}})
 for recovery guarantees.
 
 ## Tool response content and metadata
diff --git a/docs/content/docs/development/create-agent/actions.md 
b/docs/content/docs/development/create-agent/actions.md
index 097ae9e92..adcc38e33 100644
--- a/docs/content/docs/development/create-agent/actions.md
+++ b/docs/content/docs/development/create-agent/actions.md
@@ -562,8 +562,9 @@ For example, if an Action fails after a profile lookup has 
completed and its res
 been persisted, recovery can reuse that profile instead of querying the 
service again.
 Code outside the durable call may still run again when the Action is 
re-executed.
 
-Durable execution requires an external action state store. Configure it as 
described in
-[Exactly-Once Action Consistency]({{< ref 
"docs/operations/deployment#exactly-once-action-consistency" >}}).
+To save and reuse call outcomes during recovery, configure checkpointing and 
an external
+Action State Store as described in
+[Consistency]({{< ref 
"docs/operations/consistency#configure-an-action-state-store" >}}).
 
 #### A Durable Call
 
@@ -628,18 +629,10 @@ Java's `getResultClass()` provides the type needed to 
deserialize a saved result
 
 #### Recovery Behavior
 
-When recovery re-executes an unfinished Action and reaches a durable call:
-
-| State of the matching call | Recovery behavior |
-| --- | --- |
-| A successful result has been persisted | Return the saved result without 
calling the function again. |
-| A failure has been persisted | Replay the failure without calling the 
function again. |
-| No completed outcome has been persisted | Execute the function again, or use 
the reconciler for a previously recorded in-flight call. |
-
-An external operation may finish before its result is persisted. If a failure 
occurs in
-that interval, calling the operation again can repeat its side effects. For 
such
-operations, use the external service's idempotency support or a reconciler 
that can
-recover the existing outcome.
+See [Recovery flow]({{< ref "docs/operations/consistency#recovery-flow" >}}) 
for how recovery
+handles saved outcomes and in-flight calls, and
+[Limits on external side effects]({{< ref 
"docs/operations/consistency#limits-on-external-side-effects" >}})
+for operations that may repeat.
 
 #### Reconciling an In-flight Call
 
@@ -799,9 +792,10 @@ worker and batch limits. Concurrency between Actions has 
additional requirements
   the cross-language Flink versions below.
 - **Python Actions:** use `async def` and the framework's futures and `gather` 
APIs.
 
-See [Core Options]({{< ref "docs/operations/configuration#core-options" >}}) 
for worker-pool
-and concurrency limits. These scheduling requirements apply to both ordinary 
and durable
-async calls. Durable calls additionally require the configured action state 
store.
+See [Runtime and async execution]({{< ref 
"docs/operations/configuration#runtime-and-async-execution" >}})
+for worker-pool and concurrency limits. These scheduling requirements apply to 
both ordinary
+and durable async calls. Saving durable call outcomes for recovery 
additionally requires
+an Action State Store.
 
 #### Cross-language Async Requirements
 
@@ -886,7 +880,7 @@ missing value.
 
 Reading a missing attribute without a guard or applying an operation to an 
incompatible
 value can fail while evaluating a condition for an Event. For failure handling 
options, see
-[`action.trigger-condition.evaluate-failure-strategy`]({{< ref 
"docs/operations/configuration#core-options" >}})
+[`action.trigger-condition.evaluate-failure-strategy`]({{< ref 
"docs/operations/configuration#condition-handling" >}})
 in Configuration.
 
 #### Limitations
diff --git a/docs/content/docs/development/memory/memory_events.md 
b/docs/content/docs/development/memory/memory_events.md
index fd5bc9c14..49b08493e 100644
--- a/docs/content/docs/development/memory/memory_events.md
+++ b/docs/content/docs/development/memory/memory_events.md
@@ -238,7 +238,7 @@ Because `agent-run.begin-event` is disabled by default, the 
Trace Log has no bui
 
 ## Semantics & Caveats
 
-- **Trace Log level.** Configure [recording targets]({{< ref 
"docs/operations/configuration#trace-log-targets" >}}) with `scope: EVENT_ONLY` 
and `level: VERBOSE` when full memory-event attributes are required, or select 
individual Event names with an entity scope. Memory-event generation and log 
selection are independent: the emission switches determine whether Events are 
produced, while targets determine whether they are written to Trace Log.
+- **Trace Log level.** Configure [recording targets]({{< ref 
"docs/operations/monitoring#recording-targets" >}}) with `scope: EVENT_ONLY` 
and `level: VERBOSE` when full memory-event attributes are required, or select 
individual Event names with an entity scope. Memory-event generation and log 
selection are independent: the emission switches determine whether Events are 
produced, while targets determine whether they are written to Trace Log.
 - **Durable execution.** A long-term memory operation wrapped in 
`durable_execute` is not recorded again when the durable result is replayed 
from state. The generated events describe operations that actually executed in 
the current attempt.
 - **Observation failures.** Unsupported short-term or sensory values are 
omitted. Invalid long-term records are skipped individually after a valid batch 
is parsed, but a bridge, batch-serialization, or malformed-payload failure 
drops the current partition key's LTM observation batch. Warnings do not 
include observed values. These failures do not fail the action or roll back a 
completed long-term backend operation.
 - **Failed actions.** A failed action does not reach the memory-event emission 
boundary. The operator task fails and recovery rebuilds the interpreter, so 
queued actions do not continue in the same interpreter instance after the 
failure.
diff --git a/docs/content/docs/development/multi-agent/subagents.md 
b/docs/content/docs/development/multi-agent/subagents.md
index 0472561db..b207ba9fa 100644
--- a/docs/content/docs/development/multi-agent/subagents.md
+++ b/docs/content/docs/development/multi-agent/subagents.md
@@ -1253,8 +1253,12 @@ triggers recovery.
 
 Sub-agent calls use
 [durable execution]({{< ref 
"docs/development/create-agent/actions#durable-execution" >}}).
-Recovery reuses persisted results. Work whose result has not been persisted may
-run again, so external services need to handle retried requests.
+With checkpointing and an
+[Action State Store]({{< ref 
"docs/operations/consistency#configure-an-action-state-store" >}}),
+recovery reuses persisted results. Work whose result has not been persisted may
+run again, so external services need to handle retried requests. See
+[Consistency]({{< ref 
"docs/operations/consistency#recovery-guarantees-and-user-responsibilities" >}})
+for replay conditions and limits on repeated effects.
 
 The framework generates stable `session_id` and `call_id` values for recovery.
 If you supply your own IDs, keep them unchanged when the same call is retried.
diff --git a/docs/content/docs/get-started/overview.md 
b/docs/content/docs/get-started/overview.md
index 9d1934640..24cb56785 100644
--- a/docs/content/docs/get-started/overview.md
+++ b/docs/content/docs/get-started/overview.md
@@ -33,7 +33,7 @@ Like any Agent OS, it manages the core building blocks of an 
agent - orchestrati
 The key features of Apache Flink Agents include:
 - **Massive Scale and Millisecond Latency**: Processes massive-scale event 
streams in real time, leveraging Flink's distributed processing engine.
 - **Seamless Data and AI Integration**: Agents interact directly with Flink's 
DataStream and Table APIs for input and output, enabling a smooth integration 
of structured data processing and semantic AI capabilities within Flink.
-- **Reliable and Exactly-Once Execution**: Ensures exactly-once consistency 
for agent actions and their side effects by integrating Flink's checkpointing 
with an external write-ahead log, and supports durable execution - optionally 
with reconcilers to reconcile in-flight side effects upon failure recovery - so 
agents run reliably without human supervision.
+- **Reliable Execution**: Builds on Flink checkpointing and supports an 
external Action State Store to reuse saved Action completions and durable call 
outcomes during recovery. See [Consistency]({{< ref 
"docs/operations/consistency" >}}) for setup, guarantees, and limits on 
external side effects.
 - **Familiar Agent Abstractions**: Leverages well-known AI agent concepts - 
skills, short/long-term memory, prompts, tools, and dynamic orchestration - 
making it easy for developers experienced with agent-based systems to quickly 
adopt and build on Apache Flink Agents without a steep learning curve.
 - **Multi-Language Support**: Provides Java, Python, and YAML APIs for 
defining Agents. Actions and supported Resources can be implemented in Java or 
Python and used across languages. See [Choose APIs and Languages]({{< ref 
"docs/development/create-agent/choose-apis-and-languages" >}}).
 - **Rich Ecosystem**: Natively integrates mainstream LLMs, vector stores from 
diverse providers, and tools or prompts hosted on MCP servers into your agents, 
while enabling customizable extensions.
diff --git a/docs/content/docs/get-started/setup.md 
b/docs/content/docs/get-started/setup.md
index 80a61fb9a..f9eb97b7e 100644
--- a/docs/content/docs/get-started/setup.md
+++ b/docs/content/docs/get-started/setup.md
@@ -434,5 +434,6 @@ submits a complete example and shows its output:
 - [Use the Built-in ReAct Agent]({{< ref 
"docs/get-started/quickstart/react_agent" >}})
 - [Define an Agent with YAML]({{< ref "docs/get-started/quickstart/yaml_agent" 
>}})
 
-For a remote cluster, and for the consistency guarantees that apply once the 
job runs
-there, see [Deployment]({{< ref "docs/operations/deployment" >}}).
+For submission to a remote cluster, see
+[Deployment]({{< ref "docs/operations/deployment#submit-to-flink-cluster" >}}).
+For consistency guarantees, see [Consistency]({{< ref 
"docs/operations/consistency" >}}).
diff --git a/docs/content/docs/operations/configuration.md 
b/docs/content/docs/operations/configuration.md
index d30592db4..1fd208f5f 100644
--- a/docs/content/docs/operations/configuration.md
+++ b/docs/content/docs/operations/configuration.md
@@ -1,6 +1,6 @@
 ---
 title: Configuration
-weight: 2
+weight: 3
 type: docs
 ---
 <!--
@@ -24,16 +24,12 @@ under the License.
 
 ## How to configure Flink Agents
 
-There are two ways to configure Flink Agents, listed in order of **priority 
from high to low**:
+Set options through `AgentsExecutionEnvironment` or the `agent` section of a
+Flink YAML configuration file. Values set on the environment override values 
loaded
+from YAML.
 
-1. **Setting via the AgentsExecutionEnvironment**
-2. **Setting via a Flink YAML configuration file**
-
-The AgentsExecutionEnvironment applies to Agents from the 
AgentsExecutionEnvironment, and the Flink YAML configuration file applies to 
all Flink Agents Jobs using the same configuration file.
-
-{{< hint info >}}
-In case of duplicate keys, the value from the highest priority will override 
those from lower priorities.
-{{< /hint >}}
+Environment settings apply to Agents added to that environment. YAML settings 
apply
+to jobs that load the same file.
 
 ### Setting via the AgentsExecutionEnvironment
 
@@ -50,8 +46,7 @@ agents_env = 
AgentsExecutionEnvironment.get_execution_environment(env)
 # Get configuration object from the environment
 config = agents_env.get_config()
 
-# Set custom configuration using a direct key (string-based key)
-# This is suitable for user-defined or non-standardized settings.
+# Set an option using its string key
 config.set_int("kafkaActionStateTopicNumPartitions", 128)
 
 # Set framework-level configuration using a predefined ConfigOption class
@@ -70,7 +65,7 @@ AgentsExecutionEnvironment agentsEnv = 
AgentsExecutionEnvironment.getExecutionEn
 // Get configuration object
 Configuration config = agentsEnv.getConfig();
 
-// Set custom configuration using key (direct string key)
+// Set an option using its string key
 config.setInt("kafkaActionStateTopicNumPartitions", 128);  // Kafka topic 
partitions count
 
 // Set the list of event listeners
@@ -85,13 +80,11 @@ config.set(AgentExecutionOptions.MAX_RETRIES, 3);
 
 ### Setting via the Flink YAML configuration file
 
-The former `ErrorHandlingStrategy` API and `error-handling-strategy` option 
have been removed. To enable retries, set `max-retries` to a positive number 
(for example, 3 to retain the former `RETRY` mode's default budget). Its 
default is now 0, preserving the previous default behavior of making no 
retries. Terminal Chat failures are reported through failed `ChatResponseEvent` 
events instead of a `FAIL` or `IGNORE` policy.
-
 Flink Agents allows reading configurations from the Flink YAML configuration 
file.
 
 #### Format
 
-As part of the Flink configuration file, the flink agents configuration must 
follow this format, with all agent-specific settings nested under the `agent` 
key:
+Nest Flink Agents settings under the `agent` key:
 
 ```yaml
 agent:
@@ -103,261 +96,234 @@ agent:
 
 #### Loading Behavior
 
-By default, the configuration is automatically loaded from 
`$FLINK_HOME/conf/config.yaml`.
-
-**Special Condition**
+When creating an `AgentsExecutionEnvironment` for a Flink job, Flink Agents 
loads
+the `agent` section from the configuration directory specified by 
`FLINK_CONF_DIR`.
+Set this environment variable before creating the environment:
 
-In the following case, Flink Agents may not locate the corresponding 
configuration file, necessitating manual configuration. If the file is not set, 
no configuration file will be loaded, potentially resulting in unexpected 
behavior or failures.
-
-- **For MiniCluster**:
-  Manual setup is **required** — always export the environment variable before 
running the job:
+```bash
+export FLINK_CONF_DIR="/path/to/flink/conf"
+```
 
-  ```bash
-  export FLINK_CONF_DIR="path/to/your/config.yaml"
-  ```
+`FLINK_CONF_DIR` points to a directory. Java reads `config.yaml` from that 
directory.
+Python reads `flink-conf.yaml` if it exists, otherwise `config.yaml`.
 
-  This ensures that Flink can locate and load the configuration file correctly.
+If `FLINK_CONF_DIR` is unset, no YAML configuration file is loaded. 
Configuration
+through `AgentsExecutionEnvironment` still works and overrides values loaded 
from
+the file.
 
 ## Built-in configuration options
 
-### Core Options
-Here is the list of all built-in core configuration options.
-
-| Key                       | Default                    | Type                
  | Description                                                                 
                                                                                
                                                                                
                    |
-|---------------------------|----------------------------|-----------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
-| `event-listeners`         | none                       | `List<String>`      
  | The list of event listener class names. Each class must implement the 
EventListener interface and provide a public no-argument constructor. {{< hint 
warning >}} Note: Currently, custom event listeners are only supported in Java. 
{{< /hint >}} |
-| `action.trigger-condition.evaluate-failure-strategy` | `WARN_AND_SKIP` | 
ConditionEvaluationFailureStrategy | Handles failures while evaluating a 
trigger condition for an individual Event, including missing attributes, 
incompatible values, or a non-Boolean result. <br/><ul><li>`WARN_AND_SKIP` 
(default): log a warning, treat that condition as false, and continue with 
later OR conditions.</li><li>`FAIL`: throw `IllegalStateException` and fail the 
Flink task; recovery follows the job's re [...]
-| `max-retries`             | 0                          | int                 
  | Number of additional attempts per model call, including routing judge 
calls. Defaults to 0 (no retries).                                              
                                                                                
                                                                       |
-| `retry-wait-interval`     | 1                          | int                 
  | Base wait interval in seconds between retries. Uses exponential backoff: 
the actual wait time for the Nth retry is `retry-wait-interval * 2^(N-1)` 
seconds. For example, with default 1s, waits are 1s, 2s, 4s, etc. The built-in 
chat and decision actions report retry count and total wait time in 
`ChatResponseEvent` and `DecisionResponseEvent`, respectively, and record 
metrics (`retryCount`, `retryWaitSec`) un [...]
-| `chat.async`              | true                       | boolean             
  | Whether chat asynchronously for built-in chat action.                       
                                                                                
                                                                                
                    |
-| `decision.async`          | true                       | boolean             
  | Whether the built-in decision action invokes the model through durable 
async execution. Java calls through Python fall back to synchronous execution 
when the Flink runtime does not support asynchronous Python calls. |
-| `tool-call.async`         | true                       | boolean             
  | Whether the built-in tool-call action runs each tool via durable async 
execution. See [Parallel tool-call batches]({{< ref 
"docs/development/calling-llms/tool_use#parallel-tool-call-batches" >}}) for 
how this switch works with the batch settings below. |
-| `async.batch.parallelism` | os cpu count | int | Maximum concurrent calls in 
one async batch, including batches created with `ctx.gather(...)`. `1` runs 
calls sequentially. Actual concurrency is also limited by `num-async-threads`. 
See [Parallel tool-call batches]({{< ref 
"docs/development/calling-llms/tool_use#parallel-tool-call-batches" >}}). |
-| `async.batch.timeout.ms` | -1 (disabled) | long (milliseconds) | Overall 
timeout for one async batch. Non-positive values disable it. A timeout does not 
guarantee that running calls stop or undo their external effects. See [Timeouts 
and external effects]({{< ref 
"docs/development/calling-llms/tool_use#timeouts-and-external-effects" >}}) for 
tool-call behavior and constraints. |
-| `rag.async`               | true                       | boolean             
  | Whether retrieve context asynchronously for built-in context retrieval 
action.                                                                         
                                                                                
                         |
-| `num-async-threads` | os cpu count * 2 | int | Controls execution capacity 
for asynchronous calls. `async.batch.parallelism` separately limits concurrency 
within each batch. See [Parallel tool-call batches]({{< ref 
"docs/development/calling-llms/tool_use#parallel-tool-call-batches" >}}) for 
tool-call concurrency guidance. |
-| `parallel-execution.enabled` | true                    | boolean             
  | Whether plans with only **Java Actions** run Actions concurrently on the 
parallel execution engine when the continuation-based engine is unavailable 
(**JDK < 21**). Plans declaring Python Resources additionally require a Flink 
version supporting [cross-language async calls]({{< ref 
"docs/development/create-agent/actions#cross-language-async-requirements" >}}). 
Same-key input records commit in input order,  [...]
-| `max-in-flight-input-records` | 100                   | int                  
 | Maximum number of input records that may be in flight concurrently. Only 
enforced by the **JDK < 21** parallel execution engine for plans with only 
**Java Actions**; the JDK 21 coroutine engine and plans containing **Python** 
actions ignore it. Every admitted record consumes one unit of budget; at the 
cap, admission of further records blocks until an in-flight record retires. |
-| `job-identifier`          | none                       | String              
  | The unique identifier of job, remaining consistent after restoring from a 
savepoint. If not set, uses flink job id.                                       
                                                                                
                      |
-| `short-term-memory.state-ttl.ms` | 0                    | long               
   | Time-to-live for short-term memory state in milliseconds. Set to a value 
greater than 0 to enable TTL; 0 disables it.                                    
                                                                                
                       |
-| `short-term-memory.state-ttl.update-type` | `ON_READ_AND_WRITE` | 
ShortTermMemoryTtlUpdate | Update policy for short-term memory TTL. Only 
applies when `short-term-memory.state-ttl.ms` is greater than 0. Valid values: 
`ON_CREATE_AND_WRITE`, `ON_READ_AND_WRITE`. An enabled run-begin memory 
snapshot also refreshes TTL for entries it reads under `ON_READ_AND_WRITE`. |
-| `short-term-memory.state-ttl.visibility` | `NEVER_RETURN_EXPIRED` | 
ShortTermMemoryTtlVisibility | Visibility policy for expired short-term memory 
state. Only applies when `short-term-memory.state-ttl.ms` is greater than 0. 
Valid values: `NEVER_RETURN_EXPIRED`, `RETURN_EXPIRED_IF_NOT_CLEANED_UP`.       
                                                 |
-
-### Trace Log Options
-
-Trace Log records Events, Action executions, and calls made within Actions. 
See [Monitoring]({{< ref "docs/operations/monitoring#trace-log" >}}) for output 
formats and the Trace Tree reader.
-
-| Key | Default | Type | Description |
-|-----|---------|------|-------------|
-| `trace-log.output-type` | `SLF4J` | LoggerType | Trace log output: `SLF4J` 
writes JSON through a dedicated SLF4J logger, visible in Flink's Web UI Logs 
tab; `FILE` writes per-subtask files. A non-empty `trace-log.base-dir` forces 
`FILE`, even when `SLF4J` is selected. |
-| `trace-log.base-dir` | (none) | String | Directory for file output; a 
non-empty value forces `FILE`. When file output is selected without a 
directory, uses `java.io.tmpdir/flink-agents`. |
-| `trace-log.pretty-print` | false | boolean | Format JSON over multiple 
lines. When false, each record occupies one line (JSONL). |
-| `trace-log.targets` | `[{scope: EVENT_ONLY, level: STANDARD}]` | List of 
objects | Match records and control the recording level. Each target uses 
`scope` and optional `level`; see [Trace log targets](#trace-log-targets). |
-| `trace-log.standard.max-string-length` | 2000 | int | Maximum attribute 
string length at `STANDARD` level. No effect at `VERBOSE`. |
-| `trace-log.standard.max-array-elements` | 20 | int | Maximum attribute array 
length at `STANDARD` level. No effect at `VERBOSE`. |
-| `trace-log.standard.max-depth` | 5 | int | Maximum attribute nesting depth 
at `STANDARD` level. Deeper objects are summarized. No effect at `VERBOSE`. |
-
-#### Trace log targets
-
-Every target combines a `scope` with an optional `level`. Omitting `targets` 
records only Events at `STANDARD`. An explicit list replaces that default; an 
empty list records nothing.
-
-For example, this configuration records all entities at `STANDARD`, with full 
attributes for the `search` Tool:
-
-```yaml
-agent:
-  trace-log:
-    targets:
-      - scope: ALL
-        level: STANDARD
-      - scope: {tool: search}
-        level: VERBOSE
-    standard:
-      max-string-length: 2000
-      max-array-elements: 20
-      max-depth: 5
-    output-type: FILE
-    base-dir: /tmp/flink-agent-logs
-    pretty-print: false
-```
+<span id="core-options"></span>
 
-Java and Python can configure targets with `TraceLogTarget` through the same 
`TRACE_LOG_TARGETS` option:
+### Observability
 
-{{< tabs >}}
-{{< tab "python" >}}
+See [Monitoring]({{< ref "docs/operations/monitoring" >}}) for Trace Log 
output and
+record selection, and [Memory Observability]({{< ref 
"docs/development/memory/memory_events" >}})
+for the agent-run begin event and custom Event listeners.
 
-```python
-from flink_agents.api.core_options import AgentConfigOptions
-from flink_agents.api.trace_log import TraceLogLevel, TraceLogTarget
-
-agents_env.get_config().set(
-    AgentConfigOptions.TRACE_LOG_TARGETS,
-    [
-        TraceLogTarget.all(TraceLogLevel.STANDARD),
-        TraceLogTarget.tool("search", TraceLogLevel.VERBOSE),
-    ],
-)
-```
+<span id="trace-log-options"></span>
 
-{{< /tab >}}
-{{< tab "java" >}}
-
-```java
-import java.util.List;
-import org.apache.flink.agents.api.configuration.AgentConfigOptions;
-import org.apache.flink.agents.api.logger.TraceLogLevel;
-import org.apache.flink.agents.api.logger.TraceLogTarget;
-
-agentsEnv.getConfig().set(
-        AgentConfigOptions.TRACE_LOG_TARGETS,
-        List.of(
-                TraceLogTarget.all(TraceLogLevel.STANDARD),
-                TraceLogTarget.tool("search", TraceLogLevel.VERBOSE)));
-```
-
-{{< /tab >}}
-{{< /tabs >}}
+| Key | Default | Type | Description |
+| --- | --- | --- | --- |
+| `agent-run.begin-event` | false | boolean | Emit an `AgentRunBeginEvent` 
with a short-term memory value snapshot before running the Actions triggered by 
each input. Independent of `memory.generate-event`. |
+| `event-listeners` | none | `List<String>` | Fully qualified Java 
`EventListener` class names. Each class must provide a public no-argument 
constructor. Python listener implementations are not supported. |
+| `trace-log.base-dir` | (none) | String | Directory for Trace Log files. A 
nonblank value selects `FILE`, overriding `trace-log.output-type`. When unset, 
file output uses `${java.io.tmpdir}/flink-agents`. |
+| `trace-log.output-type` | `SLF4J` | LoggerType | Trace Log destination: 
`SLF4J` uses the Trace Log logger; `FILE` writes one file per operator subtask. 
See [Output]({{< ref "docs/operations/monitoring#output" >}}) for logging 
configuration and file locations. |
+| `trace-log.pretty-print` | false | boolean | Write multiline JSON when 
`true`, or compact single-line JSON when `false`. Applies to both output types. 
|
+| `trace-log.standard.max-array-elements` | 20 | int | Maximum elements 
retained in each attribute array at `STANDARD` level. `0` disables this limit. 
No effect at `VERBOSE`. |
+| `trace-log.standard.max-depth` | 5 | int | Nesting depth at which attribute 
objects are summarized at `STANDARD` level. `0` disables this limit. No effect 
at `VERBOSE`. |
+| `trace-log.standard.max-string-length` | 2000 | int | Maximum characters 
retained in each attribute string at `STANDARD` level. `0` disables this limit. 
No effect at `VERBOSE`. |
+| `trace-log.targets` | `[{scope: EVENT_ONLY, level: STANDARD}]` | List of 
objects | Select Trace Log entities and their recording levels. Each target 
requires `scope` and may specify `level`; see [Trace log 
targets](#trace-log-targets). |
 
-`TraceLogTarget` provides `all()` and whole-type factories `events()`, 
`actions()`, `tools()`, `llms()`, `parsers()`, and `subagents()`. The 
corresponding singular factories accept a name, such as `event("OrderCreated")` 
or `tool("search")`. All factories accept an optional `TraceLogLevel`. Omitting 
it preserves inheritance: `tool("search")` inherits the level of a matching 
Tool type target or `ALL`, as described below. Targets are immutable.
+#### Trace log targets
 
-In YAML, `scope` accepts `EVENT_ONLY` (match Events), `ALL` (match all 
entities), or a selector object. Use one of the keys `event`, `action`, `tool`, 
`llm`, `parser`, or `subagent` with an entity name, for example `{event: 
OrderCreated}`. The value `"*"` selects that whole type: `{event: "*"}` is 
equivalent to `EVENT_ONLY`.
+`trace-log.targets` is a list of target objects. Omitting the option records 
Events
+at `STANDARD`. An explicit list replaces this default; an empty list records 
nothing.
 
-The generic form `{entityType: custom, entityName: name}` also supports custom 
entity types reported through `ExecutionReporter`; omit `entityName` to select 
the whole type. Use `TraceLogTarget.entity(...)` for the same selection in Java 
or Python. For example, `TraceLogTarget.entity("retriever", "catalog")` 
corresponds to `scope: {entityType: retriever, entityName: catalog}`. Each 
selector object must contain exactly one category key, or the generic 
`entityType` and optional `entityName [...]
+Each target has the following fields:
 
-Direct model or tool calls inside an Action are not automatically recorded as 
separate calls, even with `scope: ALL`.
+- `scope` (required): `EVENT_ONLY`, `ALL`, a category selector with exactly 
one of
+  `event`, `action`, `tool`, `llm`, `parser`, or `subagent`, or a generic 
selector with
+  `entityType` and optional `entityName`. Do not mix category and generic 
fields.
+  A category value of `"*"`, or a generic selector without `entityName`, 
selects the
+  whole entity type. `EVENT_ONLY` is equivalent to `{event: "*"}`.
+- `level` (optional): `OFF`, `STANDARD`, or `VERBOSE`. An omitted level 
inherits
+  from matching scopes; it does not automatically mean `STANDARD`. In YAML, 
quote
+  `level: "OFF"` so Python does not interpret it as a boolean.
 
-`entityType` identifies the kind of object to observe. For `entityName`, use 
the logical name from your Agent's code or configuration. Trace Log records and 
target selectors use the same name. Common examples are:
+Duplicate scopes may combine an omitted level with an explicit level or repeat 
the
+same explicit level. Conflicting explicit levels for the same scope are 
rejected
+when logging starts. This also applies to equivalent Event type selectors.
 
-| `entityType` | What to use as `entityName` | Example |
-|--------------|----------------------------|---------|
-| `event` | The Event type used when emitting or subscribing to Events. | 
`OrderCreated` |
-| `action` | The name used to declare the Action in the Agent. | 
`create_order` |
-| `llm` | The registered ChatModel Resource name used by the call. | 
`primary_model` |
+For YAML and API examples, entity naming, and level inheritance, see
+[Recording Targets]({{< ref "docs/operations/monitoring#recording-targets" 
>}}).
 
-For example, `scope: {llm: primary_model}` selects calls to the Resource named 
`primary_model`. Its configured model or deployment is recorded separately in 
`entityMetadata.model`.
+The old `event-log.*`, `eventLoggerType`, `baseLogDir`, and `prettyPrint` keys 
are
+unsupported and rejected at startup. Use `trace-log.targets`, 
`trace-log.standard.*`,
+and the output options above.
 
-Entity names match exactly. A name ending in `.*` matches its namespace and 
descendants: `{event: "com.foo.*"}` matches `com.foo` and `com.foo.Event`, but 
not `com.foobar.Event`. Other wildcard forms are unsupported. A category 
selector with `"*"`, or a generic selector without `entityName`, matches the 
whole type.
+### Condition handling
 
-`level` accepts `OFF`, `STANDARD`, and `VERBOSE`. `OFF` writes no record. 
`STANDARD` truncates or summarizes attributes using the `standard` limits; 
`VERBOSE` retains full attributes. Both recording settings preserve all 
identity and relationship fields, entity metadata, timestamps, statuses, and 
problem categories. Media payload sanitization applies at both recording 
settings. See [Recording Level]({{< ref 
"docs/operations/monitoring#recording-level" >}}) for the truncation format and 
m [...]
+See [Condition Evaluation Failures]({{< ref 
"docs/development/create-agent/actions#condition-evaluation-failures" >}})
+for trigger condition examples.
 
-For each entity, matching scopes are considered from most to least specific:
+| Key | Default | Type | Description |
+| --- | --- | --- | --- |
+| `action.trigger-condition.evaluate-failure-strategy` | `WARN_AND_SKIP` | 
ConditionEvaluationFailureStrategy | Behavior when evaluating a trigger 
condition for an Event fails. `WARN_AND_SKIP` logs a warning and treats the 
condition as false; `FAIL` fails the Flink task. Initialization failures are 
not affected. See [Condition Evaluation Failures]({{< ref 
"docs/development/create-agent/actions#condition-evaluation-failures" >}}). |
 
-| Order | Matching scope |
-|-------|----------------|
-| 1 | Exact entity name within its type |
-| 2 | Namespace prefixes within its type, from longest to shortest |
-| 3 | Entity type (`EVENT_ONLY` is the `event` type scope) |
-| 4 | `ALL` |
+### Model calls, tools, and retries
 
-The first explicitly specified `level` in this chain applies. An omitted 
`level` inherits from the next matching scope and does not override it. If at 
least one scope matches but no matching scope specifies `level`, the entity 
uses `STANDARD`. If no scope matches, no record is written. Target order does 
not affect the result.
+See [Chat]({{< ref 
"docs/development/calling-llms/chat#handle-success-and-failure" >}})
+and [Decision Models]({{< ref 
"docs/development/decision_models#event-driven-calls" >}})
+for model-call outcomes and retries, and [Context Retrieval]({{< ref 
"docs/development/vector-search/vector_stores#built-in-events-and-actions" >}})
+for vector-store queries. See [Tool Use]({{< ref 
"docs/development/calling-llms/tool_use#parallel-tool-call-batches" >}})
+for tool-call batches and timeout behavior, and [Execution Metrics]({{< ref 
"docs/operations/monitoring#execution-metrics" >}})
+for retry metrics.
 
-An explicit `OFF` stops inheritance and suppresses recording. A more specific 
scope can enable recording by explicitly setting `STANDARD` or `VERBOSE`; 
omitting `level` continues to inherit `OFF`.
+| Key | Default | Type | Description |
+| --- | --- | --- | --- |
+| `chat.async` | true | boolean | Enable durable async execution for built-in 
Chat model calls, including routing judge calls. `false` selects synchronous 
execution. |
+| `decision.async` | true | boolean | Enable durable async execution for 
built-in Decision Model calls. `false` selects synchronous execution. |
+| `max-retries` | 0 | int | Maximum retries after the initial attempt of a 
built-in Chat or Decision Model call, including routing judge calls. 
Non-positive values disable retries. |
+| `rag.async` | true | boolean | Enable durable async execution for 
vector-store queries made by the built-in context retrieval Action. `false` 
selects synchronous execution. |
+| `retry-wait-interval` | 1 | int | Base wait in seconds between retries 
controlled by `max-retries`. The wait before retry N is `retry-wait-interval * 
2^(N-1)`. Non-positive values retry without waiting. |
+| `tool-call.async` | true | boolean | Enable durable async execution for 
ordinary tools handled by the built-in Tool Call Action. With `false`, these 
calls run synchronously, one at a time. See [Tool Use]({{< ref 
"docs/development/calling-llms/tool_use#parallel-tool-call-batches" >}}) for 
batch behavior. |
+
+These async switches select how built-in calls execute. Synchronous calls still
+use durable execution; saving outcomes for recovery requires an
+[Action State Store]({{< ref 
"docs/operations/consistency#configure-an-action-state-store" >}}).
+Built-in Chat and context retrieval calls, and Java Decision Model calls 
through
+Python, fall back to synchronous execution when the runtime cannot support 
their
+cross-language async call. See [Execution Requirements]({{< ref 
"docs/development/create-agent/actions#execution-requirements" >}})
+for cross-language async requirements.
+
+The `ErrorHandlingStrategy` API and `error-handling-strategy` option have been
+removed. Configure retries with `max-retries`; see Chat for terminal failure 
handling.
+
+### Runtime and async execution
+
+See [Execution Requirements]({{< ref 
"docs/development/create-agent/actions#execution-requirements" >}})
+for JDK, scheduling, and cross-language requirements. Batch limits also apply 
to
+the built-in [parallel tool-call batches]({{< ref 
"docs/development/calling-llms/tool_use#parallel-tool-call-batches" >}}).
 
-For example, a Tool type target at `VERBOSE` supplies the level to matching 
namespace and exact-name targets that omit it. A matching namespace target at 
`OFF` suppresses an exact-name target that omits `level`, while an exact-name 
target with explicit `STANDARD` or `VERBOSE` enables that entity. `EVENT_ONLY` 
applies only to Events; its level does not affect a Tool target.
+| Key | Default | Type | Description |
+| --- | --- | --- | --- |
+| `async.batch.parallelism` | os cpu count | int | Maximum concurrent calls in 
one `ctx.gather(...)` batch, including ordinary, durable, and built-in tool 
calls. Values of `1` or less run calls sequentially. Worker-pool capacity can 
further limit concurrency. |
+| `async.batch.timeout.ms` | -1 (disabled) | long (milliseconds) | Overall 
timeout for one `ctx.gather(...)` batch, in milliseconds. Non-positive values 
disable it. A timeout does not guarantee that running calls stop and cannot 
undo their external effects. See [Tool Use]({{< ref 
"docs/development/calling-llms/tool_use#timeouts-and-external-effects" >}}) for 
built-in tool-batch behavior. |
+| `job-identifier` | none | String | Stable job identifier used to scope Mem0 
long-term memory. Keep an explicit value unchanged across restores. If unset, 
the initial Flink JobID is stored in operator state and reused when restoring a 
checkpoint or savepoint. |
+| `max-in-flight-input-records` | 100 | int | Maximum input records in flight 
per Agent operator subtask on the experimental parallel engine. Must be 
positive. The limit is ignored by continuation-based execution and plans 
containing Python Actions. See [Execution Requirements]({{< ref 
"docs/development/create-agent/actions#execution-requirements" >}}). |
+| `num-async-threads` | os cpu count * 2 | int | Worker threads per async 
execution pool on each Agent operator subtask. Must be positive. 
`async.batch.parallelism` separately limits concurrency within each batch; see 
[Execution Requirements]({{< ref 
"docs/development/create-agent/actions#execution-requirements" >}}) for 
scheduling behavior. |
+| `parallel-execution.enabled` | true | boolean | **Experimental.** Enable the 
parallel engine for eligible plans with only Java Actions when 
continuation-based execution is unavailable. `false` selects serial execution. 
Ignored by the continuation engine and plans containing Python Actions. See 
[Execution Requirements]({{< ref 
"docs/development/create-agent/actions#execution-requirements" >}}) for JDK and 
Python Resource requirements. |
 
-Duplicate targets for the same scope merge an omitted level with an explicit 
level and accept repeated identical explicit levels. Different explicit levels 
for the same scope are rejected when logging starts, regardless of target 
order. This includes equivalent targets expressed as `EVENT_ONLY`, `{event: 
"*"}`, or `{entityType: event}`. For example, `EVENT_ONLY` without `level` and 
`{event: "*"}` with `VERBOSE` merge into one Event type target at `VERBOSE`. 
Typed targets, mapping objects [...]
+### Memory
 
-Selecting a Tool does not automatically select its parent Action; recorded 
relationships retain their IDs even when related records are omitted.
+#### Memory events {#memory-event-options}
 
-An `ALL` preset with `level: "OFF"` disables its default range, while a more 
specific target with explicit `STANDARD` or `VERBOSE` can enable recording. A 
preset is unnecessary when selecting only one entity type. To record only Tools:
+An explicitly configured operation-specific switch overrides 
`memory.generate-event`.
+If neither is set, the effective default below applies. See
+[Memory Observability]({{< ref "docs/development/memory/memory_events" >}}) 
for event
+payloads and subscription examples. The independent `agent-run.begin-event` 
switch
+is listed under [Observability](#observability).
 
-```yaml
-agent:
-  trace-log:
-    targets:
-      - scope: {tool: "*"}
-        level: VERBOSE
-```
+| Key | Raw default | Effective default | Type | Description |
+| --- | --- | --- | --- | --- |
+| `memory.generate-event` | unset | per-operation defaults | boolean | 
Fallback value for operation-specific memory-event switches that are unset. |
+| `memory.generate-event.long-term-get` | unset | on | boolean | Emit Events 
describing long-term memory reads. |
+| `memory.generate-event.long-term-search` | unset | on | boolean | Emit 
Events describing long-term memory searches. |
+| `memory.generate-event.long-term-update` | unset | on | boolean | Emit 
Events describing long-term memory additions, updates, deletions, and 
memory-set deletion. |
+| `memory.generate-event.sensory-read` | unset | off | boolean | Emit Events 
describing sensory memory reads. |
+| `memory.generate-event.sensory-write` | unset | on | boolean | Emit Events 
describing sensory memory writes. |
+| `memory.generate-event.short-term-read` | unset | off | boolean | Emit 
Events describing short-term memory reads. |
+| `memory.generate-event.short-term-write` | unset | on | boolean | Emit 
Events describing short-term memory writes. |
 
-To record everything except one Tool, use a local `OFF` target:
+#### Short-term memory TTL
 
-```yaml
-agent:
-  trace-log:
-    targets:
-      - scope: ALL
-        level: STANDARD
-      - scope: {tool: search}
-        level: "OFF"
-```
+See [Short-Term Memory TTL]({{< ref 
"docs/development/memory/sensory_and_short_term_memory#short-term-memory-2" >}})
+for TTL behavior and setup examples.
 
-The old `event-log.*`, `eventLoggerType`, `baseLogDir`, and `prettyPrint` 
configuration keys are unsupported and rejected at startup. Migrate selection 
and verbosity to `targets`, truncation limits to `trace-log.standard.*`, and 
output settings to `trace-log.output-type`, `trace-log.base-dir`, and 
`trace-log.pretty-print`.
+| Key | Default | Type | Description |
+| --- | --- | --- | --- |
+| `short-term-memory.state-ttl.ms` | 0 | long | Short-term memory TTL in 
milliseconds. A positive value enables expiration; non-positive values disable 
it. |
+| `short-term-memory.state-ttl.update-type` | `ON_READ_AND_WRITE` | 
ShortTermMemoryTtlUpdate | When TTL is enabled, refresh expiration on creation 
and writes (`ON_CREATE_AND_WRITE`), or also on reads (`ON_READ_AND_WRITE`). |
+| `short-term-memory.state-ttl.visibility` | `NEVER_RETURN_EXPIRED` | 
ShortTermMemoryTtlVisibility | When TTL is enabled, never return expired state 
(`NEVER_RETURN_EXPIRED`), or allow it until cleanup 
(`RETURN_EXPIRED_IF_NOT_CLEANED_UP`). |
 
-### Memory Event Options
+### Action consistency
 
-The eight `memory.generate-event*` options have no raw `ConfigOption` default. 
When a sub-key and the master switch are both unset, the runtime uses the 
effective default shown below. See [Memory Observability]({{< ref 
"docs/development/memory/memory_events" >}}) for resolution order, event 
payloads, and subscription examples.
+<span id="action-state-store"></span>
 
-| Key | Raw default | Effective default | Type | Description |
-|-----|-------------|-------------------|------|-------------|
-| `memory.generate-event` | unset | per-operation defaults | boolean | Master 
fallback for unset operation-specific switches. |
-| `memory.generate-event.short-term-write` | unset | on | boolean | Emit 
short-term memory write events. |
-| `memory.generate-event.short-term-read` | unset | off | boolean | Emit 
short-term memory read events. |
-| `memory.generate-event.sensory-write` | unset | on | boolean | Emit sensory 
memory write events. |
-| `memory.generate-event.sensory-read` | unset | off | boolean | Emit sensory 
memory read events. |
-| `memory.generate-event.long-term-update` | unset | on | boolean | Emit 
long-term memory add/delete events. |
-| `memory.generate-event.long-term-get` | unset | on | boolean | Emit 
long-term memory get events. |
-| `memory.generate-event.long-term-search` | unset | on | boolean | Emit 
long-term memory search events. |
-| `agent-run.begin-event` | false | off | boolean | Opt in to the agent-run 
begin event. Independent of the memory-event master switch. |
-
-### Action State Store
+For consistency modes, setup examples, and recovery behavior, see
+[Consistency]({{< ref "docs/operations/consistency" >}}).
 
 #### Common
 
 | Key                          | Default          | Type    | Description      
                                                                        |
 
|------------------------------|------------------|---------|------------------------------------------------------------------------------------------|
-| `actionStateStoreBackend`    | (none)           | String  | The backend for 
action state store. Supported values: `"kafka"`, `"fluss"`.              |
+| `actionStateStoreBackend` | (none) | String | Action State Store backend: 
`"kafka"` or `"fluss"` (case-insensitive). When unset, no external Action State 
Store is enabled. |
 
 #### Kafka-based Action State Store
 
-Here are the configuration options for Kafka-based Action State Store.
+Dedicate one topic to each logical Agent operator, shared by that operator's
+subtasks. Separate operators in the same job need separate topics.
 
-| Key                                 | Default                  | Type    | 
Description                                                                 |
-|-------------------------------------|--------------------------|---------|-----------------------------------------------------------------------------|
-| `kafkaBootstrapServers`             | "localhost:9092"         | String  | 
The config parameter specifies the Kafka bootstrap server.                  |
-| `kafkaActionStateTopic`             | (none)                   | String  | 
The Kafka topic for action state. Dedicate it to one logical Flink Agents 
operator, shared by that operator's subtasks. |
-| `kafkaActionStateTopicNumPartitions`| 64                       | Integer | 
The config parameter specifies the number of partitions for the Kafka action 
state topic. |
-| `kafkaActionStateTopicReplicationFactor` | 1                     | Integer | 
The config parameter specifies the replication factor for the Kafka action 
state topic. |
-| `kafkaActionStateTombstoneEnabled`  | false                    | Boolean | 
Whether pruning sends tombstone records so log compaction can reclaim pruned 
keys on a compacted action-state topic. Off by default: pruning does not 
invalidate older restore points, but the topic continues to grow. When enabled, 
the checkpoint whose completion triggers pruning remains usable, but restoring 
an earlier checkpoint or savepoint may replay later tombstones and re-execute 
already completed actions. E [...]
-| `kafkaActionStateCleanupControlTopic` | (none)                 | String  | 
Separate, single-partition topic containing committed checkpoint-aligned 
cleanup boundaries. It must use `cleanup.policy=compact` without delete 
retention, differ from the action-state topic, and be dedicated to the same job 
recovery history. Setting it enables boundary enforcement during recovery. It 
cannot be combined with `kafkaActionStateTombstoneEnabled`. |
+The store creates the configured topic if it does not exist, and reuses an 
existing
+topic without changing its settings. The partition-count and replication-factor
+options apply only when creating a topic.
 
-##### Checkpoint-aligned Kafka cleanup
-
-Checkpoint-aligned cleanup is an explicit administrative operation. 
`KafkaActionStateCleanupTool` provides the same command-line workflow for Java 
and Python jobs. Run it with the Flink Agents runtime JAR and the matching 
Flink State Processor API JAR on the classpath:
-
-```text
-plan --checkpoint PATH (--operator-uid UID | --operator-uid-hash HASH) 
--output FILE
-apply --plan FILE --bootstrap-servers SERVERS --control-topic TOPIC 
[--replication-factor N]
-```
+The Kafka Action State Store and its cleanup tool currently expose no 
configuration
+for passing Kafka client SASL/SSL settings.
 
-The `plan` command reads all recovery markers from the selected checkpoint or 
savepoint through Flink's State Processor API, creates a deterministic plan 
using the earliest required offset per partition, and refuses to overwrite an 
existing output file. Legacy map markers can still be restored by a job, but 
cannot authorize deletion because they do not identify the physical Kafka 
topic. The `apply` command verifies the content-derived plan ID before 
contacting Kafka. Plan files must cont [...]
-
-Review and retain the plan's deterministic JSON before applying it. The 
coordinator verifies that the selected offsets are still available, writes 
`COMMITTED` before calling Kafka `deleteRecords`, verifies every resulting 
beginning offset, and then writes `APPLIED`. Reapplying the same plan retries 
an interrupted committed operation without changing its boundary. Set 
`kafkaActionStateCleanupControlTopic` on the job only after the first plan is 
committed; recovery requires the configured  [...]
-
-Physical prefix cleanup requires the action-state data topic to use 
`cleanup.policy=compact,delete`, `retention.ms=-1`, and `retention.bytes=-1`. 
The store uses those settings when it creates a new topic. Before applying 
cleanup to an existing topic, alter it to those values. The `delete` policy 
enables Kafka's `deleteRecords` API, while both retention limits remain 
disabled so Kafka cannot independently retire records that a supported 
checkpoint still needs. Apply validates these effect [...]
-
-For the first cleanup on an existing job, stop the job, create and apply the 
plan from the recovery point that will become the oldest supported one, and 
restart from that same point with `kafkaActionStateCleanupControlTopic` 
configured. This avoids a failover window in which the old running job does not 
yet know the committed boundary. Once the job is running with boundary 
enforcement enabled, later forward-only plans may be applied while it runs. Run 
only one `apply` operation at a time [...]
+| Key                                 | Default                  | Type    | 
Description                                                                 |
+|-------------------------------------|--------------------------|---------|-----------------------------------------------------------------------------|
+| `kafkaActionStateCleanupControlTopic` | (none) | String | Separate topic 
containing committed checkpoint-aligned cleanup boundaries. When configured, 
recovery rejects points requiring data below the boundary. Mutually exclusive 
with `kafkaActionStateTombstoneEnabled=true`. See [Checkpoint-aligned Kafka 
cleanup]({{< ref "docs/operations/consistency#checkpoint-aligned-kafka-cleanup" 
>}}) for prerequisites and procedure. |
+| `kafkaActionStateTombstoneEnabled` | false | Boolean | Write deletion 
markers for execution records covered by completed checkpoints. Enabling this 
may cause completed Actions to run again when restoring older checkpoints or 
savepoints. Mutually exclusive with `kafkaActionStateCleanupControlTopic`. See 
[Kafka retention and cleanup]({{< ref 
"docs/operations/consistency#kafka-retention-and-cleanup" >}}) for the recovery 
trade-off. |
+| `kafkaActionStateTopic` | (none) | String | Topic storing Action state. 
Required when `actionStateStoreBackend` is `kafka`. |
+| `kafkaActionStateTopicNumPartitions` | 64 | Integer | Number of partitions 
when creating the topic. Does not change an existing topic. |
+| `kafkaActionStateTopicReplicationFactor` | 1 | Integer | Replication factor 
when creating the topic. Does not change an existing topic. |
+| `kafkaBootstrapServers` | "localhost:9092" | String | Bootstrap servers for 
the Kafka Action State Store. |
 
-The action-state topic and control topic must be dedicated to one job's 
recovery history. Once any boundary has been committed, every future run and 
restore of that history must retain the same control-topic configuration; 
omitting or changing it removes logical boundary enforcement. The boundary can 
move only forward. A checkpoint whose marker is below the committed boundary is 
rejected even if Kafka has not finished physical deletion.
+##### Changes to an existing Kafka store
 
-Per-key tombstones and checkpoint-aligned cleanup are mutually exclusive. 
Tombstones are not tied to the selected recovery boundary and could invalidate 
a checkpoint that checkpoint-aligned cleanup promises to retain.
+When reusing checkpoints or savepoints with this store:
 
-To migrate a job that previously emitted tombstones, first stop it cleanly so 
its Kafka producer closes and all accepted tombstone sends finish. Restart from 
the newest recovery point that is valid under the tombstone mode's existing 
recovery trade-off, with `kafkaActionStateTombstoneEnabled=false` and no 
cleanup control topic configured. After that attempt completes a new checkpoint 
or savepoint, stop it again, create and apply the first cleanup plan from that 
new recovery point, and re [...]
+- Keep the original topic and partition count. Increasing the partition count
+  prevents recovery from existing snapshots and causes checkpoints of the 
running
+  job to fail. Do not delete and recreate the topic or switch to a replacement,
+  even if it has the same name and partition count.
+- Retention changes and manual deletion must preserve the records needed by 
every
+  recovery point you intend to retain. See
+  [Kafka retention and cleanup]({{< ref "docs/operations/consistency" 
>}}#kafka-retention-and-cleanup).
 
 #### Fluss-based Action State Store
 
-Here are the configuration options for Fluss-based Action State Store.
-
-When taking a checkpoint, the store captures the bucket end offsets and then 
synchronously appends a fresh copy of each cached action state needed for 
replay before saving that recovery marker. States whose input sequence is at or 
below the key’s last completed sequence number in this checkpoint are skipped. 
Completed actions in unfinished inputs and all states for keys without a 
completed sequence number are still refreshed. This keeps durable call results 
available even when they were  [...]
+Dedicate one table to each logical Agent operator, shared by that operator's
+subtasks. Separate operators in the same job need separate tables.
 
-Fluss log retention must preserve the records from every checkpoint or 
savepoint you intend to restore. Refreshing state does not protect older 
restore points from retention or repair recovery markers created before this 
fix.
+The store creates the configured database and append-only log table if they do 
not
+exist, and reuses an existing table. The bucket-count option applies only when
+creating a table. SASL options apply when `flussSecurityProtocol` is `SASL`; 
they
+are ignored for `PLAINTEXT`.
 
 | Key                          | Default          | Type    | Description      
                                                                        |
 
|------------------------------|------------------|---------|------------------------------------------------------------------------------------------|
-| `flussBootstrapServers`      | "localhost:9123" | String  | The Fluss 
bootstrap servers address.                                                     |
-| `flussActionStateDatabase`   | "flink_agents"   | String  | The Fluss 
database name for storing action state.                                        |
-| `flussActionStateTable`      | (none)           | String  | The Fluss table 
for action state. Dedicate it to one logical Flink Agents operator, shared by 
that operator's subtasks. |
-| `flussActionStateTableBuckets` | 64             | Integer | The number of 
buckets for the Fluss action state table.                                  |
-| `flussSecurityProtocol`      | "PLAINTEXT"      | String  | The 
authentication protocol for Fluss client. Valid values: `PLAINTEXT` (default, 
no authentication), `SASL` (SASL/PLAIN authentication). |
-| `flussSaslMechanism`         | "PLAIN"          | String  | The SASL 
mechanism for Fluss authentication.                                             
|
-| `flussSaslJaasConfig`        | (none)           | String  | The JAAS 
configuration string for Fluss SASL authentication.                             
|
-| `flussSaslUsername`          | (none)           | String  | The username for 
Fluss SASL authentication.                                              |
-| `flussSaslPassword`          | (none)           | String  | The password for 
Fluss SASL authentication.                                              |
+| `flussActionStateDatabase` | "flink_agents" | String | Database containing 
the Action State Store table. |
+| `flussActionStateTable` | (none) | String | Append-only log table storing 
Action state. Required when `actionStateStoreBackend` is `fluss`. |
+| `flussActionStateTableBuckets` | 64 | Integer | Number of buckets when 
creating the table. Does not change an existing table. |
+| `flussBootstrapServers` | "localhost:9123" | String | Bootstrap servers for 
the Fluss Action State Store. |
+| `flussSaslJaasConfig` | (none) | String | JAAS configuration for Fluss SASL 
authentication. Takes precedence over `flussSaslUsername` and 
`flussSaslPassword`. |
+| `flussSaslMechanism` | "PLAIN" | String | SASL mechanism used when 
`flussSecurityProtocol` is `SASL`. Use `PLAIN` for SASL/PLAIN authentication. |
+| `flussSaslPassword` | (none) | String | Password for SASL/PLAIN 
authentication. Set together with `flussSaslUsername` when 
`flussSaslJaasConfig` is unset. |
+| `flussSaslUsername` | (none) | String | Username for SASL/PLAIN 
authentication. Set together with `flussSaslPassword` when 
`flussSaslJaasConfig` is unset. |
+| `flussSecurityProtocol` | "PLAINTEXT" | String | Fluss client authentication 
protocol: `PLAINTEXT` (no authentication) or `SASL` (SASL/PLAIN 
authentication), case-insensitive. |
+
+##### Changes to an existing Fluss store
+
+When reusing checkpoints or savepoints with this store:
+
+- Keep the original cluster, database, table, and bucket layout. Do not change 
the
+  bucket count, recreate the table, or switch to a replacement table. A 
replacement
+  with the same name and bucket count may not be detected during recovery.
+- Preserve the append-only log table type and the schema used by Flink Agents,
+  including column types and order.
+- Log retention changes and manual truncation must preserve the records needed
+  by every recovery point you intend to retain. See
+  [Fluss retention]({{< ref "docs/operations/consistency" >}}#fluss-retention).
diff --git a/docs/content/docs/operations/consistency.md 
b/docs/content/docs/operations/consistency.md
new file mode 100644
index 000000000..07a6a312a
--- /dev/null
+++ b/docs/content/docs/operations/consistency.md
@@ -0,0 +1,356 @@
+---
+title: Consistency
+weight: 2
+type: docs
+---
+<!--
+Licensed to the Apache Software Foundation (ASF) under one
+or more contributor license agreements.  See the NOTICE file
+distributed with this work for additional information
+regarding copyright ownership.  The ASF licenses this file
+to you under the Apache License, Version 2.0 (the
+"License"); you may not use this file except in compliance
+with the License.  You may obtain a copy of the License at
+
+  http://www.apache.org/licenses/LICENSE-2.0
+
+Unless required by applicable law or agreed to in writing,
+software distributed under the License is distributed on an
+"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+KIND, either express or implied.  See the License for the
+specific language governing permissions and limitations
+under the License.
+-->
+
+When a Flink job recovers from a checkpoint, it restores its state and replays 
the
+input received since that checkpoint. An Agent may therefore call a model, 
invoke
+a tool, or update an external system again. The consistency mode you choose
+determines what state must remain available for recovery and which work can be
+reused.
+
+## Choose a consistency mode
+
+With exactly-once checkpointing and the required source and sink guarantees,
+each input affects the job's state and output once, even if recovery processes 
it
+again. Flink Agents calls this **exactly-once output consistency**.
+
+Checkpoint recovery may still rerun Actions and external calls, so you can add 
an
+external Action State Store to reuse saved Action completions and durable call
+outcomes. Flink Agents calls this **exactly-once action consistency**.
+
+| Configuration | Behavior after a failure | Requirements and trade-offs |
+| --- | --- | --- |
+| No checkpointing | The job restarts from the source's configured starting 
positions without restoring Agent state or progress from a checkpoint. | 
Suitable when the application can accept lost progress or repeated work. |
+| Flink checkpointing | Flink restores checkpointed state and replays 
subsequent input. Actions and external calls may repeat, and models or queries 
may return different results. | Enable exactly-once checkpointing with durable 
checkpoint storage and the required source and sink guarantees. External calls 
must tolerate repetition. |
+| Flink checkpointing + Action State Store | Recovery replays saved 
completions without rerunning Action bodies. Unfinished Actions may run again, 
reusing saved durable call outcomes. | Add a Kafka or Fluss Action State Store. 
Saving Action completions and durable call outcomes requires synchronous 
backend writes. Retain both checkpoints and the backend records needed for 
recovery. |
+
+## Required setup
+
+Enable exactly-once checkpointing for the Flink or PyFlink job, and use source
+and sink connectors with the required
+[fault tolerance 
guarantees](https://nightlies.apache.org/flink/flink-docs-release-1.20/docs/connectors/datastream/guarantees/).
+Follow Flink's [checkpointing 
guide](https://nightlies.apache.org/flink/flink-docs-release-1.20/docs/dev/datastream/fault-tolerance/checkpointing/)
+for configuration and checkpoint storage, and
+[checkpoint recovery 
guide](https://nightlies.apache.org/flink/flink-docs-release-1.20/docs/ops/state/checkpoints/#resuming-from-a-retained-checkpoint)
+for restore procedures. Use the Flink documentation for the version you deploy.
+
+### Configure an Action State Store
+
+Before building the Agent pipeline, configure Kafka or Fluss as the Action 
State
+Store backend. Each logical Agent operator needs its **own Kafka topic or Fluss
+table**, shared only by that operator's subtasks.
+
+The Python and Java examples below use `agents_env` and `agentsEnv`, 
respectively,
+created as described in [Integrate with Flink]({{< ref 
"docs/development/integrate_with_flink" >}}).
+The YAML examples show the same settings in the `agent:` section of the job's
+Flink configuration.
+
+#### Kafka
+
+Set the backend to `kafka` and provide the broker address and a dedicated 
topic:
+
+{{< tabs "Kafka Action State Store" >}}
+{{< tab "Python" >}}
+```python
+from flink_agents.api.core_options import AgentConfigOptions
+
+config = agents_env.get_config()
+config.set(AgentConfigOptions.ACTION_STATE_STORE_BACKEND, "kafka")
+config.set(AgentConfigOptions.KAFKA_BOOTSTRAP_SERVERS, "kafka:9092")
+config.set(AgentConfigOptions.KAFKA_ACTION_STATE_TOPIC, 
"orders-agent-action-state")
+```
+{{< /tab >}}
+{{< tab "Java" >}}
+```java
+import org.apache.flink.agents.api.configuration.AgentConfigOptions;
+import org.apache.flink.agents.api.configuration.Configuration;
+
+Configuration config = agentsEnv.getConfig();
+config.set(AgentConfigOptions.ACTION_STATE_STORE_BACKEND, "kafka");
+config.set(AgentConfigOptions.KAFKA_BOOTSTRAP_SERVERS, "kafka:9092");
+config.set(AgentConfigOptions.KAFKA_ACTION_STATE_TOPIC, 
"orders-agent-action-state");
+```
+{{< /tab >}}
+{{< tab "YAML" >}}
+```yaml
+agent:
+  actionStateStoreBackend: kafka
+  kafkaBootstrapServers: "kafka:9092"
+  kafkaActionStateTopic: orders-agent-action-state
+```
+{{< /tab >}}
+{{< /tabs >}}
+
+If the topic does not exist, the store creates it with 64 partitions and a
+replication factor of 1 by default. Otherwise, it reuses the existing topic 
without
+changing its settings.
+For retention requirements and cleanup options, see
+[Kafka retention and cleanup](#kafka-retention-and-cleanup).
+
+#### Fluss
+
+Set the backend to `fluss` and provide the server address and a dedicated 
table:
+
+{{< tabs "Fluss Action State Store" >}}
+{{< tab "Python" >}}
+```python
+from flink_agents.api.core_options import AgentConfigOptions
+
+config = agents_env.get_config()
+config.set(AgentConfigOptions.ACTION_STATE_STORE_BACKEND, "fluss")
+config.set(AgentConfigOptions.FLUSS_BOOTSTRAP_SERVERS, "fluss:9123")
+config.set(AgentConfigOptions.FLUSS_ACTION_STATE_DATABASE, "flink_agents")
+config.set(AgentConfigOptions.FLUSS_ACTION_STATE_TABLE, 
"orders_agent_action_state")
+```
+{{< /tab >}}
+{{< tab "Java" >}}
+```java
+import org.apache.flink.agents.api.configuration.AgentConfigOptions;
+import org.apache.flink.agents.api.configuration.Configuration;
+
+Configuration config = agentsEnv.getConfig();
+config.set(AgentConfigOptions.ACTION_STATE_STORE_BACKEND, "fluss");
+config.set(AgentConfigOptions.FLUSS_BOOTSTRAP_SERVERS, "fluss:9123");
+config.set(AgentConfigOptions.FLUSS_ACTION_STATE_DATABASE, "flink_agents");
+config.set(AgentConfigOptions.FLUSS_ACTION_STATE_TABLE, 
"orders_agent_action_state");
+```
+{{< /tab >}}
+{{< tab "YAML" >}}
+```yaml
+agent:
+  actionStateStoreBackend: fluss
+  flussBootstrapServers: "fluss:9123"
+  flussActionStateDatabase: flink_agents
+  flussActionStateTable: orders_agent_action_state
+```
+{{< /tab >}}
+{{< /tabs >}}
+
+The store creates the database and append-only log table if needed. By default,
+it uses the `flink_agents` database and creates new tables with 64 buckets.
+
+The store does not set log retention when creating a table, so configure 
retention
+to preserve the records required by your recovery points. If the cluster 
requires
+authentication, configure the Fluss authentication options as well. See
+[Configuration]({{< ref "docs/operations/configuration" >}}) for these 
settings,
+option defaults, and configuration loading and precedence.
+
+## Recovery guarantees and user responsibilities
+
+### Recovery flow
+
+When restoring a checkpoint or savepoint with its corresponding Action State
+Store, Flink Agents handles each Action as follows:
+
+1. **Check for a saved Action completion.** If one is available, Flink Agents
+   replays its saved Sensory/Short-Term Memory updates and output Events 
without
+   running the Action body again. The replayed Events keep their original Event
+   IDs.
+2. **Rerun an unfinished Action.** If no completion is found, Flink Agents 
runs the
+   Action body again. Ordinary calls may therefore repeat, but the framework
+   checks each durable call for a matching record before executing it:
+
+   - **Saved success or failure:** replay the saved outcome without calling the
+     function again.
+   - **Recorded in-flight call:** run its reconciler if one is configured.
+     Otherwise, execute the function again.
+   - **No matching record:** execute the function normally.
+
+### Conditions for reusing saved records
+
+To match saved records to the correct work, replay must preserve the following:
+
+- **Preserve input order for each key.** Recovery must receive each key's 
inputs
+  in the same order as the original execution.
+- **For custom Actions, keep the durable call sequence stable.** An unfinished
+  Action must reach the same durable calls in the same order, with the same 
inputs.
+  See [Durable Execution]({{< ref 
"docs/development/create-agent/actions#durable-execution" >}})
+  for call identity, serialization, and Action-context requirements.
+- **Keep keys and Action names stable.** Reuse the original key types and
+  serializer configuration, and ensure custom key serializers produce
+  deterministic bytes. Changing key serialization or renaming an Action can
+  prevent recovery from finding its saved completion.
+- **Keep saved values usable.** The application must still be able to read and 
use
+  stored Events, Memory updates, and durable call outcomes, since recovery may
+  replay them without running the code that produced them.
+
+### Limits on external side effects
+
+The Action State Store cannot commit an external operation and its saved 
outcome
+as a single transaction. If the operation finishes but the job fails before its
+outcome is saved, recovery may execute it again.
+
+For operations that change external state, use the service's idempotency 
support
+or, for a durable call, provide a reconciler that can recover the original 
outcome.
+See [Reconciling an in-flight call]({{< ref 
"docs/development/create-agent/actions#reconciling-an-in-flight-call" >}})
+for the API and requirements.
+
+## Action State Store retention and cleanup
+
+For every checkpoint or savepoint you may need to restore, retain both the
+snapshot and the Action State Store records it requires. Before changing 
retention
+or cleanup settings, identify the oldest recovery point you need to keep.
+
+{{< hint warning >}}
+Hashing business keys does not encrypt Action state. Saved Events, Memory 
updates,
+and durable call outcomes may contain sensitive data. Protect access to these
+records.
+{{< /hint >}}
+
+### Kafka retention and cleanup
+
+Topics created by the store use `cleanup.policy=compact,delete`,
+`retention.ms=-1`, and `retention.bytes=-1`. The `-1` values disable the time 
and
+size limits for the `delete` policy. Compaction can still reclaim older values
+for the same key.
+
+For an existing topic, check that its retention settings preserve the records
+needed by every recovery point you intend to keep.
+
+A completed checkpoint includes the Agent's progress for the work it covers,
+which means those execution records are no longer needed to restore it. Because
+earlier recovery points may still need the same records, choose a cleanup 
method
+based on the recovery history you need to retain:
+
+| Method | Cleanup behavior | When to use it | Recovery impact |
+| --- | --- | --- | --- |
+| No tombstones (default) | With `kafkaActionStateTombstoneEnabled=false`, 
completed work is removed from the in-memory cache, while its Kafka records 
remain. | Kafka storage growth is acceptable and you need to retain older 
recovery points. | Cache cleanup alone does not invalidate older recovery 
points. Kafka retention or manual deletion can still remove required data. |
+| Per-key tombstones | The store automatically writes deletion markers for 
execution records covered by each completed checkpoint. Kafka can reclaim those 
records through background compaction. | The job only needs the latest 
completed checkpoint, or repeating affected Actions is acceptable. | The 
triggering checkpoint remains usable. Restoring an earlier checkpoint or 
savepoint may repeat completed Actions. |
+| Checkpoint-aligned cleanup | An administrator selects the oldest recovery 
point to retain. The tool records a cleanup boundary in a separate control 
topic, then deletes older Kafka data no longer needed by the selected recovery 
point. | You need to retain a selected checkpoint or savepoint while reclaiming 
space from older recovery data. | The selected point remains usable. With the 
control topic configured, recovery points requiring data below the boundary are 
rejected. |
+
+Per-key tombstones and checkpoint-aligned cleanup cannot be enabled together.
+A committed cleanup boundary takes effect even if physical deletion has not
+finished.
+
+#### Cleanup steps
+
+For per-key tombstones, set `kafkaActionStateTombstoneEnabled=true` before 
starting
+the job.
+
+##### Checkpoint-aligned Kafka cleanup
+
+Use `KafkaActionStateCleanupTool` for both Java and Python jobs. Before 
generating
+a plan, check the prerequisites below. If the job has used per-key tombstones,
+first follow [Switching from per-key 
tombstones](#switching-from-per-key-tombstones).
+
+{{< details title="Prerequisites and topic settings" >}}
+
+- The data topic must use `cleanup.policy=compact,delete`, `retention.ms=-1`, 
and
+  `retention.bytes=-1`. These are also the settings used when the store 
creates a
+  new data topic.
+- The control topic must differ from the data topic, have exactly one 
partition,
+  and use `cleanup.policy=compact` without delete retention. Dedicate it to the
+  same logical Agent operator and physical data topic; do not share it across
+  operators.
+- Before enabling `kafkaActionStateCleanupControlTopic` on the job, the control
+  topic must exist and contain a committed cleanup boundary. Recovery fails if
+  the topic or its committed history is missing.
+- The `plan` command accepts only versioned Kafka recovery markers. Legacy map
+  markers can still be used for job recovery, but cannot authorize cleanup.
+
+{{< /details >}}
+
+For the first cleanup of an existing job:
+
+1. Select the oldest completed checkpoint or savepoint to retain, and stop the
+   job while keeping that snapshot available.
+2. Generate a cleanup plan for the Agent operator in that snapshot, and check
+   that it matches the selected recovery point and Action State Store.
+3. Apply the plan with a separate cleanup control topic dedicated to that 
Action
+   State Store.
+4. After `apply` succeeds, set `kafkaActionStateCleanupControlTopic` to that 
topic
+   and `kafkaActionStateTombstoneEnabled=false`, then restart from the
+   **same selected recovery point**.
+
+{{< details title="Command examples" >}}
+
+Run the tool where the recovery point and Kafka are accessible. The classpath
+must include the full Flink Agents distribution JAR for your Flink version
+(`flink-agents-dist-flink-<FLINK_VERSION>-<FA_VERSION>.jar`, without the 
`-thin`
+classifier), the matching Flink State Processor API JAR, and Flink's `lib/`.
+
+Generate a plan with the Agent operator's UID hash in the selected snapshot.
+Replace the placeholders below, or use `--operator-uid` instead of
+`--operator-uid-hash` when specifying a UID:
+
+```bash
+java -cp 
"$FLINK_HOME/lib/*:<FLINK_AGENTS_DIST_JAR>:<FLINK_STATE_PROCESSOR_API_JAR>" \
+  org.apache.flink.agents.runtime.actionstate.KafkaActionStateCleanupTool \
+  plan --checkpoint "<CHECKPOINT_OR_SAVEPOINT_PATH>" \
+  --operator-uid-hash "<AGENT_OPERATOR_UID_HASH>" \
+  --output cleanup-plan.json
+```
+
+After checking the plan, apply it with a separate control topic. The tool 
creates
+the topic if it does not exist.
+
+```bash
+java -cp 
"$FLINK_HOME/lib/*:<FLINK_AGENTS_DIST_JAR>:<FLINK_STATE_PROCESSOR_API_JAR>" \
+  org.apache.flink.agents.runtime.actionstate.KafkaActionStateCleanupTool \
+  apply --plan cleanup-plan.json --bootstrap-servers "kafka:9092" \
+  --control-topic "orders-agent-action-state-cleanup"
+```
+
+To choose the replication factor when creating the control topic, add
+`--replication-factor N` to `apply`. The default is `1`; valid values are `1`
+through `32767`.
+
+A successful apply prints `Cleanup plan <id> is APPLIED`.
+
+{{< /details >}}
+
+If the first cleanup fails or is interrupted, keep the job stopped and retry
+`apply` with the same plan: the boundary may already have been committed, 
making
+earlier recovery points unavailable.
+
+Once boundary enforcement is enabled, later plans may be applied while the job
+runs. Apply one plan at a time, only advance the boundary, and keep the data 
topic
+and its partition count unchanged during cleanup.
+
+{{< hint warning >}}
+Keep the same cleanup control topic configured and retain its committed records
+for every subsequent run and restore of this recovery history. Omitting or
+changing the topic removes boundary enforcement; with it configured, the job 
also
+rejects a fresh start without a recovery marker.
+{{< /hint >}}
+
+#### Switching from per-key tombstones
+
+To switch a job from per-key tombstones to checkpoint-aligned cleanup:
+
+1. Stop the job cleanly and wait for pending tombstone writes to finish.
+2. Restore the newest recovery point that is still valid under the tombstone
+   policy. Set `kafkaActionStateTombstoneEnabled=false` and leave the cleanup
+   control-topic option unset.
+3. Complete and retain a new checkpoint or savepoint, then stop the job again.
+4. Use that new recovery point for the first cleanup plan and apply operation.
+5. Restart from the same point with the cleanup control topic configured.
+
+Use this new recovery point because an older one may still require records
+affected by past tombstones.
+
+### Fluss retention
+
+Flink Agents currently provides no equivalent of Kafka's per-key tombstones or
+checkpoint-aligned cleanup for the Fluss Action State Store. Instead, use 
Fluss's
+own log retention settings to manage record retention and cleanup.
diff --git a/docs/content/docs/operations/deployment.md 
b/docs/content/docs/operations/deployment.md
index 8d9ee4e96..46aad7c3e 100644
--- a/docs/content/docs/operations/deployment.md
+++ b/docs/content/docs/operations/deployment.md
@@ -84,60 +84,11 @@ For all available options, see the [Flink CLI 
documentation](https://nightlies.a
 Now you should see a Flink job submitted to the Flink Cluster in Flink web UI 
(typically accessible at http://&lt;jobmanagerHost&gt;:8081).
 
 
-## Consistency Guarantees
-
-Flink Agents leverage Flink’s checkpointing to provide **exactly-once output 
consistency**. Exactly-once output consistency does not mean each event is 
processed only once; it means that every event will affect the state and output 
result exactly once. This is sufficient for typical data-processing use cases 
where the processing of data does not cause any external side effects.
-
-However, actions in Flink Agents can trigger external side effects (for 
example, sending an email or invoking an external API). These side effects 
should not be executed more than once. To address this, Flink Agents support 
**exactly-once action consistency** by leveraging an external action state 
store.
-
-### Exactly-Once Output Consistency
-
-When a job runs on a Flink cluster, Flink periodically takes checkpoints. See 
[Flink 
Checkpointing](https://nightlies.apache.org/flink/flink-docs-release-1.20/docs/ops/state/checkpoints/)
 for details.  
-After recovery from a checkpoint, Flink Agents reprocess events that arrived 
after that checkpoint. As a result, any actions triggered by those events may 
be executed again.
-
-### Exactly-Once Action Consistency
-
-To enable exactly-once action consistency, configure an external action state 
store.
-Flink Agents record state for each Action in this store. After recovering from 
a
-checkpoint, the framework reuses Actions whose completion was persisted, 
including
-their stored Sensory/Short-Term Memory updates and output Events. Unfinished 
Actions
-may execute again.
-
-When a completed Action is reused during recovery, its stored output Events 
keep their original Event IDs. The runtime records their relationships to the 
current trigger Event in Trace Log `entityMetadata`.
-
-The same store also saves individual call outcomes for
-[Durable Execution]({{< ref 
"docs/development/create-agent/actions#durable-execution" >}}).
-When an unfinished Action runs again, durable calls can reuse persisted 
outcomes.
-An external operation can finish before its outcome is saved, so recovery may 
repeat
-that operation. Use idempotent operations or reconciliation for external side 
effects;
-see [Recovery Behavior]({{< ref 
"docs/development/create-agent/actions#recovery-behavior" >}}).
-
-{{< hint info >}}
-**Note**: Currently, Kafka and Fluss are supported as the external action 
state store.
-{{< /hint >}}
-
-{{< hint warning >}}
-The action-state key format changed in Flink Agents 0.4 and action-state 
records written by earlier versions are unsupported. When upgrading, use a 
fresh Kafka topic or Fluss table and start without an older checkpoint or 
savepoint. Recovery validates the current fields, but cannot reliably 
distinguish every older record from the current format.
+<span id="exactly-once-output-consistency"></span>
+<span id="exactly-once-action-consistency"></span>
 
-Action-state keys contain a digest of the serialized key. Changing key types 
or serializer configuration when recovering existing state is unsupported, even 
if Flink accepts the change for keyed state: the same key can serialize to 
different bytes, causing recovery to miss completed actions and repeat their 
side effects. These changes are not detected by the action-state store. 
Preserve the original key types and serializer configuration for recovery. 
Custom key serializers must produce  [...]
-
-Action-state keys identify an action by its name. Keeping an action's name 
across a code change lets recovery reuse its completed results; renaming an 
action makes its recorded results unreachable, so it is executed again on the 
next recovery.
-
-Dedicate each Kafka topic or Fluss table to one logical Flink Agents operator, 
shared by that operator's subtasks. Action-state keys do not contain a job or 
operator namespace, so sharing a backend between logical operators can allow 
otherwise identical records to collide.
-{{< /hint >}}
-
-The business-key component stored in the backend is a SHA-256 digest rather 
than the serialized key itself. This keeps record keys bounded and avoids 
embedding raw key bytes, but it is not encryption; protect the action-state 
backend with appropriate access controls.
-
-See [Action State Store Configuration]({{< ref 
"docs/operations/configuration#action-state-store" >}}) for configuration 
options.
-
-{{< hint warning >}}
-**Note**: Enabling Kafka action-state tombstones can invalidate checkpoints or 
savepoints older than the prune and cause completed actions to execute again. 
See [Action State Store Configuration]({{< ref 
"docs/operations/configuration#action-state-store" >}}) for the recovery 
trade-off.
-{{< /hint >}}
-
-{{< hint warning >}}
-**Note**: A committed checkpoint-aligned Kafka cleanup boundary permanently 
makes older recovery points unsupported. Every subsequent run and restore must 
use the same cleanup control topic so the runtime can reject those recovery 
points before replay. See [Checkpoint-aligned Kafka cleanup]({{< ref 
"docs/operations/configuration#checkpoint-aligned-kafka-cleanup" >}}).
-{{< /hint >}}
+## Consistency Guarantees
 
-{{< hint info >}}
-**Note**: Exactly-once action consistency is guaranteed only if, after 
recovering from the same checkpoint, inputs for each key arrive in the same 
order as before recovery. If this ordering requirement is not met, the system 
falls back to exactly-once output consistency.
-{{< /hint >}}
+Before submitting the job, choose and configure its consistency mode. See
+[Consistency]({{< ref "docs/operations/consistency" >}}) for required setup,
+recovery guarantees, retention and cleanup.
diff --git a/docs/content/docs/operations/monitoring.md 
b/docs/content/docs/operations/monitoring.md
index 3e67f447b..93b762bc7 100644
--- a/docs/content/docs/operations/monitoring.md
+++ b/docs/content/docs/operations/monitoring.md
@@ -1,6 +1,6 @@
 ---
 title: Monitoring
-weight: 3
+weight: 4
 type: docs
 ---
 <!--
@@ -100,7 +100,7 @@ These metrics describe LLM, Tool, Skill, MCP Server and 
sub-agent calls that the
 | **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\>.retryCount | Additional 
invocations made by framework retries (`max-retries > 0`); recorded only when a 
retry occurs. See [retry-wait-interval]({{< ref 
"docs/operations/configuration#model-calls-tools-and-retries" >}}). | 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 |
@@ -233,15 +233,15 @@ You can read the logs of a running job in the Flink Web 
UI:
 
 ## Trace Log
 
-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.
+Trace Log records Events, Action executions, and component calls as structured 
JSON. By default, it records only Events at `STANDARD` level. Configure 
[recording targets](#recording-targets) to select other entities or change 
their level.
 
 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`.
+Use `trace-log.targets` to choose which entities to record and at which level. 
Omitting it records only Events at `STANDARD`. An explicit target list replaces 
that default; an empty list records nothing.
 
-To record Events, Actions, and component calls at `STANDARD`, with full 
attributes for the `search` Tool, configure:
+For example, this configuration records Events, Actions, and component calls 
at `STANDARD`, with full attributes for the `search` Tool:
 
 ```yaml
 agent:
@@ -251,17 +251,120 @@ agent:
         level: STANDARD
       - scope: {tool: search}
         level: VERBOSE
+    standard:
+      max-string-length: 2000
+      max-array-elements: 20
+      max-depth: 5
     output-type: FILE
     base-dir: /tmp/flink-agent-logs
+    pretty-print: false
 ```
 
-This example uses file output so the records can be read locally. Each target 
combines a `scope` with an optional `level`. The specific Tool target overrides 
the `ALL` preset. Use `level: "OFF"` to suppress a matching entity. Selecting 
an entity does not automatically select its parents or children.
+This example uses file output so the records can be read locally. The 
[Recording Level](#recording-level) section explains how the selected level and 
the `standard` limits affect the recorded data.
 
-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.
+Java and Python can configure the same selection with `TraceLogTarget` through 
`TRACE_LOG_TARGETS`:
+
+{{< tabs >}}
+{{< tab "python" >}}
+
+```python
+from flink_agents.api.core_options import AgentConfigOptions
+from flink_agents.api.trace_log import TraceLogLevel, TraceLogTarget
+
+agents_env.get_config().set(
+    AgentConfigOptions.TRACE_LOG_TARGETS,
+    [
+        TraceLogTarget.all(TraceLogLevel.STANDARD),
+        TraceLogTarget.tool("search", TraceLogLevel.VERBOSE),
+    ],
+)
+```
+
+{{< /tab >}}
+{{< tab "java" >}}
+
+```java
+import java.util.List;
+import org.apache.flink.agents.api.configuration.AgentConfigOptions;
+import org.apache.flink.agents.api.logger.TraceLogLevel;
+import org.apache.flink.agents.api.logger.TraceLogTarget;
+
+agentsEnv.getConfig().set(
+        AgentConfigOptions.TRACE_LOG_TARGETS,
+        List.of(
+                TraceLogTarget.all(TraceLogLevel.STANDARD),
+                TraceLogTarget.tool("search", TraceLogLevel.VERBOSE)));
+```
+
+{{< /tab >}}
+{{< /tabs >}}
+
+`TraceLogTarget` provides `all()` and whole-type factories `events()`, 
`actions()`, `tools()`, `llms()`, `parsers()`, and `subagents()`. The 
corresponding singular factories accept a name, such as `event("OrderCreated")` 
or `tool("search")`. All factories accept an optional `TraceLogLevel`. Omitting 
it preserves inheritance: `tool("search")` inherits the level of a matching 
Tool type target or `ALL`, as described below. Targets are immutable.
+
+#### Choosing scopes and names
+
+Each target combines a `scope` with an optional `level`. In YAML, `EVENT_ONLY` 
selects Events, and `ALL` selects all entity types the framework observes. Use 
a category selector to narrow the selection to a named entity, such as `{tool: 
search}`. The category keys are `event`, `action`, `tool`, `llm`, `parser`, and 
`subagent`. A value of `"*"` selects the whole type: `{event: "*"}` is 
equivalent to `EVENT_ONLY`.
+
+For a custom entity type reported through `ExecutionReporter`, use the generic 
form `{entityType: custom, entityName: name}`; omit `entityName` to select the 
whole type. `TraceLogTarget.entity("retriever", "catalog")` makes the same 
selection as `scope: {entityType: retriever, entityName: catalog}`.
+
+For `entityName`, use the logical name from your Agent's code or 
configuration. Trace Log records and target selectors use the same name:
+
+| `entityType` | What to use as `entityName` | Example |
+|--------------|----------------------------|---------|
+| `event` | The Event type used when emitting or subscribing to Events. | 
`OrderCreated` |
+| `action` | The name used to declare the Action in the Agent. | 
`create_order` |
+| `llm` | The registered ChatModel Resource name used by the call. | 
`primary_model` |
+
+For example, `scope: {llm: primary_model}` selects calls to the Resource named 
`primary_model`. Its configured model or deployment is recorded separately in 
`entityMetadata.model`.
+
+Entity names match exactly. A name ending in `.*` matches its namespace and 
descendants: `{event: "com.foo.*"}` matches `com.foo` and `com.foo.Event`, but 
not `com.foobar.Event`. Other wildcard forms are unsupported.
+
+Direct model or tool calls inside an Action are not automatically recorded as 
separate calls, even with `scope: ALL`. Selecting an entity does not 
automatically select its parents or children. For example, selecting a Tool 
does not select its parent Action; recorded relationships retain their IDs even 
when related records are omitted.
+
+#### Matching scopes and inheriting levels
+
+For each entity, matching scopes are considered from most to least specific:
+
+| Order | Matching scope |
+|-------|----------------|
+| 1 | Exact entity name within its type |
+| 2 | Namespace prefixes within its type, from longest to shortest |
+| 3 | Entity type (`EVENT_ONLY` is the `event` type scope) |
+| 4 | `ALL` |
+
+The first explicitly specified `level` in this chain applies. An omitted 
`level` inherits from the next matching scope and does not override it. If at 
least one scope matches but no matching scope specifies `level`, the entity 
uses `STANDARD`. If no scope matches, no record is written. Target order does 
not affect the result.
+
+An explicit `OFF` stops inheritance and suppresses recording. A more specific 
scope can enable recording by explicitly setting `STANDARD` or `VERBOSE`; 
omitting `level` continues to inherit `OFF`. In YAML, write `level: "OFF"` with 
quotes so Python does not interpret it as a boolean.
+
+For example, a Tool type target at `VERBOSE` supplies the level to matching 
namespace and exact-name targets that omit it. A matching namespace target at 
`OFF` suppresses an exact-name target that omits `level`, while an exact-name 
target with explicit `STANDARD` or `VERBOSE` enables that entity. `EVENT_ONLY` 
applies only to Events; its level does not affect a Tool target.
+
+An `ALL` target at `OFF` can provide a disabled baseline, with more specific 
targets explicitly enabling recording. It is unnecessary when selecting only 
one entity type. To record only Tools:
+
+```yaml
+agent:
+  trace-log:
+    targets:
+      - scope: {tool: "*"}
+        level: VERBOSE
+```
+
+To record everything except one Tool, use a local `OFF` target:
+
+```yaml
+agent:
+  trace-log:
+    targets:
+      - scope: ALL
+        level: STANDARD
+      - scope: {tool: search}
+        level: "OFF"
+```
+
+For accepted target shapes, duplicate-target validation, and all observability 
options, see the [Configuration reference]({{< ref 
"docs/operations/configuration#observability" >}}).
 
 ### Output
 
-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`.
+The default output is **SLF4J**. Select **file** output with 
`trace-log.output-type: FILE` or by setting a nonblank `trace-log.base-dir`; a 
configured base directory takes precedence over `output-type`.
 
 **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 ` [...]
 
@@ -316,6 +419,10 @@ For an Event, `attributes` contains its payload and 
`entityMetadata` its ID and
 
 `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`.
 
+When recovery reuses a completed Action, its saved output Events keep their 
original
+Event IDs. Trace Log `entityMetadata` records their relationships to the 
current
+trigger Event.
+
 An LLM call uses its configured ChatModel Resource as `entityName`:
 
 ```json

Reply via email to