You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by da...@apache.org on 2026/10/09 12:58:23 UTC

(causeway) 08/10: CAUSEWAY-4068: provide automatic agent observation bridge

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

danhaywood pushed a commit to branch CAUSEWAY-4068-v4
in repository https://gitbox.apache.org/repos/asf/causeway.git

commit 906a897df3770ee40c7db9a0187401f1f5f3200f
Author: Dan Haywood <da...@haywood-associates.co.uk>
AuthorDate: Wed Oct 7 12:10:50 2026 +0100

    CAUSEWAY-4068: provide automatic agent observation bridge
---
 .../ROOT/pages/2026/4.0.0-M3/otel-howto.adoc       |  79 ++-----------
 .../modules/ROOT/pages/observability.adoc          |  59 ++--------
 core/config/pom.xml                                |   7 ++
 core/config/src/main/java/module-info.java         |   2 +
 .../CausewayAgentEnvironmentPostProcessor.java     |  75 ++++++++++++
 .../CausewayAgentObservationAutoConfiguration.java |  63 ++++++++++
 .../CausewayObservationAutoConfiguration.java      |   8 +-
 .../src/main/resources/META-INF/spring.factories   |   2 +
 ...rk.boot.autoconfigure.AutoConfiguration.imports |   1 +
 ...sewayAgentObservationAutoConfigurationTest.java | 127 +++++++++++++++++++++
 .../provide-agent-observation-bridge/tasks.md      |  12 +-
 .../provide-agent-observation-bridge/validation.md |  12 ++
 .../planned-changes/otel-forward-port-roadmap.md   |   4 +
 .../tracing/EntryPointTracingFixture.java          |   3 +-
 .../tracing/MicrometerTracingAgentFixture.java     |  25 +---
 .../MicrometerTracingCompatibilityTest.java        |  19 ++-
 .../sample-htmx-petclinic/README.adoc              |   2 +-
 .../webcomponents/sample-htmx-petclinic/pom.xml    |  19 ---
 .../main/resources/application-agent.properties    |  25 ----
 .../petclinic/AgentObservationConfiguration.java   |  51 ---------
 20 files changed, 342 insertions(+), 253 deletions(-)

diff --git a/antora/components/relnotes/modules/ROOT/pages/2026/4.0.0-M3/otel-howto.adoc b/antora/components/relnotes/modules/ROOT/pages/2026/4.0.0-M3/otel-howto.adoc
index eb043efad60..b185e4d538c 100644
--- a/antora/components/relnotes/modules/ROOT/pages/2026/4.0.0-M3/otel-howto.adoc
+++ b/antora/components/relnotes/modules/ROOT/pages/2026/4.0.0-M3/otel-howto.adoc
@@ -194,7 +194,7 @@ Note that Prometheus metric names can differ from contextual span names, includi
 
 The principal benefit of using agent-managed tracing is that the agent will instrument libraries, such as JDBC access.
 When an agent is configured, the agent now owns the tracing SDK, exporter and automatic HTTP/JDBC instrumentation.
-A registry bridge joins Causeway observations to that context, and the `agent` profile disables competing Boot tracing auto-configuration.
+Causeway supplies a registry bridge to join its observations to that context, and the `agent` profile automatically disables competing Boot tracing auto-configuration.
 
 ### Try it out
 
@@ -229,74 +229,19 @@ Some config can be overridden using env vars:
 
 
 [#apply-the-same-change-to-another-application]
-=== Apply the same change to another application
+=== Configure agent-managed tracing in another application
 
-Keep the metrics configuration above, add the BOM-managed `io.micrometer:micrometer-tracing-bridge-otel` dependency, and register the following configuration through component scanning or an explicit import:
+Keep the metrics configuration above and add the BOM-managed `io.micrometer:micrometer-tracing-bridge-otel` dependency.
+Causeway supplies the registry bridge automatically when both `observation` and `agent` profiles are active; no application Java configuration or manual Boot exclusions are needed.
+The bridge uses the agent's global OpenTelemetry instance without constructing another SDK or exporter.
 
-[source,java]
-----
-import java.util.List;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
-
-import io.micrometer.observation.ObservationRegistry;
-import io.micrometer.tracing.handler.DefaultTracingObservationHandler;
-import io.micrometer.tracing.otel.bridge.OtelBaggageManager;
-import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext;
-import io.micrometer.tracing.otel.bridge.OtelTracer;
-import io.opentelemetry.api.GlobalOpenTelemetry;
-
-/** Bridges Causeway observations into the agent's HTTP/JDBC trace context. */
-@Configuration(proxyBeanMethods = false)
-@Profile("agent")
-public class AgentObservationConfiguration {
-    @Bean
-    io.micrometer.tracing.Tracer agentTracer() {
-        // The attached agent owns the global SDK and exporter. Do not construct another SDK here.
-        var currentContext = new OtelCurrentTraceContext();
-        return new OtelTracer(GlobalOpenTelemetry.getTracer("org.apache.causeway"), currentContext,
-                event -> {}, new OtelBaggageManager(currentContext, List.of(), List.of()));
-    }
-
-    @Bean
-    ObservationRegistry agentObservationRegistry(io.micrometer.tracing.Tracer tracer) {
-        var registry = ObservationRegistry.create();
-        registry.observationConfig().observationHandler(new DefaultTracingObservationHandler(tracer));
-        return registry;
-    }
-}
-----
-
-The registry and entry-span classifier share this tracer bean, which uses the agent's global OpenTelemetry instance without constructing another SDK.
-A custom registry without a tracer bean can still create observations, but cannot classify agent-owned entry spans.
-When observations are active, supply a single tracer or designate a primary if multiple tracers exist.
-Add an agent-specific configuration that replaces tracing ownership while retaining Micrometer metrics:
+The `agent` profile automatically excludes competing Boot tracing and WebMVC observation configuration, while preserving any unrelated application exclusions.
+The WebMVC exclusion lets the agent own the HTTP entry span without a second Boot HTTP observation around it; foreground classification tags the agent's span.
+It also removes Boot's HTTP request observation meters in this configuration; JVM and Causeway Micrometer metrics remain available.
 
-[source,yaml]
-.application-agent.yaml
-----
-spring:
-  autoconfigure:
-    exclude:
-      - org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration
-      - org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.OpenTelemetryTracingAutoConfiguration
-      - org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.otlp.OtlpTracingAutoConfiguration
-      - org.springframework.boot.micrometer.tracing.autoconfigure.MicrometerTracingAutoConfiguration
-      - org.springframework.boot.webmvc.autoconfigure.WebMvcObservationAutoConfiguration
-management:
-  opentelemetry:
-    map-environment-variables: false
-  tracing:
-    export:
-      enabled: false
-----
+If your application supplies its own registry or tracer, Causeway backs off from creating the corresponding bean.
+A custom registry must register its own tracing handlers using the same single or primary tracer that the entry-span classifier uses.
 
-The exclusions above match the Spring Boot 4.2.0-M1 baseline used by this checkout.
-The WebMVC exclusion lets the agent own the HTTP entry span without a second Boot HTTP observation around it; foreground classification then tags the agent's span.
-It also removes Boot's HTTP request observation meters in this configuration; JVM and Causeway Micrometer metrics remain available.
-Build the application with the bridge configuration included.
 Activate `observation,agent` alongside any other required application profiles and attach the agent to the application JVM:
 
 [source,bash]
@@ -313,8 +258,8 @@ java -javaagent:/absolute/path/to/opentelemetry-javaagent.jar \
   -jar application.jar --spring.profiles.active=observation,agent
 ----
 
-`OTEL_METRICS_EXPORTER=none` disables the agent's exporter.
-`management.opentelemetry.map-environment-variables=false` prevents Boot from mapping that agent flag onto Micrometer and disabling its metrics too.
+`OTEL_METRICS_EXPORTER=none` disables the agent's metrics exporter.
+Causeway sets `management.opentelemetry.map-environment-variables=false` for the `agent` profile, preventing Boot from mapping that agent flag onto Micrometer and disabling its metrics too.
 The shared `management.otlp.metrics.export.enabled=true` configuration therefore retains Micrometer's separate metrics pipeline.
 When returning to Boot-managed tracing, remove `-javaagent`, deactivate the `agent` profile, and unset the exported `OTEL_*` variables above so that Boot does not inherit the agent-only settings.
 
diff --git a/antora/components/userguide/modules/ROOT/pages/observability.adoc b/antora/components/userguide/modules/ROOT/pages/observability.adoc
index 51500b04358..d86d505accb 100644
--- a/antora/components/userguide/modules/ROOT/pages/observability.adoc
+++ b/antora/components/userguide/modules/ROOT/pages/observability.adoc
@@ -29,56 +29,13 @@ An active observation profile does not require an agent or exporter to start suc
 == Java-agent-owned tracing
 
 An alternative configuration lets the OpenTelemetry Java agent own the SDK, automatic HTTP/JDBC instrumentation and export.
-Provide a registry whose Micrometer tracing handler bridges to the agent's global OpenTelemetry context.
-Add the BOM-managed `io.micrometer:micrometer-tracing-bridge-otel` dependency for this application configuration.
+Add the BOM-managed `io.micrometer:micrometer-tracing-bridge-otel` dependency and activate `observation,agent`.
+Causeway supplies the missing tracer and registry beans, bridges observations to the agent's global context, and automatically excludes competing Boot SDK/tracing and WebMVC observation configuration.
+No application Java bridge or manual exclusions are needed.
 
-[source,java]
-----
-@Configuration(proxyBeanMethods = false)
-@Profile("agent")
-class AgentObservationConfiguration {
-    @Bean
-    io.micrometer.tracing.Tracer agentTracer() {
-        var currentContext = new OtelCurrentTraceContext();
-        return new OtelTracer(
-                GlobalOpenTelemetry.getTracer("org.apache.causeway"),
-                currentContext,
-                event -> {},
-                new OtelBaggageManager(currentContext, List.of(), List.of()));
-    }
-    @Bean
-    ObservationRegistry agentObservationRegistry(io.micrometer.tracing.Tracer tracer) {
-        var registry = ObservationRegistry.create();
-        registry.observationConfig()
-                .observationHandler(new DefaultTracingObservationHandler(tracer));
-        return registry;
-    }
-}
-----
-
-The types are from `io.micrometer.observation`, `io.micrometer.tracing.handler`, `io.micrometer.tracing.otel.bridge`, `io.opentelemetry.api`, `java.util`, and Spring's configuration annotations.
-Import this configuration into the application.
-For the validated Boot 4.2.0-M1 configuration, exclude the competing SDK/tracing auto-configurations:
-
-[source,yaml]
-----
-spring:
-  profiles:
-    active: observation,agent
-  autoconfigure:
-    exclude:
-      - org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration
-      - org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.OpenTelemetryTracingAutoConfiguration
-      - org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.otlp.OtlpTracingAutoConfiguration
-      - org.springframework.boot.micrometer.tracing.autoconfigure.MicrometerTracingAutoConfiguration
-      - org.springframework.boot.webmvc.autoconfigure.WebMvcObservationAutoConfiguration
-management:
-  opentelemetry:
-    map-environment-variables: false
-  tracing:
-    export:
-      enabled: false
-----
+A supplied registry or tracer takes precedence over the corresponding default.
+A custom registry remains responsible for its handlers; those handlers and Causeway's entry-span classifier must share a single or primary tracer.
+Causeway does not construct an SDK or exporter, and the profiles do not attach an agent: attachment is still required for agent export.
 
 Launch the JVM with `-javaagent:/path/to/opentelemetry-javaagent.jar` and configure the agent's service name, sampler and OTLP endpoint through its normal settings.
 For example, a local tracing-only run can use:
@@ -91,12 +48,12 @@ export OTEL_EXPORTER_OTLP_PROTOCOL=http/protobuf
 export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces
 export OTEL_METRICS_EXPORTER=none
 export OTEL_LOGS_EXPORTER=none
-java -javaagent:/path/to/opentelemetry-javaagent.jar -jar application.jar
+java -javaagent:/path/to/opentelemetry-javaagent.jar -jar application.jar --spring.profiles.active=observation,agent
 ----
 
 The registry and entry-span classifier share the exposed Micrometer tracer.
 The WebMVC exclusion avoids a second Boot HTTP observation inside the agent's entry span; Boot HTTP request observation meters are consequently absent, while other Micrometer metrics remain available.
-Disabling environment-variable mapping prevents the agent's `OTEL_METRICS_EXPORTER=none` from disabling a separately configured Micrometer metrics exporter.
+Causeway disables Boot environment-variable mapping for the `agent` profile, preventing the agent's `OTEL_METRICS_EXPORTER=none` from disabling a separately configured Micrometer metrics exporter.
 Do not also construct an independent SDK/export pipeline in this configuration.
 The agent configuration is an explicit application choice; it does not replace the Boot-managed default.
 With the agent profile but without the `observation` profile, automatic agent spans remain active and Causeway observations remain inactive.
diff --git a/core/config/pom.xml b/core/config/pom.xml
index 4d2a2f500ce..615bd54fc86 100644
--- a/core/config/pom.xml
+++ b/core/config/pom.xml
@@ -97,6 +97,13 @@
             <artifactId>micrometer-tracing</artifactId>
         </dependency>
 
+        <!-- Agent bridge is opt-in for consuming applications. -->
+        <dependency>
+            <groupId>io.micrometer</groupId>
+            <artifactId>micrometer-tracing-bridge-otel</artifactId>
+            <optional>true</optional>
+        </dependency>
+
         <!--
         as per https://github.com/spring-projects/spring-boot/issues/30986, must be
         before the spring-boot-configuration-processor
diff --git a/core/config/src/main/java/module-info.java b/core/config/src/main/java/module-info.java
index eba57e8c336..38892445d5f 100644
--- a/core/config/src/main/java/module-info.java
+++ b/core/config/src/main/java/module-info.java
@@ -35,6 +35,8 @@
     exports org.apache.causeway.core.config.viewer.web;
 
     requires static lombok;
+    requires static micrometer.tracing.bridge.otel;
+    requires static io.opentelemetry.api;
 
     requires transitive org.apache.causeway.applib;
     requires transitive org.apache.causeway.commons;
diff --git a/core/config/src/main/java/org/apache/causeway/core/config/observation/CausewayAgentEnvironmentPostProcessor.java b/core/config/src/main/java/org/apache/causeway/core/config/observation/CausewayAgentEnvironmentPostProcessor.java
new file mode 100644
index 00000000000..241eb26963e
--- /dev/null
+++ b/core/config/src/main/java/org/apache/causeway/core/config/observation/CausewayAgentEnvironmentPostProcessor.java
@@ -0,0 +1,75 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.core.config.observation;
+
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+
+import org.springframework.boot.EnvironmentPostProcessor;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.context.config.ConfigDataEnvironmentPostProcessor;
+import org.springframework.boot.context.properties.bind.Binder;
+import org.springframework.core.Ordered;
+import org.springframework.core.env.ConfigurableEnvironment;
+import org.springframework.core.env.MapPropertySource;
+import org.springframework.core.env.Profiles;
+import org.springframework.util.ClassUtils;
+
+/** Selects agent trace ownership before Boot evaluates its auto-configurations. */
+public class CausewayAgentEnvironmentPostProcessor implements EnvironmentPostProcessor, Ordered {
+
+    private static final List<String> TRACE_AUTO_CONFIGURATIONS = List.of(
+            "org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration",
+            "org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.OpenTelemetryTracingAutoConfiguration",
+            "org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.otlp.OtlpTracingAutoConfiguration",
+            "org.springframework.boot.micrometer.tracing.autoconfigure.MicrometerTracingAutoConfiguration",
+            "org.springframework.boot.webmvc.autoconfigure.WebMvcObservationAutoConfiguration");
+
+    @Override
+    public int getOrder() {
+        // Active profiles and application YAML must already have been loaded.
+        return ConfigDataEnvironmentPostProcessor.ORDER + 1;
+    }
+
+    @Override
+    public void postProcessEnvironment(final ConfigurableEnvironment environment,
+            final SpringApplication application) {
+        if (!environment.acceptsProfiles(Profiles.of("agent"))) {
+            return;
+        }
+        if (environment.acceptsProfiles(Profiles.of("observation"))
+                && !ClassUtils.isPresent("io.micrometer.tracing.otel.bridge.OtelTracer", application.getClassLoader())) {
+            throw new IllegalStateException("The observation,agent profiles require "
+                    + "io.micrometer:micrometer-tracing-bridge-otel. Add the BOM-managed dependency "
+                    + "and attach the OpenTelemetry Java agent to the application JVM.");
+        }
+        var exclusions = new LinkedHashSet<>(Binder.get(environment)
+                .bind("spring.autoconfigure.exclude", String[].class)
+                .map(List::of).orElseGet(List::of));
+        exclusions.addAll(TRACE_AUTO_CONFIGURATIONS);
+        // Ownership is a profile contract, so these values override command-line
+        // defaults too. Existing unrelated exclusions are retained above.
+        // Avoid mapping OTEL_METRICS_EXPORTER=none onto Micrometer metrics.
+        environment.getPropertySources().addFirst(new MapPropertySource("causewayAgentTracing", Map.of(
+                "spring.autoconfigure.exclude", String.join(",", exclusions),
+                "management.opentelemetry.map-environment-variables", "false",
+                "management.tracing.export.enabled", "false")));
+    }
+}
diff --git a/core/config/src/main/java/org/apache/causeway/core/config/observation/CausewayAgentObservationAutoConfiguration.java b/core/config/src/main/java/org/apache/causeway/core/config/observation/CausewayAgentObservationAutoConfiguration.java
new file mode 100644
index 00000000000..c3df3a3a106
--- /dev/null
+++ b/core/config/src/main/java/org/apache/causeway/core/config/observation/CausewayAgentObservationAutoConfiguration.java
@@ -0,0 +1,63 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.core.config.observation;
+
+import java.util.List;
+
+import org.springframework.boot.autoconfigure.AutoConfiguration;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
+import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Profile;
+
+import io.micrometer.observation.ObservationRegistry;
+import io.micrometer.tracing.Tracer;
+import io.micrometer.tracing.handler.DefaultTracingObservationHandler;
+import io.micrometer.tracing.otel.bridge.OtelBaggageManager;
+import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext;
+import io.micrometer.tracing.otel.bridge.OtelTracer;
+import io.opentelemetry.api.GlobalOpenTelemetry;
+
+/**
+ * Joins Causeway observations to the attached Java agent's global context.
+ * This configuration creates neither an SDK nor an exporter. Application bean
+ * definitions take precedence; a custom registry owns its own handlers.
+ */
+@AutoConfiguration(beforeName = "org.springframework.boot.micrometer.observation.autoconfigure.ObservationAutoConfiguration")
+@Profile("observation & agent")
+@ConditionalOnClass(name = {"io.micrometer.tracing.otel.bridge.OtelTracer",
+        "io.opentelemetry.api.GlobalOpenTelemetry"})
+public class CausewayAgentObservationAutoConfiguration {
+
+    @Bean
+    @ConditionalOnMissingBean(Tracer.class)
+    public Tracer causewayAgentTracer() {
+        var currentContext = new OtelCurrentTraceContext();
+        return new OtelTracer(GlobalOpenTelemetry.getTracer("org.apache.causeway"), currentContext,
+                event -> {}, new OtelBaggageManager(currentContext, List.of(), List.of()));
+    }
+
+    @Bean
+    @ConditionalOnMissingBean(ObservationRegistry.class)
+    public ObservationRegistry causewayAgentObservationRegistry(final Tracer tracer) {
+        var registry = ObservationRegistry.create();
+        registry.observationConfig().observationHandler(new DefaultTracingObservationHandler(tracer));
+        return registry;
+    }
+}
diff --git a/core/config/src/main/java/org/apache/causeway/core/config/observation/CausewayObservationAutoConfiguration.java b/core/config/src/main/java/org/apache/causeway/core/config/observation/CausewayObservationAutoConfiguration.java
index 816db189e9d..aa26458dbdc 100644
--- a/core/config/src/main/java/org/apache/causeway/core/config/observation/CausewayObservationAutoConfiguration.java
+++ b/core/config/src/main/java/org/apache/causeway/core/config/observation/CausewayObservationAutoConfiguration.java
@@ -42,9 +42,9 @@
  * produce (for example, tracing spans); this configuration does not create an SDK
  * or exporter.
  *
- * <p>Normally Boot configures the tracing handlers and export pipeline. An
- * application can instead supply a registry bridged to the OpenTelemetry Java
- * agent's context, with competing Boot tracing auto-configuration excluded.
+ * <p>Normally Boot configures the tracing handlers and export pipeline. The
+ * {@code observation,agent} profiles instead enable Causeway's built-in bridge
+ * to the Java agent and exclude competing Boot tracing auto-configuration.
  * Both arrangements use the same Causeway integration below.
  *
  * <p>With the profile inactive, only Causeway's integration is disabled. We do
@@ -77,7 +77,7 @@ public boolean isExportable(final FinishedSpan span) {
 	 * available. As with Boot's fallback, creating a registry does not itself
 	 * enable export; observation handlers must be registered by the tracing setup.
 	 */
-	@Profile("observation")
+	@Profile("observation & !agent")
 	@Bean
 	@ConditionalOnMissingBean
 	public ObservationRegistry observationRegistry() {
diff --git a/core/config/src/main/resources/META-INF/spring.factories b/core/config/src/main/resources/META-INF/spring.factories
new file mode 100644
index 00000000000..c4f2abacea0
--- /dev/null
+++ b/core/config/src/main/resources/META-INF/spring.factories
@@ -0,0 +1,2 @@
+org.springframework.boot.EnvironmentPostProcessor=\
+org.apache.causeway.core.config.observation.CausewayAgentEnvironmentPostProcessor
diff --git a/core/config/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports b/core/config/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
new file mode 100644
index 00000000000..c59ba34ad0f
--- /dev/null
+++ b/core/config/src/main/resources/META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports
@@ -0,0 +1 @@
+org.apache.causeway.core.config.observation.CausewayAgentObservationAutoConfiguration
diff --git a/core/config/src/test/java/org/apache/causeway/core/config/observation/CausewayAgentObservationAutoConfigurationTest.java b/core/config/src/test/java/org/apache/causeway/core/config/observation/CausewayAgentObservationAutoConfigurationTest.java
new file mode 100644
index 00000000000..f6907aa1d3c
--- /dev/null
+++ b/core/config/src/test/java/org/apache/causeway/core/config/observation/CausewayAgentObservationAutoConfigurationTest.java
@@ -0,0 +1,127 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *        http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+package org.apache.causeway.core.config.observation;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static org.mockito.Mockito.*;
+
+import java.util.Map;
+
+import org.junit.jupiter.api.Test;
+import org.springframework.boot.SpringApplication;
+import org.springframework.boot.autoconfigure.AutoConfigurations;
+import org.springframework.boot.test.context.FilteredClassLoader;
+import org.springframework.boot.test.context.runner.ApplicationContextRunner;
+import org.springframework.core.env.MapPropertySource;
+import org.springframework.core.env.StandardEnvironment;
+
+import io.micrometer.observation.ObservationRegistry;
+import io.micrometer.tracing.Tracer;
+
+class CausewayAgentObservationAutoConfigurationTest {
+    private final ApplicationContextRunner runner = new ApplicationContextRunner()
+            .withUserConfiguration(CausewayObservationAutoConfiguration.class)
+            .withConfiguration(AutoConfigurations.of(CausewayAgentObservationAutoConfiguration.class));
+
+    @Test void bridgeCreatesOneRegistryAndTracer() {
+        runner.withPropertyValues("spring.profiles.active=observation,agent").run(context -> {
+            assertThat(context).hasSingleBean(Tracer.class).hasSingleBean(ObservationRegistry.class);
+            assertThat(context.getBean(CausewayObservationIntegration.class).observationRegistry())
+                    .isSameAs(context.getBean(ObservationRegistry.class));
+            assertThat(context.getBean(ObservationRegistry.class).isNoop()).isFalse();
+        });
+    }
+
+    @Test void customTracerIsSharedWithDefaultRegistryAndClassifier() {
+        var tracer = mock(Tracer.class, withSettings().defaultAnswer(RETURNS_DEEP_STUBS));
+        runner.withPropertyValues("spring.profiles.active=observation,agent")
+                .withBean(Tracer.class, () -> tracer).run(context -> {
+                    assertThat(context).hasSingleBean(Tracer.class).hasSingleBean(ObservationRegistry.class);
+                    assertThat(context.getBean(Tracer.class)).isSameAs(tracer);
+                    context.getBean(CausewayTraceClassifier.class)
+                            .classifyCurrentSpan(CausewayTraceClassifier.ExecutionMode.FOREGROUND);
+                    verify(tracer.currentSpan()).tag("causeway.execution.mode", "foreground");
+                    context.getBean(CausewayObservationIntegration.class)
+                            .createNotStarted(getClass(), "custom-tracer").observe(() -> {});
+                    verify(tracer).nextSpan();
+                });
+    }
+
+    @Test void customRegistryIsPreserved() {
+        var custom = ObservationRegistry.create();
+        runner.withPropertyValues("spring.profiles.active=observation,agent")
+                .withBean(ObservationRegistry.class, () -> custom).run(context -> {
+                    assertThat(context).hasSingleBean(ObservationRegistry.class);
+                    assertThat(context.getBean(CausewayObservationIntegration.class).observationRegistry()).isSameAs(custom);
+                    assertThat(custom.isNoop()).isTrue(); // no handler installed into application registry
+                });
+    }
+
+    @Test void inactiveObservationDoesNotCreateAgentBridge() {
+        runner.withPropertyValues("spring.profiles.active=agent").run(context -> {
+            assertThat(context).doesNotHaveBean(Tracer.class).doesNotHaveBean(ObservationRegistry.class);
+            assertThat(context.getBean(CausewayObservationIntegration.class).isNoop()).isTrue();
+        });
+    }
+
+    @Test void bootModeStartsWithoutOptionalBridge() {
+        runner.withClassLoader(new FilteredClassLoader("io.micrometer.tracing.otel", "io.opentelemetry"))
+                .withPropertyValues("spring.profiles.active=observation").run(context -> {
+                    assertThat(context).hasNotFailed().hasSingleBean(ObservationRegistry.class);
+                    assertThat(context).doesNotHaveBean(Tracer.class);
+                });
+    }
+
+    @Test void ownershipExclusionsPreserveYamlListAndMetrics() {
+        var environment = new StandardEnvironment();
+        environment.setActiveProfiles("observation", "agent");
+        environment.getPropertySources().addFirst(new MapPropertySource("app", Map.of(
+                "spring.autoconfigure.exclude[0]", "example.CustomAutoConfiguration",
+                "management.otlp.metrics.export.enabled", "true",
+                "management.tracing.export.enabled", "true")));
+        var processor = new CausewayAgentEnvironmentPostProcessor();
+        processor.postProcessEnvironment(environment, new SpringApplication());
+        processor.postProcessEnvironment(environment, new SpringApplication()); // repeat is harmless
+        assertThat(environment.getProperty("spring.autoconfigure.exclude"))
+                .contains("example.CustomAutoConfiguration", "OpenTelemetrySdkAutoConfiguration", "WebMvcObservationAutoConfiguration");
+        assertThat(environment.getProperty("management.otlp.metrics.export.enabled")).isEqualTo("true");
+        assertThat(environment.getProperty("management.opentelemetry.map-environment-variables")).isEqualTo("false");
+        assertThat(environment.getProperty("management.tracing.export.enabled")).isEqualTo("false");
+    }
+
+    @Test void missingAgentDependencyExplainsRemedy() throws Exception {
+        var environment = new StandardEnvironment();
+        environment.setActiveProfiles("observation", "agent");
+        try (var loader = new FilteredClassLoader("io.micrometer.tracing.otel")) {
+            var application = new SpringApplication(new org.springframework.core.io.DefaultResourceLoader(loader));
+            assertThatThrownBy(() -> new CausewayAgentEnvironmentPostProcessor()
+                    .postProcessEnvironment(environment, application))
+                    .isInstanceOf(IllegalStateException.class)
+                    .hasMessageContaining("micrometer-tracing-bridge-otel");
+        }
+    }
+
+    @Test void bootEnvironmentRemainsUnchanged() {
+        var environment = new StandardEnvironment();
+        environment.setActiveProfiles("observation");
+        new CausewayAgentEnvironmentPostProcessor().postProcessEnvironment(environment, new SpringApplication());
+        assertThat(environment.getPropertySources().contains("causewayAgentTracing")).isFalse();
+    }
+}
diff --git a/openspec/changes/provide-agent-observation-bridge/tasks.md b/openspec/changes/provide-agent-observation-bridge/tasks.md
index 868a03e4094..15ad126cca8 100644
--- a/openspec/changes/provide-agent-observation-bridge/tasks.md
+++ b/openspec/changes/provide-agent-observation-bridge/tasks.md
@@ -1,11 +1,11 @@
 ## 1. Framework wiring
 
-- [ ] 1.1 Add optional OTel bridge dependency and guarded agent tracer/registry auto-configuration.
-- [ ] 1.2 Add early agent ownership defaults preserving user exclusions and metrics.
-- [ ] 1.3 Test profiles, optional dependency, custom beans and exclusion merging.
+- [x] 1.1 Add optional OTel bridge dependency and guarded agent tracer/registry auto-configuration.
+- [x] 1.2 Add early agent ownership defaults preserving user exclusions and metrics.
+- [x] 1.3 Test profiles, optional dependency, custom beans and exclusion merging.
 
 ## 2. Adoption and verification
 
-- [ ] 2.1 Remove Petclinic and fixture bridge copies/manual agent exclusions; exercise production wiring.
-- [ ] 2.2 Update M3 how-to, user guide, sample instructions and roadmap.
-- [ ] 2.3 Validate focused core tests, exported Boot/agent trace regressions and sample packaging.
+- [x] 2.1 Remove Petclinic and fixture bridge copies/manual agent exclusions; exercise production wiring.
+- [x] 2.2 Update M3 how-to, user guide, sample instructions and roadmap.
+- [x] 2.3 Validate focused core tests, exported Boot/agent trace regressions and sample packaging.
diff --git a/openspec/changes/provide-agent-observation-bridge/validation.md b/openspec/changes/provide-agent-observation-bridge/validation.md
new file mode 100644
index 00000000000..5e55ff19bcf
--- /dev/null
+++ b/openspec/changes/provide-agent-observation-bridge/validation.md
@@ -0,0 +1,12 @@
+# Validation
+
+2026-10-07, branch CAUSEWAY-4068-v4. Java 25.0.4, Maven 3.9.13, Boot 4.2.0-M1, Micrometer 1.18.0-M1 / Tracing 1.8.0-M1, OTel 1.64.0 and Java agent 2.31.1.
+
+- Focused core/config reactor: 19 tests passed (8 agent wiring, 7 registry integration, 4 classifier).
+- Exported-trace compatibility suite: 9 tests passed. Real attached-agent HTTP/JDBC and servlet/Quartz fixtures use production auto-configuration, with no fixture bridge or manually supplied agent exclusions. Verifies single trace ownership, framework ancestry, identities, classification, replay correlation and inactive observations. The agent fixture also exports Micrometer metrics to a local OTLP receiver with OTEL_METRICS_EXPORTER=none.
+- Petclinic observation profile: clean reactor package passed after removing application-owned bridge source and agent properties. Configured frontend build completed.
+- Both updated AsciiDoc pages rendered with Asciidoctor; strict change validation and git diff whitespace check passed.
+
+The framework creates no SDK/exporter and does not attach the agent. Custom registry owners remain responsible for tracing handlers. Boot HTTP observation meters are absent in agent mode by design; other Micrometer metrics remain available. No interactive Docker/browser verification was performed in this change.
+
+Before archive, the focused and exported-trace suites are rerun after the implementation checkpoint; results recorded below.
diff --git a/openspec/planned-changes/otel-forward-port-roadmap.md b/openspec/planned-changes/otel-forward-port-roadmap.md
index a1b5dc1dc74..d44a966fda1 100644
--- a/openspec/planned-changes/otel-forward-port-roadmap.md
+++ b/openspec/planned-changes/otel-forward-port-roadmap.md
@@ -118,3 +118,7 @@ Classification/correlation archived on 2026-10-07 after fresh focused and tracin
 The [remove-jpa-duration-filtering](../changes/archive/2026-10-07-remove-jpa-duration-filtering/proposal.md) change removes the narrow JPA-only policy while preserving explicit transaction discard behavior. Replacement filtering is deferred to [general span duration filtering](general-span-duration-filtering.md).
 
 Removal completed and archived on 2026-10-07. Implementation checkpoint `39faf40ded6`; 34 Java tests passed before and after the checkpoint, Petclinic packaged successfully, and metadata/docs/launcher checks passed. Specs were synced; all completed changes are archived. General span filtering remains backlog exploration.
+
+## Built-in agent bridge follow-up (2026-10-07)
+
+[provide-agent-observation-bridge](../changes/provide-agent-observation-bridge/proposal.md) moves the sample-owned bridge and manual exclusions into core configuration. Consumers add the optional BOM-managed OTel bridge dependency, attach the Java agent and activate `observation,agent`; Micrometer metrics remain independent. Exported-trace fixtures exercise the production configuration. This is an adoption simplification on CAUSEWAY-4068, with general duration filtering still deferred.
diff --git a/regressiontests/tracing-compatibility/src/test/java/org/apache/causeway/regressiontests/tracing/EntryPointTracingFixture.java b/regressiontests/tracing-compatibility/src/test/java/org/apache/causeway/regressiontests/tracing/EntryPointTracingFixture.java
index 1dae5a93b22..b6a26488555 100644
--- a/regressiontests/tracing-compatibility/src/test/java/org/apache/causeway/regressiontests/tracing/EntryPointTracingFixture.java
+++ b/regressiontests/tracing-compatibility/src/test/java/org/apache/causeway/regressiontests/tracing/EntryPointTracingFixture.java
@@ -65,8 +65,7 @@
 /** Real servlet/Quartz entry points; business collaborators are controlled fixtures. */
 @SpringBootConfiguration(proxyBeanMethods = false)
 @EnableAutoConfiguration
-@Import({MicrometerTracingAgentFixture.AgentRegistry.class,
-        CausewayObservationAutoConfiguration.class, WebObservationConfiguration.class})
+@Import({CausewayObservationAutoConfiguration.class, WebObservationConfiguration.class})
 public class EntryPointTracingFixture {
     static InteractionServiceDefault interactions;
     static CausewayObservationIntegration integration;
diff --git a/regressiontests/tracing-compatibility/src/test/java/org/apache/causeway/regressiontests/tracing/MicrometerTracingAgentFixture.java b/regressiontests/tracing-compatibility/src/test/java/org/apache/causeway/regressiontests/tracing/MicrometerTracingAgentFixture.java
index 524dccdd861..79b25dd545f 100644
--- a/regressiontests/tracing-compatibility/src/test/java/org/apache/causeway/regressiontests/tracing/MicrometerTracingAgentFixture.java
+++ b/regressiontests/tracing-compatibility/src/test/java/org/apache/causeway/regressiontests/tracing/MicrometerTracingAgentFixture.java
@@ -36,9 +36,7 @@
 import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
 import org.springframework.boot.builder.SpringApplicationBuilder;
 import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
 import org.springframework.context.annotation.Import;
-import org.springframework.context.annotation.Profile;
 
 import org.apache.causeway.applib.Identifier;
 import org.apache.causeway.applib.id.LogicalType;
@@ -64,17 +62,11 @@
 import org.apache.causeway.core.runtimeservices.ia.InteractionServiceDefault;
 import org.apache.causeway.core.runtimeservices.transaction.TransactionServiceSpring;
 
-import io.micrometer.observation.ObservationRegistry;
-import io.micrometer.tracing.handler.DefaultTracingObservationHandler;
-import io.micrometer.tracing.otel.bridge.OtelBaggageManager;
-import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext;
-import io.micrometer.tracing.otel.bridge.OtelTracer;
-import io.opentelemetry.api.GlobalOpenTelemetry;
 
 /** Child JVM fixture: real framework boundaries with mocked non-telemetry collaborators. */
 @SpringBootConfiguration(proxyBeanMethods = false)
 @EnableAutoConfiguration
-@Import({CausewayObservationAutoConfiguration.class, MicrometerTracingAgentFixture.AgentRegistry.class})
+@Import({CausewayObservationAutoConfiguration.class})
 public class MicrometerTracingAgentFixture {
     public static void main(String[] args) throws Exception {
         try (var context = new SpringApplicationBuilder(MicrometerTracingAgentFixture.class)
@@ -171,19 +163,4 @@ private static void executeSql() {
             } catch (java.sql.SQLException ex) { throw new IllegalStateException(ex); }
         }
     }
-    @Configuration(proxyBeanMethods = false)
-    @Profile("agent")
-    static class AgentRegistry {
-        @Bean io.micrometer.tracing.Tracer agentTracer() {
-            var currentContext = new OtelCurrentTraceContext();
-            return new OtelTracer(GlobalOpenTelemetry.getTracer("org.apache.causeway"), currentContext,
-                    event -> {}, new OtelBaggageManager(currentContext, List.of(), List.of()));
-        }
-        @Bean
-        ObservationRegistry agentObservationRegistry(io.micrometer.tracing.Tracer tracer) {
-            var registry = ObservationRegistry.create();
-            registry.observationConfig().observationHandler(new DefaultTracingObservationHandler(tracer));
-            return registry;
-        }
-    }
 }
diff --git a/regressiontests/tracing-compatibility/src/test/java/org/apache/causeway/regressiontests/tracing/MicrometerTracingCompatibilityTest.java b/regressiontests/tracing-compatibility/src/test/java/org/apache/causeway/regressiontests/tracing/MicrometerTracingCompatibilityTest.java
index 29c310120a3..cf3e841a456 100644
--- a/regressiontests/tracing-compatibility/src/test/java/org/apache/causeway/regressiontests/tracing/MicrometerTracingCompatibilityTest.java
+++ b/regressiontests/tracing-compatibility/src/test/java/org/apache/causeway/regressiontests/tracing/MicrometerTracingCompatibilityTest.java
@@ -41,6 +41,7 @@ class MicrometerTracingCompatibilityTest {
     @Test void agentJoinsFrameworkAndJdbcWithoutDuplicateSpans() throws Exception {
         try (var collector = new Collector()) {
             launch("agent", collector);
+            assertFalse(collector.metricNames.isEmpty(), "Micrometer metrics must export despite OTEL_METRICS_EXPORTER=none");
             var spans = collector.spans;
             var roots = spans.stream().filter(s -> s.getName().equals("Causeway Root Interaction")).toList();
             assertEquals(2, roots.size(), names(spans));
@@ -180,9 +181,10 @@ private void launchFixture(Class<?> fixture, String mode, Collector collector, S
         command.add(fixture.getName());
         command.add("--spring.profiles.active=" + (mode.equals("inactive") ? "agent" : agent ? "observation,agent" : "observation"));
         command.add("--management.tracing.sampling.probability=1.0");
+        command.add("--management.otlp.metrics.export.url=" + collector.metricsEndpoint());
         command.add("--management.opentelemetry.tracing.export.otlp.endpoint=" + collector.endpoint());
-        command.add("--management.tracing.export.enabled=" + mode.equals("boot"));
-        if (agent || mode.equals("none")) {
+        if (!agent) command.add("--management.tracing.export.enabled=" + mode.equals("boot"));
+        if (mode.equals("none")) {
             command.add("--spring.autoconfigure.exclude=" + String.join(",",
                     "org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration",
                     "org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.OpenTelemetryTracingAutoConfiguration",
@@ -190,7 +192,7 @@ private void launchFixture(Class<?> fixture, String mode, Collector collector, S
                     "org.springframework.boot.micrometer.tracing.autoconfigure.MicrometerTracingAutoConfiguration",
                     "org.springframework.boot.webmvc.autoconfigure.WebMvcObservationAutoConfiguration"));
         }
-        command.add("--management.opentelemetry.map-environment-variables=false");
+        if (!agent) command.add("--management.opentelemetry.map-environment-variables=false");
         command.addAll(List.of(properties));
         var builder = new ProcessBuilder(command).redirectErrorStream(true);
         builder.environment().keySet().removeIf(key -> key.startsWith("OTEL_"));
@@ -221,6 +223,7 @@ private void launchFixture(Class<?> fixture, String mode, Collector collector, S
     private String names(List<Span> spans) { return spans.stream().map(s -> s.getName() + ":" + s.getKind()).toList().toString(); }
     private static class Collector implements AutoCloseable {
         final List<Span> spans = new CopyOnWriteArrayList<>();
+        final List<String> metricNames = new CopyOnWriteArrayList<>();
         final HttpServer server;
         Collector() throws Exception {
             server = HttpServer.create(new InetSocketAddress("127.0.0.1", 0), 0);
@@ -232,9 +235,19 @@ private static class Collector implements AutoCloseable {
                     exchange.sendResponseHeaders(200, 0);
                 } finally { exchange.close(); }
             });
+            server.createContext("/v1/metrics", exchange -> {
+                try (var body = "gzip".equals(exchange.getRequestHeaders().getFirst("Content-Encoding"))
+                        ? new GZIPInputStream(exchange.getRequestBody()) : exchange.getRequestBody()) {
+                    var request = io.opentelemetry.proto.collector.metrics.v1.ExportMetricsServiceRequest.parseFrom(body);
+                    request.getResourceMetricsList().forEach(resource -> resource.getScopeMetricsList().forEach(scope ->
+                            scope.getMetricsList().forEach(metric -> metricNames.add(metric.getName()))));
+                    exchange.sendResponseHeaders(200, 0);
+                } finally { exchange.close(); }
+            });
             server.start();
         }
         String endpoint() { return "http://127.0.0.1:" + server.getAddress().getPort() + "/v1/traces"; }
+        String metricsEndpoint() { return "http://127.0.0.1:" + server.getAddress().getPort() + "/v1/metrics"; }
         public void close() { server.stop(0); }
     }
 }
diff --git a/viewers/webcomponents/sample-htmx-petclinic/README.adoc b/viewers/webcomponents/sample-htmx-petclinic/README.adoc
index c5431752e41..5237d9f0d34 100644
--- a/viewers/webcomponents/sample-htmx-petclinic/README.adoc
+++ b/viewers/webcomponents/sample-htmx-petclinic/README.adoc
@@ -72,7 +72,7 @@ Stop Petclinic with Ctrl-C and switch tracing ownership by adding one flag:
 ----
 
 The agent owns traces and automatic HTTP/JDBC instrumentation; Micrometer still owns metrics.
-The optional registry bridge joins Causeway observations to agent context and the agent profile excludes competing Boot tracing configuration.
+Causeway supplies the registry bridge automatically for `observation,agent` and excludes competing Boot tracing configuration. No application bridge code is required.
 Agent metrics/log export is disabled; Micrometer metrics stay enabled.
 Maven obtains agent 2.31.1 on first use, or set `OTEL_JAVAAGENT_PATH` to an existing JAR.
 
diff --git a/viewers/webcomponents/sample-htmx-petclinic/pom.xml b/viewers/webcomponents/sample-htmx-petclinic/pom.xml
index 4905dd1075b..e441f10e880 100644
--- a/viewers/webcomponents/sample-htmx-petclinic/pom.xml
+++ b/viewers/webcomponents/sample-htmx-petclinic/pom.xml
@@ -121,25 +121,6 @@
                     <artifactId>micrometer-tracing-bridge-otel</artifactId>
                 </dependency>
             </dependencies>
-            <build>
-                <plugins>
-                    <plugin>
-                        <groupId>org.codehaus.mojo</groupId>
-                        <artifactId>build-helper-maven-plugin</artifactId>
-                        <version>3.6.2</version>
-                        <executions>
-                            <execution>
-                                <id>add-observation-source</id>
-                                <phase>generate-sources</phase>
-                                <goals><goal>add-source</goal></goals>
-                                <configuration>
-                                    <sources><source>src/observation/java</source></sources>
-                                </configuration>
-                            </execution>
-                        </executions>
-                    </plugin>
-                </plugins>
-            </build>
         </profile>
         <profile>
             <id>playwright</id>
diff --git a/viewers/webcomponents/sample-htmx-petclinic/src/main/resources/application-agent.properties b/viewers/webcomponents/sample-htmx-petclinic/src/main/resources/application-agent.properties
deleted file mode 100644
index 0c0be7c69f7..00000000000
--- a/viewers/webcomponents/sample-htmx-petclinic/src/main/resources/application-agent.properties
+++ /dev/null
@@ -1,25 +0,0 @@
-#
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements.  See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership.  The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License.  You may obtain a copy of the License at
-#
-#       https://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied.  See the License for the
-# specific language governing permissions and limitations
-# under the License.
-#
-# The Java agent owns tracing. This profile requires its registry bridge and -javaagent.
-# Agent servlet instrumentation owns the HTTP entry; avoid a second Boot HTTP observation span.
-spring.autoconfigure.exclude=org.springframework.boot.opentelemetry.autoconfigure.OpenTelemetrySdkAutoConfiguration,org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.OpenTelemetryTracingAutoConfiguration,org.springframework.boot.micrometer.tracing.opentelemetry.autoconfigure.otlp.OtlpTracingAutoConfiguration,org.springframework.boot.micrometer.tracing.autoconfigure.MicrometerTracingAutoConfiguration,org.springframework.boot.webmvc.autoconfigure.WebMvcObservationAuto [...]
-# Keep OTEL_* settings owned by the agent; do not map its metrics disable flag onto Micrometer.
-management.opentelemetry.map-environment-variables=false
-management.tracing.export.enabled=false
-# Micrometer metrics remain enabled by application-observation.properties.
diff --git a/viewers/webcomponents/sample-htmx-petclinic/src/observation/java/org/apache/causeway/viewer/webcomponents/sample/htmx/petclinic/AgentObservationConfiguration.java b/viewers/webcomponents/sample-htmx-petclinic/src/observation/java/org/apache/causeway/viewer/webcomponents/sample/htmx/petclinic/AgentObservationConfiguration.java
deleted file mode 100644
index 05530056067..00000000000
--- a/viewers/webcomponents/sample-htmx-petclinic/src/observation/java/org/apache/causeway/viewer/webcomponents/sample/htmx/petclinic/AgentObservationConfiguration.java
+++ /dev/null
@@ -1,51 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements.  See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership.  The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License.  You may obtain a copy of the License at
- *
- *       https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied.  See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-package org.apache.causeway.viewer.webcomponents.sample.htmx.petclinic;
-
-import java.util.List;
-
-import org.springframework.context.annotation.Bean;
-import org.springframework.context.annotation.Configuration;
-import org.springframework.context.annotation.Profile;
-
-import io.micrometer.observation.ObservationRegistry;
-import io.micrometer.tracing.handler.DefaultTracingObservationHandler;
-import io.micrometer.tracing.otel.bridge.OtelBaggageManager;
-import io.micrometer.tracing.otel.bridge.OtelCurrentTraceContext;
-import io.micrometer.tracing.otel.bridge.OtelTracer;
-import io.opentelemetry.api.GlobalOpenTelemetry;
-
-/** Bridges Causeway observations into the agent's HTTP/JDBC trace context. */
-@Configuration(proxyBeanMethods = false)
-@Profile("agent")
-public class AgentObservationConfiguration {
-    @Bean
-    io.micrometer.tracing.Tracer agentTracer() {
-        // The attached agent owns the global SDK and exporter. Do not construct another SDK here.
-        var currentContext = new OtelCurrentTraceContext();
-        return new OtelTracer(GlobalOpenTelemetry.getTracer("org.apache.causeway"), currentContext,
-                event -> {}, new OtelBaggageManager(currentContext, List.of(), List.of()));
-    }
-    @Bean
-    ObservationRegistry agentObservationRegistry(io.micrometer.tracing.Tracer tracer) {
-        var registry = ObservationRegistry.create();
-        registry.observationConfig().observationHandler(new DefaultTracingObservationHandler(tracer));
-        return registry;
-    }
-}