release

SwarmAI 1.0.24 Release Notes

What changed in SwarmAI 1.0.24.

23 min read

Released: 2026-05-14
Theme: The framework gets a provider-neutral inference SPI, a native Anthropic adapter that unlocks features Spring AI doesn't surface yet (prompt caching, extended thinking, inline citations, server-side tools), a full compliance gate substrate (citation-required, single-writer invariant, delegation-depth policy, universal disclaimer), and the first slice of the financial-services vertical (Excel/PowerPoint authoring + audit, four finance computation tools, a curated MCP-connector catalog with license-aware activation).


Headline

This release adds the substrate that makes SwarmAI fit for compliance-heavy use cases โ€” financial services first, but the gates and primitives are domain-neutral. Three structural moves stand out:

  1. A provider-neutral LlmClient SPI lands alongside a new swarmai-native-anthropic module. Spring AI stays the default path; the native adapter is opt-in and unlocks prompt caching (5m / 1h tiers + read/write telemetry), extended thinking budgets, inline citations with char_location / page_location anchors, PDF document blocks, and the 2026-02-09 / 2026-01-20 / 2025-08-18 server-side tools โ€” none of which Spring AI's ChatClient currently surfaces.
  1. Compliance gates become first-class. CitationRequiredGate rejects financial output lacking citations; UniversalDisclaimer auto-stamps draft-work-product language onto every user prompt; DelegationDepthPolicy enforces one-level subagent delegation (matching the orchestrator โ†’ worker pattern from Anthropic's financial-services repo); SingleWriterInvariantEnforcer blocks two agents from writing the same correlation-id's workspace. The full LifecycleHook family (14 event types) and a permission-rule DSL (Bash(npm run ), WebFetch(domain:.acme.io), MCP(server:tool)) plug into the same hook chain.
  1. The financial vertical gets its computation layer. Four deterministic finance tools (dcf_model, lbo_model, comps_analysis, tear_sheet) ship in swarmai-tools/tool/finance/. Excel + PowerPoint authoring (XlsxAuthorTool, PptxAuthorTool) and their audit counterparts (XlsxAuditTool, DeckQcTool โ€” flags placeholders, hardcodes-in-formula-regions, magic numbers) cover the artifact side. A curated FinancialMcpConnectors catalog (LSEG LFA, S&P Capital IQ, FactSet, PitchBook, Chronograph, Daloopa, Morningstar, Moody's, MT Newswires, Aiera, Egnyte) plus a license-aware McpConnectorRegistry and McpConnectorBinder (with credential resolution) cover the data side.

What's new

Native Anthropic adapter (swarmai-native-anthropic)

  • New module swarmai-native-anthropic wraps the official com.anthropic:anthropic-java:2.32.0 SDK. Opt-in: Spring-AI users see no change.
  • AnthropicNativeClient โ€” sync send(AnthropicRequest) โ†’ AnthropicResponse. Honours every Phase-1 substrate feature.
  • Prompt caching: CachePolicy with up to 4 CacheBreakpoints anchored on logical slots (tools, system, documents, messages:N, messages:last), 5-minute and 1-hour TTL tiers. CacheUsage surfaces read tokens + per-tier write breakdown on every response.
  • Extended thinking: ThinkingBudget with ENABLED / INTERLEAVED modes (the latter via beta interleaved-thinking-2025-05-14), explicit budget_tokens, FULL / SUMMARIZED / OMITTED display modes.
  • Inline citations: CitationConfig + DocumentBlock (PDF base64 / PDF URL / plain text / custom-content blocks). Responses carry InlineCitations with sealed CitationLocation variants โ€” CharLocation, PageLocation, ContentBlockLocation.
  • Server-side tools (ServerToolSpec sealed family): WebSearch (allow / block domain lists, max uses), WebFetch, CodeExecution, Memory. Translated to the dated SDK types (WebSearchTool20260209, CodeExecutionTool20260120, MemoryTool20250818) inside WireMapper; consumers see stable names.
  • Tool roundtrip: full ToolUse โ†’ caller-executes โ†’ ToolResult flow with disable_parallel_tool_use, strict tool use, ToolChoice (auto / any / none).
  • AnthropicLlmClient adapter implements the provider-neutral LlmClient SPI on top of AnthropicNativeClient โ€” declares its supported features via LlmClient.Feature.

Provider-neutral LlmClient SPI (swarmai-core/agent/llm/)

  • LlmClient interface with send(LlmRequest) โ†’ LlmResponse + supportedFeatures() introspection.
  • LlmRequest / LlmResponse carry the substrate types (CachePolicy, ThinkingBudget, CitationConfig, DocumentBlock, InlineCitation, TokenUsage) so callers don't lose features when running on a non-Anthropic provider โ€” adapters that don't support a feature silently drop it.
  • LlmMessage / LlmToolCall / LlmRole / LlmStopReason provide a uniform message shape across providers.
  • LlmClient.Feature enum (PROMPT_CACHING, EXTENDED_THINKING, INLINE_CITATIONS, PDF_DOCUMENTS, VISION, STRICT_TOOL_USE, STREAMING, CACHE_TELEMETRY) lets compliance gates fail fast when a required behaviour isn't honoured by the active adapter.

Hook lifecycle + permission DSL (swarmai-core/agent/hook/, tool/permission/, agent/permission/)

  • LifecycleEvent sealed family โ€” 14 event types: UserPromptSubmit, SessionStart, SessionEnd, PreCompact, PostCompact, Stop, SubagentStart, SubagentStop, TaskCreated, TaskCompleted, FileChanged, PermissionRequest, PermissionDenied, Notification.
  • LifecycleHook interface with per-event default methods + a pattern-matching onEvent fallback. HookResult carries PROCEED / BLOCK / MODIFY / ASK semantics; MODIFY lets hooks rewrite prompts mid-flight.
  • LifecycleHookRegistry โ€” first-non-PROCEED-wins dispatch; isolates throwing hooks so a buggy audit hook can't crash the agent loop; CopyOnWriteArrayList-backed for hot registration.
  • Permission rule DSL โ€” PermissionRule.Specifier sealed variants: Any, Glob, Domain, McpQualified. RuleParser.parse("Bash(npm run )") / "Read(./.env)" / "WebFetch(domain:.acme.io)" / "MCP(slack:post-message)". PermissionRules with deny-first-then-ask-then-allow evaluation and scope merge (managed > flags > local > project > user).
  • PermissionMode โ€” DEFAULT, ACCEPT_EDITS, PLAN, AUTO, DONT_ASK, BYPASS_PERMISSIONS. Resolves the UNMATCHED decision from rule evaluation based on the tool's PermissionLevel.

Reliability substrate (swarmai-core/agent/resilience/, agent/llm/, agent/parallel/, tool/hooks/)

  • FailoverReason enum + ErrorClassifier โ€” classifies HTTP-status + exception-chain shapes into one of RETRYABLE, SHOULD_COMPRESS, SHOULD_ROTATE_CREDENTIAL, SHOULD_FALLBACK, NOT_RECOVERABLE. Provider-agnostic โ€” pattern-matches on common error strings.
  • ToolCallArgRepair โ€” string-literal-aware state machine that fixes trailing commas, unclosed braces / brackets, Python literals (True / False / None โ†’ true / false / null), surrogate halves, C0 control characters in tool-call JSON.
  • ToolCallSafetyAnalyzer โ€” partitions a batch of pending tool calls into ParallelGroups based on PARALLEL_SAFE_TOOLS allowlist, NEVER_PARALLEL_TOOLS denylist, and path-overlap detection. Conservative on conflict (serialises when in doubt).
  • LoopDetector โ€” ToolHook impl tracking per-agent history. Warns at N identical-args failures, blocks at 2N. Also detects idempotent-tool same-result loops.

Compliance gates (swarmai-core/governance/compliance/)

  • ComplianceGate interface + ComplianceEvaluator that runs an ordered list and aggregates ComplianceFindings (BLOCKER / WARNING / INFO severities) into a ComplianceReport.
  • CitationRequiredGate โ€” strict() and lenient() modes. Per-sentence numeric-claim anchoring via shared numeric tokens with %/x/$ suffix awareness (so 30% FY25 doesn't false-anchor to a citation talking about 25%).
  • UniversalDisclaimer โ€” LifecycleHook that prepends a draft-work-product directive to every user prompt. Default text is FS-grade; customisable.
  • DelegationDepthPolicy โ€” LifecycleHook tracking subagent depth per correlation id; blocks SubagentStart events that would exceed maxDepth (default 1, matches the one-level-delegation pattern).
  • SingleWriterInvariantEnforcer โ€” LifecycleHook on FileChanged events; first writer per correlation id claims the slot; second writer is blocked. yieldWriter(correlationId, agentId) releases the slot for explicit handoffs.

Skill disclosure + loader (swarmai-core/skill/disclosure/)

  • SkillCatalog โ€” Tier-1 metadata listing (list(options)) + Tier-2 body fetch (view(name)). Scope cascade with precedence PROJECT > PERSONAL > PLUGIN > ENTERPRISE; token-budget enforcement; explicit allow/deny scopes filter for /mcp status-style UIs.
  • SkillMetadata record โ€” Tier-1 entry (~50 tokens) carrying name, description, whenToUse, argumentHint, scope, SkillReadiness (AVAILABLE / SETUP_NEEDED / UNSUPPORTED).
  • SkillFrontmatter โ€” extended fields beyond the existing SkillDefinition: allowedTools (per-skill tool allowlist), model, effort (MINIMAL / STANDARD / EXTENDED), contextMode (INHERIT / FORK), agent, hooks, paths, disableModelInvocation, userInvocableOnly, argumentHint, setupHint.
  • SkillLoader โ€” disk scanner. Walks .swarm/skills/, ~/.swarm/skills, plugin directories; parses SKILL.md via SkillDefinition.fromSkillMd; registers entries with the catalog under the appropriate scope. refresh() re-scans.

Office authoring + audit (swarmai-tools/tool/office/)

  • XlsxAuthorTool โ€” Apache POI-backed .xlsx builder. Per-cell text / number / percent / formula / bold directives. Counts hardcodes vs formulas in the output (hardcodeRatio returned).
  • XlsxAuditTool โ€” reads .xlsx; reports per-sheet formula/hardcode/error counts; flags formula-evaluation errors (#REF!, #DIV/0!), magic numbers (large unformatted constants), and exceeds-hardcode-threshold sheets. audit: PASS|FAIL verdict.
  • PptxAuthorTool โ€” deck builder; title + subtitle + bullets + tables; layout selection (TITLE, TITLE_AND_CONTENT, BLANK).
  • DeckQcTool โ€” reads .pptx; flags placeholders (TBD, [TK], X.X%, $XXX), missing titles, empty slides. qc: PASS|FAIL verdict.
  • Apache POI added as an true dep โ€” pulls in ~10 MB only for consumers that want office output.

Finance computation tools (swarmai-tools/tool/finance/)

  • FinanceTool marker interface + AbstractFinanceTool base class โ€” typed input coercion (asDouble, asInt, required), standard ok()/error() envelope, exception-to-error-response wrapping, 4-decimal rounding convention.
  • DcfModelTool โ€” FCF projection from base-year financials + assumptions; Gordon growth terminal value; 3ร—3 WACC/terminal-growth sensitivity grid.
  • LboModelTool โ€” entry purchase โ†’ year-by-year operating + debt paydown โ†’ exit equity / IRR / MOIC.
  • CompsAnalysisTool โ€” peer-set multiples โ†’ median/mean/low/high โ†’ implied equity per share for each multiple. P/E is treated as equity-level; EV multiples are enterprise-level (net-debt adjustment applied).
  • TearSheetTool โ€” composes the other tools' output into a structured .xlsx via XlsxAuthorTool (summary + financials + multiples sheets).
  • FinanceToolBundle.instances() one-line registration for non-Spring callers.

Financial MCP connectors (swarmai-tools/tool/finance/mcp/)

  • McpConnectorDefinition record โ€” name, vendor, endpoint, AuthScheme (OAUTH / BEARER / API_KEY / NONE), requiredEdition (COMMUNITY / TEAM / BUSINESS / ENTERPRISE from existing LicenseProvider), requiredFeature flag, advertised tool preview.
  • FinancialMcpConnectors โ€” curated catalog of 11 definitions: LSEG LFA, S&P Capital IQ, FactSet, PitchBook, Chronograph (Enterprise tier); Daloopa, Morningstar, Moody's, MT Newswires, Aiera (Business tier); Egnyte (Team tier).
  • McpConnectorRegistry โ€” license-aware filtering (activeConnectors()), explicit allowlist / denylist overrides, structured statusReport() (per-connector ACTIVE / LICENSE_FLOOR_NOT_MET / FEATURE_FLAG_MISSING / DISABLED).
  • McpCredentials record + McpCredentialResolver interface โ€” FromEnvironment reads SWARM_MCP__BEARER / _API_KEY / _OAUTH_TOKEN; InMemory impl for tests.
  • McpConnectorBinder โ€” uses HttpClientSseClientTransport from the MCP Java SDK to connect to each active connector, injects auth header per scheme, enumerates server-side tools via listTools(). Returns Binding(definition, client, tools) per connector. Skips connectors with missing credentials; statusReport() explains every decision.

Reasoning + recall primitives

  • MixtureOfAgentsTool (swarmai-core/tool/reasoning/) โ€” provider-agnostic BaseTool that fans out a single prompt to N reference inference functions in parallel, then synthesises via an aggregator function (not voting). Caller supplies the inference lambdas; supports mixing Claude + GPT + Gemini + local models.
  • TrajectoryWriter (swarmai-core/observability/trajectory/) โ€” splits runs into trajectory_samples.jsonl + failed_trajectories.jsonl. TrajectoryEntry carries goal + turns + verdict + attributes โ€” ShareGPT-compatible. Thread-safe.
  • GoalLockMode (swarmai-core/agent/mode/) โ€” LifecycleHook that re-injects a goal directive on every user-prompt event. Keeps agents anchored on long-running cron tasks.
  • SessionIndex + SessionSearchTool (swarmai-core/memory/fts/ + tool/memory/) โ€” SQLite FTS5-backed full-text index over conversation transcripts. BM25 ranking, prefix wildcards, NEAR/n proximity. Per-agent filtering; forgetSession() for deletion requests.
  • TurnSnapshotStore (swarmai-core/agent/checkpoint/) โ€” JGit-backed shadow git repo at ~/.swarm/checkpoints/store/. Per-turn snapshot, restore, unified diff between any two snapshots, recent-log enumeration. Distinct from GitWorktreeWorkspace (per-task isolation): this is global + automatic + per-turn.

Build hardening (swarmai-tools/pom.xml)

  • Five Spring deps declared explicitly at compile scope and one at test scope (spring-jdbc, spring-web, spring-ai-vector-store, mcp, spring-ai-client-chat). Maven's nearest-wins rule was giving the test-scoped spring-boot-starter-data-jpa precedence over swarmai-core's transitive compile-scope, so the explicit declarations make the classpath stable regardless of how upstream deps shift. No behavioural change โ€” the imports were already resolving correctly until adding optional sqlite-jdbc / jgit to swarmai-core perturbed Maven's resolution path enough to expose the latent issue.

1.0.24 follow-ups (shipped in this release)

The 1.0.24 substrate landed broad-but-shallow: each subsystem ships as a callable library, but several were not yet wired into the existing Agent runtime. The follow-up work below lands the wiring + closes the gaps the original release deferred to the roadmap. Everything in this section is in 1.0.24-SNAPSHOT and demonstrated by a corresponding example under swarm-ai-examples/.

Adaptive thinking for Opus 4.7 (swarmai-core/agent/llm/, swarmai-native-anthropic)

Claude Opus 4.7 retired thinking.type=enabled + budget_tokens and now requires thinking.type=adaptive with an output_config.effort ceiling (LOW / MEDIUM / HIGH / XHIGH / MAX). The adapter now speaks both:

  • New ThinkingEffort enum (LOW/MEDIUM/HIGH/XHIGH/MAX) maps 1:1 to Anthropic's effort tiers.
  • New ThinkingMode.ADAPTIVE and ThinkingBudget.adaptive(effort) factory; the existing ThinkingBudget(mode, budgetTokens, display) constructor is preserved so existing call-sites keep working.
  • WireMapper.toThinkingConfig now switches on mode and emits ThinkingConfigAdaptive for ADAPTIVE; new WireMapper.toOutputConfig builds the OutputConfig.effort block, wired in AnthropicNativeClient.toSdkParams.
  • Demo: swarm-ai-examples/citation-required-pipeline was switched back from Sonnet 4.5 to Opus 4.7 + ThinkingBudget.adaptive(MEDIUM). End-to-end PASS with 5 inline citations.

LlmClient SPI plugged into Agent.callLlm (swarmai-core/agent/Agent.java)

The provider-neutral SPI is now actually wired into Agent:

  • New Agent.Builder.llmClient(LlmClient) โ€” when set, single-shot text-only calls bypass Spring AI entirely and route through the SPI.
  • The Spring AI ChatClient stays required (and remains the fallback for tool-bearing calls and streaming) โ€” substituting the SPI at the LLM-dispatch layer doesn't force a rewrite of either the agent loop or tool orchestration.
  • Agent.callLlm synthesises a Spring AI ChatResponse from the SPI's LlmResponse (AssistantMessage + Generation + ChatResponseMetadata carrying a DefaultUsage) so downstream code (token extraction, observability, retry) sees the same shape on both paths.
  • Demo: swarm-ai-examples/llm-client-spi runs the same task once with the default Spring AI baseline, once with AnthropicLlmClient plugged in. The SPI path returns ~1000 chars in ~9s with Anthropic's token usage (428 input / 196 output) plumbed through to SwarmOutput.getTotalPromptTokens().

Reliability substrate wired into Agent.callWithRetry (swarmai-core/agent/Agent.java)

The Phase-3 deliverables were standalone classes; the agent's existing ad-hoc retry loop ignored them. The follow-up routes the retry decision through the substrate:

  • Agent.callWithRetry now calls ErrorClassifier.classify(cause) on every exception. The classifier informs log lines and observability (classifier=RETRYABLE / classifier=SHOULD_ROTATE_CREDENTIAL appear in warn output) but does not narrow what is retried โ€” backward compatibility with 1.0.16 is preserved by keeping the original wide-net rule (if msg.contains("400") || "401" || "403" || "context_length_exceeded" || "NonTransient" โ†’ fail fast; everything else โ†’ retry). The classifier's role is advisory taxonomy, not gatekeeping. This guarantees no workflow that ran on 1.0.16 sees a new retry refusal on 1.0.24.
  • ErrorClassifier itself was strengthened: Spring AI's response-side breakage (RestClientException โ†’ HttpMessageNotReadableException โ†’ JsonEOFException) and bare java.io.IOException now classify as RETRYABLE instead of falling through to NOT_RECOVERABLE. Two new regression tests in ErrorClassifierTest lock the chain in place.
  • Default retry count bumped from 3 โ†’ 5 with exponential backoff (2 s / 4 s / 8 s / 16 s โ†’ 30 s wall ceiling) so transient OpenAI 5xx and edge-truncation events have more chances to recover before falling through to the per-workflow failure path.
  • LoopDetector, LlmCircuitBreaker, ToolCallSafetyAnalyzer remain opt-in: LoopDetector plugs in via Agent.builder().toolHook(new LoopDetector()); LlmCircuitBreaker and ToolCallSafetyAnalyzer are documented as standalone substrate.
  • Demo: swarm-ai-examples/reliability-substrate is an offline self-test that exercises all four primitives (10/10 assertions pass). Includes the 7-row classifier table, the loop-detector deny-on-attempt-7 verification, the circuit-breaker CLOSEDโ†’OPEN transition trace, and the read/write parallel-batching case.

HTTP client compatibility shim (swarmai-core/config/HttpClientCompatConfiguration.java)

Starting 2026-05-14 on JDK 21.0.10 (WSL2 + Spring AI 1.0.4), OpenAI's edge began returning truncated bodies for chat-completion requests sent over Transfer-Encoding: chunked whenever the request carried tool definitions. Every retry hit the same JsonEOFException: Unexpected end-of-input because the response stream was cut before the closing brace. The same request body sent with an explicit Content-Length header returned cleanly โ€” verified by routing requests through a local HTTPโ†’HTTPS proxy that re-frames chunked into Content-Length. Non-tool requests were small enough not to trip the bug.

The 1.0.24 framework now ships a compatibility shim that fixes this transparently for every user:

  • New auto-configuration HttpClientCompatConfiguration registered via META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports.
  • Provides a ClientHttpRequestFactory bean backed by SimpleClientHttpRequestFactory with setBufferRequestBody(true) โ€” Spring's HttpURLConnection-based factory pre-buffers the JSON body and sends it with an accurate Content-Length header instead of chunked framing.
  • Provides a @Primary RestClient.Builder bean wired to that factory, plus a defensive BeanPostProcessor that rewrites any other RestClient.Builder bean (covering Spring AI's various injection patterns).
  • Activates only when RestClient is on the classpath; respects user overrides via @ConditionalOnMissingBean.
  • Backward compatible: zero API change. Existing 1.0.16-shaped apps upgrade to 1.0.24 by bumping the version โ€” no code, no config, no flag.
  • Verified: the standalone tool-calling example, which failed every retry on every direct HTTPS call under the conditions above, now completes in 23 s with no retries. The full judge-all regression now exercises tool-bearing workflows cleanly.

This is the single most load-bearing fix in 1.0.24 for users on JDK 21 + OpenAI today.

Lifecycle hooks wired into Agent.callLlm (swarmai-core/agent/Agent.java)

LifecycleHookRegistry was a callable library; agents didn't fire events. The follow-up dispatches the most-load-bearing event:

  • New Agent.Builder.lifecycleHookRegistry(LifecycleHookRegistry) โ€” when set, every LLM call emits a LifecycleEvent.UserPromptSubmit to the registered hooks before the model receives the prompt.
  • Hooks may return BLOCK (raises AgentExecutionException), MODIFY (rewrites the prompt before dispatch), ASK (escalation hook for AskUserQuestion integration โ€” TODO), or PROCEED.
  • When unset (default), dispatch is a no-op with zero overhead โ€” existing apps see no behaviour change.
  • The other 13 lifecycle events (SessionStart, Stop, SubagentStart, FileChanged, PermissionRequest, โ€ฆ) remain dispatchable but are not yet emitted from inside the agent loop. Each future emission point is a focused follow-up.
  • Demo: the lifecycle hook chain is exercised in swarm-ai-examples/governance-substrate step 1 โ€” a counting hook + PII-redactor on a fake-SSN prompt, with MODIFY short-circuiting the chain after the counter sees the event.

Settings layer (swarmai-core/settings/)

New utility for the project / user / machine config layering pattern:

  • New SettingsLayer enum with defaultPath() per layer (./.swarmai/settings.yml, ~/.swarmai/settings.yml, /etc/swarmai/settings.yml).
  • New SettingsMerger โ€” caller-supplies parsed Map per layer (YAML-loader-agnostic so swarmai-core doesn't pull a YAML dependency); the merger flattens nested maps to dotted keys (llm.model.name) and applies PROJECT > USER > MACHINE precedence.
  • New MergedSettings โ€” read-only view exposing both effective values and provenance per dotted key. Answers "where did this setting come from?" for support cases without diffing files.
  • Not auto-loaded from the filesystem yet โ€” that's a follow-up. The merge logic is the shipping piece.
  • Demo: swarm-ai-examples/governance-substrate step 2 โ€” three layers with overlapping keys, asserts each effective value comes from the right layer (4 โœ“).

Tool-call risk classifier (swarmai-core/agent/permission/)

New pure-function classifier for the auto-classification piece:

  • New ToolCallRiskClassifier returns a Tier (READ_ONLY / NETWORK / MUTATION / DESTRUCTIVE) for any (toolName, arguments) pair.
  • Three signals: tool-name keyword lexicons (read/get/list โ†’ READ_ONLY, write/create/update โ†’ MUTATION, delete/drop/rm โ†’ DESTRUCTIVE, โ€ฆ); argument-key inspection (path arg promotes to MUTATION, url arg to NETWORK); caller-supplied overrides for tools whose names lie about their effect (force_full_clean etc.).
  • Conservative: when ambiguous, returns the higher tier.
  • Not auto-applied yet โ€” feeds into PermissionRequest via a custom hook today; auto-stamping into the permission pipeline is a follow-up.
  • Demo: swarm-ai-examples/governance-substrate step 3 โ€” 8 representative calls covering all four tiers (8 โœ“).

Anthropic Message Batches client (swarmai-native-anthropic/llm/anthropic/batch/)

Was deferred in 1.0.24's roadmap. Now shipped:

  • New AnthropicBatchClient with submit(List) โ†’ BatchHandle, status(id), cancel(id), results(id) โ†’ List, resultsForEach(id, consumer) for streaming, delete(id).
  • New substrate types: BatchProcessingStatus (IN_PROGRESS/CANCELING/ENDED), BatchHandle (id + counts + timestamps + results-url), BatchItem (customId + AnthropicRequest), BatchItemOutcome (sealed: Succeeded(AnthropicResponse) | Errored(type, message) | Canceled | Expired), BatchResult, BatchRequestCounts.
  • Per-item translation mirrors AnthropicNativeClient.toSdkParams โ€” caching breakpoints, extended/adaptive thinking, server-side tools, tool-choice all propagate through the batch path unchanged.
  • WireMapper lifted to public final (with internal-API javadoc) so the batch sub-package can reuse the field-by-field translation.
  • Demo: swarm-ai-examples/anthropic-batch submits 5 synthetic 10-K excerpts as one batch, polls until ENDED (~1โ€“2 min for small batches), joins results back by customId. Self-verifying: counts how many returned summaries contain the source-of-truth revenue figure (5/5 โœ“), then prints the realised cost saved ($0.002553 saved on this run, scales to ~$51 on a 100K-item batch) using Sonnet 4.5's published prices and the 50% batch discount.

Anthropic Files API client (swarmai-native-anthropic/llm/anthropic/files/)

Was deferred in 1.0.24's roadmap. Now shipped:

  • New AnthropicFilesClient with upload(Path), upload(InputStream), list(), metadata(fileId), download(fileId) โ†’ byte[], download(fileId, Path), delete(fileId). Sets the files-api-2025-04-14 beta header on every call.
  • New AnthropicFile substrate (id + filename + MIME type + sizeBytes + createdAt + downloadable).
  • The downloadable flag is exposed so callers can detect that arbitrary user uploads are accepted-for-reference but not echoable (Anthropic only re-emits bytes for code-execution / model-generated artifacts as of 2026-Q1).
  • Substrate integration of DocumentBlock.File(file_id) for the messages path is not yet shipped โ€” it requires the beta messages surface and is captured as a future change. This client is most useful today for upload + lifecycle management ahead of caller code that does its own message dispatch.
  • Demo: swarm-ai-examples/anthropic-files walks the full lifecycle (write local โ†’ upload โ†’ metadata round-trip โ†’ list โ†’ conditional download with SHA-256 round-trip โ†’ cleanup in finally). All assertions print โœ“/โœ—; the downloadable=false case is detected and labelled [skipped] with the rationale rather than crashing.

--demo Spring profile (swarm-ai-examples/.infra/src/main/resources/application-demo.yml)

Quality-of-life for the example runner:

  • New profile silences Spring Boot/Hibernate/Hikari/Spring AI startup chatter (root logger โ†’ WARN, examples + workflow-runner stay at INFO, banner off, one-line console pattern).
  • run.sh accepts DEMO=1 and appends ,demo to the active profile string. run.sh now also passes --spring.profiles.active=$profile on the Ollama branch (previously only on the OpenAI branch โ€” that bug masked the profile silently).
  • All five new 1.0.24 examples and their per-example wrappers default DEMO=1, so the first thing the user sees is the example output, not 100 lines of init log.

Five new example wrappers (swarm-ai-examples/)

Each has its own folder, run.sh, README, and registration in SwarmAIWorkflowRunner:

FolderWhat it shows
anthropic-batch/Batches client end-to-end, fact-checks summaries against source map, computes realised $ saved
anthropic-files/Files API lifecycle with SHA-256 round-trip + downloadable-flag handling
llm-client-spi/Same task two paths: Spring AI baseline vs AnthropicLlmClient SPI side-by-side
reliability-substrate/Offline self-test of ErrorClassifier + LoopDetector + LlmCircuitBreaker + ToolCallSafetyAnalyzer (10/10 assertions)
governance-substrate/Offline self-test of LifecycleHookRegistry + SettingsMerger + ToolCallRiskClassifier (15/15 assertions)

Both substrate self-tests run offline (no API keys) so they double as smoke tests on every clean checkout.


Test coverage delta

CategoryTests added
Native Anthropic adapter (substrate validation + WireMapper + end-to-end with mocked SDK)33
Hook lifecycle + permission rule DSL + permission modes35
Reliability substrate (ErrorClassifier, ToolCallArgRepair, ToolCallSafetyAnalyzer, LoopDetector)39
Skill disclosure (SkillCatalog) + SkillLoader19
Office authoring + audit (Xlsx + Pptx + audit + QC)12
Compliance gates (CitationRequired strict/lenient, UniversalDisclaimer, DelegationDepth, SingleWriter)22
Finance tools (DCF, LBO, comps, tear sheet)8
Financial MCP connector registry + binder + credential resolvers14
Server-side tool specs + Anthropic LlmClient adapter14
Quality + recall primitives (MoA, TrajectoryWriter, GoalLockMode, SessionIndex, TurnSnapshotStore)31

Total: ~227 new tests. Full suite remains green; the swarmai-tools build hardening detailed above ensures pre-existing tests continue to compile on the new dep graph.


Breaking changes

None. This release is additive:

  • The new swarmai-native-anthropic module is opt-in via Maven coordinate. Apps that don't depend on it are unaffected.
  • The LlmClient SPI is a new interface; existing Agent.builder().chatClient(...) callers continue to use Spring AI's ChatClient directly. Opting in via the new Agent.builder().llmClient(...) is additive โ€” both paths can coexist on the same agent.
  • All new substrate types (CachePolicy, ThinkingBudget, LifecycleHook, PermissionRules, ComplianceGate, etc.) are net-new APIs โ€” no existing callers reference them yet.
  • New tools (XlsxAuthorTool, DcfModelTool, โ€ฆ) register as @Components and are picked up by Spring's component scan; non-Spring users continue to instantiate tools explicitly.
  • swarmai-tools/pom.xml gained five explicit dep declarations; these were already on the transitive classpath at compile scope until this release's swarmai-core changes perturbed Maven's resolution. Making them explicit is a robustness improvement, not a breaking change for consumers.

Migration

Existing applications need no changes to upgrade. To opt into the new primitives:

// Native Anthropic adapter (add the new module as a Maven dependency first)
AnthropicNativeClient anthropic = AnthropicNativeClient.from(AnthropicConfig.fromEnv());
AnthropicResponse resp = anthropic.send(AnthropicRequest.builder()
        .model("claude-opus-4-7")
        .system("You are a financial analyst")
        .message(AnthropicMessage.userText("Summarise ACME's 10-K"))
        .document(DocumentBlock.Pdf.ofBytes("ACME-10K", "ACME 10-K", pdfBytes, /*cite*/ true))
        .cachePolicy(CachePolicy.builder()
                .breakpoint(CacheBreakpoint.oneHour("system"))
                .breakpoint(CacheBreakpoint.fiveMinutes("documents"))
                .build())
        // Opus 4.7 requires adaptive thinking + output_config.effort.
        // For Sonnet 4.5 / Opus 4.1, use ThinkingBudget.enabled(8192) instead.
        .thinkingBudget(ThinkingBudget.adaptive(ThinkingEffort.MEDIUM))
        .citationConfig(CitationConfig.ENABLED)
        .build());

// Plug the SPI into Agent so single-shot text-only calls bypass Spring AI
// and unlock native Anthropic features (caching, citations, thinking) without
// rewriting the agent loop.
Agent analyst = Agent.builder()
        .role("Analyst")
        .goal("Summarise filings")
        .backstory("โ€ฆ")
        .chatClient(chatClient)               // still required: tool-calling + streaming fallback
        .llmClient(new AnthropicLlmClient(anthropic))
        .lifecycleHookRegistry(hookRegistry)  // optional: PII redactor, audit emitter, prompt-injection sanitiser
        .modelName("claude-sonnet-4-5")
        .build();

// Anthropic Message Batches โ€” 50% of standard request price for offline workloads
AnthropicBatchClient batch = AnthropicBatchClient.from(AnthropicConfig.fromEnv());
BatchHandle h = batch.submit(items);
while (h.processingStatus() != BatchProcessingStatus.ENDED) {
    Thread.sleep(5_000);
    h = batch.status(h.id());
}
for (BatchResult r : batch.results(h.id())) {
    switch (r.outcome()) {
        case BatchItemOutcome.Succeeded ok -> handle(r.customId(), ok.response());
        case BatchItemOutcome.Errored err  -> log.warn("{}: {}", r.customId(), err.message());
        case BatchItemOutcome.Canceled c   -> /* batch cancelled */;
        case BatchItemOutcome.Expired e    -> /* hit the 24h deadline */;
    }
}

// Anthropic Files API โ€” upload once, reference by file_id (current scope: lifecycle mgmt)
AnthropicFilesClient files = AnthropicFilesClient.fromEnv();
AnthropicFile uploaded = files.upload(Path.of("acme-10k.pdf"));
try { /* reference uploaded.id() in subsequent calls */ }
finally { files.delete(uploaded.id()); }

// Layered settings (PROJECT > USER > MACHINE) with provenance lookup
MergedSettings merged = new SettingsMerger()
        .with(SettingsLayer.MACHINE, machineYaml)
        .with(SettingsLayer.USER, userYaml)
        .with(SettingsLayer.PROJECT, projectYaml)
        .merge();
String model = (String) merged.get("llm.model").orElseThrow();
SettingsLayer source = merged.provenance("llm.model").orElseThrow();   // โ†’ PROJECT

// Risk-classify a tool call before letting it through a permission gate
ToolCallRiskClassifier classifier = new ToolCallRiskClassifier();
Tier tier = classifier.classify("file_delete", Map.of("path", "/etc/hosts"));
if (tier.atLeast(Tier.MUTATION)) {
    /* require user approval */
}

// Compliance gate before delivering output
ComplianceEvaluator gate = new ComplianceEvaluator(List.of(CitationRequiredGate.strict()));
ComplianceReport report = gate.evaluate(
        ComplianceGate.Output.of(resp.text(), resp.citations()));
if (!report.pass()) {
    // surface findings to user / hold for human review
}

// Permission rule DSL โ€” gate tool execution before invocation
PermissionRules rules = PermissionRules.builder()
        .allow("Bash(npm *)")
        .deny("Bash(rm -rf *)")
        .ask("Bash(git push *)")
        .build();
PermissionDecision decision = rules.evaluate(
        PermissionRequest.of("Bash", Map.of("command", "npm install")));
PermissionDecision final = PermissionMode.DEFAULT.resolve(decision, tool.getPermissionLevel());

// Disk-based skill loader โ€” register every SKILL.md under the project + user dirs
SkillCatalog catalog = new SkillCatalog();
new SkillLoader(catalog)
        .scan(SkillScope.PROJECT,    Path.of(".swarm/skills"))
        .scan(SkillScope.PERSONAL,   Path.of(System.getProperty("user.home"), ".swarm/skills"))
        .loadAll();
String listing = catalog.renderListing(SkillCatalog.SkillListingOptions.defaults());

// Author + audit an Excel financial model
XlsxAuthorTool author = new XlsxAuthorTool(Path.of("output"));
author.execute(Map.of("spec", dcfSpec));
Map<String,Object> auditReport = (Map<String,Object>) new XlsxAuditTool(Path.of("output"))
        .execute(Map.of("path", "dcf.xlsx", "hardcodeThresholdRatio", 0.4));

// Activate a financial MCP connector under an enterprise license
McpConnectorRegistry registry = McpConnectorRegistry.defaultRegistry(licenseProvider);
McpConnectorBinder binder = new McpConnectorBinder(registry,
        new McpCredentialResolver.FromEnvironment());
List<McpConnectorBinder.Binding> bindings = binder.bindAll();
// each binding carries a live McpSyncClient + advertised tool list

Known limitations + roadmap

The following are deliberately deferred โ€” substrate is shipped, integration into the existing Agent.callLlm() flow is its own focused work:

  • LlmClient SPI is not yet plugged into Agent.callLlm(). Callers using the SPI directly get the full benefit; existing Agent.builder().chatClient(...) users continue with Spring AI as before. The follow-up wires Agent.Builder.llmClient(...) so the substrate features (caching, citations, thinking budgets) become the default.
  • LifecycleHookRegistry + PermissionRules are not yet fired by Agent. They're callable libraries; the follow-up emits events at agent lifecycle points and routes tool execution through the permission decision pipeline.
  • ErrorClassifier is not yet wired into LlmCircuitBreaker. Retry / fallback decisions still use the raw exception types. The follow-up reads the FailoverReason taxonomy instead.
  • Anthropic Message Batches API client โ€” the SDK's BatchCreateParams.Request.Params shape requires duplicating the wire-mapping logic from toSdkParams. Worth doing for 50%-cost overnight workflows but captured as a separate task.
  • Anthropic Files API client โ€” multipart upload + retrieve / download. Pairs with the file_id source variant on DocumentBlock.Pdf for long-doc workflows.
  • Skill loader live change detection โ€” current refresh() is manual; a watch-service-driven incremental updater is a follow-up.
  • OAuth token refresh for MCP connectors โ€” current McpConnectorBinder uses the token as-is; refresh / expiry monitoring is deferred.

Acknowledgements

The compliance-gate pattern (citation-required, single-writer invariant, one-level delegation) draws on Anthropic's published financial-services repo. The provider-neutral LLM SPI shape mirrors the structure LangChain settled on for its multi-provider story. The Tier-1 / Tier-2 progressive skill disclosure pattern is a common technique across modern agent frameworks. The substrate, types, and integration are written for SwarmAI.