Skip to content

Instantly share code, notes, and snippets.

@jfarcand
Created April 12, 2026 11:36
Show Gist options
  • Select an option

  • Save jfarcand/89de15e8130dba020e7d2244542819a3 to your computer and use it in GitHub Desktop.

Select an option

Save jfarcand/89de15e8130dba020e7d2244542819a3 to your computer and use it in GitHub Desktop.

Atmosphere 4.0.36-SNAPSHOT — What's New Since 4.0.35

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).

New Features

Unified @Agent API (Phases 0-12, Waves 1-6, Polish)

HITL (Human-in-the-Loop) Unification — Phase 0

  • ApprovalStrategy + ApprovalRegistry + ToolExecutionHelper.executeWithApproval() is now the single approval gate across all runtime bridges
  • Fixed P0: AiPipeline was building contexts with null approvalStrategy — non-websocket callers (@Agent, @Coordinator, A2A, AG-UI, channels) had no HITL gate. Now AiPipeline owns an ApprovalRegistry
  • ADK tool invocation routed through executeWithApproval (was bypassing approval entirely)
  • ToolApprovalPolicy sealed interface: AllowAll, DenyAll, Annotated (default), Custom(Predicate)

Semantic Kernel Runtime — Phase 12

  • 6th AgentRuntime adapter: SemanticKernelAgentRuntime wrapping SK-Java's ChatCompletionService
  • Contract test subclass with full cross-runtime parity

SPI Scaffolding — Phases 1-8

  • AgentLifecycleListeneronStart/onToolCall/onToolResult/onCompletion/onError hooks
  • ExecutionHandle — cooperative cancellation (Spring AI Disposable.dispose(), ADK AdkEventAdapter.whenDone(), LC4j/Koog/Built-in soft-cancel polled flags, Built-in InputStream.close hard-cancel)
  • StreamingSession.usage(TokenUsage) — typed token telemetry (ai.tokens.input/output/total)
  • StreamingSession.toolCallDelta() — incremental tool-call streaming
  • Content.Text/Image/Audio/File sealed multimodal interface
  • CacheHint record (metadata-sidecar pattern on AgentExecutionContext.metadata)
  • RetryPolicy record with DEFAULT/NONE constants + RetryPolicy.of(maxRetries, baseDelay) factory
  • EmbeddingRuntime SPI + EmbeddingRuntimeResolver (ServiceLoader, priority-ordered)
  • AgentExecutionResult execution-result record
  • ToolArgumentValidator interface
  • 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_APPROVAL in its capabilities() 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 AI Media, LC4j ImageContent, ADK Part.fromBytes(), Built-in OpenAI image_url content array
  • Cross-runtime contract test: runtimeWithVisionCapabilityAcceptsImagePart() with TINY_PNG fixture

Wave 3 — Lifecycle Listeners + Tool Call Deltas (3a92220a3d)

  • onToolCall/onToolResult fired from every runtime bridge via ToolExecutionHelper.executeWithApproval()
  • toolCallDelta emitted by Built-in runtime (other runtimes can't without bypassing their high-level streaming APIs — documented intentional divergence)

Wave 4 — Prompt Caching (cdeb9906de)

  • CacheHint rides AgentExecutionContext.metadata("ai.cache.hint")
  • Spring AI emits OpenAiChatOptions.promptCacheKey(), LC4j emits customParameters("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 ChatMessage expansion needed (avoided 135-call-site migration via metadata sidecar)

Wave 5 — Embedding Runtimes (570fc59492 + d4f9634177)

  • SpringAiEmbeddingRuntime wrapping org.springframework.ai.embedding.EmbeddingModel (priority 200)
  • LangChain4jEmbeddingRuntime wrapping dev.langchain4j.model.embedding.EmbeddingModel (priority 190)
  • SemanticKernelEmbeddingRuntime wrapping TextEmbeddingGenerationService with Mono.block() sync boundary + List<Float>float[] unwrap (priority 180)
  • EmbabelEmbeddingRuntime.kt wrapping com.embabel.common.ai.model.EmbeddingService (priority 170)
  • BuiltInEmbeddingRuntime posting to /v1/embeddings (OpenAI-compatible, priority 50, zero-dep fallback)
  • AbstractEmbeddingRuntimeContractTest TCK base: 6 parity assertions × 5 subclasses
  • ServiceLoader discovery via META-INF/services/org.atmosphere.ai.EmbeddingRuntime × 5

Wave 6 — Per-Request Retry Policy (ae0c2d06ad)

  • RetryPolicy becomes the 19th canonical field on AgentExecutionContext (default RetryPolicy.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.buildRequest now skips threading context.retryPolicy() when reference-equal to RetryPolicy.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 to public (test-only)
  • OpenAiCompatibleClient.retryPolicy() package-private accessor (drops reflection hack in test)

Cross-Runtime Contract Testing

  • AbstractAgentRuntimeContractTest grew 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

Bug Fixes

  • fix(spring-boot): catch LinkageError in AtmosphereConsoleInfoEndpoint.detectRuntime() — non-AI samples returned 500 due to NoClassDefFoundError on AgentRuntimeResolver
  • fix(spring-boot): honor LLM_BASE_URL, LLM_MODEL, LLM_MODE env vars — null property defaults let env vars override framework fallbacks
  • fix(spring-boot): console prefers configured ai.path over handler iteration — avoids picking the wrong @AiEndpoint in multi-endpoint samples
  • fix(langchain4j): eagerly link SuccessfulHttpResponse to avoid fat-jar classloader bug (FJP callbacks couldn't resolve JdkHttpClient response types)
  • test(integration): poll Socket.status() in GrpcWasyncTransportTest to fix CI race on async status updates

Refactoring

  • refactor(spring-boot): reuse AiConfig.DEFAULT_MODE/DEFAULT_MODEL constants (eliminate duplicated string literals)
  • refactor(ai): delete stale 14-arg AgentExecutionContext shim, thread approvalStrategy explicitly

Documentation

  • Module READMEs corrected: capabilities, versions, FQCNs across all runtime modules
  • Sample READMEs fixed: dead links, fabricated snippets, version drift, AiSupportAgentRuntime rename
  • 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_OUTPUT restored for BuiltInAgentRuntime (runtime truth correction)

Stats

  • 40 commits, 7 feat, 6 fix, 5 docs, 3 chore, 2 refactor, 1 revert, 1 test
  • ~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-green through wave-6-green, polish-1-green through polish-3-green
  • Review gist: https://gist.github.com/jfarcand/63137ea2694dfa0a146cb0226cc7bd4d
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment