# SwarmAI 1.0.24 — Native Anthropic adapter + compliance substrate + financial vertical foundation

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

2. **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.

3. **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 `CacheBreakpoint`s 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 `InlineCitation`s 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 `ParallelGroup`s 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 `ComplianceFinding`s (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 `<optional>true</optional>` 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_<NAME>_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<String,Object>` 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<BatchItem>) → BatchHandle`, `status(id)`, `cancel(id)`, `results(id) → List<BatchResult>`, `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`:

| Folder | What 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

| Category | Tests added |
|---|---|
| Native Anthropic adapter (substrate validation + WireMapper + end-to-end with mocked SDK) | 33 |
| Hook lifecycle + permission rule DSL + permission modes | 35 |
| Reliability substrate (`ErrorClassifier`, `ToolCallArgRepair`, `ToolCallSafetyAnalyzer`, `LoopDetector`) | 39 |
| Skill disclosure (`SkillCatalog`) + `SkillLoader` | 19 |
| 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 resolvers | 14 |
| Server-side tool specs + Anthropic LlmClient adapter | 14 |
| 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 `@Component`s 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:

```java
// 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.
