This is an automated email from the ASF dual-hosted git repository. davsclaus pushed a commit to branch quick-fix/governed-ai-agents-doc in repository https://gitbox.apache.org/repos/asf/camel.git
commit 27da7b8b1333d519137b9d3b1f5c969a221c4855 Author: Claus Ibsen <[email protected]> AuthorDate: Tue Oct 6 12:53:48 2026 +0200 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]> --- docs/user-manual/modules/ROOT/nav.adoc | 1 + .../modules/ROOT/pages/governed-ai-agents.adoc | 330 +++++++++++++++++++++ 2 files changed, 331 insertions(+) diff --git a/docs/user-manual/modules/ROOT/nav.adoc b/docs/user-manual/modules/ROOT/nav.adoc index 376c02502415..6f2a61b995e8 100644 --- a/docs/user-manual/modules/ROOT/nav.adoc +++ b/docs/user-manual/modules/ROOT/nav.adoc @@ -72,6 +72,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..634d4977b974 --- /dev/null +++ b/docs/user-manual/modules/ROOT/pages/governed-ai-agents.adoc @@ -0,0 +1,330 @@ += 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: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:components::opa-component.adoc[Open Policy Agent] or xref:components::openfga-component.adoc[OpenFGA] + +|Does this action make sense? +|A fast guard decision with the xref:components:languages:semantic-language.adoc[Semantic] language and a +System One model + +|What happened? +|xref: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: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: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: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: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:components:languages:semantic-language.adoc[Semantic] language declares the questions once, by name, and +uses them anywhere Camel accepts a predicate or an expression: + +[tabs] +==== +Java:: ++ +[source,java] +---- +semanticQuestions(this) + .question("actionable") + .type("boolean") + .instructions("Does this message contain an actionable request?") + .threshold(0.8) + .uncertainty(0.1) + .uncertaintyPolicy("fail") + .end().question("department") + .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: + question: + actionable: + type: boolean + instructions: Does this message contain an actionable request? + threshold: 0.8 + uncertainty: 0.1 + uncertaintyPolicy: fail + department: + 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.
