40 commits, 4.0.36-SNAPSHOT. The release is dominated by the Unified @Agent API close-out — a 6-wave roadmap that wired 12 cross-cutting capabilities across all 7 AI runtimes (Built-in, Spring AI, LangChain4j, Google ADK, JetBrains Koog, Embabel, Microsoft Semantic Kernel).
HITL (Human-in-the-Loop) Unification — Phase 0
ApprovalStrategy+ApprovalRegistry+ToolExecutionHelper.executeWithApproval()is now the single approval gate across all runtime bridges- Fixed P0:
AiPipelinewas building contexts with nullapprovalStrategy— non-websocket callers (@Agent, @Coordinator, A2A, AG-UI, channels) had no HITL gate. NowAiPipelineowns anApprovalRegistry - ADK tool invocation routed through
executeWithApproval(was bypassing approval entirely) ToolApprovalPolicysealed interface:AllowAll,DenyAll,Annotated(default),Custom(Predicate)
Semantic Kernel Runtime — Phase 12
- 6th
AgentRuntimeadapter:SemanticKernelAgentRuntimewrapping SK-Java'sChatCompletionService - Contract test subclass with full cross-runtime parity
SPI Scaffolding — Phases 1-8
AgentLifecycleListener—onStart/onToolCall/onToolResult/onCompletion/onErrorhooksExecutionHandle— cooperative cancellation (Spring AIDisposable.dispose(), ADKAdkEventAdapter.whenDone(), LC4j/Koog/Built-in soft-cancel polled flags, Built-inInputStream.closehard-cancel)StreamingSession.usage(TokenUsage)— typed token telemetry (ai.tokens.input/output/total)StreamingSession.toolCallDelta()— incremental tool-call streamingContent.Text/Image/Audio/Filesealed multimodal interfaceCacheHintrecord (metadata-sidecar pattern onAgentExecutionContext.metadata)RetryPolicyrecord withDEFAULT/NONEconstants +RetryPolicy.of(maxRetries, baseDelay)factoryEmbeddingRuntimeSPI +EmbeddingRuntimeResolver(ServiceLoader, priority-ordered)AgentExecutionResultexecution-result recordToolArgumentValidatorinterface- Capability flags:
TOOL_APPROVAL,VISION,MULTI_MODAL,PROMPT_CACHING,TOKEN_USAGE,CANCEL,HANDOFF
Wave 1 — Tool Approval + Models (2cd6c272a4)
- Every tool-calling runtime declares
TOOL_APPROVALin itscapabilities()set AgentRuntime.models()returns the runtime's configured model names (runtime truth, not config intent)
Wave 2 — Multi-Modal Content (e8adc27a47)
Content.Image/Audio/File→ per-runtime translation: Spring AIMedia, LC4jImageContent, ADKPart.fromBytes(), Built-in OpenAIimage_urlcontent array- Cross-runtime contract test:
runtimeWithVisionCapabilityAcceptsImagePart()withTINY_PNGfixture
Wave 3 — Lifecycle Listeners + Tool Call Deltas (3a92220a3d)
onToolCall/onToolResultfired from every runtime bridge viaToolExecutionHelper.executeWithApproval()toolCallDeltaemitted by Built-in runtime (other runtimes can't without bypassing their high-level streaming APIs — documented intentional divergence)
Wave 4 — Prompt Caching (cdeb9906de)
CacheHintridesAgentExecutionContext.metadata("ai.cache.hint")- Spring AI emits
OpenAiChatOptions.promptCacheKey(), LC4j emitscustomParameters("prompt_cache_key", ...), Built-in emits on the OpenAI wire format - ADK: log-and-ignore (App-scoped
ContextCacheConfig, per-request cache would churn memory) - No
ChatMessageexpansion needed (avoided 135-call-site migration via metadata sidecar)
Wave 5 — Embedding Runtimes (570fc59492 + d4f9634177)
SpringAiEmbeddingRuntimewrappingorg.springframework.ai.embedding.EmbeddingModel(priority 200)LangChain4jEmbeddingRuntimewrappingdev.langchain4j.model.embedding.EmbeddingModel(priority 190)SemanticKernelEmbeddingRuntimewrappingTextEmbeddingGenerationServicewithMono.block()sync boundary +List<Float>→float[]unwrap (priority 180)EmbabelEmbeddingRuntime.ktwrappingcom.embabel.common.ai.model.EmbeddingService(priority 170)BuiltInEmbeddingRuntimeposting to/v1/embeddings(OpenAI-compatible, priority 50, zero-dep fallback)AbstractEmbeddingRuntimeContractTestTCK base: 6 parity assertions × 5 subclasses- ServiceLoader discovery via
META-INF/services/org.atmosphere.ai.EmbeddingRuntime× 5
Wave 6 — Per-Request Retry Policy (ae0c2d06ad)
RetryPolicybecomes the 19th canonical field onAgentExecutionContext(defaultRetryPolicy.DEFAULT)- 12th canonical field on
ChatCompletionRequest(null = inherit client-level default) OpenAiCompatibleClient.sendWithRetry()accepts per-request override; recursive tool-loop preserves override across rounds- Framework runtimes (Spring AI, LC4j, ADK, Koog, Embabel) correctly NOT wrapping native retry (documented mode-parity exception)
- Cross-runtime contract test:
runtimeAcceptsCustomRetryPolicyOnContext()
Polish Pass (9cf4618394, 5360d08e70, d4f9634177)
- Fixed F-6.3:
BuiltInAgentRuntime.buildRequestnow skips threadingcontext.retryPolicy()when reference-equal toRetryPolicy.DEFAULT— client-level builder config stays live for unannotated callers - New
OpenAiCompatibleClientRetryBehaviorTest: local HttpServer returning 500, asserts NONE=1 attempt, absent=4 attempts, explicit=3 attempts EmbeddingRuntimeResolver.resetCache()promoted topublic(test-only)OpenAiCompatibleClient.retryPolicy()package-private accessor (drops reflection hack in test)
AbstractAgentRuntimeContractTestgrew from ~15 to ~26 assertions with opt-in hooks:createImageContext(),createCacheContext(),createRetryContext()AbstractEmbeddingRuntimeContractTest: 6 assertions × 5 concrete subclasses (30 cross-runtime embedding tests)- 30+ new unit tests across wire validators, retry behavior, multi-modal JSON, cache key injection
fix(spring-boot): catchLinkageErrorinAtmosphereConsoleInfoEndpoint.detectRuntime()— non-AI samples returned 500 due toNoClassDefFoundErroronAgentRuntimeResolverfix(spring-boot): honorLLM_BASE_URL,LLM_MODEL,LLM_MODEenv vars — null property defaults let env vars override framework fallbacksfix(spring-boot): console prefers configuredai.pathover handler iteration — avoids picking the wrong@AiEndpointin multi-endpoint samplesfix(langchain4j): eagerly linkSuccessfulHttpResponseto avoid fat-jar classloader bug (FJP callbacks couldn't resolve JdkHttpClient response types)test(integration): pollSocket.status()inGrpcWasyncTransportTestto fix CI race on async status updates
refactor(spring-boot): reuseAiConfig.DEFAULT_MODE/DEFAULT_MODELconstants (eliminate duplicated string literals)refactor(ai): delete stale 14-argAgentExecutionContextshim, threadapprovalStrategyexplicitly
- Module READMEs corrected: capabilities, versions, FQCNs across all runtime modules
- Sample READMEs fixed: dead links, fabricated snippets, version drift,
AiSupport→AgentRuntimerename - Main README aligned with ai4jvm submission: portable AI runtime layer framing
- Broken doc-site links repointed at Astro routes after legacy flat-file docs removal
STRUCTURED_OUTPUTrestored forBuiltInAgentRuntime(runtime truth correction)
- 40 commits, 7
feat, 6fix, 5docs, 3chore, 2refactor, 1revert, 1test - ~3,000+ lines of new code across
modules/ai,modules/ai-test,modules/spring-ai,modules/langchain4j,modules/adk,modules/koog,modules/embabel,modules/semantic-kernel - 7 AI runtimes with cross-runtime parity enforced by TCK
- 5 embedding runtimes with ServiceLoader discovery
- 9 tags on origin:
wave-1-greenthroughwave-6-green,polish-1-greenthroughpolish-3-green - Review gist: https://gist.github.com/jfarcand/63137ea2694dfa0a146cb0226cc7bd4d