Skip to content

Commit 05c7094

Browse files
committed
docs+website: correct hallucinated claims from validation sweep
1 parent 64c1780 commit 05c7094

11 files changed

Lines changed: 103 additions & 69 deletions

File tree

docs/src/content/docs/agents/embeddings.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -179,14 +179,15 @@ Every concrete `EmbeddingRuntime` ships with a contract-test subclass of
179179
deterministic fake embedder so the bridge plumbing is validated without
180180
live network calls.
181181

182-
The six parity assertions are:
182+
The seven parity assertions are:
183183

184184
1. `runtimeHasStableName()``name()` returns a non-blank, stable identifier
185185
2. `embedSingleTextReturnsVectorOfExpectedDimension()` — single-text round-trip
186186
3. `embedAllReturnsVectorPerInputInOrder()` — batch round-trip preserves order
187187
4. `embedAllWithEmptyListReturnsEmptyList()` — edge case
188188
5. `runtimeIsAvailableAfterFakeInjection()` — availability gate flips on injection
189189
6. `dimensionsAccessorIsNonNegativeOrMinusOne()` — dimension advertising contract
190+
7. `runtimeDeclaresStablePriority()``priority()` returns a stable integer
190191

191192
If you add a new `EmbeddingRuntime` implementation, subclass the base and
192193
supply a deterministic fake embedder via `installFakeEmbedder()` — no

docs/src/content/docs/integrations/koog.md

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,12 @@ See [ExecutionHandle](../../reference/execution-handle/) for the full cancel con
7676
|-------|---------|
7777
| `KoogAgentRuntime` | `AgentRuntime` SPI implementation |
7878
| `AtmosphereToolBridge` | Converts `ToolDefinition` → Koog `ToolDescriptor` + `ToolRegistry` with approval-gate hooks |
79-
| `KoogEmbeddingRuntime` | `EmbeddingRuntime` SPI for Koog-provided embedders (deferred — see below) |
8079

8180
## Capability matrix
8281

82+
This mirrors `KoogAgentRuntime.capabilities()` exactly — runtime truth, not
83+
aspiration.
84+
8385
| Capability | Status | Notes |
8486
|------------|:------:|-------|
8587
| `TEXT_STREAMING` || `executeStreaming` flow + `AIAgent.run()` |
@@ -88,14 +90,14 @@ See [ExecutionHandle](../../reference/execution-handle/) for the full cancel con
8890
| `STRUCTURED_OUTPUT` || System-prompt schema injection via the pipeline layer |
8991
| `SYSTEM_PROMPT` || Honored by the Koog prompt builder |
9092
| `CONVERSATION_MEMORY` || Per-session memory threaded through `AgentExecutionContext` |
91-
| `TOKEN_USAGE` || Emitted as `ai.tokens.*` metadata when the Koog client reports it |
9293
| `AGENT_ORCHESTRATION` || Works with `@Coordinator` and `@Fleet` |
94+
| `TOKEN_USAGE` || Not declared. Koog reports usage on its response objects but the bridge does not yet thread it into `StreamingSession.usage(TokenUsage)`. Tier 1 follow-up. |
9395
| `VISION` || Koog 0.7.x does not expose a stable multi-modal input API on the bridge path |
9496
| `MULTI_MODAL` || Same limitation |
9597
| `PROMPT_CACHING` || Koog 0.7.3 only ships Bedrock-specific cache variants; no OpenAI-compatible passthrough |
9698
| `PER_REQUEST_RETRY` || Inherits Koog's native retry layer (not per-request overridable from the bridge) |
9799

98-
Exclusions are **honest** — Koog declares them as absent in its `capabilities()` set so runtime-truth advertising is accurate (Correctness Invariant #5). When Koog upstream adds these surfaces in a future release, the bridge will honor them without a breaking change.
100+
Exclusions are **honest** — Koog declares them as absent in its `capabilities()` set so runtime-truth advertising is accurate (Correctness Invariant #5). When Koog upstream adds these surfaces in a future release, the bridge will honor them without a breaking change. No `KoogEmbeddingRuntime` ships today — if you need Koog-backed embeddings, wire a Spring AI or LangChain4j `EmbeddingModel` alongside the Koog agent runtime.
99101

100102
## Samples
101103

docs/src/content/docs/integrations/semantic-kernel.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ Text streaming, system prompts, conversation memory, structured output (via pipe
8383
| `CONVERSATION_MEMORY` || Per-session memory threaded through `AgentExecutionContext` |
8484
| `STRUCTURED_OUTPUT` || Via pipeline-layer schema injection (no runtime-specific support needed) |
8585
| `TOKEN_USAGE` || Reported via `TokenUsage` when SK surfaces it |
86-
| `AGENT_ORCHESTRATION` | | Works with `@Coordinator` and `@Fleet` |
86+
| `AGENT_ORCHESTRATION` | | Not declared. SK can participate in a `@Coordinator` fleet as a worker runtime, but it does not own a multi-agent dispatch loop the way ADK / Embabel / Koog do, so the capability flag is not advertised. |
8787
| `TOOL_CALLING` || **Deferred in 4.0.36.** SK's Java tool-calling API is still stabilizing; bridging will land in a follow-up. Documented as an honest exclusion in `modules/semantic-kernel/README.md`. |
8888
| `TOOL_APPROVAL` || Requires `TOOL_CALLING` — see above |
8989
| `VISION` || SK Java does not yet expose a stable multi-modal input API |

docs/src/content/docs/reference/execution-handle.md

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -44,17 +44,19 @@ Each adapter wraps its framework's native cancel mechanism so a single
4444
| Runtime | Native primitive | Hard cancel |
4545
|---------|------------------|:-----------:|
4646
| Built-in | `HttpClient` request + SSE `InputStream.close()` | ✅ true HTTP-level cancel |
47-
| Google ADK | `AdkEventAdapter.whenDone()` + `Flux.dispose()` on Runner subscription | ✅ Reactor disposal |
48-
| JetBrains Koog | `coroutineContext[Job].cancel()` captured inside `runBlocking` | ✅ coroutine cancel |
49-
| Spring AI | `Disposable.dispose()` on the streaming `Flux` | ✅ Reactor disposal |
50-
| LangChain4j | `CompletableFuture.completeExceptionally(CancellationException)` + soft-cancel flag | ⚠️ caller-side unblock; underlying HTTP drains naturally |
51-
| Embabel | Soft-cancel flag polled at goal boundaries | ⚠️ cooperative only |
52-
| Semantic Kernel | Soft-cancel flag polled at message boundaries | ⚠️ cooperative only |
53-
54-
"Hard cancel" means the underlying HTTP or coroutine is actually torn down.
55-
"Cooperative" means the caller sees cancellation immediately but the
56-
runtime's internal thread continues draining the stream until the LLM
57-
finishes — token budgets may still be consumed.
47+
| Spring AI | `reactor.core.Disposable.dispose()` on the streaming `Flux` | ✅ Reactor disposal |
48+
| LangChain4j | `CompletableFuture.completeExceptionally(CancellationException)` + `AtomicBoolean` soft-cancel flag consulted in the streaming response handler | ⚠️ caller-side unblock; underlying HTTP drains naturally |
49+
| Google ADK | `AdkEventAdapter.cancel()``io.reactivex.rxjava3.disposables.Disposable.dispose()` on the Runner subscription | ✅ RxJava3 disposal |
50+
| JetBrains Koog | `AtomicReference<Job>` captured by `executeInternal``Job.cancel()` + virtual-thread `Thread.interrupt()` belt-and-suspenders + immediate `done.complete(null)` fallback | ✅ coroutine cancel |
51+
| Semantic Kernel | **None**. `SemanticKernelAgentRuntime` does not override `doExecuteWithHandle`, so it inherits `AbstractAgentRuntime`'s default which calls `doExecute(...)` and returns the `ExecutionHandle.completed()` sentinel. `handle.cancel()` is a no-op. ||
52+
| Embabel | **None**. `EmbabelAgentRuntime` does not override `executeWithHandle`, so it inherits the `AgentRuntime` interface default which calls `execute(...)` and returns the `ExecutionHandle.completed()` sentinel. `handle.cancel()` is a no-op. ||
53+
54+
"Hard cancel" means the underlying HTTP request, coroutine, or Reactor/RxJava
55+
subscription is actually torn down. "Caller-side unblock" means the caller
56+
sees cancellation immediately but the runtime's internal thread continues
57+
draining the stream until the LLM finishes — token budgets may still be
58+
consumed. **None** means no cancel primitive is wired and `cancel()` is an
59+
idempotent no-op — a known gap for Embabel and Semantic Kernel.
5860

5961
## Usage
6062

docs/src/content/docs/reference/lifecycle-listener.md

Lines changed: 57 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -113,25 +113,41 @@ with another.
113113

114114
## Runtime coverage
115115

116-
Every tool-calling `AgentRuntime` bridge fires `onToolCall` and `onToolResult`
117-
through the `AgentLifecycleListener.fireToolCall()` /
118-
`fireToolResult()` static dispatch helpers. The helpers catch and swallow any
119-
exception so one broken listener cannot abort the pipeline (Correctness
120-
Invariant #2 — Terminal Path Completeness).
116+
Tool-calling runtime bridges fire `onToolCall` and `onToolResult` through the
117+
`AgentLifecycleListener.fireToolCall()` / `fireToolResult()` static dispatch
118+
helpers. `onStart` / `onCompletion` / `onError` are fired by
119+
`AbstractAgentRuntime`'s template-method wrapper via the protected
120+
`fireStart` / `fireCompletion` / `fireError` helpers — so they fire
121+
automatically for any runtime that extends `AbstractAgentRuntime`. Runtimes
122+
that implement `AgentRuntime` directly are responsible for firing lifecycle
123+
events themselves.
124+
125+
All helpers catch and swallow listener exceptions so one broken listener
126+
cannot abort the pipeline (Correctness Invariant #2 — Terminal Path
127+
Completeness).
121128

122129
| Runtime | `onStart` | `onToolCall` | `onToolResult` | `onCompletion` | `onError` |
123130
|---------|:---------:|:------------:|:--------------:|:--------------:|:---------:|
124131
| Built-in ||||||
125132
| Spring AI ||||||
126133
| LangChain4j ||||||
127134
| Google ADK ||||||
128-
| JetBrains Koog ||||||
129-
| Embabel ||||||
130-
| Semantic Kernel || ⚠️ | ⚠️ |||
131-
132-
Embabel has no tool-calling path — `onToolCall`/`onToolResult` never fire.
133-
Semantic Kernel's tool-calling is deferred in 4.0.36 (documented in the
134-
`modules/semantic-kernel/README.md` exclusion note).
135+
| Semantic Kernel ||||||
136+
| JetBrains Koog ||||||
137+
| Embabel ||||||
138+
139+
Built-in / Spring AI / LC4j / ADK / Semantic Kernel all extend
140+
`AbstractAgentRuntime`, so `onStart` / `onCompletion` / `onError` fire
141+
automatically in the base-class `execute` wrapper. SK is also honest about
142+
having no tool-calling path in 4.0.36.
143+
144+
Koog and Embabel implement `AgentRuntime` directly. Koog's `AtmosphereToolBridge`
145+
fires `onToolCall` / `onToolResult`, but `KoogAgentRuntime.executeWithHandle`
146+
does not currently call `fireStart` / `fireCompletion` / `fireError` — that's
147+
a documented gap the bridge will close once it either subclasses
148+
`AbstractAgentRuntime` or inlines the three calls. Embabel's runtime does
149+
neither and has no tool path, so no lifecycle events fire today — that is
150+
also a known gap.
135151

136152
## Listener error isolation
137153

@@ -222,17 +238,39 @@ replay, debugging, or eval harnesses. See
222238

223239
## Testing
224240

225-
The `AiTestClient` in `atmosphere-ai-test` exposes a capturing listener for
226-
assertions:
241+
The easiest pattern is a tiny anonymous subclass that records events into a
242+
list you can assert against:
227243

228244
```java
229-
var captured = new CapturingLifecycleListener();
230-
var context = testContext.withListeners(List.of(captured));
245+
var captured = new java.util.ArrayList<String>();
246+
var listener = new AgentLifecycleListener() {
247+
@Override
248+
public void onStart(AgentExecutionContext ctx) { captured.add("start"); }
249+
250+
@Override
251+
public void onToolCall(String toolName, java.util.Map<String, Object> args) {
252+
captured.add("toolCall:" + toolName);
253+
}
254+
255+
@Override
256+
public void onToolResult(String toolName, String resultPreview) {
257+
captured.add("toolResult:" + toolName);
258+
}
259+
260+
@Override
261+
public void onCompletion(AgentExecutionContext ctx) { captured.add("complete"); }
262+
263+
@Override
264+
public void onError(AgentExecutionContext ctx, Throwable error) {
265+
captured.add("error:" + error.getClass().getSimpleName());
266+
}
267+
};
268+
269+
var context = testContext.withListeners(java.util.List.of(listener));
231270
runtime.execute(context, session);
232271

233-
assertThat(captured.toolCalls()).containsExactly("get_weather");
234-
assertThat(captured.toolResults()).hasSize(1);
235-
assertThat(captured.completed()).isTrue();
272+
assertThat(captured).containsExactly("start", "toolCall:get_weather",
273+
"toolResult:get_weather", "complete");
236274
```
237275

238276
See [AI Testing](../testing/) for the full contract test surface.

docs/src/content/docs/reference/tool-approval-policy.md

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -222,19 +222,21 @@ void denyAllSkipsApprovalGate() {
222222
var context = testContext()
223223
.withApprovalPolicy(ToolApprovalPolicy.denyAll());
224224

225+
// Signature: (toolName, tool, args, session, strategy, policy)
225226
var result = ToolExecutionHelper.executeWithApproval(
226227
tool.name(), tool, Map.of(),
228+
testSession, // streaming session sink
227229
ApprovalStrategy.autoApprove(), // strategy should NEVER fire
228-
context.approvalPolicy(),
229-
context.listeners());
230+
context.approvalPolicy()); // DenyAll short-circuits before strategy
230231

231232
assertThat(result).contains("\"status\":\"cancelled\"");
232233
// Tool executor never ran — the AssertionError is never thrown
233234
}
234235
```
235236

236-
Contract tests in `AbstractAgentRuntimeContractTest.runtimeWithToolApprovalHonorsPolicy()`
237-
exercise every runtime subclass against all four policy variants.
237+
Contract assertions in `AbstractAgentRuntimeContractTest` exercise every
238+
runtime subclass against the four policy variants through
239+
`hitlPendingApprovalEmitsProtocolEvent` and related assertions.
238240

239241
## See also
240242

docs/src/content/docs/tutorial/24-durable-hitl.md

Lines changed: 10 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,8 @@ Three pieces cooperate:
4343
the invocation through the `ApprovalStrategy`. You already know this.
4444
2. **`CheckpointStore`** — SPI that persists the `WorkflowSnapshot` (the
4545
agent's conversation history, pending tool call, approval ID) to a
46-
durable backend (SQLite, Redis, Postgres) before the thread parks.
46+
durable backend (SQLite in-tree; Redis/Postgres/etc. are pluggable)
47+
before the thread parks.
4748
3. **`/__approval/<id>/approve`** REST endpoint that accepts the decision
4849
out-of-band, loads the snapshot, and resumes execution on the new JVM.
4950

@@ -53,16 +54,17 @@ for the REST callback.
5354

5455
## Wiring the CheckpointStore
5556

56-
Atmosphere ships three `CheckpointStore` implementations:
57+
Atmosphere 4.0.36 ships two `CheckpointStore` implementations in-tree:
5758

58-
- **`InMemoryCheckpointStore`** — default; non-durable (lost on restart)
59+
- **`InMemoryCheckpointStore`** — default; non-durable (lost on restart).
60+
Use in tests and single-process dev environments.
5961
- **`SqliteCheckpointStore`** — file-backed; survives restarts on the same
60-
host (single-node durability)
61-
- **`RedisCheckpointStore`** — network-backed; survives restarts across a
62-
fleet (multi-node durability)
62+
host (single-node durability).
6363

64-
Pick SQLite for single-host deployments, Redis for multi-host. Both share
65-
the same SPI, so your agent code doesn't change.
64+
The `CheckpointStore` interface is an SPI: you can implement your own
65+
backing store (Redis, Postgres, DynamoDB, S3…) by writing an adapter that
66+
implements `save`, `load`, `listByCoordination`, and `delete`. `SqliteCheckpointStore`
67+
is the reference implementation and the file to copy.
6668

6769
### Spring Boot configuration
6870

@@ -75,19 +77,6 @@ atmosphere:
7577
path: /var/lib/atmosphere/checkpoints.db
7678
```
7779
78-
Or for Redis:
79-
80-
```yaml
81-
atmosphere:
82-
ai:
83-
checkpoint:
84-
store: redis
85-
redis:
86-
host: redis.internal
87-
port: 6379
88-
keyspace: atmosphere:checkpoints
89-
```
90-
9180
The auto-config wires the chosen store to the coordination journal and to
9281
the `ApprovalRegistry` so pending approvals land in the store before the
9382
virtual thread parks.

website/src/components/Atmosphere.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ const pillars = [
4747
{ name: '@Coordinator & @Fleet', detail: 'Declarative multi-agent orchestration. A @Coordinator injects an AgentFleet and dispatches work to @Agent beans over local, A2A, or MCP transports. Portable across Spring Boot, Quarkus, and plain JVM.', url: 'https://async-io.org/docs/agents/coordinator/', urlLabel: 'Coordinator Docs' },
4848
{ name: '@RequiresApproval (HITL)', detail: 'Park a virtual thread until a user approves or denies a tool call via the /__approval/ wire protocol. Every @AiTool annotated with @RequiresApproval gates through the ToolApprovalPolicy sealed interface (AllowAll, DenyAll, Annotated, Custom).', url: 'https://async-io.org/docs/reference/tool-approval-policy/', urlLabel: 'ToolApprovalPolicy' },
4949
{ name: 'Agent Handoffs', detail: 'session.handoff("billing", message) transfers a conversation with full history to another @Agent. Built-in cycle guard prevents infinite handoff loops; AiEvent.Handoff surfaces on the wire for UI state transitions.', url: 'https://async-io.org/docs/tutorial/10-ai-tools/', urlLabel: 'Tutorial' },
50-
{ name: 'Durable HITL', detail: 'CheckpointStore SPI (in-memory, SQLite, Redis) persists parent-chained conversation snapshots. An approval that arrives after a JVM restart resumes the exact turn and resurrects the execution on a fresh virtual thread.', url: 'https://async-io.org/docs/tutorial/24-durable-hitl/', urlLabel: 'Durable HITL Tutorial' },
50+
{ name: 'Durable HITL', detail: 'CheckpointStore SPI with InMemory and SQLite backends in-tree, pluggable for your own store. Parent-chained conversation snapshots so an approval arriving after a JVM restart resumes the exact turn on a fresh virtual thread.', url: 'https://async-io.org/docs/tutorial/24-durable-hitl/', urlLabel: 'Durable HITL Tutorial' },
5151
{ name: 'ExecutionHandle', detail: 'Cooperative cancel for in-flight agent executions. Each runtime wraps its native cancel primitive (HTTP close, coroutine Job cancel, Reactor Flux dispose). handle.cancel() is idempotent and thread-safe.', url: 'https://async-io.org/docs/reference/execution-handle/', urlLabel: 'ExecutionHandle Docs' },
5252
],
5353
extras: ['LLM-as-judge eval', 'Long-term memory', 'Conditional routing', 'Lifecycle listeners'],

website/src/components/GetStarted.astro

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,8 @@ const steps = [
99
{
1010
n: '02',
1111
title: 'Create a project',
12-
code: 'atmosphere create my-agent --template ai-chat',
13-
note: '11 templates: ai-chat, ai-tools, rag-chat, mcp-server, coordinator…',
12+
code: 'atmosphere new my-agent --template ai-chat',
13+
note: 'Handlers: chat, ai-chat, mcp-server',
1414
},
1515
{
1616
n: '03',
@@ -75,7 +75,7 @@ const steps = [
7575
First App Tutorial
7676
</a>
7777
<a href="https://github.com/Atmosphere/atmosphere/tree/main/samples" class="btn btn-tertiary">
78-
Browse 24 samples &rarr;
78+
Browse 20 samples &rarr;
7979
</a>
8080
</div>
8181
</div>

website/src/components/WhatsNew.astro

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,7 @@ const highlights = [
5252
{
5353
badge: 'Feature',
5454
title: 'Durable HITL workflows',
55-
description: 'CheckpointStore SPI with in-memory, SQLite, and Redis backends. An approval that arrives after a JVM restart resumes the exact turn on a fresh virtual thread.',
55+
description: 'CheckpointStore SPI with InMemory + SQLite backends, pluggable for your own backing store. An approval that arrives after a JVM restart resumes the exact turn on a fresh virtual thread.',
5656
link: 'https://async-io.org/docs/tutorial/24-durable-hitl/',
5757
linkLabel: 'Durable HITL tutorial',
5858
},

0 commit comments

Comments
 (0)