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 b8896f81b7f1 chore: docs - Governed AI Agents page in the user manual
(#27436)
b8896f81b7f1 is described below
commit b8896f81b7f18f7a9a4ce5904a5c70f0f2699f37
Author: Claus Ibsen <[email protected]>
AuthorDate: Sun Oct 25 20:11:43 2026 +0100
chore: docs - Governed AI Agents page in the user manual (#27436)
* chore: docs - Governed AI Agents page in the user manual
One page that tells how Camel governs what an AI agent may do on each
tool call, in the route that does the work: SPIFFE workload identity,
authorizationPolicy on ai-tool with OPA or OpenFGA, guard decisions with
the Semantic language and a System One model (TypeSafe AI / Jev), and
GenAI observability. Examples in Java, XML and YAML.
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
* chore: docs - Governed AI Agents page links the 4.23 component pages as
next, until the website has 4.23.x
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
* chore: docs - Governed AI Agents page declares the Semantic evaluations
with their expert, as main does
Co-Authored-By: Claude Opus 5.5 (1M context) <[email protected]>
Signed-off-by: Claus Ibsen <[email protected]>
---------
Signed-off-by: Claus Ibsen <[email protected]>
Co-authored-by: Claude Opus 5.5 (1M context) <[email protected]>
---
docs/user-manual/modules/ROOT/nav.adoc | 1 +
.../modules/ROOT/pages/governed-ai-agents.adoc | 334 +++++++++++++++++++++
2 files changed, 335 insertions(+)
diff --git a/docs/user-manual/modules/ROOT/nav.adoc
b/docs/user-manual/modules/ROOT/nav.adoc
index 8440caa8c701..dc7d53dc0e7e 100644
--- a/docs/user-manual/modules/ROOT/nav.adoc
+++ b/docs/user-manual/modules/ROOT/nav.adoc
@@ -75,6 +75,7 @@
** xref:security.adoc[Security]
** xref:security-policy.adoc[Security Policy Enforcement]
** xref:security-model.adoc[Security Model]
+** xref:governed-ai-agents.adoc[Governed AI Agents]
* xref:architecture.adoc[Architecture]
** xref:backlog-debugger.adoc[Backlog debugger]
** xref:backlog-tracer.adoc[Backlog Tracer]
diff --git a/docs/user-manual/modules/ROOT/pages/governed-ai-agents.adoc
b/docs/user-manual/modules/ROOT/pages/governed-ai-agents.adoc
new file mode 100644
index 000000000000..d8b9b16e5b98
--- /dev/null
+++ b/docs/user-manual/modules/ROOT/pages/governed-ai-agents.adoc
@@ -0,0 +1,334 @@
+= Governed AI Agents
+:tabs-sync-option:
+
+An AI agent decides which tools to call, and a tool can do real work: issue a
refund, read a customer record,
+delete a file. The model is not a security boundary. A prompt injection, a
confused model or a wrong caller can all
+ask for the wrong tool call.
+
+So the decision about what an agent may do sits below the model, on the tool
call itself. In Camel a tool is a
+route, and the route that does the work also checks:
+
+[cols="1,2"]
+|===
+|Question |Building block
+
+|Who is calling?
+|xref:next@components::spiffe-component.adoc[SPIFFE] workload identity and
mutual TLS
+
+|May they do this?
+|An `authorizationPolicy` on every xref:components::ai-tool-component.adoc[AI
Tool] call, with
+xref:next@components::opa-component.adoc[Open Policy Agent] or
xref:next@components::openfga-component.adoc[OpenFGA]
+
+|Does this action make sense?
+|A fast guard decision with the
xref:next@components:languages:semantic-language.adoc[Semantic] language and a
+System One model
+
+|What happened?
+|xref:next@components:others:ai-observability.adoc[AI Observability]:
OpenTelemetry spans and Micrometer metrics for
+every LLM call
+|===
+
+All of it runs in-process, in the Camel application, with no extra gateway.
The components are Preview, so details
+may still change.
+
+== Tools are routes
+
+A tool is a Camel route that starts with `ai-tool:`. The same route works with
the LangChain4j, Spring AI and OpenAI
+agents, and the built-in xref:components:others:mcp-server.adoc[MCP Server]
exposes it to any MCP client:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("ai-tool:refundOrder?tags=support&description=Refund a customer order by
its id"
+ + "¶meter.orderId=string¶meter.amount=integer")
+ .to("bean:refundLedger");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from uri="ai-tool:refundOrder?tags=support&description=Refund a
customer order by its
id&parameter.orderId=string&parameter.amount=integer"/>
+ <to uri="bean:refundLedger"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: ai-tool:refundOrder
+ parameters:
+ tags: support
+ description: "Refund a customer order by its id"
+ parameter.orderId: string
+ parameter.amount: integer
+ steps:
+ - to:
+ uri: bean:refundLedger
+----
+====
+
+Because the tool is a route, everything Camel has for routes applies to tool
calls: error handling, retries,
+circuit breakers, tracing, and the checks below.
+
+== Who is calling: SPIFFE
+
+https://spiffe.io/[SPIFFE] gives every workload a cryptographic identity,
issued and rotated by the local SPIRE
+agent, with no passwords or API keys to manage.
+
+The xref:next@components::spiffe-component.adoc[SPIFFE] component validates a
caller's JWT-SVID on an incoming request,
+fetches one for outbound calls, and backs `SSLContextParameters` with rotating
mutual TLS for every component that
+already supports TLS:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+from("platform-http:/api")
+
.to("spiffe:auth?operation=validateJwtSvid&audience=spiffe://example.org/api")
+ // keep the verified caller as an exchange property
+ .setProperty("subject", header(SpiffeConstants.SPIFFE_ID))
+ // the token is a credential; drop it before the exchange goes further
+ .removeHeaders("Authorization")
+ .to("direct:handleRequest");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from uri="platform-http:/api"/>
+ <to
uri="spiffe:auth?operation=validateJwtSvid&audience=spiffe://example.org/api"/>
+ <!-- keep the verified caller as an exchange property -->
+ <setProperty name="subject">
+ <header>CamelSpiffeSpiffeId</header>
+ </setProperty>
+ <!-- the token is a credential; drop it before the exchange goes further
-->
+ <removeHeaders pattern="Authorization"/>
+ <to uri="direct:handleRequest"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: platform-http:/api
+ steps:
+ - to:
+ uri:
"spiffe:auth?operation=validateJwtSvid&audience=spiffe://example.org/api"
+ # keep the verified caller as an exchange property
+ - setProperty:
+ name: subject
+ expression:
+ header:
+ expression: CamelSpiffeSpiffeId
+ # the token is a credential; drop it before the exchange goes further
+ - removeHeaders:
+ pattern: Authorization
+ - to:
+ uri: direct:handleRequest
+----
+====
+
+Keep the verified identity in an exchange property, not a header. Headers are
part of the message, and on a tool
+route they carry the arguments the model filled in; a property is out of reach
of both the sender and the model.
+
+== May they do this: authorize every tool call
+
+Set an `authorizationPolicy` on the `ai-tool` component and every tool route
is guarded by construction. Set it on
+one endpoint to override:
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+// one policy guarding every ai-tool route
+AiToolComponent ai = context.getComponent("ai-tool", AiToolComponent.class);
+ai.getConfiguration().setAuthorizationPolicy(myAuthorizationPolicy);
+
+// ...or override on a single endpoint
+from("ai-tool:transferFunds?tags=banking&description=Transfer
funds&authorizationPolicy=#myAuthorizationPolicy")
+ .to("bean:ledger");
+----
+
+XML::
++
+[source,xml]
+----
+<route>
+ <from uri="ai-tool:transferFunds?tags=banking&description=Transfer
funds&authorizationPolicy=#myAuthorizationPolicy"/>
+ <to uri="bean:ledger"/>
+</route>
+----
+
+YAML::
++
+[source,yaml]
+----
+- route:
+ from:
+ uri: ai-tool:transferFunds
+ parameters:
+ tags: banking
+ description: "Transfer funds"
+ authorizationPolicy: "#myAuthorizationPolicy"
+ steps:
+ - to:
+ uri: bean:ledger
+----
+====
+
+The check runs before the route does any work. A denied call never runs the
tool; the model receives a short
+refusal it can relay to the user, not a stack trace.
+
+The policy can be:
+
+* xref:next@components::opa-component.adoc[Open Policy Agent]: rules in Rego,
versioned and tested apart from the route.
+ Evaluate a WebAssembly bundle in-process, so a tool call costs no network
hop.
+* xref:next@components::openfga-component.adoc[OpenFGA]: relationship-based
authorization ("may this user refund this
+ order?"). It fails closed.
+* The policies Camel already has: SPIFFE,
xref:components::keycloak-component.adoc[Keycloak],
+ xref:components:others:shiro.adoc[Shiro] or
xref:components:others:spring-security.adoc[Spring Security].
+
+Two rules make the decision trustworthy:
+
+. *The tool name comes from the route*, never from model output.
+. *The caller comes from an exchange property* set before the agent ran, for
example by `camel-spiffe` or
+ `camel-keycloak`. Never authorize on message headers: on a tool route they
carry the arguments the model filled in.
+
+See xref:components::ai-tool-component.adoc[AI Tool] for which agent runtimes
carry the caller identity onto the
+tool call, including over MCP.
+
+== Does this action make sense: System One guard decisions
+
+Authorization answers "may this caller use this tool". Some questions are
about meaning instead: is this request
+actionable, which team should handle it, does this answer stay on topic.
+
+A https://typesafe.ai/blog/introducing-system-one-models-and-jev[System One
model], such as Jev from
+xref:next@components::typesafe-ai-component.adoc[TypeSafe AI], is built for
exactly that: fast, structured decisions
+instead of generated text. Give it the state and a question, and it returns a
yes/no, one category or a score.
+
+The xref:next@components:languages:semantic-language.adoc[Semantic] language
declares the evaluations once, by name, and
+uses them anywhere Camel accepts a predicate or an expression. Each evaluation
names its expert, a registry bean; here
+`decisions` is the TypeSafe AI adapter
+(`camel.beans.decisions=#class:org.apache.camel.component.typesafeai.TypeSafeAiSemanticAdapter`):
+
+[tabs]
+====
+Java::
++
+[source,java]
+----
+semanticEvaluations(this)
+ .evaluation("actionable").expert("decisions")
+ .type("boolean")
+ .instructions("Does this message contain an actionable request?")
+ .threshold(0.8)
+ .uncertainty(0.1)
+ .uncertaintyPolicy("fail")
+ .end().evaluation("department").expert("decisions")
+ .type("choice")
+ .instructions("Which department should handle this message?")
+ .criterion("billing", "Invoices, payments and refunds")
+ .criterion("technical", "Bugs, outages and technical problems")
+ .register();
+
+from("direct:tickets")
+ .filter().language("semantic", "ref:actionable")
+ .setProperty("department").language("semantic", "ref:department")
+ .to("direct:dispatch");
+----
+
+YAML::
++
+[source,yaml]
+----
+- semantic:
+ evaluation:
+ actionable:
+ expert: decisions
+ type: boolean
+ instructions: Does this message contain an actionable request?
+ threshold: 0.8
+ uncertainty: 0.1
+ uncertaintyPolicy: fail
+ department:
+ expert: decisions
+ type: choice
+ instructions: Which department should handle this message?
+ criteria:
+ billing: Invoices, payments and refunds
+ technical: Bugs, outages and technical problems
+
+- route:
+ from:
+ uri: direct:tickets
+ steps:
+ - filter:
+ expression:
+ language:
+ language: semantic
+ expression: ref:actionable
+ steps:
+ - setProperty:
+ name: department
+ expression:
+ language:
+ language: semantic
+ expression: ref:department
+ - to:
+ uri: direct:dispatch
+----
+====
+
+The decision is a plain value, so what happens next is ordinary Camel:
`filter`, `choice`, `switch`, `validate`, a
+dead letter channel or a human review queue. A guard decision is not
authorization: it complements the policy above
+and never replaces it.
+
+== What happened: GenAI observability
+
+Add `camel-ai-observability` next to `camel-opentelemetry2` or
`camel-micrometer`, and every LLM call from
+`langchain4j-chat`, `langchain4j-agent`, `langchain4j-embeddings`, `openai`
and `spring-ai-chat` emits a child span
+and metrics, following the OpenTelemetry GenAI semantic conventions:
operation, model, input and output tokens,
+duration.
+
+The LLM spans sit inside the route's own trace, so one trace shows the
request, the agent, each tool call and each
+model call, in the observability stack you already run.
+
+== In the route, or at the perimeter
+
+Everything above runs inside the Camel application that does the work. That is
the right place for decisions that
+depend on the business data: this customer, this order, this amount.
+
+When many applications and teams expose tools to many agents, and you want one
place where every agent call is
+governed, add a governed proxy in front. https://wanaku.ai/[Wanaku] publishes
Camel `ai-tool` routes through its
+https://github.com/wanaku-ai/camel-integration-capability[Integration
Capability for Apache Camel]. The two work
+together: the proxy governs at the perimeter, and the route still checks the
call it is about to run.
+
+== Try it
+
+*
https://github.com/apache/camel-examples/tree/main/ai-tools-spiffe-opa[ai-tools-spiffe-opa
example]: a support
+ assistant with three tools, two callers with SPIFFE identities and an OPA
WebAssembly policy, all with Docker
+ Compose.
+* https://camel.apache.org/blog/2026/09/securing-ai-agent-tools/[Authorizing
what an AI agent may do in Apache Camel]
+*
https://camel.apache.org/blog/2026/09/camel-spiffe-workload-identity/[Workload
identity in Apache Camel with SPIFFE and SPIRE]
+*
https://camel.apache.org/blog/2026/09/semantic-evaluation-system-one/[TypeSafe
Jev meets Apache Camel: semantic decisions in Camel routes]
+* https://camel.apache.org/blog/2026/10/semantic-agent-routing/[One request,
several agents: semantic routing with Apache Camel and Jev]
+*
https://camel.apache.org/blog/2026/09/camel-genai-observability-jbang/[Observe
your Camel AI routes with GenAI OpenTelemetry]
+
+See also xref:security-model.adoc[Security Model] for where Camel draws its
trust boundaries.