Camel Components

Semantic Evaluation

Since Camel 4.23

The Semantic language invokes named evaluations through provider-independent experts. Each expert owns its operations, accepted inputs, evaluation parameters and result meaning. Boolean evaluations produce decisions, choice evaluations produce category strings, score evaluations produce numbers, and classification evaluations produce sets of labels. Calls are synchronous and may block while inference runs. Provider errors propagate through normal Camel error handling.

Dependencies and providers

Add org.apache.camel:camel-semantic and a provider, such as TypeSafe AI (camel-typesafe-ai), using the same Camel version. The provider owns credentials, model selection, request timeout, concurrency limits and transport resources. TypeSafe AI uses camel.component.typesafe-ai.* settings even when a route contains no TypeSafe AI endpoint.

An expert is a configured semantic evaluator, implemented by SemanticAdapter. It is not an autonomous agent. Each named evaluation can select a registry bean using expert. Without that field, camel.language.semantic.default-expert=myExpert selects the default bean. The existing camel.language.semantic.adapter=myAdapter setting remains supported as the default when default-expert is absent; it also accepts a fully qualified implementation class name.

Without either default, Camel selects the sole eligible registered instance or advertised adapter. Registry aliases for one instance count once. A registered instance supersedes discovery of its implementation class, including advertised superclasses of registered subclasses. Differently configured instances remain distinct candidates. No eligible expert, or multiple candidates, is an initialization error listing the candidates. Unknown explicit names and invalid defaults fail without fallback. Selection never uses the evaluation’s result type: two boolean experts can evaluate entirely different properties. Use plain names without #bean: or #class: prefixes. Registry lookup takes precedence over class resolution. Class selection uses Camel’s class resolver and injector. Created adapters are registered as camelSemanticAdapter and managed by the context. A collision at that name is an error. Referenced beans retain their existing lifecycle owner.

Camel Main and Camel JBang bind camel.language.semantic.adapter, camel.language.semantic.default-expert and camel.language.semantic.default-state to the language. Embedded applications can resolve SemanticLanguage from the context and call setDefaultExpert, setAdapter and setDefaultState before route setup. The catalog describes the generic language expression model; it does not provide a dedicated semantic Spring Boot configuration class. Configure the language bean explicitly in Spring Boot until generic-language starter configuration is available.

Expert-owned evaluations

An expert is a registry bean implementing SemanticAdapter, annotated with @SemanticExpert. The class annotation defines its operations. Each operation declares its accepted input shapes, parameters, result type and meaning, and any optional probabilities or confidence. A configured instance cannot redefine this contract. Endpoint addresses, credentials and model deployment settings remain bean configuration, separate from evaluation parameters.

Register the beans with Camel Main or Camel JBang, for example:

camel.beans.security=#class:com.example.SecurityExpert
camel.beans.decisions=#class:org.apache.camel.component.typesafeai.TypeSafeAiSemanticAdapter

SecurityExpert below is an illustrative application-provided expert exposing injection and classify; these names and its taxonomy are defined by that expert. The instruction-driven decisions bean uses the TypeSafe AI component configuration.

- semantic:
    expert: security
    state: "${body}"
    evaluation:
      injection:
        operation: injection
      categories:
        operation: classify
      department:
        expert: decisions
        type: choice
        parameters:
          instructions: Which department should handle this message?
          criteria:
            billing: Payments, invoices and refunds
            technical: Errors, outages and technical questions
            sales: Product and purchasing questions

The block supplies default expert and state values; individual evaluations can override them. Evaluation names belong to the application, while operation names belong to the expert. type: choice is shorthand for the expert operation named choice; it does not let the route change an operation’s result type. Specify exactly one of operation and type. Declaring an evaluation does not execute it.

parameters can contain strings, booleans, numbers, nested maps and lists. Camel copies them into immutable declarations. Java, YAML and XML builders resolve property placeholders in string parameter values, including nested list and map values; map keys stay literal. Resolved strings remain strings. For numeric parameters, including values inside nested maps or lists, use an explicit numeric value after placeholder resolution. YAML accepts !number "{{limit:0.8}}", XML accepts <number>{{limit:0.8}}</number>, and Java accepts Double.valueOf(getContext().resolvePropertyPlaceholders("{{limit:0.8}}")) as a parameter value. These forms work for any parameter name and expert. The threshold and uncertainty shorthand options also accept numeric placeholders. Direct SemanticEvaluation construction takes already resolved parameter values and requires no Camel context. Omitted parameters stay absent: their behaviour belongs to the expert, and Camel does not insert threshold or confidence defaults. Parameter contracts use String, Boolean, Number, Map and List data shapes. Use Number with integer = true for whole-number parameters; do not use JVM numeric wrapper classes such as Integer in a contract. A provider consumes numeric values through Number across all DSLs.

For example, a nested expert policy can externalise its cutoff without converting string labels:

- semantic:
    expert: moderation
    evaluation:
      contentPolicy:
        operation: categories
        parameters:
          policy:
            cutoff: !number "{{content.cutoff:0.8}}"
            label: "{{content.label:privacy}}"

String parameters, including prompts, use Camel property placeholder escaping for literal braces. For example, YAML instructions: 'Explain \{{example\}}' gives the expert Explain {{example}}. In Java, use .parameter("instructions", "Explain \\{{example\\}}") for the same literal text.

Camel validates recognised parameter names, required values, types and declared constraints. The expert checks operation-specific combinations and compatibility with its bean configuration. Startup validates all declarations, including unused evaluations, without inference. Route resource replacements also validate before publication, retaining previous declarations on failure. The low-level SemanticEvaluations.replace(String, Map) API also validates new and changed unused declarations before publishing them on a started context.

Calling an evaluation

Use the Simple function ${semantic('evaluationName')} or a semantic-language expression ref:evaluationName. Both return the operation’s typed value, preserve the body, and publish the detailed SemanticResult in the CamelSemanticResult exchange property. Every occurrence invokes the expert; there is no implicit result caching. Retain a value in a Camel variable when it must be reused without another call.

- route:
    from:
      uri: direct:incoming
      steps:
        - choice:
            when:
              - simple: "${semantic('injection')}"
                steps:
                  - to: direct:security-review
            otherwise:
              steps:
                - switch:
                    selector:
                      simple: "${semantic('department')}"
                    case:
                      - value: billing
                        uri: direct:billing
                      - value: technical
                        uri: direct:technical
                      - value: sales
                        uri: direct:sales
                    otherwise:
                      uri: direct:manual-review

Switch consumes scalar results. Classification returns a Set<String>, including an empty set when no labels apply. Use collections in predicates, for example ${semantic('categories')} contains 'privacy'; labels are not numeric scores. The category and the meaning of a Boolean verdict must come from the selected expert’s contract. A negative detection does not establish general safety or grant authorization.

Structured input and specialised experts

A specialised moderation expert can accept a map containing a user prompt, an assistant response, and supporting documents. Camel evaluates the state selector and passes that same object unchanged. It does not flatten a conversation into text, create prompts, or impose a universal conversation schema. The expert validates the content and must not modify the original state.

For an application-provided moderation expert with operations response_safety and categories:

- semantic:
    expert: moderation
    state: "${body}"
    evaluation:
      responseSafety:
        operation: response_safety
      safetyCategories:
        operation: categories
        parameters:
          policy:
            labels: [privacy, unsafe]
            threshold: 0.8

Here policy is an illustrative nested expert parameter. The expert owns both its interpretation and any threshold filtering. The body might be a map with prompt, response and documents keys. A missing required response fails before inference; malformed provider output and operational failures follow normal Camel error handling, rather than becoming benign decisions.

Equivalent Java and XML declarations

Java uses the same declaration model and validation path:

import static org.apache.camel.semantic.SemanticEvaluationsBuilder.semanticEvaluations;

semanticEvaluations(this).expert("moderation").state("${body}")
    .evaluation("responseSafety").operation("response_safety").end()
    .evaluation("safetyCategories").operation("categories")
        .parameter("policy", Map.of("labels", List.of("privacy", "unsafe"), "threshold", 0.8))
    .register();

XML uses explicit typed values. A parameter and a map entry each contain exactly one value: string, number, boolean, null, list or map. Null is supported inside nested maps and lists when permitted by the expert. Top-level parameters must have their declared type; omit a parameter to request the expert’s omission behaviour.

<semantic xmlns="http://camel.apache.org/schema/semantic" expert="moderation" state="${body}">
  <evaluation name="responseSafety" operation="response_safety"/>
  <evaluation name="safetyCategories" operation="categories">
    <parameters>
      <parameter name="policy">
        <map>
          <entry key="labels"><list><string>privacy</string><string>unsafe</string></list></entry>
          <entry key="threshold"><number>0.8</number></entry>
        </map>
      </parameter>
    </parameters>
  </evaluation>
</semantic>

Instruction-driven shorthand

The shorthand fields and Java convenience methods below target instruction-driven operations named boolean, choice and score. Their fields become expert parameters and are validated by the selected expert. TypeSafe AI implements these operations and requires nonblank instructions. All operations use the same evaluation declaration syntax; specialised operations supply their expert-owned parameters as shown above.

Named evaluations

The YAML DSL supports declarations alongside routes, including declarations after their use:

- semantic:
    evaluation:
      department:
        type: choice
        instructions: Which department should handle this message?
        criteria:
          billing: Invoices, payments and refunds
          technical: Bugs, outages and technical problems
      actionable:
        type: boolean
        instructions: Does this message contain an actionable request?
        threshold: 0.8
        uncertainty: 0.1
        uncertaintyPolicy: fail
      urgency:
        type: score
        instructions: Assess urgency
        criteria:
          - Routine request
          - Time-sensitive request
          - Immediate attention needed
- route:
    from:
      uri: direct:tickets
      steps:
        - setProperty:
            name: department
            expression:
              language:
                language: semantic
                expression: ref:department
        - choice:
            when:
              - expression:
                  simple:
                    expression: "${exchangeProperty.department} == 'billing'"
                steps:
                  - to:
                      uri: direct:billing
              - expression:
                  simple:
                    expression: "${exchangeProperty.department} == 'technical'"
                steps:
                  - to:
                      uri: direct:technical
            otherwise:
              steps:
                - to:
                    uri: direct:review

Names are context-wide. Duplicate declarations across resources and unknown references fail. Reloading a resource replaces its complete set of evaluations, including removing declarations no longer present. Development-mode route reload also removes definitions from deleted or renamed files before parsing replacements. This replacement does not make the surrounding route reload transactional. Existing expressions resolve the current definition on their next evaluation. Reload validates the candidate declaration snapshot for initialized expressions before publishing it, including expert capabilities, boolean predicate types, Simple state selectors, and compatible batch selectors across resources. Failed validation leaves the previous declarations available. Startup validates unused declarations as well. Resource reload validates new and changed declarations before publishing them, including unused evaluations. Removed declarations remain removable; an expression that still references a removed name fails when evaluated. Validation registrations are weakly held: the registry does not keep expressions or their providers alive. Constraints can remain until an unused expression is garbage-collected; stopping a route does not necessarily release an expression that is still referenced elsewhere. Loading declarations does not perform inference or start provider resources for validation.

For evaluations using expert or default-expert, the registry bean is resolved when the expression is compiled. Rebinding that name does not update already compiled expressions. After replacing the bean, recreate the consuming routes or expressions, or reload their evaluation declarations so the expressions compile against the replacement bean.

YAML reload and expert bean preparation

YAML declarations are registered after all resources in the load operation have prepared their beans and before routes are constructed. This applies both at startup and on reload, so an expert bean and its evaluations can be introduced together, including in separate resources of the same load operation. XML declaration loading follows the same preparation order; Java helpers validate when register() is called.

Java declarations

Use the fluent helper from camel-semantic inside an ordinary RouteBuilder:

import static org.apache.camel.semantic.SemanticEvaluationsBuilder.semanticEvaluations;

@Override
public void configure() {
    semanticEvaluations(this)
        .evaluation("department")
            .type("choice")
            .state("${header.myState}")
            .instructions("Which department should handle this message?")
            .criterion("billing", "Invoices, payments, and refunds")
            .criterion("technical", "Bugs, outages, and technical problems")
            .criterion("other", "Everything else")
        .register();

    from("direct:tickets")
        .setProperty("department").language("semantic", "ref:department")
        .to("direct:dispatch");
}

Call .end().evaluation("anotherName") to add another evaluation to the group, then .register() once to validate and install the entire group atomically. Register before creating expressions that refer to these names. When adding builders to a running context, add the builder declaring shared evaluations before builders whose routes reference them.

Use .type("boolean") with optional .threshold(0.8), .uncertainty(0.1) and .uncertaintyPolicy("non-match") for boolean evaluations. Use .type("score") and successive .level("description") calls for ordered score levels. Applications can also register immutable SemanticEvaluation definitions using SemanticEvaluations.get(context).replace(source, evaluations). On a running context, replacement definitions are validated before publication. If validation fails, the previous definitions remain available. The public get(name) and get(names) methods always read published definitions, including calls made from an expert’s validation callback.

Each Java resource owns one group. Register all of its evaluations together; registering again replaces that resource’s previous group. Reloading a Java resource without the helper removes its declarations. Embedded builders without a resource receive distinct generated source keys. The helper reserves "java:" + resource.getLocation() as the resource source key; passing that key and an empty map to replace removes the resource’s evaluations.

XML declarations

The XML extension belongs to camel-semantic. Include camel-semantic and XML DSL (camel-xml-io-dsl). Camel discovers the extension automatically at startup: use ordinary *.xml files without registering a loader or changing the Camel core model. XML documents without semantic declarations are handled by the standard XML loader, including its bean and route configuration support. XML loaders registered by the application before route resource discovery retain precedence over automatic discovery. Loader discovery is refreshed when the context starts or routes are reloaded. Filenames may contain dots, such as my.tickets.xml. Routes retain their original resource locations and line numbers for debugging and error messages.

Both layouts are supported. To keep declarations alongside routes, use tickets.xml:

<routes>
  <semantic>
    <evaluation name="department" type="choice" state="${header.myState}">
      <instructions>Which department should handle this message?</instructions>
      <criterion key="billing" value="Invoices, payments, and refunds"/>
      <criterion key="technical" value="Bugs, outages, and technical problems"/>
      <criterion key="other" value="Everything else"/>
    </evaluation>
  </semantic>
  <route id="classify-ticket">
    <from uri="direct:tickets"/>
    <setProperty name="department">
      <language language="semantic">ref:department</language>
    </setProperty>
    <to uri="direct:dispatch"/>
  </route>
</routes>

To share declarations across route files, use a standalone evaluations.xml:

<semantic>
  <evaluation name="department" type="choice" state="${header.myState}">
    <instructions>Which department should handle this message?</instructions>
    <criterion key="billing" value="Invoices, payments, and refunds"/>
    <criterion key="technical" value="Bugs, outages, and technical problems"/>
    <criterion key="other" value="Everything else"/>
  </evaluation>
</semantic>

Load this together with ordinary *.xml, Java or YAML route resources that use ref:department. For example, with Camel Main, set camel.main.routes-include-pattern=classpath:evaluations.xml,classpath:routes.xml. Declarations are registered before consuming routes are configured. Reload replaces the source’s evaluations; removing the semantic block, using an empty block, or deleting the resource removes obsolete definitions. XML source keys are the resource locations.

The combined format supports a routes root with one optional semantic block and ordinary route elements. The standalone format uses a semantic root. Namespace-free documents are supported, as are documents consistently using http://camel.apache.org/schema/semantic, http://camel.apache.org/schema/xml-io, or http://camel.apache.org/schema/spring. A semantic block can also declare xmlns="http://camel.apache.org/schema/semantic" inside a standard routes document; its evaluation elements inherit that namespace. The extension detects the declaration block or semantic root namespace automatically.

These extensions do not validate against Camel’s standard core XSDs. Automatic discovery applies to Camel’s route resource loader; it does not extend Spring’s XML application-context parser or direct JAXB unmarshalling.

For boolean evaluations, threshold, uncertainty and uncertaintyPolicy are optional evaluation attributes. For score evaluations, replace the named criterion elements with ordered level elements, such as <level>Routine</level><level>Urgent</level><level>Critical</level>.

The numeric threshold and uncertainty options accept property placeholders in all three DSLs. For example, Java accepts .threshold("{{semantic.threshold:0.5}}"), and XML accepts threshold="{{semantic.threshold:0.5}}". Values are resolved and validated when declarations are registered.

Java and XML declarations use the same context-wide registry, validation, defaults and adapters as YAML. They do not require camel-yaml-dsl. Their evaluations can also be selected together using refs:name1,name2, as described below.

Exporting routes

Evaluation declarations live outside Camel’s core route model. Generic model exports and runtime route dumps such as camel.main.dumpRoutes=yaml contain only routes; keep declarations separately and load them before reloading an exported route.

The MCP route conversion tool rejects Java or YAML inputs containing semantic declarations because a generic route export would lose those declarations. Convert the routes separately. Extended XML documents must also be separated into declarations and ordinary routes before using the generic converter.

Selected state

A evaluation’s optional state Simple expression overrides camel.language.semantic.default-state, whose default is ${body}. Selectors are compiled before evaluation; selected strings, maps and lists are passed as data and are never evaluated recursively. A missing selected header fails instead of falling back to the body. A selector must be nonblank before property placeholders are resolved. A placeholder that resolves to an empty string retains the existing Simple behavior: it selects an empty string as state. Invalid Simple syntax fails. The original message is preserved. CamelSemanticResult contains the latest successful normalized result and is cleared before each evaluation, including one that fails.

State must be a string, map or list. For byte arrays or stream bodies, explicitly select $\{bodyAs(String)}. Enable stream caching before evaluating a stream when later processors also need to read it. Unsupported state types fail instead of being implicitly converted.

YAML declarations are provided by camel-semantic through the YAML deserializer resolver SPI. Include both camel-semantic and camel-yaml-dsl when using them. The YAML DSL does not pull in semantic evaluation, and Java applications using camel-semantic do not pull in the YAML DSL.

Results and policy

Only boolean evaluations can be predicates. A category string is never implicitly a boolean. A direct semantic predicate rejects other result types at initialization. Simple’s bare or negated ${semantic('name')} predicate rejects them before inference when it is matched; comparisons and membership tests can consume their typed values. Decision policy is expert-owned. Camel never derives a Boolean from probability or applies a second threshold to the expert’s returned value. The TypeSafe AI adapter uses an inclusive Boolean probability threshold (default 0.5). A nonzero uncertainty defines an inclusive band around that threshold. fail (the default) raises an error within the band; non-match returns false. Its Choice results must name a supplied criterion, and its scores range from zero to the last rubric index and may be fractional. Other experts declare their own policy parameters, labels and score ranges in their contracts.

Probabilities, provider confidence and selected values are separate. Missing optional fields remain absent. Results can retain provider/model identity and usage metadata. These fields do not imply equivalent quality or calibration when switching providers. Timeouts, malformed answers and unsupported capabilities are errors, distinct from valid negative decisions or unmatched Choice results.

Batching named evaluations

Use refs:name1,name2 to evaluate independent evaluations against the same selected state. The expression returns a map of normalized decisions keyed by evaluation name. For example, using the declarations above:

from("direct:tickets")
    .setProperty("decision").language("semantic", "refs:actionable,department,urgency")
    .choice()
        .when(simple("${exchangeProperty.decision[actionable]} == true && ${exchangeProperty.decision[department]} == 'billing'"))
            .to("direct:billing")
        .otherwise()
            .to("direct:review");

The same expression works through the generic language integration in YAML and XML. For example, replace the route above with:

- route:
    from:
      uri: direct:tickets
      steps:
        - setProperty:
            name: decision
            expression:
              language:
                language: semantic
                expression: refs:actionable,department,urgency
        - setHeader:
            name: urgency
            expression:
              simple:
                expression: "${exchangeProperty.decision[urgency]}"
        - choice:
            when:
              - expression:
                  simple:
                    expression: "${exchangeProperty.decision[department]} == 'billing'"
                steps:
                  - to:
                      uri: direct:billing
            otherwise:
              steps:
                - to:
                    uri: direct:review

A result might contain actionable → true, department → "billing" and urgency → 1.2. Reading the stored map does not invoke the provider again. Each evaluation retains its own expert-owned threshold and uncertainty policy. There is no implicit AND/OR: batch expressions, including a batch containing one boolean evaluation, cannot be used as predicates.

CamelSemanticResults holds an immutable map of the detailed SemanticResult objects, keyed by the same names. These retain available probabilities, confidence and provider metadata. CamelSemanticResult remains the single-evaluation diagnostic property for ref:name. Both diagnostic properties are cleared before every semantic evaluation; a batch publishes its decisions and diagnostics only after every answer passes validation. Missing, extra or invalid answers, operational failures and an uncertainty policy of fail fail the entire evaluation through Camel error handling. A valid negative decision or uncertainty handled by non-match remains a normal false result. A route property assigned by Set Property is owned by the route; clear it explicitly before reevaluation if an error handler could otherwise reuse a decision from an earlier attempt.

Batch references are comma-separated names with surrounding whitespace removed. Empty, duplicate and unknown references are rejected. There is no quoting or escaping in this syntax: names containing commas or leading/trailing whitespace require a single ref:name evaluation. Existing single references are unchanged.

All selected evaluations must use the same effective Simple state selector, after applying camel.language.semantic.default-state and resolving property placeholders. Selectors must match exactly; different expressions that happen to return equal values are not interchangeable. The selector is evaluated once per batch. Missing or unsupported state fails before inference. One snapshot of the evaluation definitions is used throughout each batch, including validation and decision policies; the next evaluation sees replacements from resource reload. Evaluations that need a previous answer require separate evaluations.

SemanticAdapter.evaluateBatch(Map<String, SemanticEvaluation>, Object) has a default implementation that invokes the existing single-evaluation method sequentially, preserving experts that do not implement a provider-specific batch optimisation. Evaluations are grouped by the resolved expert instance, and each group is passed to that instance’s batch method. Two instances of the same class remain separate groups. Each group must return exactly its assigned names. Camel validates the complete combined result before publishing decisions or diagnostics, preserving the original reference order.

TypeSafe AI sends its group in one HTTP request using the component’s existing timeout and concurrency limits. A batch spanning experts can make multiple inference requests. No results are cached or combined across exchanges.

EIP integration

Use language("semantic", "ref:name") wherever an expression or boolean predicate is accepted.

EIP / integration point

Usage

Choice

Store a category with Set Property, then compare that property in ordinary when predicates.

Filter

Use a boolean evaluation as the filter predicate.

Validate

Use a boolean evaluation; false follows normal validation failure handling.

Set Header / Set Property

Store a category or score for explicit reuse in later steps.

Aggregate correlation

Use a category as a correlation expression, retaining tenant or case identifiers where needed.

Aggregate completion

Evaluate a boolean evaluation against the accumulated body, with a size or timeout limit. eagerCheckCompletion instead sees the incoming exchange.

Recipient List

Map a category to a configured list of recipient URIs.

Routing Slip

Map a category to a predefined processing sequence.

Enrich

Map a category to a configured resource URI and use an ordinary aggregation strategy.

Loop

Reevaluate a boolean evaluation on updated state, with an explicit iteration or time budget.

Sort

Score each item once, store the scores, then sort using a deterministic comparator.

On Exception / retryWhile

Evaluate whether another attempt is worthwhile, with an explicit retry budget checked before inference.

Contextual action validation

Use Validate after ordinary permission checks and before executing the action.

Destination mappings belong to trusted route configuration; provider output should select known labels rather than supply unrestricted endpoint URIs. Expressions reevaluate on each invocation. There is no implicit exchange-wide inference cache. Store results explicitly when reuse is intended, and reevaluate after relevant input changes.

Predicates and explicit reuse

from("direct:actionable")
    .filter().language("semantic", "ref:actionable")
    .to("direct:accepted");

from("direct:validate")
    .validate().language("semantic", "ref:actionable")
    .to("direct:valid");

from("direct:tag")
    .setHeader("department").language("semantic", "ref:department")
    .setProperty("urgency").language("semantic", "ref:urgency")
    .choice()
        .when(header("department").isEqualTo("billing")).to("direct:billing")
        .otherwise().to("direct:technical");

Storing the category before Choice performs one semantic evaluation each time execution reaches that Set Header or Set Property step. The branches compare the stored result without calling the provider again. Place that step inside a loop when the decision must be refreshed on each iteration. Nested decisions can use separate properties to retain their own results. Boolean evaluations can also be used directly as ordinary when predicates. These patterns use the existing EIP model and work with the Java, XML and YAML DSLs.

Aggregation and destinations

For Aggregate and other Java APIs accepting an Expression, use new LanguageExpression("semantic", "ref:department") from org.apache.camel.model.language. For example, group messages with that expression and a GroupedBodyAggregationStrategy, using completionSize(10) and completionTimeout(5000) to bound the group. Include a trusted tenant or case identifier in the correlation key when messages must remain isolated.

A completion predicate can be obtained with context.resolveLanguage("semantic").createPredicate("ref:actionable"). The aggregation strategy must first put the accumulated conversation in the selected state. Use completionPredicate(predicate).completionSize(10) to retain a deterministic size limit.

Classify once, then map the stored label to destinations supplied by the route author:

from("direct:dispatch")
    .setProperty("department").language("semantic", "ref:department")
    .process(exchange -> {
        String department = exchange.getProperty("department", String.class);
        exchange.getMessage().setHeader("recipients",
            Map.of("billing", "direct:billing,direct:audit",
                   "technical", "direct:technical").get(department));
        exchange.getMessage().setHeader("slip",
            Map.of("billing", "direct:invoice,direct:archive",
                   "technical", "direct:diagnose,direct:archive").get(department));
        exchange.getMessage().setHeader("resource",
            Map.of("billing", "direct:billingKnowledge",
                   "technical", "direct:technicalKnowledge").get(department));
    })
    .recipientList(header("recipients")).end()
    .routingSlip(header("slip"))
    .enrich().header("resource").aggregationStrategy((original, resource) -> {
        original.getMessage().setHeader("knowledge", resource.getMessage().getBody());
        return original;
    });

Changed state and sorting

A loop must have a finite budget in addition to its semantic predicate. Combine the predicate with a check of CamelLoopIndex, and let the loop body update the selected state. Each new iteration evaluates the current content. For example, a predicate can first check that exchange.getProperty(Exchange.LOOP_INDEX, 0, Integer.class) < 5 and then call the boolean semantic predicate, before loopDoWhile invokes a refinement processor.

For sorting, evaluate ref:urgency once for each item and build a list containing each item and its score. Use .sort(body(), Comparator.comparingDouble(ScoredItem::score)) with that stored score. The comparator must not call the provider: sorting can compare an item multiple times and in an implementation-dependent order.

Bounded semantic retry

A boolean evaluation can supply the retryWhile predicate for an exception clause. Ask whether another attempt is worthwhile using the current failure and selected request context. Restrict this to operations that are safe to retry.

retryWhile replaces the maximumRedeliveries decision. Check the retry budget inside the predicate, before calling the provider. A provider that always returns true must not cause unlimited retries or evaluations.

Declare a boolean evaluation named retryable with state: $\{exchangeProperty.retryState}. In this example, the request body is a string. onExceptionOccurred prepares the state before the retry predicate runs; onRedelivery runs later and is too late for this purpose. Include only the failure details needed by the evaluation, excluding credentials and sensitive data.

Predicate retryable = context.resolveLanguage("semantic").createPredicate("ref:retryable");

onException(IOException.class)
    .onExceptionOccurred(exchange -> {
        Exception failure = exchange.getProperty(Exchange.EXCEPTION_CAUGHT, Exception.class);
        exchange.setProperty("retryState", Map.of(
            "request", exchange.getMessage().getBody(String.class),
            "failureType", failure.getClass().getSimpleName(),
            "attempt", exchange.getMessage().getHeader(Exchange.REDELIVERY_COUNTER, Integer.class)));
    })
    .retryWhile(exchange ->
        exchange.getMessage().getHeader(Exchange.REDELIVERY_COUNTER, 0, Integer.class) <= 3
            && retryable.matches(exchange))
    .redeliveryDelay(1000)
    .handled(true)
    .to("direct:escalate");

The counter starts at one when deciding the first redelivery. This permits at most three redeliveries after the initial attempt. Each failure refreshes the selected state; redelivery restarts at the failed processor, not at the beginning of the route. A negative decision or an exhausted budget sends the message to direct:escalate through normal exception handling.

If the retry predicate throws (such as a provider timeout, or a malformed or uncertain result), Camel logs the evaluation failure at WARN level and regards the predicate as false. The message is not redelivered and is sent to direct:escalate through normal exception handling, the same as for a negative decision. The original exception is kept as the caught exception, with the evaluation failure attached as a suppressed exception. To tell an evaluation failure apart from a negative decision in the escalation route, check getSuppressed() of the Exchange.EXCEPTION_CAUGHT exception. Bound provider timeouts as well as the retry count.

Contextual action validation

Use a semantic evaluation for an additional check such as "Does this proposed action serve the approved task?" after normal identity, permission and tenant checks have succeeded. The semantic decision must not grant permissions that those checks denied.

For example, declare this evaluation alongside routes:

- semantic:
    evaluation:
      withinScope:
        type: boolean
        instructions: Does the proposed action serve the approved task?
        state: ${body}
        threshold: 0.8
        uncertainty: 0.05
        uncertaintyPolicy: fail

The selected state should contain the approved task and the proposed action. Obtain the approved task from trusted application state; do not let the proposed action redefine it. Treat the proposed action as untrusted input: its text can attempt to steer the provider (prompt injection). A positive semantic decision must never override the application’s authorization checks. In this example, direct:checkPermissions performs the application’s existing authorization and rejects unauthorized requests before semantic evaluation:

from("direct:action")
    .to("direct:checkPermissions")
    .validate().language("semantic", "ref:withinScope")
    .to("direct:performAction");

A negative decision raises normal validation failure. With this evaluation’s fail policy, an uncertain decision raises an evaluation error. Timeouts and malformed responses also fail, and none of these outcomes should execute the protected action. Failure handling may reject the request or send it for human review; do not use continued(true) to resume at the action. The example’s threshold is a decision policy, not an accuracy guarantee.

This pattern uses Validate and existing authorization services. It does not add a semantic AuthorizationPolicy implementation or replace a component’s internal guardrail interfaces.

Static operation contracts

Read SemanticCapabilities.from(MyExpert.class) to inspect a contract without constructing an expert, loading a model or contacting a service. The runtime uses the same cached class contract. The returned operation and parameter maps, supported labels and input shapes are immutable.

For example, an expert can declare a fixed detection operation:

@SemanticExpert(name = "detector", description = "Text injection detector",
    provider = "example", artifactId = "example-expert",
    operations = @SemanticOperation(name = "injection", description = "Detect injection",
        inputTypes = SemanticExpert.InputType.TEXT,
        inputRequirements = "Text to assess",
        resultType = SemanticExpert.ResultType.BOOLEAN,
        resultMeaning = "True means injection was detected",
        probability = true, probabilityMeaning = "Probability of injection",
        parameters = @SemanticParameter(name = "threshold", description = "Detection threshold",
            type = Number.class, minimum = 0, maximum = 1,
            omission = "Use the service's configured threshold")))
public class SecurityExpert implements SemanticAdapter {
    // validate, validateInput and evaluate implement this declared contract.
}

Each operation declares a result type and its meaning. Score operations document their scale and can declare numeric bounds. Choice and classification operations can declare a fixed label set; an instruction-driven expert validates its application-supplied vocabulary itself. Optional probability, per-label probability and confidence fields must declare their meaning. Camel does not manufacture confidence from a verdict, nor require optional information on every response. It rejects information the contract did not declare, malformed numeric values, wrong result types and results outside declared common constraints.

A score operation can link its result scale to a parameter with @SemanticOperation(scoreLevelsParameter = "rubric", …​). The named parameter must be declared as List.class with itemType = String.class, and the operation must return SCORE. For a supplied nonempty list of N descriptions, the effective range is 0 through N-1. Index i describes score i; scores may be fractional between levels. When per-level probabilities are returned, their keys are the decimal index strings ("0", "1", and so on). For example, ["Routine", "Needs attention", "Critical"] defines 0…2, and 1.6 lies between "Needs attention" and "Critical". Static minimum and maximum still describe supported bounds.

The TypeSafe AI adapter publishes scoreLevelsParameter = "criteria" for its score operation. Other experts may name the parameter differently. SemanticCapabilities.Operation.getScoreLevelsParameter() and the semantic metadata console expose the same relationship without inference. An empty value (the default) declares no relationship, so tooling must not infer one from parameter names. If the parameter is omitted and the expert supplies its own default levels, tooling cannot derive the effective range from the declaration. Experts continue to validate configured score bounds and probability keys themselves.

An expert applies explicitly requested policy exactly once, either in its service or its adapter. It preserves service defaults when parameters are absent. If requested policy needs unavailable confidence or probability, the expert rejects the declaration when known statically, or fails that evaluation when the absence is response-dependent. It must not silently ignore the request. Uncertain outcomes use an operation’s documented values or an error; Camel has no universal uncertainty sentinel. Classification filtering, including per-label thresholds, also belongs to the expert. TypeSafe AI’s uncertaintyPolicy supports fail or non-match; other experts need not support that vocabulary.

Generated expert descriptors, expert-specific offline validation and Catalog API extensions are separate work. This contract does not add a new Camel Catalog model.

Implementing an adapter

Implement org.apache.camel.semantic.SemanticAdapter. Advertise the implementation in META-INF/services/org/apache/camel/semantic-adapter using Camel’s FactoryFinder format:

class=com.example.MySemanticAdapter

Camel components can generate this descriptor with @JdkService("semantic-adapter"). Discovery checks the declarations for ambiguity before resolving the implementation through FactoryFinder. Repeated declarations of the same class are accepted; different classes require explicit selection. Only the selected adapter is constructed, through Camel’s Injector. Explicit bean or class selection also works when packaging does not expose discovery resources.

The language checks the static operation contract and then calls validate for operation-specific parameter combinations and bean configuration before traffic. validateInput checks detailed message requirements before evaluation. All input checks in a batch run before any expert is invoked. evaluate returns a typed SemanticResult after applying the expert’s policy. A probability-only Boolean result is invalid: the expert must supply its Boolean verdict. Providers should also validate when their SPI is called directly. evaluate must be thread-safe, bound time and resource consumption, honor interruption and propagate operational failures without disclosing input or credentials. Implement CamelContextAware for context injection and Camel Service interfaces when the adapter owns resources. Created service adapters are stopped and shut down by Camel; registry references are not started or stopped again by the language. Provider-specific component services remain under normal Camel lifecycle management.

For TypeSafe AI, boolean, choice and score map to Noul, Choice and Score respectively. The adapter reuses the component endpoint’s managed HTTP client, including timeout, concurrency and cancellation. Routine integration tests use deterministic adapters and local HTTP fixtures; they do not establish a model’s classification quality.

The Semantic Evaluation language supports the following options which are listed below.

Name Default Java Type Description

language (common)

String

Required The name of the language to use.

trim (advanced)

true

Boolean

Whether to trim the source code to remove leading and trailing whitespaces and line breaks.

resolveResource (advanced)

false

Boolean

Whether a result of the expression that is a String starting with resource: is loaded as a resource and its content becomes the result, e.g. a script that returns resource:file:order.json or resource:classpath:templates/order.json (a name without a scheme is a classpath resource). Off by default; the resource: prefix on the expression text itself is always resolved. Applies to the expression used as a value, not as a predicate.

Developer consoles

Use camel semantic to inspect definitions and camel semantic eval to evaluate them from the Camel CLI.

semantic-metadata exposes definitions and expert contracts without performing inference. With overview=true, a failed default selection includes defaultError and a stable defaultErrorCode: no_unique_expert means automatic discovery found zero or multiple eligible experts; default_expert_error identifies a configuration, contract or descriptor failure. Consumers can distinguish these cases without parsing the diagnostic message.

semantic-evaluate evaluates a named definition against a sample exchange or calls an expert operation directly. It uses the configured provider and its normal validation, without sending the sample through an application route.

Enabling the HTTP developer console also exposes semantic-evaluate. Unlike metadata inspection, calling it invokes the configured provider and can incur charges. Restrict access to trusted operators, as described in the security model.

Evaluation calls have a default and maximum timeout of 50,000 milliseconds. The console’s timeout option can select a shorter positive timeout. At most two evaluations run concurrently; further calls receive a busy response. A timeout or interrupted caller requests cancellation by interrupting the worker. Providers may continue remote or native inference after interruption, so configure their own timeouts too. The CLI connector runs evaluations independently of its action and snapshot threads; responses remain associated with the originating request.

Auditing semantic evaluations

The semantic DSL can enable an audit trail independently of telemetry and independently of whether a TUI is connected. Declare audit configuration in one semantic resource. The default is disabled. A configured expert’s explicit enabled value overrides the master default in either direction; omitted values inherit it. Names refer to registry bean instances, so two instances of the same expert can have different audit settings.

For existing expert beans named security and decisions:

- semantic:
    audit:
      enabled: true
      experts:
        security:
          enabled: true
        decisions:
          enabled: false
      sinks: [memory, log]
      reader: memory
      capacity: 1000
      queueCapacity: 1000
    evaluation:
      screenPrompt:
        expert: security
        operation: injection
        state: "${body}"
      chooseDepartment:
        expert: decisions
        operation: choice
        state: "${body}"
        parameters:
          instructions: Choose the department for this request
          criteria:
            sales: Sales inquiries
            support: Technical support

This records security evaluations and omits decisions evaluations. Both experts still execute. Changing the master to false continues recording security because its explicit true overrides the default. Removing that override restores inheritance. Application properties may supply DSL placeholders, for example enabled: "{{audit.enabled:false}}". There is no second, property-based audit configuration hierarchy. Audit settings are fixed when the audit service starts with the context, not when it is first registered. Editing or removing the audit block in an existing resource is rejected if it would change those settings. Deleting its file removes that file’s evaluation declarations and logs one warning while retaining the active audit configuration. Unrelated reloads continue, and a renamed resource may redeclare the same configuration. Stop the context, update its declarations, and start it to apply different settings. Backends are resolved again on startup. A changed memory capacity creates an empty store and invalidates old cursors; unchanged capacity retains its history across an in-place restart. Retained history is not erased by a query. Unknown backend references fail startup. Expert overrides can name beans registered later and do not instantiate experts; misspelled references never match an invocation.

OpenTelemetry remains separately configured. For example, use the existing Camel Main camel.opentelemetry2.enabled=false setting with the audit block above to run auditing without the OpenTelemetry tracer.

The Java declaration helper accepts the same immutable configuration:

semanticEvaluations(this)
    .audit(new SemanticAuditConfiguration(true, Map.of("security", true, "decisions", false),
        List.of("memory", "log"), "memory", 1000, 1000))
    .evaluation("screenPrompt").expert("security").operation("injection").end()
    .register();

XML declarations use an audit child of semantic:

<semantic xmlns="http://camel.apache.org/schema/semantic">
  <audit enabled="true" reader="memory" capacity="1000" queueCapacity="1000">
    <expert name="security" enabled="true"/>
    <expert name="decisions" enabled="false"/>
    <sink ref="memory"/>
    <sink ref="log"/>
  </audit>
  <evaluation name="screenPrompt" expert="security" operation="injection"/>
</semantic>

Recording a route decision

Evaluation records describe what the expert reported. They do not assert that a route allowed, blocked, quarantined or otherwise acted on a message. Record that action explicitly at the point where the route applies its policy, using SemanticAuditDecision as a normal Camel processor/bean, or SemanticAudit.get(context).decision(…​) in Java.

- beans:
  - name: recordBlockedRequest
    type: org.apache.camel.semantic.SemanticAuditDecision
    properties:
      fields:
        action: block
        operation: tools/call
        target: support-request
        namespace: default
        reasonCode: prompt_injection
        policyId: support-access
        rule: injection-check
- route:
    from:
      uri: direct:check-request
      steps:
        - choice:
            when:
              - expression:
                  language:
                    language: semantic
                    expression: ref:screenPrompt
                steps:
                  - process:
                      ref: recordBlockedRequest
                  - stop: {}

The processor uses the latest invocation’s CamelSemanticAuditReferences exchange property for its evidence IDs. A batch supplies the IDs of its observed members. An expert disabled for both audit and observation supplies no reference. Save and combine those IDs explicitly if a decision uses earlier invocations; set the processor’s evidenceProperty to that exchange property. A Java caller can supply an empty list for a decision with no supporting evaluation. Explicit decisions are captured whenever the global default or at least one expert override enables auditing. Thus enabled: false with security: {enabled: true} retains both security evaluations and route decisions, including decisions without evidence. Expert overrides filter evaluation records; they do not select which application’s actions are recorded. Disabling the global default and all expert overrides disables decision capture. decisionsEnabled reports this effective state. A reference may therefore point to an evaluation not retained by a particular backend; the inspector displays it as unavailable.

A decision records the application’s declared action, not successful execution of a later tool call. The immutable record is never updated by a later route branch.

Backends and delivery

SemanticAuditSink accepts immutable SemanticAuditRecord objects. Register custom sinks as Camel beans and list their bean names in sinks. SemanticAuditReader is a separate capability with bounded query and get operations; write-only sinks need not implement it. Choose the reader bean explicitly with reader. The built-in memory reader requires memory in sinks, otherwise configuration fails rather than displaying an unfed history. Readers can override getAll(List<String>) to fetch up to 100 linked evidence records with one backend query; the console calls it once per selected record. The default implementation delegates to get, and the memory implementation scans its bounded history once. Missing IDs remain visibly unavailable. The reserved backend names are memory and log:

  • memory is a bounded recent-history sink and reader. capacity defaults to 1000 (range 1 to 100000); old records are evicted and counted. Its history is not persistent.

  • log writes JSON to the org.apache.camel.semantic.audit logger using the application’s normal logging configuration. Logging does not provide a query reader.

The supported backend APIs are SemanticAuditSink, SemanticAuditReader, SemanticAuditRecord, SemanticAuditQuery and SemanticAuditPage. Record maps use a validated, bounded version-1 serialization schema; typed timestamp, duration and evidence accessors avoid backend casts. query.matches(record) provides exact case-sensitive AND filtering with an inclusive timestamp boundary. Record field order is preserved for structured logging. Classes in org.apache.camel.semantic.internal are runtime plumbing, not a supported integration API. Routes use SemanticAuditDecision or SemanticAudit and its documented REFERENCES property; observers use SemanticObserver.

The context starts and stops its configured backends. A single worker dispatches each accepted record to the sinks in configuration order. queueCapacity defaults to 1000 (range 1 to 100000). A full queue drops the new record and increments dropped; backend append failures increment that sink’s error counter and do not change inference or route decisions. An append failure does not prevent trying the remaining sinks. A slow sink delays this dispatcher, so sink implementations must bound I/O and honor interruption. Shutdown allows five seconds to drain, then cancels queued work and counts it as dropped. Delivery can be partial and process failure can lose queued records.

Returning from append means acceptance according to that backend’s documented contract; it does not universally mean durable commit. The dispatcher does not retry failed writes. Backend-specific retries must retain the event ID. This mode does not guarantee a durable record before a protected side effect. Persistent implementations can reconstruct the versioned immutable snapshot with SemanticAuditRecord.fromMap.

Records and observations

Version 1 records contain UTC occurrence timestamps, unique event and invocation IDs, plus startedAt for evaluations. An evaluation’s timestamp denotes completion; the start observer sees the start occurrence in timestamp, and startedAt stays unchanged in the completed record. Duration uses the monotonic clock. Records also contain category, configured expert reference, operation, definition and execution origin, and available Camel context/exchange/route identifiers. Evaluation records add execution status, duration, a stable reason code, typed result, probability/confidence when present, and the static result meanings captured at invocation time. provider identifies the static expert contract. Optional model and revision are copied only from those exact result metadata keys, only when they are nonblank strings of at most 256 code points without control characters. They are omitted rather than truncated if invalid; provider metadata cannot override the contract’s provider identity. No other result metadata is captured. Adapters should supply the actual producing model and revision when known; absent identity stays absent. Batch members have individual invocation IDs and a shared batch ID. A failed group and groups never executed are distinct. Input validation completes before inference: a member whose input passed but whose sibling failed validation is not_executed. When a provider returns a complete batch, each returned answer is validated and audited independently, even if a sibling answer is invalid. The route still receives a batch failure and no partial result/diagnostic map. A valid answer is evidence of evaluation, not a successful batch or route action. Console cancellation requests are separate records linked to eventual provider completion: request timeout does not prove that native or remote inference stopped. A timeout before an expert is resolved emits no request record because its audit eligibility is not yet known. Declaration failures without an identifiable expert also omit an evaluation record; failures with a known reference follow that expert’s override.

Decision records carry explicit action, operation, target, optional namespace/correlation and policy identifiers, reason code and evidence IDs. They never derive application correlation or tenant identity from arbitrary inbound headers.

The CLI and TUI tables show Camel’s breadcrumbId to connect evaluations with route decisions. Enable camel.main.use-breadcrumb=true in Camel Main, or context.setUseBreadcrumb(true) in Java, to let Camel generate missing breadcrumbs when a route exchange starts. Audit copies the existing Exchange.BREADCRUMB_ID value; it does not generate an ID or fall back to an application correlation or exchange ID. Split exchanges retain the original breadcrumb. Records without a breadcrumb, including direct expert calls, show no value. Only nonblank string IDs of at most 256 code points without control characters are captured; invalid values are omitted without conversion or truncation. Use breadcrumbId in an audit query, or --breadcrumb-id in the CLI, to retrieve records with the same breadcrumb.

Input is omitted by default. Other headers, variables, arbitrary provider metadata and exception messages are never copied automatically. Caller-authored score descriptions are also omitted; static capability meanings remain available. Result and metadata strings are limited to 256 code points, and their collections to 100 entries. An oversized result is represented by resultOmitted: snapshot_limit without changing the evaluation outcome.

Storing evaluation input

Enable input capture explicitly for each expert whose submitted state should be retained:

- semantic:
    audit:
      enabled: true
      experts:
        security:
          enabled: true
          input:
            enabled: true
            maxChars: 4096
            # redactor: auditInputRedactor
        decisions:
          enabled: false
      sinks: [memory]
      reader: memory

input.enabled defaults to false and does not enable auditing itself. It captures the selected state passed to the expert (state, including a selected header), before provider validation or execution. It does not take a copy of the entire exchange. Named, batch, direct and console evaluations use the same settings. Decisions link to their evaluation records; input is stored on the evaluation rather than duplicated on the decision.

maxChars defaults to 4096 and accepts 1 through 100000. It bounds the total Unicode code points in strings, map keys and scalar representations. Snapshots accept text, JSON-compatible maps/lists and scalar values, with at most eight nested levels and 1000 nodes (including map keys). They are immutable; later changes to the submitted state cannot change history. Oversized, cyclic or unsupported input is omitted with inputOmitted: snapshot_limit_or_unsupported_type. Input is never truncated, and streams or arbitrary objects are not converted to text. inputOmitted: unavailable means the selected state was not obtained, for example because the state expression failed.

The limit applies per record, not to total memory. With the memory backend, retained input can reach capacity × maxChars code points, with up to queueCapacity × maxChars more in the pending delivery queue. Use the largest enabled expert’s maxChars when limits differ. For example, capacity: 1000 and maxChars: 100000 allow 100 million retained code points, before pending records, in-flight captures and object overhead. There is no aggregate byte budget; size both capacities and the per-expert limit for the available heap. Code points are not bytes, and maps/lists also allocate objects.

An optional redactor names a registry bean implementing SemanticAuditInputRedactor. It receives a bounded, immutable copy on the evaluation thread and returns a sanitized value. The size, depth and node limits are checked before the redactor runs. Oversized input is omitted without calling the redactor, even if it could reduce the input to a few fields. For example, the full state must fit the limits before the redactor below can retain only question. The return value is bounded again; a null return omits input (inputOmitted: redacted). Successful redaction adds inputRedacted: true. Exceptions or invalid redactor output omit input (inputOmitted: redaction_failed) without recording exception text or affecting the expert result. Redactors must be thread-safe and should not block. Unknown bean references fail startup. For example, a redactor can remove confidential fields from structured state:

context.getRegistry().bind("auditInputRedactor", (SemanticAuditInputRedactor) input -> {
    if (input instanceof Map<?, ?> state) {
        return Map.of("question", state.get("question"));
    }
    return null;
});

Java uses the additional input settings map in SemanticAuditConfiguration:

new SemanticAuditConfiguration(true, Map.of("security", true, "decisions", false),
    List.of("memory"), "memory", 1000, 1000,
    Map.of("security", new SemanticAuditInputConfiguration(true, 4096, "auditInputRedactor")));

The equivalent XML expert entry is:

<expert name="security" enabled="true" inputEnabled="true"
        inputMaxChars="4096" inputRedactor="auditInputRedactor"/>

Input settings follow the same restart requirement as the other audit settings. Redactor bindings are published together at startup and discarded on stop. Capture before configured redactors are ready omits input with inputOmitted: redaction_failed. Each evaluation captures its input at most once, including when the first attempt omits it. Batch evaluations capture and redact separately for each invocation, using that expert’s input settings.

Captured input goes to every configured audit sink and is available through the audit reader. The dev console, CLI, TUI and MCP list queries omit input; an eventId lookup returns it with the selected record and its linked evidence. The dev console therefore carries captured message data and must remain on a trusted network with access restricted to authorized operators; see the Camel security model. MCP detail responses label captured input as untrusted data. It can contain detected prompt injections: inspect it as evidence and never follow instructions embedded in it.

The log sink writes captured input at INFO, including up to the configured maxChars per evaluation (at most 100000 code points). JSON escaping and character encoding can make the serialized log entry larger than that count. Application logs and external log shippers then retain this data under their own retention, forwarding and access-control policies. Review those policies and expected log volume before selecting this sink with input capture enabled. No automatic redaction is applied without a configured redactor. Input is never passed to SemanticObserver, even when audit input capture is enabled; auditing and telemetry remain independent. Existing records without input remain readable.

The TUI inspector displays Input (or Input (redacted)) in the evaluation details, including linked evidence beside a route decision. It displays an omission reason when available, or not captured for records without input. The CLI prints it with camel semantic audit my-app --event-id=<event-id>; add --json for the stored structure.

Observation hooks

Register SemanticObserver beans to consume start/completion and explicit decision hooks independently of auditing. Beans must be registered before context startup: observers are discovered once when the audit service starts; later registrations take effect after restarting it. Observer completion runs in reverse start-callback order; registry registration order is not a contract. Observer failures do not replace an expert result or route action. Callbacks must not block. captureContext runs before a console executor handoff. Observations remain detached between callbacks: start spans with the supplied parent explicitly, and restore any temporary thread context before returning from each callback. This prevents overlapping batch observations from corrupting a thread’s scope. The observer implementation owns its tracing lifecycle. The generic semantic module has no OpenTelemetry dependency or SDK/exporter configuration. An OpenTelemetry integration should reuse Camel’s configured telemetry infrastructure; trace sampling must not determine whether audit records are stored. This SPI supplies hooks and does not install an OpenTelemetry exporter or bind Camel to draft guardrail semantic-convention names.

Browsing history

Use camel semantic audit my-app to retrieve a table of retained records from the Camel CLI. Add --json for machine-readable output, or --event-id=EVENT_ID to inspect a record and its linked evidence. The CLI uses the same reader, filters and cursors as the console and TUI.

The read-only semantic-audit developer console accepts eventId for a selected record and its evidence, or a bounded page query with limit (1 to 200), cursor, since (UTC ISO-8601), category, action, expert, routeId, breadcrumbId, namespace and correlationId. Cursors belong to their backend and filter. Query responses report evictions, expired cursors, effective settings, delivery errors and dropped records. Backend error messages are replaced with a stable code. Querying does not evaluate a definition or require that an old definition still exists.

The Semantic tab in Camel TUI provides an Audit view with filters, a table and a linked-record inspector. tui_get_audit exposes this history through the TUI MCP server without changing the visible tab, filters, page or selection. Pass eventId alone to inspect a record and its evidence, or the console’s filters, limit and returned cursor to query pages. TUI AI sessions load this tool only for integrations exposing the audit console, in both core and full tool modes. Screen navigation remains an explicit TUI action. r refreshes the current page and g returns to the latest page. Pending refreshes are coalesced, while the latest changed filter/page is queried after the active request finishes. tui_get_table also includes audit rows, filters, selected details, linked evidence and backend status when Audit is selected. The OpenTelemetry status recognizes both Camel OpenTelemetry tracer implementations, including subclasses. It reports the context tracer state; it does not claim that a semantic observer/exporter has been installed.