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

davsclaus pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/camel.git


The following commit(s) were added to refs/heads/main by this push:
     new 72fbc1a97df8 CAMEL-26786: camel-jbang - camel cmd chaos-experiment: 
one experiment in one call, with its traffic and the wait for its verdict 
(#29498)
72fbc1a97df8 is described below

commit 72fbc1a97df801fa6cdb0c0b0d66c279ad516695
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 22:47:27 2026 +0100

    CAMEL-26786: camel-jbang - camel cmd chaos-experiment: one experiment in 
one call, with its traffic and the wait for its verdict (#29498)
    
    Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
 .../org/apache/camel/catalog/docs/chaos.adoc       |   4 +
 components/camel-chaos/src/main/docs/chaos.adoc    |   4 +
 .../modules/ROOT/pages/camel-jbang-managing.adoc   |  30 +++
 .../camel-jbang-cmd-chaos-experiment.adoc          |  36 +++
 .../camel-jbang-cmd-chaos-explore-finish.adoc      |  26 +++
 .../camel-jbang-cmd-chaos-explore-start.adoc       |  28 +++
 .../ROOT/pages/jbang-commands/camel-jbang-cmd.adoc |   3 +
 .../META-INF/camel-jbang-commands-metadata.json    |   2 +-
 .../dsl/jbang/core/commands/CamelJBangMain.java    |   3 +
 .../action/CamelChaosExperimentAction.java         | 241 ++++++++++++++++++++
 .../action/CamelChaosExploreFinishAction.java      |  71 ++++++
 .../action/CamelChaosExploreStartAction.java       |  75 +++++++
 .../dsl/jbang/core/commands/process/ListChaos.java |  25 ++-
 .../dsl/jbang/core/common/ChaosExperimentRun.java  | 245 +++++++++++++++++++++
 .../core/commands/action/CamelChaosActionTest.java | 102 ++++++++-
 .../jbang/core/common/ChaosExperimentRunTest.java  | 204 +++++++++++++++++
 .../src/test/resources/golden/cli-help.txt         | 211 ++++++++++++++----
 17 files changed, 1261 insertions(+), 49 deletions(-)

diff --git 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
index 00bdae584b34..66d44391258b 100644
--- 
a/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
+++ 
b/catalog/camel-catalog/src/generated/resources/org/apache/camel/catalog/docs/chaos.adoc
@@ -420,6 +420,10 @@ $ camel get chaos --report
         note: orders has no error handler
 ----
 
+The Camel CLI runs an experiment in one call: `camel cmd chaos-experiment` 
begins it, sends its traffic (`--feed` with
+`--body`, `--repeat` and `--interval`, as `camel cmd send`), waits until its 
expectations are decided (at most
+`--wait`), and ends it. `camel cmd chaos-explore-start` and 
`chaos-explore-finish` start and finish the exploration.
+
 == Scenarios
 
 A scenario is the rules of an experiment in a file of the project, so it can 
be run again, shared and kept with the
diff --git a/components/camel-chaos/src/main/docs/chaos.adoc 
b/components/camel-chaos/src/main/docs/chaos.adoc
index 00bdae584b34..66d44391258b 100644
--- a/components/camel-chaos/src/main/docs/chaos.adoc
+++ b/components/camel-chaos/src/main/docs/chaos.adoc
@@ -420,6 +420,10 @@ $ camel get chaos --report
         note: orders has no error handler
 ----
 
+The Camel CLI runs an experiment in one call: `camel cmd chaos-experiment` 
begins it, sends its traffic (`--feed` with
+`--body`, `--repeat` and `--interval`, as `camel cmd send`), waits until its 
expectations are decided (at most
+`--wait`), and ends it. `camel cmd chaos-explore-start` and 
`chaos-explore-finish` start and finish the exploration.
+
 == Scenarios
 
 A scenario is the rules of an experiment in a file of the project, so it can 
be run again, shared and kept with the
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc 
b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
index db2a227f25d0..d41e7b421294 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
@@ -913,6 +913,36 @@ why and the `chaos-add` command that adds it.
 been tried per route (the kinds of fault, how it went, and what the 
suggestions offer that no rule tried yet). Chaos is for
 development only: it stays off with profile `prod`, and the commands cannot 
switch it on.
 
+=== Chaos explorations
+
+An exploration is a time to try chaos rules in, which camel-chaos enforces: at 
its deadline the rules are removed,
+whatever happens. In it, `camel cmd chaos-experiment` runs one experiment in 
one call. It adds the rules (one with an
+expectation that decides the hypothesis), sends the messages of `--feed` as 
`camel cmd send` does, waits until the
+expectations are decided (at most `--wait`), ends the experiment and prints 
what came of it:
+
+[source,bash]
+----
+camel cmd chaos-explore-start --budget=10m --focus=refunds
+camel cmd chaos-experiment --hypothesis='a failed refund is read again' \
+    --rule='route=refunds error rate=every:5 expect=refunds:failed<=0 
within=30s' \
+    --feed=refunds --body=file:test/chaos/refunds-fails/ --repeat=20 
--interval=200ms
+camel cmd chaos-explore-finish
+----
+
+[source,text]
+----
+Experiment 1 FAIL  a failed refund is read again (r1, ended after 6s)
+  expect refunds:failed<=0: unmet, 4 failed
+  routes: refunds 16 completed, 4 failed
+  traffic: 20 messages sent to direct://refunds in 4s
+  waited 1s for the verdict; 9m51s left of the exploration
+----
+
+The verdict is `FAIL` when an expectation is unmet (exit code 1), `PASS` when 
all are met, and `INCONCLUSIVE` when the
+wait was up before an expectation was decided. `--feed` takes the endpoint, or 
the id of the route, as the `feed:` line
+of `chaos-suggest` has it. `camel get chaos --report` shows the exploration 
with its experiments. See
+xref:next@components:others:chaos.adoc#_an_exploration[An exploration] in the 
camel-chaos documentation.
+
 === Chaos scenarios
 
 A scenario keeps the rules of an experiment in a file of the project, such as 
`test/chaos/outage.chaos`: one rule per line,
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-experiment.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-experiment.adoc
new file mode 100644
index 000000000000..f058c4f411d6
--- /dev/null
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-experiment.adoc
@@ -0,0 +1,36 @@
+
+// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
+= camel cmd chaos-experiment
+
+Run one chaos experiment in the exploration of a running integration: add its 
rules, send its traffic, wait for the verdict, and end it
+
+
+== Usage
+
+[source,bash]
+----
+camel cmd chaos-experiment [options]
+----
+
+
+
+== Options
+
+[cols="2,5,1,2",options="header"]
+|===
+| Option | Description | Default | Type
+| `--body` | The body of the messages: text, file:<path>, or file:<folder> for 
a message per file |  | String
+| `--end` | End the open experiment (with --note) instead of running a new one 
|  | boolean
+| `--feed` | The endpoint, or the id of the route, to send the messages of the 
experiment to |  | String
+| `--header` | A header of the messages as key=value; can be given more than 
once |  | List
+| `--hypothesis` | What the experiment tests, in a few words, such as 'orders 
survive a payment outage' |  | String
+| `--interval` | The pause between two messages, such as 200ms, or a range 
such as 100..500 to draw each pause at random (at most 60s) |  | String
+| `--note` | What to note with the experiment when it ends |  | String
+| `--repeat` | How many messages to send in all (at most 10000) |  | int
+| `--rule` | A rule in its one-line text form, as an entry of 
camel.chaos.rules; can be given more than once, and one of them needs an 
expectation (expect=) |  | List
+| `--timeout` | How long to wait for the integration to answer, in millis or 
as a duration such as 30s | 10000 | long
+| `--wait` | The longest wait for the verdict after the messages were sent (at 
most 10m) | 2m | String
+| `-h,--help` | Display the help and sub-commands |  | boolean
+|===
+
+
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-explore-finish.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-explore-finish.adoc
new file mode 100644
index 000000000000..7b480686f24b
--- /dev/null
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-explore-finish.adoc
@@ -0,0 +1,26 @@
+
+// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
+= camel cmd chaos-explore-finish
+
+Finish the chaos exploration of a running integration before its deadline
+
+
+== Usage
+
+[source,bash]
+----
+camel cmd chaos-explore-finish [options]
+----
+
+
+
+== Options
+
+[cols="2,5,1,2",options="header"]
+|===
+| Option | Description | Default | Type
+| `--timeout` | How long to wait for the integration to answer, in millis or 
as a duration such as 30s | 10000 | long
+| `-h,--help` | Display the help and sub-commands |  | boolean
+|===
+
+
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-explore-start.adoc
 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-explore-start.adoc
new file mode 100644
index 000000000000..3147bbe800b0
--- /dev/null
+++ 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-explore-start.adoc
@@ -0,0 +1,28 @@
+
+// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
+= camel cmd chaos-explore-start
+
+Start a chaos exploration in a running integration: a time to try chaos rules 
in, that camel-chaos enforces
+
+
+== Usage
+
+[source,bash]
+----
+camel cmd chaos-explore-start [options]
+----
+
+
+
+== Options
+
+[cols="2,5,1,2",options="header"]
+|===
+| Option | Description | Default | Type
+| `--budget` | How long the exploration runs, between 10s and 2h | 10m | String
+| `--focus` | What the exploration is about, such as a route |  | String
+| `--timeout` | How long to wait for the integration to answer, in millis or 
as a duration such as 30s | 10000 | long
+| `-h,--help` | Display the help and sub-commands |  | boolean
+|===
+
+
diff --git 
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd.adoc 
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd.adoc
index f6a8cd0288cb..1c4a85c0047e 100644
--- a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd.adoc
+++ b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd.adoc
@@ -22,6 +22,9 @@ camel cmd [options]
 | xref:jbang-commands/camel-jbang-cmd-chaos-add.adoc[chaos-add] | Add chaos 
rules to a running integration (camel-chaos, for development only)
 | xref:jbang-commands/camel-jbang-cmd-chaos-change.adoc[chaos-change] | Change 
a chaos rule of a running integration (camel-chaos, for development only)
 | xref:jbang-commands/camel-jbang-cmd-chaos-clear.adoc[chaos-clear] | Remove 
all chaos rules of running integrations (camel-chaos, for development only)
+| xref:jbang-commands/camel-jbang-cmd-chaos-experiment.adoc[chaos-experiment] 
| Run one chaos experiment in the exploration of a running integration: add its 
rules, send its traffic, wait for the verdict, and end it
+| 
xref:jbang-commands/camel-jbang-cmd-chaos-explore-finish.adoc[chaos-explore-finish]
 | Finish the chaos exploration of a running integration before its deadline
+| 
xref:jbang-commands/camel-jbang-cmd-chaos-explore-start.adoc[chaos-explore-start]
 | Start a chaos exploration in a running integration: a time to try chaos 
rules in, that camel-chaos enforces
 | xref:jbang-commands/camel-jbang-cmd-chaos-load.adoc[chaos-load] | Load a 
chaos scenario into a running integration (camel-chaos, for development only)
 | xref:jbang-commands/camel-jbang-cmd-chaos-remove.adoc[chaos-remove] | Remove 
a chaos rule of a running integration (camel-chaos, for development only)
 | xref:jbang-commands/camel-jbang-cmd-chaos-suggest.adoc[chaos-suggest] | 
Suggest chaos rules to try in a running integration, per route (camel-chaos, 
for development only)
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
index d04b44c65d5d..ccde7b460870 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/generated/resources/META-INF/camel-jbang-commands-metadata.json
@@ -3,7 +3,7 @@
     { "name": "ask", "fullName": "ask", "description": "Ask a question about a 
running Camel application using AI", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Ask", "options": [ { "names": 
"--api-key", "description": "API key. Also reads ANTHROPIC_API_KEY, 
OPENAI_API_KEY, WATSONX_APIKEY, or LLM_API_KEY env vars", "javaType": 
"java.lang.String", "type": "string" }, { "names": "--api-type", "description": 
"API type: 'ollama', 'openai', 'anthropic', or 'watsonx'", "javaType":  [...]
     { "name": "bind", "fullName": "bind", "description": "DEPRECATED: Bind 
source and sink Kamelets as a new Camel integration", "deprecated": true, 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.bind.Bind", "options": 
[ { "names": "--error-handler", "description": "Add error handler 
(none|log|sink:<endpoint>). Sink endpoints are expected in the format 
\"[[apigroup\/]version:]kind:[namespace\/]name\", plain Camel URIs or Kamelet 
name.", "javaType": "java.lang.String", "type": " [...]
     { "name": "catalog", "fullName": "catalog", "description": "List artifacts 
from Camel Catalog", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.catalog.CatalogCommand", "options": [ 
{ "names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"component", "fullName": "catalog component", "description": "List components 
from the Camel Catalog", "sourceClass": "org.apache.camel.dsl.jbang.co [...]
-    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in 
the running Camel integrations, such as start\/stop route, or change logging 
levels.", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"browse", "fullName": "cmd browse", "description": "Browse pending messages on 
endpoints [...]
+    { "name": "cmd", "fullName": "cmd", "description": "Performs commands in 
the running Camel integrations, such as start\/stop route, or change logging 
levels.", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.action.CamelAction", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": 
"browse", "fullName": "cmd browse", "description": "Browse pending messages on 
endpoints [...]
     { "name": "completion", "fullName": "completion", "description": "Generate 
completion script for bash\/zsh", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.Complete", "options": [ { "names": 
"-h,--help", "description": "Display the help and sub-commands", "javaType": 
"boolean", "type": "boolean" } ] },
     { "name": "config", "fullName": "config", "description": "Get and set user 
configuration values", "sourceClass": 
"org.apache.camel.dsl.jbang.core.commands.config.ConfigCommand", "options": [ { 
"names": "-h,--help", "description": "Display the help and sub-commands", 
"javaType": "boolean", "type": "boolean" } ], "subcommands": [ { "name": "get", 
"fullName": "config get", "description": "Display user configuration value", 
"sourceClass": "org.apache.camel.dsl.jbang.core.commands.config. [...]
     { "name": "debug", "fullName": "debug", "description": "Debug local Camel 
integration", "sourceClass": "org.apache.camel.dsl.jbang.core.commands.Debug", 
"options": [ { "names": "--ago", "description": "Use ago instead of yyyy-MM-dd 
HH:mm:ss in timestamp.", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background", "description": "Run in the background", "defaultValue": 
"false", "javaType": "boolean", "type": "boolean" }, { "names": 
"--background-wait", "description": "How [...]
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
index fb056fd2da4e..89693244f2aa 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/CamelJBangMain.java
@@ -262,6 +262,9 @@ public class CamelJBangMain implements Callable<Integer> {
                     .addSubcommand("chaos-add", new CommandLine(new 
CamelChaosAddAction(this)))
                     .addSubcommand("chaos-change", new CommandLine(new 
CamelChaosChangeAction(this)))
                     .addSubcommand("chaos-clear", new CommandLine(new 
CamelChaosClearAction(this)))
+                    .addSubcommand("chaos-experiment", new CommandLine(new 
CamelChaosExperimentAction(this)))
+                    .addSubcommand("chaos-explore-finish", new CommandLine(new 
CamelChaosExploreFinishAction(this)))
+                    .addSubcommand("chaos-explore-start", new CommandLine(new 
CamelChaosExploreStartAction(this)))
                     .addSubcommand("chaos-load", new CommandLine(new 
CamelChaosLoadAction(this)))
                     .addSubcommand("chaos-remove", new CommandLine(new 
CamelChaosRemoveAction(this)))
                     .addSubcommand("chaos-suggest", new CommandLine(new 
CamelChaosSuggestAction(this)))
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
new file mode 100644
index 000000000000..e2b2404ec08c
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExperimentAction.java
@@ -0,0 +1,241 @@
+/*
+ * 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.
+ */
+package org.apache.camel.dsl.jbang.core.commands.action;
+
+import java.io.File;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.function.Consumer;
+
+import org.apache.camel.cli.connector.RepeatedSend;
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.commands.process.ListChaos;
+import org.apache.camel.dsl.jbang.core.common.ChaosExperimentRun;
+import org.apache.camel.dsl.jbang.core.common.CliException;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
+import org.apache.camel.dsl.jbang.core.common.ProcessFinder.CamelProcess;
+import org.apache.camel.dsl.jbang.core.common.RuntimeHelper;
+import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.json.JsonObject;
+import picocli.CommandLine;
+
[email protected](name = "chaos-experiment",
+                     description = "Run one chaos experiment in the 
exploration of a running integration: add its rules,"
+                                   + " send its traffic, wait for the verdict, 
and end it",
+                     sortOptions = false, showDefaultValues = true,
+                     footer = {
+                             "%nAn experiment is part of an exploration (camel 
cmd chaos-explore-start). It tests a",
+                             "hypothesis with rules, one of them with an 
expectation (expect=) that decides it.",
+                             "The rules are added together, the messages of 
--feed are sent (as camel cmd send",
+                             "sends them, with --body, --header, --repeat and 
--interval), and then it waits until",
+                             "the expectations are decided, or --wait is up. 
The experiment is ended then, its",
+                             "rules are removed, and what came of it is 
printed: the verdict, the expectations,",
+                             "what the routes did, and the traffic. It stays 
in the journal of the exploration",
+                             "(camel get chaos --report).",
+                             "%nThe verdict is FAIL when an expectation is 
unmet (exit code 1), PASS when all are",
+                             "met, and INCONCLUSIVE when it ended before an 
expectation was decided: wait longer",
+                             "than the within= of the expectation. --feed can 
be the id of a route, as the feed:",
+                             "line of camel cmd chaos-suggest has it.",
+                             "%nExamples:",
+                             "  camel cmd chaos-experiment --hypothesis='a 
failed refund is read again' \\",
+                             "      --rule='route=refunds error rate=every:5 
expect=refunds:failed<=0 within=30s' \\",
+                             "      --feed=refunds 
--body=file:test/chaos/refunds-fails/ --repeat=20 --interval=200ms",
+                             "  camel cmd chaos-experiment myApp 
--hypothesis='orders survive a payment outage' \\",
+                             "      --rule='endpoint=http:payment* error 
status=503 until=1m expect=orders:failed<=0 within=70s' \\",
+                             "      --wait=90s",
+                             "  camel cmd chaos-experiment --end 
--note='stopped by hand'" })
+public class CamelChaosExperimentAction extends CamelChaosAction {
+
+    @CommandLine.Parameters(description = "Name or pid of the running Camel 
integration (must match one); without it,"
+                                          + " the only one running, or the 
only one with chaos switched on",
+                            arity = "0..1")
+    String name;
+
+    @CommandLine.Option(names = { "--hypothesis" },
+                        description = "What the experiment tests, in a few 
words, such as 'orders survive a payment outage'")
+    String hypothesis;
+
+    @CommandLine.Option(names = { "--rule" },
+                        description = "A rule in its one-line text form, as an 
entry of camel.chaos.rules; can be given more"
+                                      + " than once, and one of them needs an 
expectation (expect=)")
+    List<String> rules;
+
+    @CommandLine.Option(names = { "--feed" },
+                        description = "The endpoint, or the id of the route, 
to send the messages of the experiment to")
+    String feed;
+
+    @CommandLine.Option(names = { "--body" },
+                        description = "The body of the messages: text, 
file:<path>, or file:<folder> for a message per file")
+    String body;
+
+    @CommandLine.Option(names = { "--header" },
+                        description = "A header of the messages as key=value; 
can be given more than once")
+    List<String> headers;
+
+    @CommandLine.Option(names = { "--repeat" },
+                        description = "How many messages to send in all (at 
most " + RepeatedSend.MAX_MESSAGES + ")")
+    int repeat;
+
+    @CommandLine.Option(names = { "--interval" },
+                        description = "The pause between two messages, such as 
200ms, or a range such as 100..500 to draw"
+                                      + " each pause at random (at most 60s)")
+    String interval;
+
+    @CommandLine.Option(names = { "--wait" }, defaultValue = "2m",
+                        description = "The longest wait for the verdict after 
the messages were sent (at most 10m)")
+    String wait;
+
+    @CommandLine.Option(names = { "--note" },
+                        description = "What to note with the experiment when 
it ends")
+    String note;
+
+    @CommandLine.Option(names = { "--end" },
+                        description = "End the open experiment (with --note) 
instead of running a new one")
+    boolean end;
+
+    public CamelChaosExperimentAction(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doCall() throws Exception {
+        if (end) {
+            CamelProcess process = findChaosTarget(name);
+            JsonObject ended = chaos(process, "experiment-end", root -> {
+                if (note != null && !note.isBlank()) {
+                    root.put("note", note);
+                }
+            });
+            print(ended);
+            return exitCode(ended);
+        }
+        if (rules == null || rules.isEmpty()) {
+            throw CliException.usage("--rule is required: the rules of the 
experiment, one of them with expect=");
+        }
+        if (feed == null && (body != null || headers != null || repeat > 0 || 
interval != null)) {
+            throw CliException.usage("--body, --header, --repeat and 
--interval go with --feed: where to send the messages");
+        }
+        if (repeat < 0 || repeat > RepeatedSend.MAX_MESSAGES) {
+            throw CliException.usage("--repeat must be between 1 and " + 
RepeatedSend.MAX_MESSAGES + ", was: " + repeat);
+        }
+        long waitMillis;
+        try {
+            RepeatedSend.interval(interval);
+            waitMillis = ChaosExperimentRun.waitMillis(wait);
+        } catch (IllegalArgumentException e) {
+            throw CliException.usage(e.getMessage().replace("The interval", 
"--interval").replace("The wait", "--wait"));
+        }
+        CamelProcess process = findChaosTarget(name);
+        ChaosExperimentRun.Feed traffic = feed != null
+                ? new ChaosExperimentRun.Feed(
+                        feed, absolute(body), headers != null ? 
String.join("\n", headers) : null,
+                        repeat, interval)
+                : null;
+        JsonObject result = ChaosExperimentRun.run(new 
ChaosExperimentRun.Integration() {
+            @Override
+            public JsonObject chaos(String command, Consumer<JsonObject> 
options) {
+                return CamelChaosExperimentAction.this.chaos(process, command, 
options);
+            }
+
+            @Override
+            public JsonObject send(ChaosExperimentRun.Feed f) {
+                return RuntimeHelper.sendMessage(process.pid(), f.endpoint(), 
f.body(), f.headers(), f.repeat(),
+                        f.interval());
+            }
+
+            @Override
+            public JsonObject status() {
+                return RuntimeHelper.readStatus(process.pid());
+            }
+        }, hypothesis, String.join(";", rules), traffic, waitMillis, note);
+        print(result);
+        return exitCode(result);
+    }
+
+    // a file or a folder of messages here is sent with its absolute path, as 
the integration may run elsewhere
+    private static String absolute(String body) {
+        if (body != null && body.startsWith("file:")) {
+            File f = new File(body.substring(5));
+            if (f.exists()) {
+                return "file:" + f.getAbsolutePath();
+            }
+        }
+        return body;
+    }
+
+    private void print(JsonObject experiment) {
+        lines(experiment).forEach(printer()::println);
+    }
+
+    /** What came of the experiment: its lines as the report has them, the 
traffic, and the time it waited. */
+    static List<String> lines(JsonObject experiment) {
+        List<String> lines = new 
ArrayList<>(ListChaos.experimentLines(experiment, ""));
+        if (!lines.isEmpty()) {
+            lines.set(0, "Experiment " + lines.get(0));
+        }
+        JsonObject traffic = (JsonObject) experiment.get("traffic");
+        if (traffic != null) {
+            lines.add("  traffic: " + traffic(traffic));
+        }
+        StringBuilder sb = new StringBuilder();
+        if (experiment.containsKey("waited")) {
+            sb.append("waited 
").append(time(experiment.getLong("waited"))).append(" for the verdict");
+        }
+        if (experiment.containsKey("timeLeft")) {
+            sb.append(sb.isEmpty() ? "" : "; 
").append(TimeUtils.printDuration(experiment.getLong("timeLeft")))
+                    .append(" left of the exploration");
+        }
+        if (!sb.isEmpty()) {
+            lines.add("  " + sb);
+        }
+        if ("INCONCLUSIVE".equals(experiment.getString("verdict"))) {
+            lines.add("  An expectation was not decided when it ended: wait 
longer than its within= (--wait)");
+        }
+        return lines;
+    }
+
+    private static String traffic(JsonObject traffic) {
+        String endpoint = traffic.getString("endpoint");
+        String error = traffic.getString("error");
+        if (error != null) {
+            return "not sent to " + endpoint + ": " + error;
+        }
+        if (traffic.containsKey("sent")) {
+            long sent = traffic.getLongOrDefault("sent", 0);
+            long failed = traffic.getLongOrDefault("failed", 0);
+            return sent + (sent == 1 ? " message" : " messages") + " sent to " 
+ endpoint
+                   + (failed > 0 ? " (" + failed + " failed)" : "") + " in "
+                   + time(traffic.getLongOrDefault("elapsed", 0));
+        }
+        // a single message: the answer of the send
+        return "1 message sent to " + endpoint + 
("success".equals(traffic.getString("status")) ? "" : " (failed)");
+    }
+
+    // a time under a second in millis, as a send of a few messages takes
+    private static String time(long millis) {
+        return TimeUtils.printDuration(millis, millis < 1000);
+    }
+
+    private static int exitCode(JsonObject experiment) {
+        return "FAIL".equals(experiment.getString("verdict")) ? 
CliExitCode.FAILURE : CliExitCode.OK;
+    }
+
+    @Override
+    protected String describe(JsonObject answer) {
+        return String.join("\n", lines(answer));
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExploreFinishAction.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExploreFinishAction.java
new file mode 100644
index 000000000000..5b3c4ed771b9
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExploreFinishAction.java
@@ -0,0 +1,71 @@
+/*
+ * 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.
+ */
+package org.apache.camel.dsl.jbang.core.commands.action;
+
+import java.util.List;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.ProcessFinder.CamelProcess;
+import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.json.JsonObject;
+import picocli.CommandLine;
+
[email protected](name = "chaos-explore-finish",
+                     description = "Finish the chaos exploration of a running 
integration before its deadline",
+                     sortOptions = false, showDefaultValues = true,
+                     footer = {
+                             "%nThe rules are removed (an open experiment is 
ended with what came of it so far), and",
+                             "the journal stays in the report (camel get chaos 
--report) until the next",
+                             "exploration starts or the report is reset.",
+                             "%nExamples:",
+                             "  camel cmd chaos-explore-finish",
+                             "  camel cmd chaos-explore-finish myApp" })
+public class CamelChaosExploreFinishAction extends CamelChaosAction {
+
+    @CommandLine.Parameters(description = "Name or pid of the running Camel 
integration (must match one); without it,"
+                                          + " the only one running, or the 
only one with chaos switched on",
+                            arity = "0..1")
+    String name;
+
+    public CamelChaosExploreFinishAction(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doCall() throws Exception {
+        CamelProcess process = findChaosTarget(name);
+        printer().println(describe(chaos(process, "explore-finish", null)));
+        return 0;
+    }
+
+    @Override
+    protected String describe(JsonObject answer) {
+        long ran = answer.getLongOrDefault("finishedAt", 0) - 
answer.getLongOrDefault("startedAt", 0);
+        List<Object> rules = answer.getCollection("rules");
+        List<Object> experiments = answer.getCollection("experiments");
+        StringBuilder sb = new StringBuilder("Chaos exploration finished after 
").append(TimeUtils.printDuration(ran))
+                .append(": ").append(rules != null ? rules.size() : 
0).append(" rules tried, ")
+                .append(answer.getLongOrDefault("removed", 0)).append(" 
removed");
+        if (experiments != null && !experiments.isEmpty()) {
+            long failed = experiments.stream()
+                    .filter(o -> o instanceof JsonObject jo && 
"FAIL".equals(jo.getString("verdict"))).count();
+            sb.append(", 
").append(experiments.size()).append(experiments.size() == 1 ? " experiment" : 
" experiments")
+                    .append(" (").append(failed).append(" failed)");
+        }
+        return sb.toString();
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExploreStartAction.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExploreStartAction.java
new file mode 100644
index 000000000000..4a8cc6d20f6a
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosExploreStartAction.java
@@ -0,0 +1,75 @@
+/*
+ * 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.
+ */
+package org.apache.camel.dsl.jbang.core.commands.action;
+
+import org.apache.camel.dsl.jbang.core.commands.CamelJBangMain;
+import org.apache.camel.dsl.jbang.core.common.ProcessFinder.CamelProcess;
+import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.json.JsonObject;
+import picocli.CommandLine;
+
[email protected](name = "chaos-explore-start",
+                     description = "Start a chaos exploration in a running 
integration: a time to try chaos rules in,"
+                                   + " that camel-chaos enforces",
+                     sortOptions = false, showDefaultValues = true,
+                     footer = {
+                             "%nWhile the exploration runs, camel-chaos keeps 
its journal: the baseline (the counts",
+                             "per route before its first rule), the rules 
tried, and the experiments",
+                             "(camel cmd chaos-experiment). A rule ends at the 
deadline at the latest, and at the",
+                             "deadline the rules are removed and the 
exploration is finished, whatever happens.",
+                             "One exploration runs at a time. camel get chaos 
--report shows it.",
+                             "%nExamples:",
+                             "  camel cmd chaos-explore-start",
+                             "  camel cmd chaos-explore-start myApp 
--budget=30m --focus=orders" })
+public class CamelChaosExploreStartAction extends CamelChaosAction {
+
+    @CommandLine.Parameters(description = "Name or pid of the running Camel 
integration (must match one); without it,"
+                                          + " the only one running, or the 
only one with chaos switched on",
+                            arity = "0..1")
+    String name;
+
+    @CommandLine.Option(names = { "--budget" }, defaultValue = "10m",
+                        description = "How long the exploration runs, between 
10s and 2h")
+    String budget;
+
+    @CommandLine.Option(names = { "--focus" },
+                        description = "What the exploration is about, such as 
a route")
+    String focus;
+
+    public CamelChaosExploreStartAction(CamelJBangMain main) {
+        super(main);
+    }
+
+    @Override
+    public Integer doCall() throws Exception {
+        CamelProcess process = findChaosTarget(name);
+        printer().println(describe(chaos(process, "explore-start", root -> {
+            root.put("budget", budget);
+            if (focus != null && !focus.isBlank()) {
+                root.put("focus", focus);
+            }
+        })));
+        return 0;
+    }
+
+    @Override
+    protected String describe(JsonObject answer) {
+        String focus = answer.getString("focus");
+        return "Chaos exploration started: " + 
TimeUtils.printDuration(answer.getLongOrDefault("budget", 0))
+               + (focus != null ? " (focus: " + focus + ")" : "");
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaos.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaos.java
index f81650fb1806..ed5f4bc11332 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaos.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/process/ListChaos.java
@@ -350,7 +350,18 @@ public class ListChaos extends ProcessWatchCommand {
 
     /** An experiment: its verdict and hypothesis, its rules, its 
expectations, and what the routes did meanwhile. */
     private void printExperiment(JsonObject experiment) {
-        StringBuilder sb = new StringBuilder("      
").append(experiment.getIntegerOrDefault("number", 0)).append(' ')
+        experimentLines(experiment, "      ").forEach(printer()::println);
+    }
+
+    /**
+     * The lines of an experiment, as camel get chaos --report and camel cmd 
chaos-experiment print it: its verdict and
+     * hypothesis, its rules and how long it ran, its expectations, what the 
routes did meanwhile, and its note.
+     *
+     * @param indent the indent of the first line; the others are indented two 
more
+     */
+    public static List<String> experimentLines(JsonObject experiment, String 
indent) {
+        List<String> lines = new ArrayList<>();
+        StringBuilder sb = new 
StringBuilder(indent).append(experiment.getIntegerOrDefault("number", 
0)).append(' ')
                 .append(experiment.getString("verdict"));
         String hypothesis = experiment.getString("hypothesis");
         if (hypothesis != null) {
@@ -368,23 +379,25 @@ public class ListChaos extends ProcessWatchCommand {
                     ? "ended at the deadline" : "finished".equals(by) ? "ended 
with the exploration" : "ended")
                     .append(" after ").append(elapsed);
         }
-        printer().println(sb.append(')').toString());
+        lines.add(sb.append(')').toString());
+        String more = indent + "  ";
         List<Object> expectations = experiment.getCollection("expectations");
         if (expectations != null) {
             for (Object o : expectations) {
                 JsonObject x = (JsonObject) o;
-                printer().println("        expect " + x.getString("expect") + 
": " + x.getString("state") + ", "
-                                  + x.getString("value"));
+                lines.add(more + "expect " + x.getString("expect") + ": " + 
x.getString("state") + ", "
+                          + x.getString("value"));
             }
         }
         List<Object> routes = experiment.getCollection("routes");
         if (routes != null && !routes.isEmpty()) {
-            printer().println("        routes: " + routeCounts(routes));
+            lines.add(more + "routes: " + routeCounts(routes));
         }
         String note = experiment.getString("note");
         if (note != null) {
-            printer().println("        note: " + note);
+            lines.add(more + "note: " + note);
         }
+        return lines;
     }
 
     private static String routeCounts(List<Object> routes) {
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
new file mode 100644
index 000000000000..40b0a5a1b751
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRun.java
@@ -0,0 +1,245 @@
+/*
+ * 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.
+ */
+package org.apache.camel.dsl.jbang.core.common;
+
+import java.util.List;
+import java.util.function.Consumer;
+
+import org.apache.camel.util.TimeUtils;
+import org.apache.camel.util.json.JsonObject;
+
+/**
+ * One chaos experiment in one call (CAMEL-26786), as camel cmd 
chaos-experiment and the chaos_experiment tool run it:
+ * it begins the experiment in the running exploration (its hypothesis and 
rules), sends the traffic of its feed, waits
+ * until its expectations are decided, and ends it. The answer is what came of 
it (as the journal has it), with the
+ * traffic that was sent and the time left of the exploration.
+ * <p/>
+ * The wait reads the report in the status of the integration, which is 
written every second. It ends as soon as an
+ * expectation is unmet (FAIL) or all are met (PASS), when the exploration 
ended the experiment at its deadline, or
+ * after the longest wait; an experiment ended then with an expectation not 
decided is INCONCLUSIVE. It is ended in any
+ * case, also when the traffic could not be sent, so its rules do not stay.
+ */
+public final class ChaosExperimentRun {
+
+    /** How long to wait for the verdict after the traffic, by default. */
+    public static final long DEFAULT_WAIT = 2 * 60_000L;
+    /** The longest wait for the verdict (the exploration ends the experiment 
at its deadline anyway). */
+    public static final long MAX_WAIT = 10 * 60_000L;
+    // the status of the integration is written every second
+    private static final long POLL = 1000;
+
+    private ChaosExperimentRun() {
+    }
+
+    /** What the experiment needs of the integration: the CLI and the tools 
each reach it their own way. */
+    public interface Integration {
+
+        /**
+         * Sends the chaos action, and returns the answer.
+         *
+         * @throws RuntimeException when the integration refuses it, with the 
reason
+         */
+        JsonObject chaos(String command, Consumer<JsonObject> options);
+
+        /** Sends the messages of the feed, and returns the summary of the 
send action. */
+        JsonObject send(Feed feed);
+
+        /** The status of the integration, as it wrote it last, or null. */
+        JsonObject status();
+    }
+
+    /**
+     * The traffic of an experiment, as camel cmd send sends it.
+     *
+     * @param endpoint the endpoint or the id of the route to send to
+     * @param body     the body, file:&lt;path&gt;, or file:&lt;folder&gt; for 
a message per file
+     * @param headers  the headers as key=value lines, or null
+     * @param repeat   how many messages in all; 0 to send once (each file of 
a folder once)
+     * @param interval the pause between two messages, such as 200ms or a 
range such as 100..500, or null
+     */
+    public record Feed(String endpoint, String body, String headers, int 
repeat, String interval) {
+    }
+
+    /** The time, and a pause; the tests give a clock that does not wait. */
+    interface Clock {
+        long now();
+
+        void sleep(long millis) throws InterruptedException;
+    }
+
+    private static final Clock SYSTEM = new Clock() {
+        @Override
+        public long now() {
+            return System.currentTimeMillis();
+        }
+
+        @Override
+        public void sleep(long millis) throws InterruptedException {
+            Thread.sleep(millis);
+        }
+    };
+
+    /** The longest wait as text, such as 2m: 2 minutes when empty. */
+    public static long waitMillis(String text) {
+        if (text == null || text.isBlank()) {
+            return DEFAULT_WAIT;
+        }
+        long millis;
+        try {
+            millis = TimeUtils.toMilliSeconds(text.strip());
+        } catch (RuntimeException e) {
+            throw new IllegalArgumentException("The wait is not a time, such 
as 90s or 2m: " + text);
+        }
+        if (millis < 0 || millis > MAX_WAIT) {
+            throw new IllegalArgumentException("The wait must be at most 10m, 
was: " + text);
+        }
+        return millis;
+    }
+
+    /**
+     * Runs the experiment.
+     *
+     * @param  hypothesis what it tests, such as "a failed refund is read 
again" (optional)
+     * @param  rules      its rules in their one-line form, separated by ;, 
one of them with an expectation
+     * @param  feed       the traffic, or null when the routes feed themselves 
(a timer) or are fed otherwise
+     * @param  wait       the longest wait for the verdict after the traffic, 
in millis
+     * @param  note       what to note with it when it ends (optional)
+     * @return            the experiment as it ended, with traffic, waited and 
timeLeft
+     */
+    public static JsonObject run(Integration integration, String hypothesis, 
String rules, Feed feed, long wait, String note) {
+        try {
+            return run(integration, hypothesis, rules, feed, wait, note, 
SYSTEM);
+        } catch (InterruptedException e) {
+            Thread.currentThread().interrupt();
+            throw new IllegalStateException("Interrupted while waiting for the 
verdict of the experiment", e);
+        }
+    }
+
+    static JsonObject run(
+            Integration integration, String hypothesis, String rules, Feed 
feed, long wait, String note, Clock clock)
+            throws InterruptedException {
+        JsonObject begun = integration.chaos("experiment-begin", root -> {
+            if (hypothesis != null && !hypothesis.isBlank()) {
+                root.put("hypothesis", hypothesis);
+            }
+            root.put("rule", rules);
+        });
+        int number = begun.getIntegerOrDefault("number", 0);
+        JsonObject traffic = null;
+        long waited = 0;
+        try {
+            if (feed != null) {
+                traffic = traffic(integration, feed);
+            }
+            long start = clock.now();
+            while (true) {
+                JsonObject seen = experiment(integration.status(), number);
+                if (seen != null && (!seen.getBooleanOrDefault("open", true)
+                        || !"PENDING".equals(seen.getString("verdict")))) {
+                    break;
+                }
+                if (clock.now() - start >= wait) {
+                    break;
+                }
+                clock.sleep(POLL);
+            }
+            waited = clock.now() - start;
+        } finally {
+            // ended whatever happened, so its rules do not stay
+            JsonObject result = end(integration, number, note, clock);
+            if (result != null) {
+                begun = result;
+            }
+        }
+        JsonObject answer = new JsonObject(begun);
+        if (traffic != null) {
+            answer.put("traffic", traffic);
+        }
+        answer.put("waited", waited);
+        JsonObject exploration = exploration(integration.status());
+        if (exploration != null && exploration.containsKey("timeLeft")) {
+            answer.put("timeLeft", exploration.getLong("timeLeft"));
+        }
+        return answer;
+    }
+
+    /** Sends the traffic: the summary of the send, or why it could not be 
sent. */
+    private static JsonObject traffic(Integration integration, Feed feed) {
+        JsonObject answer;
+        try {
+            answer = integration.send(feed);
+        } catch (RuntimeException e) {
+            answer = new JsonObject();
+            answer.put("status", "failed");
+            answer.put("error", e.getMessage());
+        }
+        if (answer == null) {
+            answer = new JsonObject();
+            answer.put("status", "failed");
+        }
+        if (!answer.containsKey("endpoint")) {
+            answer.put("endpoint", feed.endpoint());
+        }
+        return answer;
+    }
+
+    /** Ends the experiment: what came of it, also when the exploration ended 
it at its deadline meanwhile. */
+    private static JsonObject end(Integration integration, int number, String 
note, Clock clock)
+            throws InterruptedException {
+        try {
+            return integration.chaos("experiment-end", root -> {
+                if (note != null && !note.isBlank()) {
+                    root.put("note", note);
+                }
+            });
+        } catch (RuntimeException e) {
+            // no experiment is open: the exploration ended it, which the next 
status says
+            for (int i = 0; i < 3; i++) {
+                JsonObject seen = experiment(integration.status(), number);
+                if (seen != null && !seen.getBooleanOrDefault("open", true)) {
+                    return seen;
+                }
+                clock.sleep(POLL);
+            }
+            throw e;
+        }
+    }
+
+    /** The experiment with the number in the report of the status, or null. */
+    static JsonObject experiment(JsonObject status, int number) {
+        JsonObject exploration = exploration(status);
+        List<Object> experiments = exploration != null ? 
exploration.getCollection("experiments") : null;
+        if (experiments == null) {
+            return null;
+        }
+        for (Object o : experiments) {
+            if (o instanceof JsonObject experiment && 
experiment.getIntegerOrDefault("number", -1) == number) {
+                return experiment;
+            }
+        }
+        return null;
+    }
+
+    /** The exploration in the report of the chaos console in the status, or 
null. */
+    static JsonObject exploration(JsonObject status) {
+        if (status == null || !(status.get("chaos") instanceof JsonObject 
chaos)
+                || !(chaos.get("report") instanceof JsonObject report)) {
+            return null;
+        }
+        return report.get("exploration") instanceof JsonObject exploration ? 
exploration : null;
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
index dfab28212f77..6a84a4bd54ce 100644
--- 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/action/CamelChaosActionTest.java
@@ -36,8 +36,9 @@ import static org.assertj.core.api.Assertions.assertThat;
 import static org.assertj.core.api.Assertions.assertThatThrownBy;
 
 /**
- * camel cmd chaos-add, chaos-change, chaos-remove, chaos-clear, chaos-load 
and chaos-suggest: the chaos action they
- * send, what they print, and that a refused rule is an error with the reason 
of the integration.
+ * camel cmd chaos-add, chaos-change, chaos-remove, chaos-clear, chaos-load, 
chaos-suggest, chaos-explore-start,
+ * chaos-explore-finish and chaos-experiment: the chaos action they send, what 
they print, and that a refused rule is an
+ * error with the reason of the integration.
  */
 @ExtendWith(MockitoExtension.class)
 class CamelChaosActionTest extends ActionCommandTestSupport {
@@ -481,6 +482,103 @@ class CamelChaosActionTest extends 
ActionCommandTestSupport {
         assertThat(printer.getLines()).containsExactly("Removed chaos rules: 
3");
     }
 
+    @Test
+    void exploreStart() throws Exception {
+        writeStatusFile(TEST_PID, "orders");
+
+        assertThat(callWithResponse(command(new 
CamelChaosExploreStartAction(main()), "orders", "--budget=30m",
+                "--focus=orders"), new JsonObject(Map.of("running", true, 
"budget", 1_800_000L, "focus", "orders"))))
+                .isZero();
+
+        JsonObject action = readActionFile(TEST_PID);
+        assertThat(action.getString("command")).isEqualTo("explore-start");
+        assertThat(action.getString("budget")).isEqualTo("30m");
+        assertThat(action.getString("focus")).isEqualTo("orders");
+        assertThat(printer.getLines()).containsExactly("Chaos exploration 
started: 30m0s (focus: orders)");
+    }
+
+    @Test
+    void exploreFinish() throws Exception {
+        writeStatusFile(TEST_PID, "orders");
+        JsonObject answer = new JsonObject(
+                Map.of("running", false, "startedAt", 1_000_000L, 
"finishedAt", 1_250_000L,
+                        "rules", new JsonArray(List.of("r1", "r2", "r3")), 
"removed", 1L,
+                        "experiments", new JsonArray(
+                                List.of(new JsonObject(Map.of("number", 1, 
"verdict", "FAIL")),
+                                        new JsonObject(Map.of("number", 2, 
"verdict", "PASS"))))));
+
+        assertThat(callWithResponse(command(new 
CamelChaosExploreFinishAction(main()), "orders"), answer)).isZero();
+
+        
assertThat(readActionFile(TEST_PID).getString("command")).isEqualTo("explore-finish");
+        assertThat(printer.getLines()).containsExactly(
+                "Chaos exploration finished after 4m10s: 3 rules tried, 1 
removed, 2 experiments (1 failed)");
+    }
+
+    @Test
+    void experimentEndsTheOpenOne() throws Exception {
+        writeStatusFile(TEST_PID, "orders");
+        JsonObject answer = new JsonObject(
+                Map.of("number", 3, "open", false, "verdict", "FAIL", 
"endedBy", "ended",
+                        "hypothesis", "an order never fails", "elapsed", 
64_000L, "rules", new JsonArray(List.of("r4")),
+                        "expectations", new JsonArray(
+                                List.of(new JsonObject(
+                                        Map.of("expect", "orders:failed<=0", 
"state", "unmet", "value", "9 failed")))),
+                        "note", "stopped by hand"));
+
+        // a FAIL verdict is exit code 1, as camel run --chaos has it
+        assertThat(callWithResponse(command(new 
CamelChaosExperimentAction(main()), "orders", "--end",
+                "--note=stopped by hand"), 
answer)).isEqualTo(CliExitCode.FAILURE);
+
+        JsonObject action = readActionFile(TEST_PID);
+        assertThat(action.getString("command")).isEqualTo("experiment-end");
+        assertThat(action.getString("note")).isEqualTo("stopped by hand");
+        assertThat(printer.getLines()).containsExactly(
+                "Experiment 3 FAIL  an order never fails (r4, ended after 
1m4s)",
+                "expect orders:failed<=0: unmet, 9 failed", "note: stopped by 
hand");
+    }
+
+    @Test
+    void experimentNeedsItsRulesAndAFeedForItsMessages() {
+        assertThatThrownBy(() -> command(new 
CamelChaosExperimentAction(main()), "orders").doCall())
+                
.isInstanceOf(CliException.class).hasMessageStartingWith("--rule is required")
+                .extracting(e -> ((CliException) 
e).getExitCode()).isEqualTo(CliExitCode.USAGE);
+        assertThatThrownBy(() -> command(new 
CamelChaosExperimentAction(main()), "orders",
+                "--rule=route=orders error expect=orders:failed<=0", 
"--repeat=20").doCall())
+                .isInstanceOf(CliException.class).hasMessageContaining("go 
with --feed");
+        assertThatThrownBy(() -> command(new 
CamelChaosExperimentAction(main()), "orders",
+                "--rule=route=orders error expect=orders:failed<=0", 
"--wait=1h").doCall())
+                .isInstanceOf(CliException.class).hasMessage("--wait must be 
at most 10m, was: 1h");
+        assertThatThrownBy(() -> command(new 
CamelChaosExperimentAction(main()), "orders",
+                "--rule=route=orders error expect=orders:failed<=0", 
"--feed=orders", "--interval=500..100").doCall())
+                
.isInstanceOf(CliException.class).hasMessageContaining("--interval must go from 
the shortest");
+    }
+
+    @Test
+    void experimentPrintsTheTrafficAndTheTimeLeft() {
+        JsonObject experiment = new JsonObject(
+                Map.of("number", 2, "open", false, "verdict", "INCONCLUSIVE",
+                        "endedBy", "ended", "elapsed", 125_000L, "rules", new 
JsonArray(List.of("r3")),
+                        "expectations", new JsonArray(
+                                List.of(new JsonObject(
+                                        Map.of("expect", "orders:100", 
"state", "pending", "value", "41 completed")))),
+                        "traffic", new JsonObject(
+                                Map.of("endpoint", "direct://orders", "sent", 
20L, "failed", 2L,
+                                        "elapsed", 950L)),
+                        "waited", 120_000L, "timeLeft", 495_000L));
+
+        
assertThat(CamelChaosExperimentAction.lines(experiment)).containsExactly(
+                "Experiment 2 INCONCLUSIVE (r3, ended after 2m5s)",
+                "  expect orders:100: pending, 41 completed",
+                "  traffic: 20 messages sent to direct://orders (2 failed) in 
950ms",
+                "  waited 2m0s for the verdict; 8m15s left of the exploration",
+                "  An expectation was not decided when it ended: wait longer 
than its within= (--wait)");
+
+        JsonObject notSent = new JsonObject(
+                Map.of("number", 3, "verdict", "PASS", "elapsed", 1_000L,
+                        "traffic", new JsonObject(Map.of("endpoint", 
"refunds", "status", "failed", "error", "No endpoint"))));
+        assertThat(CamelChaosExperimentAction.lines(notSent)).contains("  
traffic: not sent to refunds: No endpoint");
+    }
+
     /** The status of the integration orders, with the chaos dev console as 
the CLI connector puts it there. */
     private static void writeChaosStatus(boolean enabled, String... ids) 
throws Exception {
         JsonObject chaos = new JsonObject();
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
new file mode 100644
index 000000000000..b799e19b21d7
--- /dev/null
+++ 
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/common/ChaosExperimentRunTest.java
@@ -0,0 +1,204 @@
+/*
+ * 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.
+ */
+package org.apache.camel.dsl.jbang.core.common;
+
+import java.util.ArrayDeque;
+import java.util.ArrayList;
+import java.util.Deque;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+
+/**
+ * One chaos experiment in one call (CAMEL-26786): begin, traffic, the wait 
for the verdict in the status, and the end,
+ * against a fake integration and a clock that does not wait.
+ */
+class ChaosExperimentRunTest {
+
+    /** The integration: what it was asked, and the status it shows, one per 
read (the last one stays). */
+    private static final class FakeIntegration implements 
ChaosExperimentRun.Integration {
+        final List<JsonObject> actions = new ArrayList<>();
+        final Deque<JsonObject> statuses = new ArrayDeque<>();
+        ChaosExperimentRun.Feed sent;
+        RuntimeException sendFails;
+        RuntimeException endFails;
+        RuntimeException beginFails;
+        JsonObject ended = experiment("FAIL", false);
+
+        @Override
+        public JsonObject chaos(String command, Consumer<JsonObject> options) {
+            JsonObject action = new JsonObject();
+            action.put("command", command);
+            options.accept(action);
+            actions.add(action);
+            if ("experiment-begin".equals(command)) {
+                if (beginFails != null) {
+                    throw beginFails;
+                }
+                return experiment("PENDING", true);
+            }
+            if (endFails != null) {
+                throw endFails;
+            }
+            return ended;
+        }
+
+        @Override
+        public JsonObject send(ChaosExperimentRun.Feed feed) {
+            sent = feed;
+            if (sendFails != null) {
+                throw sendFails;
+            }
+            return new JsonObject(Map.of("status", "success", "endpoint", 
"direct://refunds", "sent", 20L, "failed", 0L));
+        }
+
+        @Override
+        public JsonObject status() {
+            return statuses.size() > 1 ? statuses.poll() : statuses.peek();
+        }
+
+        void shows(String verdict, boolean open) {
+            statuses.add(statusWith(experiment(verdict, open)));
+        }
+    }
+
+    /** A clock that moves on when it is asked to sleep. */
+    private static final class FakeClock implements ChaosExperimentRun.Clock {
+        long now = 1_000_000;
+        int sleeps;
+
+        @Override
+        public long now() {
+            return now;
+        }
+
+        @Override
+        public void sleep(long millis) {
+            sleeps++;
+            now += millis;
+        }
+    }
+
+    private static JsonObject experiment(String verdict, boolean open) {
+        return new JsonObject(Map.of("number", 1, "verdict", verdict, "open", 
open, "rules", new JsonArray(List.of("r1"))));
+    }
+
+    private static JsonObject statusWith(JsonObject experiment) {
+        JsonObject exploration = new JsonObject(
+                Map.of("running", true, "timeLeft", 300_000L, "experiments",
+                        new JsonArray(List.of(experiment))));
+        JsonObject report = new JsonObject(Map.of("exploration", exploration));
+        return new JsonObject(Map.of("chaos", new JsonObject(Map.of("enabled", 
true, "report", report))));
+    }
+
+    private final FakeIntegration integration = new FakeIntegration();
+    private final FakeClock clock = new FakeClock();
+
+    private static final String RULE = "route=refunds error rate=every:5 
expect=refunds:failed<=0 within=30s";
+    private static final ChaosExperimentRun.Feed FEED
+            = new ChaosExperimentRun.Feed("refunds", "file:/feed/", null, 20, 
"200ms");
+
+    @Test
+    void theTrafficThenTheWaitUntilTheVerdict() throws Exception {
+        integration.shows("PENDING", true);
+        integration.shows("PENDING", true);
+        integration.shows("FAIL", true);
+
+        JsonObject answer = ChaosExperimentRun.run(integration, "a failed 
refund is read again", RULE, FEED, 60_000,
+                "kafka commits a failed record", clock);
+
+        assertThat(integration.actions).extracting(a -> a.getString("command"))
+                .containsExactly("experiment-begin", "experiment-end");
+        
assertThat(integration.actions.get(0).getString("hypothesis")).isEqualTo("a 
failed refund is read again");
+        
assertThat(integration.actions.get(0).getString("rule")).isEqualTo(RULE);
+        
assertThat(integration.actions.get(1).getString("note")).isEqualTo("kafka 
commits a failed record");
+        assertThat(integration.sent).isEqualTo(FEED);
+        // two reads that were pending, a second apart
+        assertThat(clock.sleeps).isEqualTo(2);
+        assertThat(answer.getString("verdict")).isEqualTo("FAIL");
+        assertThat(answer.getLong("waited")).isEqualTo(2_000);
+        assertThat(answer.getLong("timeLeft")).isEqualTo(300_000);
+        assertThat(((JsonObject) 
answer.get("traffic")).getLong("sent")).isEqualTo(20);
+    }
+
+    @Test
+    void theWaitIsUpBeforeTheVerdict() throws Exception {
+        integration.shows("PENDING", true);
+        integration.ended = experiment("INCONCLUSIVE", false);
+
+        JsonObject answer = ChaosExperimentRun.run(integration, null, RULE, 
null, 5_000, null, clock);
+
+        assertThat(answer.getString("verdict")).isEqualTo("INCONCLUSIVE");
+        assertThat(answer.getLong("waited")).isEqualTo(5_000);
+        assertThat(answer).doesNotContainKey("traffic");
+        assertThat(integration.actions.get(0)).doesNotContainKey("hypothesis");
+        assertThat(integration.actions.get(1)).doesNotContainKey("note");
+    }
+
+    @Test
+    void endedWhenTheTrafficCannotBeSent() throws Exception {
+        integration.shows("PENDING", true);
+        integration.sendFails = new IllegalArgumentException("Header must be 
in key=value format, was: x");
+        integration.ended = experiment("INCONCLUSIVE", false);
+
+        JsonObject answer = ChaosExperimentRun.run(integration, null, RULE, 
FEED, 2_000, null, clock);
+
+        JsonObject traffic = (JsonObject) answer.get("traffic");
+        assertThat(traffic.getString("status")).isEqualTo("failed");
+        assertThat(traffic.getString("error")).isEqualTo("Header must be in 
key=value format, was: x");
+        assertThat(traffic.getString("endpoint")).isEqualTo("refunds");
+        assertThat(integration.actions).extracting(a -> 
a.getString("command")).contains("experiment-end");
+    }
+
+    @Test
+    void theExplorationEndedItAtItsDeadline() throws Exception {
+        integration.shows("PENDING", true);
+        // ended by the exploration: the end is refused, and the status has 
what came of it
+        integration.shows("FAIL", false);
+        integration.endFails = new IllegalStateException("No experiment is 
open");
+
+        JsonObject answer = ChaosExperimentRun.run(integration, null, RULE, 
null, 60_000, null, clock);
+
+        assertThat(answer.getString("verdict")).isEqualTo("FAIL");
+        assertThat(answer.getBoolean("open")).isFalse();
+    }
+
+    @Test
+    void aRefusedExperimentIsNotEnded() {
+        integration.beginFails = new IllegalStateException("Experiment 1 is 
open: end it first");
+
+        assertThatThrownBy(() -> ChaosExperimentRun.run(integration, null, 
RULE, FEED, 60_000, null, clock))
+                .hasMessage("Experiment 1 is open: end it first");
+        assertThat(integration.actions).hasSize(1);
+        assertThat(integration.sent).isNull();
+    }
+
+    @Test
+    void theWait() {
+        
assertThat(ChaosExperimentRun.waitMillis(null)).isEqualTo(ChaosExperimentRun.DEFAULT_WAIT);
+        assertThat(ChaosExperimentRun.waitMillis("90s")).isEqualTo(90_000);
+        assertThatThrownBy(() -> 
ChaosExperimentRun.waitMillis("11m")).hasMessageContaining("at most 10m");
+        assertThatThrownBy(() -> 
ChaosExperimentRun.waitMillis("soon")).hasMessageContaining("not a time");
+    }
+}
diff --git 
a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt 
b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
index a4b2b6dd9b2d..2900be01a02a 100644
--- a/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
+++ b/dsl/camel-jbang/camel-jbang-core/src/test/resources/golden/cli-help.txt
@@ -551,46 +551,54 @@ Performs commands in the running Camel integrations, such 
as start/stop route,
 or change logging levels. (use cmd --help to see sub commands)
   -h, --help   Display the help and sub-commands
 Commands:
-  browse             Browse pending messages on endpoints
-  chaos-add          Add chaos rules to a running integration (camel-chaos, for
-                       development only)
-  chaos-change       Change a chaos rule of a running integration (camel-chaos,
-                       for development only)
-  chaos-clear        Remove all chaos rules of running integrations
-                       (camel-chaos, for development only)
-  chaos-load         Load a chaos scenario into a running integration
-                       (camel-chaos, for development only)
-  chaos-remove       Remove a chaos rule of a running integration (camel-chaos,
-                       for development only)
-  chaos-suggest      Suggest chaos rules to try in a running integration, per
-                       route (camel-chaos, for development only)
-  disable-processor  Disable Camel processor
-  enable-processor   Enable Camel processor
-  gc                 Trigger Java Memory Garbage Collector
-  heap-dump          Write a heap dump (.hprof) file for deep memory analysis
-  heap-histogram     Display class-level heap memory usage in a running Camel
-                       integration
-  memory-leak        Diagnose memory leaks in a running Camel integration
-  load               Loads new source files into an existing Camel
-  logger             List or change logging levels
-  receive            Receive and dump messages from remote endpoints
-  reload             Trigger reloading Camel
-  reset-stats        Reset performance statistics
-  resume-route       Resume Camel routes
-  route-diagram      Display Camel route diagram in the terminal
-  route-dump         Dump Camel route in XML, YAML, or Java DSL format
-  route-structure    Dump Camel route structure
-  route-topology     Display inter-route topology connections
-  send               Send messages to endpoints
-  sql                Execute SQL query on a DataSource
-  span               Display OpenTelemetry spans from running Camel 
integrations
-  start-group        Start Camel route groups
-  start-route        Start Camel routes
-  stop-group         Stop Camel route groups
-  stop-route         Stop Camel routes
-  stub               Browse stub endpoints
-  suspend-route      Suspend Camel routes
-  thread-dump        List threads in a running Camel integration
+  browse                Browse pending messages on endpoints
+  chaos-add             Add chaos rules to a running integration (camel-chaos,
+                          for development only)
+  chaos-change          Change a chaos rule of a running integration
+                          (camel-chaos, for development only)
+  chaos-clear           Remove all chaos rules of running integrations
+                          (camel-chaos, for development only)
+  chaos-experiment      Run one chaos experiment in the exploration of a
+                          running integration: add its rules, send its traffic,
+                          wait for the verdict, and end it
+  chaos-explore-finish  Finish the chaos exploration of a running integration
+                          before its deadline
+  chaos-explore-start   Start a chaos exploration in a running integration: a
+                          time to try chaos rules in, that camel-chaos enforces
+  chaos-load            Load a chaos scenario into a running integration
+                          (camel-chaos, for development only)
+  chaos-remove          Remove a chaos rule of a running integration
+                          (camel-chaos, for development only)
+  chaos-suggest         Suggest chaos rules to try in a running integration,
+                          per route (camel-chaos, for development only)
+  disable-processor     Disable Camel processor
+  enable-processor      Enable Camel processor
+  gc                    Trigger Java Memory Garbage Collector
+  heap-dump             Write a heap dump (.hprof) file for deep memory 
analysis
+  heap-histogram        Display class-level heap memory usage in a running
+                          Camel integration
+  memory-leak           Diagnose memory leaks in a running Camel integration
+  load                  Loads new source files into an existing Camel
+  logger                List or change logging levels
+  receive               Receive and dump messages from remote endpoints
+  reload                Trigger reloading Camel
+  reset-stats           Reset performance statistics
+  resume-route          Resume Camel routes
+  route-diagram         Display Camel route diagram in the terminal
+  route-dump            Dump Camel route in XML, YAML, or Java DSL format
+  route-structure       Dump Camel route structure
+  route-topology        Display inter-route topology connections
+  send                  Send messages to endpoints
+  sql                   Execute SQL query on a DataSource
+  span                  Display OpenTelemetry spans from running Camel
+                          integrations
+  start-group           Start Camel route groups
+  start-route           Start Camel routes
+  stop-group            Stop Camel route groups
+  stop-route            Stop Camel routes
+  stub                  Browse stub endpoints
+  suspend-route         Suspend Camel routes
+  thread-dump           List threads in a running Camel integration
 
 == camel cmd browse --help
 Usage: camel cmd browse [-h] [--dump] [--fresh-size] [--json] [--mask]
@@ -741,6 +749,129 @@ Examples:
   camel cmd chaos-clear myApp
   camel cmd chaos-clear myApp --reset
 
+== camel cmd chaos-experiment --help
+Usage: camel cmd chaos-experiment [-h] [--end] [--body=<body>] [--feed=<feed>]
+                                  [--hypothesis=<hypothesis>]
+                                  [--interval=<interval>] [--note=<note>]
+                                  [--repeat=<repeat>] [--timeout=<timeout>]
+                                  [--wait=<wait>] [--header=<headers>]...
+                                  [--rule=<rules>]... [<name>]
+Run one chaos experiment in the exploration of a running integration: add its
+rules, send its traffic, wait for the verdict, and end it
+      [<name>]              Name or pid of the running Camel integration (must
+                              match one); without it, the only one running, or
+                              the only one with chaos switched on
+  -h, --help                Display the help and sub-commands
+      --timeout=<timeout>   How long to wait for the integration to answer, in
+                              millis or as a duration such as 30s
+                              Default: 10000
+      --hypothesis=<hypothesis>
+                            What the experiment tests, in a few words, such as
+                              'orders survive a payment outage'
+      --rule=<rules>        A rule in its one-line text form, as an entry of
+                              camel.chaos.rules; can be given more than once,
+                              and one of them needs an expectation (expect=)
+      --feed=<feed>         The endpoint, or the id of the route, to send the
+                              messages of the experiment to
+      --body=<body>         The body of the messages: text, file:<path>, or
+                              file:<folder> for a message per file
+      --header=<headers>    A header of the messages as key=value; can be given
+                              more than once
+      --repeat=<repeat>     How many messages to send in all (at most 10000)
+                              Default: 0
+      --interval=<interval> The pause between two messages, such as 200ms, or a
+                              range such as 100..500 to draw each pause at
+                              random (at most 60s)
+      --wait=<wait>         The longest wait for the verdict after the messages
+                              were sent (at most 10m)
+                              Default: 2m
+      --note=<note>         What to note with the experiment when it ends
+      --end                 End the open experiment (with --note) instead of
+                              running a new one
+
+An experiment is part of an exploration (camel cmd chaos-explore-start). It
+tests a
+hypothesis with rules, one of them with an expectation (expect=) that decides
+it.
+The rules are added together, the messages of --feed are sent (as camel cmd 
send
+sends them, with --body, --header, --repeat and --interval), and then it waits
+until
+the expectations are decided, or --wait is up. The experiment is ended then, 
its
+rules are removed, and what came of it is printed: the verdict, the
+expectations,
+what the routes did, and the traffic. It stays in the journal of the 
exploration
+(camel get chaos --report).
+
+The verdict is FAIL when an expectation is unmet (exit code 1), PASS when all
+are
+met, and INCONCLUSIVE when it ended before an expectation was decided: wait
+longer
+than the within= of the expectation. --feed can be the id of a route, as the
+feed:
+line of camel cmd chaos-suggest has it.
+
+Examples:
+  camel cmd chaos-experiment --hypothesis='a failed refund is read again' \
+      --rule='route=refunds error rate=every:5 expect=refunds:failed<=0
+within=30s' \
+      --feed=refunds --body=file:test/chaos/refunds-fails/ --repeat=20
+--interval=200ms
+  camel cmd chaos-experiment myApp --hypothesis='orders survive a payment
+outage' \
+      --rule='endpoint=http:payment* error status=503 until=1m expect=orders:
+failed<=0 within=70s' \
+      --wait=90s
+  camel cmd chaos-experiment --end --note='stopped by hand'
+
+== camel cmd chaos-explore-finish --help
+Usage: camel cmd chaos-explore-finish [-h] [--timeout=<timeout>] [<name>]
+Finish the chaos exploration of a running integration before its deadline
+      [<name>]              Name or pid of the running Camel integration (must
+                              match one); without it, the only one running, or
+                              the only one with chaos switched on
+  -h, --help                Display the help and sub-commands
+      --timeout=<timeout>   How long to wait for the integration to answer, in
+                              millis or as a duration such as 30s
+                              Default: 10000
+
+The rules are removed (an open experiment is ended with what came of it so
+far), and
+the journal stays in the report (camel get chaos --report) until the next
+exploration starts or the report is reset.
+
+Examples:
+  camel cmd chaos-explore-finish
+  camel cmd chaos-explore-finish myApp
+
+== camel cmd chaos-explore-start --help
+Usage: camel cmd chaos-explore-start [-h] [--budget=<budget>] [--focus=<focus>]
+                                     [--timeout=<timeout>] [<name>]
+Start a chaos exploration in a running integration: a time to try chaos rules
+in, that camel-chaos enforces
+      [<name>]              Name or pid of the running Camel integration (must
+                              match one); without it, the only one running, or
+                              the only one with chaos switched on
+  -h, --help                Display the help and sub-commands
+      --timeout=<timeout>   How long to wait for the integration to answer, in
+                              millis or as a duration such as 30s
+                              Default: 10000
+      --budget=<budget>     How long the exploration runs, between 10s and 2h
+                              Default: 10m
+      --focus=<focus>       What the exploration is about, such as a route
+
+While the exploration runs, camel-chaos keeps its journal: the baseline (the
+counts
+per route before its first rule), the rules tried, and the experiments
+(camel cmd chaos-experiment). A rule ends at the deadline at the latest, and at
+the
+deadline the rules are removed and the exploration is finished, whatever
+happens.
+One exploration runs at a time. camel get chaos --report shows it.
+
+Examples:
+  camel cmd chaos-explore-start
+  camel cmd chaos-explore-start myApp --budget=30m --focus=orders
+
 == camel cmd chaos-load --help
 Usage: camel cmd chaos-load [-h] [--timeout=<timeout>] <file> [<name>]
 Load a chaos scenario into a running integration (camel-chaos, for development

Reply via email to