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 bf7a8ea5b22e CAMEL-26787: camel-jbang - let there be chaos: the
playbook as an MCP prompt, and camel cmd chaos-explore with the AI of camel ask
(#29501)
bf7a8ea5b22e is described below
commit bf7a8ea5b22e4a2883c942bb5c09f4dfa23fa6b3
Author: Claus Ibsen <[email protected]>
AuthorDate: Mon Nov 9 23:59:38 2026 +0100
CAMEL-26787: camel-jbang - let there be chaos: the playbook as an MCP
prompt, and camel cmd chaos-explore with the AI of camel ask (#29501)
Co-Authored-By: Claude Opus 5.5 <[email protected]>
---
.../org/apache/camel/catalog/docs/chaos.adoc | 2 +
components/camel-chaos/src/main/docs/chaos.adoc | 2 +
.../modules/ROOT/pages/camel-jbang-managing.adoc | 17 +-
.../modules/ROOT/pages/camel-jbang-mcp.adoc | 8 +
.../camel-jbang-cmd-chaos-explore.adoc | 37 ++++
.../ROOT/pages/jbang-commands/camel-jbang-cmd.adoc | 1 +
.../META-INF/camel-jbang-commands-metadata.json | 2 +-
.../apache/camel/dsl/jbang/core/commands/Ask.java | 7 +-
.../dsl/jbang/core/commands/CamelJBangMain.java | 1 +
.../dsl/jbang/core/commands/ChaosExplore.java | 211 +++++++++++++++++++++
.../core/commands/ai/ChaosExplorePlaybook.java | 108 +++++++++++
.../dsl/jbang/core/common/ChaosExperimentRun.java | 2 +-
.../dsl/jbang/core/commands/ChaosExploreTest.java | 83 ++++++++
.../core/commands/ai/ChaosExplorePlaybookTest.java | 55 ++++++
.../src/test/resources/golden/cli-help.txt | 69 +++++++
.../jbang/core/commands/mcp/PromptDefinitions.java | 22 +++
.../core/commands/mcp/PromptDefinitionsTest.java | 16 ++
17 files changed, 639 insertions(+), 4 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 6713e09b3b2d..5f74f1a59e07 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
@@ -426,6 +426,8 @@ $ camel get chaos --report
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.
+`camel cmd chaos-explore` hands a whole exploration to an AI: it runs the
experiments by itself for the budget, and
+reports what held and what broke (the MCP prompt `camel_chaos_explore` is the
same playbook).
== Scenarios
diff --git a/components/camel-chaos/src/main/docs/chaos.adoc
b/components/camel-chaos/src/main/docs/chaos.adoc
index 6713e09b3b2d..5f74f1a59e07 100644
--- a/components/camel-chaos/src/main/docs/chaos.adoc
+++ b/components/camel-chaos/src/main/docs/chaos.adoc
@@ -426,6 +426,8 @@ $ camel get chaos --report
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.
+`camel cmd chaos-explore` hands a whole exploration to an AI: it runs the
experiments by itself for the budget, and
+reports what held and what broke (the MCP prompt `camel_chaos_explore` is the
same playbook).
== Scenarios
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 d41e7b421294..4720ad63b47e 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-managing.adoc
@@ -940,7 +940,22 @@ Experiment 1 FAIL a failed refund is read again (r1,
ended after 6s)
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
+of `chaos-suggest` has it. `camel get chaos --report` shows the exploration
with its experiments.
+
+`camel cmd chaos-explore` lets the AI of `camel ask` do it all: let there be
chaos. It starts the exploration with the
+budget, and the AI reads the suggestions, runs one experiment per call, reads
each verdict, and goes on while there is
+time. It asks nothing on the way, as starting the command is the consent, and
it never changes the routes. When it is
+done the exploration is finished, and its report and the journal of the
experiments are printed (exit code 1 when an
+experiment failed):
+
+[source,bash]
+----
+camel cmd chaos-explore --budget=10m
+camel cmd chaos-explore myApp --budget=1h --focus=refunds --api-type=anthropic
+----
+
+Run the integration with `--stub=remote`, so the experiments do not reach real
systems. A route fed over HTTP or from
+files cannot be fed by an experiment yet. MCP clients have the same playbook
as the prompt `camel_chaos_explore`. See
xref:next@components:others:chaos.adoc#_an_exploration[An exploration] in the
camel-chaos documentation.
=== Chaos scenarios
diff --git a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
index 5b5d95819925..67415f5f6890 100644
--- a/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
+++ b/docs/user-manual/modules/ROOT/pages/camel-jbang-mcp.adoc
@@ -1298,4 +1298,12 @@ instructions and executes each step by calling the
referenced tools.
| `routeId` (optional), `goal` (optional)
| Guided workflow to optimize a Camel application's performance: analyze
throughput, identify bottlenecks,
review resource usage, and produce prioritized optimization recommendations.
+
+| `camel_chaos_explore`
+| `budget` (optional), `focus` (optional), `name` (optional)
+| Let there be chaos: the AI explores by itself how a running application with
camel-chaos copes with faults, for the
+ `budget` (10 minutes by default). It starts an exploration, runs one
experiment per call
+ (`camel_runtime_chaos_experiment`) from the suggestions, reads each verdict,
and finishes with a report: what held,
+ what broke with the evidence and a hint, and what was not tried. It asks
nothing on the way and never changes the
+ routes; camel-chaos removes the rules at the deadline. The same playbook as
`camel cmd chaos-explore`.
|===
diff --git
a/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-explore.adoc
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-explore.adoc
new file mode 100644
index 000000000000..8ddbf5b6a012
--- /dev/null
+++
b/docs/user-manual/modules/ROOT/pages/jbang-commands/camel-jbang-cmd-chaos-explore.adoc
@@ -0,0 +1,37 @@
+
+// AUTO-GENERATED by camel-package-maven-plugin - DO NOT EDIT THIS FILE
+= camel cmd chaos-explore
+
+Let there be chaos: an AI explores by itself how a running integration copes
with faults, for a time budget, and reports what held and what broke
+
+
+== Usage
+
+[source,bash]
+----
+camel cmd chaos-explore [options]
+----
+
+
+
+== Options
+
+[cols="2,5,1,2",options="header"]
+|===
+| Option | Description | Default | Type
+| `--api-key` | API key. Also reads ANTHROPIC_API_KEY, OPENAI_API_KEY,
WATSONX_APIKEY, or LLM_API_KEY env vars | | String
+| `--api-type` | API type: 'ollama', 'openai', 'anthropic', or 'watsonx' | |
ApiType
+| `--budget` | How long the AI explores, between 10s and 2h | 10m | String
+| `--focus` | What to explore, such as a route id (default: all routes) | |
String
+| `--max-iterations` | Maximum number of tool-calling rounds | 100 | int
+| `--model` | Model to use. Default: the provider's default; with Ollama the
installed model best at tool calling | | String
+| `--show-tools` | Show the tool calls and results as they happen | | boolean
+| `--timeout` | Timeout in seconds for one answer of the LLM (an experiment
can take minutes) | 300 | int
+| `--tools` | The tools the model gets: core (a small set, for a local model),
full, or auto | auto | ToolSet
+| `--url` | LLM API endpoint URL, as for camel ask. Auto-detected if not
specified | | String
+| `--verbose` | Print debug information of the LLM calls | | boolean
+| `--yes` | Let the AI send messages to endpoints the integration does not
have, and run the other calls that camel ask asks for first, without asking |
| boolean
+| `-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 1c4a85c0047e..6d3b52059917 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
@@ -23,6 +23,7 @@ camel cmd [options]
| 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.adoc[chaos-explore] | Let
there be chaos: an AI explores by itself how a running integration copes with
faults, for a time budget, and reports what held and what broke
|
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)
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 ccde7b460870..b7bba9ca7fc9 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/Ask.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Ask.java
index c3a78931d857..dbdccd7ca25b 100644
---
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Ask.java
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/Ask.java
@@ -21,6 +21,7 @@ import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Locale;
+import java.util.Set;
import java.util.function.Function;
import org.apache.camel.dsl.jbang.core.commands.ai.AnswerChecks;
@@ -128,6 +129,9 @@ public class Ask extends CamelCommand {
/** Below this size (billions of parameters) a model's answers about a
running integration are often wrong. */
static final double SMALL_MODEL_BILLIONS = 8;
+ // the tools the model does not get, such as starting a chaos exploration
that camel cmd chaos-explore started
+ Set<String> hiddenTools = Set.of();
+
private long targetPid;
private AskTools askTools;
// the chat's reader, or one made on the terminal for the first call to
confirm
@@ -182,7 +186,8 @@ public class Ask extends CamelCommand {
process != null ? process.pid() : -1,
process != null ? process.name() : null,
core, core ? askTools.groupGuidance() : List.of());
- List<LlmClient.ToolDef> toolDefs = askTools.buildToolDefinitions(core);
+ List<LlmClient.ToolDef> toolDefs = new
ArrayList<>(askTools.buildToolDefinitions(core));
+ toolDefs.removeIf(t -> hiddenTools.contains(t.name()));
if (question == null || question.isEmpty()) {
return runInteractiveChat(client, process, systemPrompt, toolDefs);
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 89693244f2aa..e768d48e8c5f 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
@@ -263,6 +263,7 @@ public class CamelJBangMain implements Callable<Integer> {
.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", new CommandLine(new
ChaosExplore(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)))
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ChaosExplore.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ChaosExplore.java
new file mode 100644
index 000000000000..4a59b7eef5fc
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ChaosExplore.java
@@ -0,0 +1,211 @@
+/*
+ * 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;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import org.apache.camel.dsl.jbang.core.commands.ai.ChaosExplorePlaybook;
+import org.apache.camel.dsl.jbang.core.commands.process.ListChaos;
+import org.apache.camel.dsl.jbang.core.common.ActionClient;
+import org.apache.camel.dsl.jbang.core.common.ActionClient.ActionResult;
+import org.apache.camel.dsl.jbang.core.common.ChaosExperimentRun;
+import org.apache.camel.dsl.jbang.core.common.CliExitCode;
+import org.apache.camel.dsl.jbang.core.common.ProcessFinder;
+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.Command;
+import picocli.CommandLine.Option;
+import picocli.CommandLine.Parameters;
+
+/**
+ * Let there be chaos (CAMEL-26787): the AI of camel ask explores chaos on a
running integration by itself, for the
+ * budget of the user, and reports at the end.
+ */
+@Command(name = "chaos-explore",
+ description = "Let there be chaos: an AI explores by itself how a
running integration copes with faults, for a"
+ + " time budget, and reports what held and what broke",
+ sortOptions = false, showDefaultValues = true,
+ footer = {
+ "%nThis command starts a chaos exploration with the budget
(camel cmd chaos-explore-start), and hands",
+ "it to the AI of camel ask with a playbook: read the
suggestions, run one experiment per call",
+ "(camel cmd chaos-experiment), read the verdicts, and go on
while there is time. The AI asks",
+ "nothing on the way: starting this command is the consent to
the exploration, and camel-chaos",
+ "removes every rule at the deadline. The AI never changes the
routes. When it is done, the",
+ "exploration is finished, and its report and the journal of
the experiments are printed. The exit",
+ "code is 1 when an experiment failed.",
+ "%nThe integration needs camel-chaos switched on (camel run
--chaos), and remote endpoints are best",
+ "stubbed (camel run --stub=remote), so the experiments do not
reach real systems.",
+ "%nExamples:",
+ " camel cmd chaos-explore",
+ " camel cmd chaos-explore myApp --budget=30m
--focus=refunds",
+ " camel cmd chaos-explore --api-type=anthropic --budget=1h"
})
+public class ChaosExplore extends CamelCommand {
+
+ @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;
+
+ @Option(names = { "--budget" }, defaultValue = "10m",
+ description = "How long the AI explores, between 10s and 2h")
+ String budget;
+
+ @Option(names = { "--focus" },
+ description = "What to explore, such as a route id (default: all
routes)")
+ String focus;
+
+ @Option(names = { "--url" },
+ description = "LLM API endpoint URL, as for camel ask.
Auto-detected if not specified")
+ String url;
+
+ @Option(names = { "--api-type" },
+ description = "API type: 'ollama', 'openai', 'anthropic', or
'watsonx'")
+ LlmClient.ApiType apiType;
+
+ @Option(names = { "--api-key" },
+ description = "API key. Also reads ANTHROPIC_API_KEY,
OPENAI_API_KEY, WATSONX_APIKEY, or LLM_API_KEY env vars")
+ String apiKey;
+
+ @Option(names = { "--model" },
+ description = "Model to use. Default: the provider's default; with
Ollama the installed model best at"
+ + " tool calling")
+ String model;
+
+ @Option(names = { "--timeout" }, defaultValue = "300",
+ description = "Timeout in seconds for one answer of the LLM (an
experiment can take minutes)")
+ int timeout = 300;
+
+ @Option(names = { "--max-iterations" }, defaultValue = "100",
+ description = "Maximum number of tool-calling rounds")
+ int maxIterations = 100;
+
+ @Option(names = { "--tools" }, defaultValue = "auto",
+ description = "The tools the model gets: core (a small set, for a
local model), full, or auto")
+ Ask.ToolSet tools = Ask.ToolSet.auto;
+
+ @Option(names = { "--yes" },
+ description = "Let the AI send messages to endpoints the
integration does not have, and run the other calls"
+ + " that camel ask asks for first, without asking")
+ boolean yes;
+
+ @Option(names = { "--show-tools" },
+ description = "Show the tool calls and results as they happen")
+ boolean showTools;
+
+ @Option(names = { "--verbose" },
+ description = "Print debug information of the LLM calls")
+ boolean verbose;
+
+ public ChaosExplore(CamelJBangMain main) {
+ super(main);
+ }
+
+ @Override
+ public Integer doCall() throws Exception {
+ CamelProcess process = ProcessFinder.requireOneForChaos(name);
+ // the exploration has the budget of the user, not one the AI picks
+ ActionResult started = ActionClient.execute(process, "chaos", root -> {
+ root.put("command", "explore-start");
+ root.put("budget", budget);
+ if (focus != null && !focus.isBlank()) {
+ root.put("focus", focus);
+ }
+ }, 10000);
+ if (!started.isOk()) {
+ printer().printErr(started.error());
+ return started.exitCode();
+ }
+ printer().println("Let there be chaos: " + process.name() + " (pid " +
process.pid() + "), "
+ +
TimeUtils.printDuration(started.reply().getLongOrDefault("budget", 0))
+ + (focus != null && !focus.isBlank() ? ", focus: " +
focus : ""));
+ int answer;
+ try {
+ answer = ask(process).doCall();
+ } catch (Exception e) {
+ // the LLM fails with an exit code; anything else must not cost
the report of what was explored so far
+ printer().printErr("camel ask failed: " + e.getMessage());
+ answer = CliExitCode.FAILURE;
+ } finally {
+ // the AI may have finished it already; else its rules go now
+ ActionClient.execute(process, "chaos", root -> root.put("command",
"explore-finish"), 10000);
+ }
+ JsonObject exploration =
ChaosExperimentRun.exploration(RuntimeHelper.readStatus(process.pid()));
+ List<String> lines = journal(exploration);
+ printer().println();
+ lines.forEach(printer()::println);
+ if (failed(exploration)) {
+ return CliExitCode.FAILURE;
+ }
+ return answer;
+ }
+
+ /** camel ask, with the playbook as its question, for the integration. */
+ Ask ask(CamelProcess process) {
+ Ask ask = new Ask(getMain());
+ ask.question =
List.of(ChaosExplorePlaybook.text(ChaosExplorePlaybook.ASK, process.name(),
budget, focus, true));
+ ask.nameOrPid = Long.toString(process.pid());
+ ask.url = url;
+ ask.apiType = apiType;
+ ask.apiKey = apiKey;
+ ask.model = model;
+ ask.timeout = timeout;
+ ask.maxIterations = maxIterations;
+ ask.tools = tools;
+ ask.yes = yes;
+ ask.showTools = showTools;
+ ask.showStats = true;
+ ask.verbose = verbose;
+ // the exploration runs with the budget of the user: the AI does not
start another one
+ ask.hiddenTools = Set.of("chaos_explore_start");
+ return ask;
+ }
+
+ /** The journal of the exploration: how it ended, and a line per
experiment with its expectations. */
+ static List<String> journal(JsonObject exploration) {
+ List<String> lines = new ArrayList<>();
+ if (exploration == null) {
+ lines.add("No journal of the exploration in the status of the
integration");
+ return lines;
+ }
+ List<Object> experiments = exploration.getCollection("experiments");
+ int count = experiments != null ? experiments.size() : 0;
+ long failed = experiments == null
+ ? 0 : experiments.stream()
+ .filter(o -> o instanceof JsonObject jo &&
"FAIL".equals(jo.getString("verdict"))).count();
+ long ran = exploration.getLongOrDefault("finishedAt", 0) -
exploration.getLongOrDefault("startedAt", 0);
+ lines.add("Chaos exploration " +
("deadline".equals(exploration.getString("finishedBy"))
+ ? "finished at its deadline" : "finished")
+ + (ran > 0 ? " after " + TimeUtils.printDuration(ran) : "")
+ ": " + count
+ + (count == 1 ? " experiment" : " experiments") + ", " +
failed + " failed");
+ if (experiments != null) {
+ for (Object o : experiments) {
+ lines.addAll(ListChaos.experimentLines((JsonObject) o, " "));
+ }
+ }
+ return lines;
+ }
+
+ private static boolean failed(JsonObject exploration) {
+ List<Object> experiments = exploration != null ?
exploration.getCollection("experiments") : null;
+ return experiments != null
+ && experiments.stream().anyMatch(o -> o instanceof JsonObject
jo && "FAIL".equals(jo.getString("verdict")));
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybook.java
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybook.java
new file mode 100644
index 000000000000..33a80209ac75
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/main/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybook.java
@@ -0,0 +1,108 @@
+/*
+ * 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.ai;
+
+/**
+ * The playbook of "let there be chaos" (CAMEL-26787): what an AI does to
explore chaos on an integration by itself, for
+ * the time of an exploration, and the report it comes back with. One text for
the MCP prompt
+ * {@code camel_chaos_explore} and for {@code camel cmd chaos-explore} (the AI
of camel ask), each with the names of its
+ * own tools.
+ * <p/>
+ * The AI steers on its own and asks the user nothing: the user agreed to the
exploration, camel-chaos removes its rules
+ * at the deadline, and the AI never changes the routes. One experiment per
call keeps it to about ten calls in ten
+ * minutes, which a small local model can do.
+ */
+public final class ChaosExplorePlaybook {
+
+ /** The names of the tools the playbook tells the AI to call. */
+ public record Tools(String start, String report, String suggest, String
experiment, String finish, String errors) {
+ }
+
+ /** The tools of the MCP server. */
+ public static final Tools MCP = new Tools(
+ "camel_runtime_chaos_explore_start", "camel_runtime_chaos_report",
"camel_runtime_chaos_suggest",
+ "camel_runtime_chaos_experiment",
"camel_runtime_chaos_explore_finish", "camel_get_errors");
+
+ /** The tools of camel ask. */
+ public static final Tools ASK = new Tools(
+ "chaos_explore_start", "chaos_report", "chaos_suggest",
"chaos_experiment", "chaos_explore_finish",
+ "get_errors");
+
+ private ChaosExplorePlaybook() {
+ }
+
+ /**
+ * The playbook.
+ *
+ * @param tools the names of the tools
+ * @param integration the name of the integration, or null for the one the
tools select
+ * @param budget how long the exploration runs, such as 10m (10m when
null)
+ * @param focus what to explore, such as a route, or null for all
routes
+ * @param started whether the exploration is already running (camel
cmd chaos-explore starts it with the budget
+ * of the user), else the AI starts it
+ */
+ public static String text(Tools tools, String integration, String budget,
String focus, boolean started) {
+ String where = integration != null && !integration.isBlank()
+ ? "the integration " + integration.strip()
+ : "the running integration";
+ String time = budget != null && !budget.isBlank() ? budget.strip() :
"10m";
+ String about = focus != null && !focus.isBlank()
+ ? "Focus on " + focus.strip() + ", and the routes it hands
messages to."
+ : "Explore all routes, the most worth trying first.";
+ String start = started
+ ? "The exploration is running (" + time + "): do not start
another one."
+ : "Start the exploration with " + tools.start() + " (budget "
+ time + ").";
+ return """
+ Let there be chaos: explore how %s copes with faults, by
yourself, for %s. %s
+ camel-chaos adds the faults and removes every rule at the
deadline of the exploration. Do not ask \
+ the user anything, and never change the routes or files: try,
measure, and report at the end.
+
+ 1. %s Then read %s: the routes and their counts before chaos
(the baseline), and what was tried.
+ 2. Call %s for ideas: rules per route with why, and the feed
of each route (how messages get in). \
+ Prefer the kinds not tried yet; single faults first, then the
combined ones (two faults at once).
+ 3. Run one experiment per idea with %s. A rule makes exchanges
fail or wait on purpose, so do not \
+ expect that nothing fails where the fault is: expect what
should hold despite it, such as nothing \
+ lost after a hand-off to another route, a failure handled by
the error handler or redelivered, or the \
+ route still completing its messages in time.
+ - hypothesis: what should hold, in a few words, such as "a
failed refund is retried, not lost"
+ - rule: the suggested rule with a short until= (such as
until=30s), and one expectation that \
+ decides the hypothesis, with within= a little longer than
until=: expect=<route>:failed<=0 \
+ (nothing fails), expect=<route>-><next route>:lost<=0 (nothing
lost between two routes), \
+ expect=<route>:20 (20 completed), expect=<route>:maxTime<=5s
(none slower than 5s)
+ - feed: when the feed of the suggestion is kind send, its
endpoint, with body, repeat (such as \
+ 20) and interval (such as 100..300); leave feed out when the
route feeds itself (kind self). \
+ Routes fed over http or from files cannot be fed by an
experiment yet: try them only when they \
+ feed themselves
+ - wait: longer than the within= of the expectation
+ - note: what you make of the result, in one sentence
+ 4. Read the result: the verdict (FAIL, PASS or INCONCLUSIVE),
the expectations, what the routes \
+ did, and the traffic. On FAIL, call %s for the failed
exchanges and their exceptions, and say why it \
+ broke. On INCONCLUSIVE, run it again with a longer wait. On
PASS, move on.
+ 5. Go on while timeLeft of the result is more than the next
experiment needs (its until= and wait, \
+ plus a minute). Stop with about a minute left.
+ 6. Finish with %s, and write the report:
+ - Summary: how many experiments, how many held and how many
broke
+ - Findings: per FAIL the route, what broke, the evidence
(the expectation and its actual value, \
+ the exception), and a hint for the route in terms of its error
handling (a dead letter channel, \
+ redelivery, onException, a circuit breaker, idempotency), not
endpoint options you are not sure \
+ exist; say when it looks like a bug in Camel rather than in
the route
+ - What held: one line per PASS
+ - Not tried: the kinds of fault or routes left out, and why
(no feed, out of time)
+ """.formatted(where, time, about, start, tools.report(),
tools.suggest(), tools.experiment(),
+ tools.errors(), tools.finish());
+ }
+}
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
index 40b0a5a1b751..742c29a7c6dc 100644
---
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
@@ -235,7 +235,7 @@ public final class ChaosExperimentRun {
}
/** The exploration in the report of the chaos console in the status, or
null. */
- static JsonObject exploration(JsonObject status) {
+ public static JsonObject exploration(JsonObject status) {
if (status == null || !(status.get("chaos") instanceof JsonObject
chaos)
|| !(chaos.get("report") instanceof JsonObject report)) {
return null;
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ChaosExploreTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ChaosExploreTest.java
new file mode 100644
index 000000000000..fa94b0c72251
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ChaosExploreTest.java
@@ -0,0 +1,83 @@
+/*
+ * 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;
+
+import java.util.List;
+import java.util.Map;
+
+import org.apache.camel.dsl.jbang.core.common.ProcessFinder.CamelProcess;
+import org.apache.camel.util.json.JsonArray;
+import org.apache.camel.util.json.JsonObject;
+import org.junit.jupiter.api.Test;
+import picocli.CommandLine;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * camel cmd chaos-explore (CAMEL-26787): camel ask gets the playbook and the
options, and the journal is printed when
+ * the AI is done.
+ */
+class ChaosExploreTest {
+
+ @Test
+ void camelAskGetsThePlaybookAndTheOptions() {
+ ChaosExplore command = new ChaosExplore(new CamelJBangMain());
+ CommandLine.populateCommand(command, "shop", "--budget=30m",
"--focus=refunds", "--model=qwen3.6",
+ "--api-type=ollama", "--tools=core", "--show-tools");
+
+ Ask ask = command.ask(new CamelProcess(4711, "shop", null));
+
+ assertThat(ask.question).singleElement().asString()
+ .contains("explore how the integration shop copes", "The
exploration is running (30m)", "chaos_experiment");
+ assertThat(ask.nameOrPid).isEqualTo("4711");
+ assertThat(ask.model).isEqualTo("qwen3.6");
+ assertThat(ask.apiType).isEqualTo(LlmClient.ApiType.ollama);
+ assertThat(ask.tools).isEqualTo(Ask.ToolSet.core);
+ assertThat(ask.showTools).isTrue();
+ // an experiment can take minutes, and a run many rounds
+ assertThat(ask.timeout).isEqualTo(300);
+ assertThat(ask.maxIterations).isEqualTo(100);
+ assertThat(ask.yes).isFalse();
+ // the exploration runs with the budget of the user: the AI does not
get the tool to start another one
+ assertThat(ask.hiddenTools).containsExactly("chaos_explore_start");
+ }
+
+ @Test
+ void theJournalWhenTheAiIsDone() {
+ JsonObject failed = new JsonObject(
+ Map.of("number", 1, "verdict", "FAIL", "open", false,
"endedBy", "ended",
+ "elapsed", 31_000L, "hypothesis", "a refund never
fails", "rules", new JsonArray(List.of("r1")),
+ "expectations", new JsonArray(
+ List.of(new JsonObject(
+ Map.of("expect", "refunds:failed<=0",
"state", "unmet", "value", "4 failed"))))));
+ JsonObject held = new JsonObject(
+ Map.of("number", 2, "verdict", "PASS", "open", false,
"endedBy", "ended",
+ "elapsed", 12_000L, "rules", new
JsonArray(List.of("r2"))));
+ JsonObject exploration = new JsonObject(
+ Map.of("running", false, "finishedBy", "deadline",
+ "startedAt", 1_000_000L, "finishedAt", 1_180_000L,
"experiments",
+ new JsonArray(List.of(failed, held))));
+
+ assertThat(ChaosExplore.journal(exploration)).containsExactly(
+ "Chaos exploration finished at its deadline after 3m0s: 2
experiments, 1 failed",
+ " 1 FAIL a refund never fails (r1, ended after 31s)",
+ " expect refunds:failed<=0: unmet, 4 failed",
+ " 2 PASS (r2, ended after 12s)");
+ assertThat(ChaosExplore.journal(null)).containsExactly(
+ "No journal of the exploration in the status of the
integration");
+ }
+}
diff --git
a/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybookTest.java
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybookTest.java
new file mode 100644
index 000000000000..df3d3d47c071
--- /dev/null
+++
b/dsl/camel-jbang/camel-jbang-core/src/test/java/org/apache/camel/dsl/jbang/core/commands/ai/ChaosExplorePlaybookTest.java
@@ -0,0 +1,55 @@
+/*
+ * 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.ai;
+
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * The playbook of "let there be chaos" (CAMEL-26787): the same steps for
camel ask and the MCP server, each with the
+ * names of its own tools, which exist.
+ */
+class ChaosExplorePlaybookTest {
+
+ @Test
+ void camelAskGetsItsOwnToolsAndARunningExploration() {
+ String text = ChaosExplorePlaybook.text(ChaosExplorePlaybook.ASK,
"shop", "30m", "refunds", true);
+
+ assertThat(text).contains("explore how the integration shop copes with
faults, by yourself, for 30m",
+ "The exploration is running (30m): do not start another one.",
"Focus on refunds",
+ "chaos_report", "chaos_suggest", "chaos_experiment",
"chaos_explore_finish", "get_errors")
+ .doesNotContain("camel_runtime_", "chaos_explore_start");
+ }
+
+ @Test
+ void theToolsItNamesExist() {
+ ChaosExplorePlaybook.Tools t = ChaosExplorePlaybook.ASK;
+ for (String name : new String[] { t.start(), t.report(), t.suggest(),
t.experiment(), t.finish(), t.errors() }) {
+ assertThat(ToolRegistry.findTool(name)).as(name).isNotNull();
+ }
+ }
+
+ @Test
+ void theExpectationsItTeachesAreRulesCamelChaosTakes() {
+ String text = ChaosExplorePlaybook.text(ChaosExplorePlaybook.MCP,
null, null, null, false);
+
+ // the forms of expect= that ChaosRuleParser reads
+ assertThat(text).contains("expect=<route>:failed<=0",
"expect=<route>-><next route>:lost<=0",
+ "expect=<route>:20", "expect=<route>:maxTime<=5s", "within=");
+ }
+}
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 2900be01a02a..db328bcb784c 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
@@ -561,6 +561,9 @@ Commands:
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 Let there be chaos: an AI explores by itself how a
+ running integration copes with faults, for a time
+ budget, and reports what held and what broke
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
@@ -823,6 +826,72 @@ failed<=0 within=70s' \
--wait=90s
camel cmd chaos-experiment --end --note='stopped by hand'
+== camel cmd chaos-explore --help
+Usage: camel cmd chaos-explore [-h] [--show-tools] [--verbose] [--yes]
+ [--api-key=<apiKey>] [--api-type=<apiType>]
+ [--budget=<budget>] [--focus=<focus>]
+ [--max-iterations=<maxIterations>]
+ [--model=<model>] [--timeout=<timeout>]
+ [--tools=<tools>] [--url=<url>] [<name>]
+Let there be chaos: an AI explores by itself how a running integration copes
+with faults, for a time budget, and reports what held and what broke
+ [<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
+ --budget=<budget> How long the AI explores, between 10s and 2h
+ Default: 10m
+ --focus=<focus> What to explore, such as a route id (default: all
+ routes)
+ --url=<url> LLM API endpoint URL, as for camel ask.
+ Auto-detected if not specified
+ --api-type=<apiType> API type: 'ollama', 'openai', 'anthropic', or
+ 'watsonx'
+ --api-key=<apiKey> API key. Also reads ANTHROPIC_API_KEY,
+ OPENAI_API_KEY, WATSONX_APIKEY, or LLM_API_KEY
+ env vars
+ --model=<model> Model to use. Default: the provider's default;
+ with Ollama the installed model best at tool
+ calling
+ --timeout=<timeout> Timeout in seconds for one answer of the LLM (an
+ experiment can take minutes)
+ Default: 300
+ --max-iterations=<maxIterations>
+ Maximum number of tool-calling rounds
+ Default: 100
+ --tools=<tools> The tools the model gets: core (a small set, for a
+ local model), full, or auto
+ Default: auto
+ --yes Let the AI send messages to endpoints the
+ integration does not have, and run the other
+ calls that camel ask asks for first, without
+ asking
+ --show-tools Show the tool calls and results as they happen
+ --verbose Print debug information of the LLM calls
+
+This command starts a chaos exploration with the budget (camel cmd
+chaos-explore-start), and hands
+it to the AI of camel ask with a playbook: read the suggestions, run one
+experiment per call
+(camel cmd chaos-experiment), read the verdicts, and go on while there is time.
+The AI asks
+nothing on the way: starting this command is the consent to the exploration,
+and camel-chaos
+removes every rule at the deadline. The AI never changes the routes. When it is
+done, the
+exploration is finished, and its report and the journal of the experiments are
+printed. The exit
+code is 1 when an experiment failed.
+
+The integration needs camel-chaos switched on (camel run --chaos), and remote
+endpoints are best
+stubbed (camel run --stub=remote), so the experiments do not reach real
systems.
+
+Examples:
+ camel cmd chaos-explore
+ camel cmd chaos-explore myApp --budget=30m --focus=refunds
+ camel cmd chaos-explore --api-type=anthropic --budget=1h
+
== 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
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitions.java
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitions.java
index 2cb4a1371637..1cafc11a8f34 100644
---
a/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitions.java
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/main/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitions.java
@@ -23,6 +23,7 @@ import jakarta.enterprise.context.ApplicationScoped;
import io.quarkiverse.mcp.server.Prompt;
import io.quarkiverse.mcp.server.PromptArg;
import io.quarkiverse.mcp.server.PromptMessage;
+import org.apache.camel.dsl.jbang.core.commands.ai.ChaosExplorePlaybook;
/**
* MCP Prompt definitions that provide structured multi-step workflows for
LLMs.
@@ -382,4 +383,25 @@ public class PromptDefinitions {
return List.of(PromptMessage.withUserRole(instructions));
}
+
+ /**
+ * Let there be chaos (CAMEL-26787): the AI explores chaos on a running
integration by itself for a time budget, one
+ * experiment per call, and reports at the end. The playbook is the one of
camel cmd chaos-explore.
+ */
+ @Prompt(name = "camel_chaos_explore",
+ description = "Let there be chaos: explore by yourself how a
running Camel application copes with faults, "
+ + "for a time budget: start a chaos exploration, run
one experiment per call (rules, traffic "
+ + "and the wait for the verdict), and report what
held and what broke, with hints. "
+ + "Needs camel-chaos switched on (camel run
--chaos).")
+ public List<PromptMessage> chaosExplore(
+ @PromptArg(name = "budget", description = "How long to explore,
such as 10m or 1h (default 10m, at most 2h)",
+ required = false) String budget,
+ @PromptArg(name = "focus", description = "What to explore, such as
a route id (default: all routes)",
+ required = false) String focus,
+ @PromptArg(name = "name", description = "Name or PID of the Camel
application (default: the only one"
+ + " running, or the only
one with chaos switched on)",
+ required = false) String name) {
+ return List.of(PromptMessage.withUserRole(
+ ChaosExplorePlaybook.text(ChaosExplorePlaybook.MCP, name,
budget, focus, false)));
+ }
}
diff --git
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitionsTest.java
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitionsTest.java
index 5ae648d67beb..79a25fd392e9 100644
---
a/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitionsTest.java
+++
b/dsl/camel-jbang/camel-jbang-mcp/src/test/java/org/apache/camel/dsl/jbang/core/commands/mcp/PromptDefinitionsTest.java
@@ -265,6 +265,22 @@ class PromptDefinitionsTest {
assertThat(mentioned).as("tools named by the
prompts").isSubsetOf(tools);
}
+ // ---- camel_chaos_explore ----
+
+ @Test
+ void chaosExploreIsThePlaybookWithTheMcpTools() {
+ String text = extractText(prompts.chaosExplore("1h", "refunds",
"shop"));
+
+ assertThat(text).contains("Let there be chaos: explore how the
integration shop copes with faults, by"
+ + " yourself, for 1h",
+ "Focus on refunds",
+ "Start the exploration with camel_runtime_chaos_explore_start
(budget 1h)",
+ "camel_runtime_chaos_suggest",
"camel_runtime_chaos_experiment", "camel_runtime_chaos_explore_finish",
+ "Do not ask the user anything");
+ assertThat(extractText(prompts.chaosExplore(null, null, null)))
+ .contains("the running integration copes with faults, by
yourself, for 10m", "Explore all routes");
+ }
+
// ---- helper ----
private String extractText(List<PromptMessage> messages) {