|
2 | 2 |
|
3 | 3 | All notable changes to Agents.KT are documented here. The format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Pre-1.0, minor bumps may add new public API; existing API surface is preserved. |
4 | 4 |
|
| 5 | +## [0.5.0] — 2026-05-16 |
| 6 | + |
| 7 | +The platform release. Streaming runtime end-to-end, MCP-as-skills unification, every composition operator surfacing typed event flows. v0.4.x was about correctness (typed boundaries, KSP, reflect-optional); v0.5.0 is about visibility — what's happening inside an agent's loop and across the wire is now first-class. |
| 8 | + |
| 9 | +### Added |
| 10 | + |
| 11 | +#### Streaming runtime |
| 12 | +- **`agent.session(input): AgentSession<OUT>`** — primary entry point for observing agent execution. Returns a cold `Flow<AgentEvent<OUT>>` of typed events plus a `suspend fun await(): OUT` terminal. Each call starts a fresh invocation; sharing across collectors is via `events.shareIn(...)`. Defined in `agents_engine.runtime.events`. Backward compat preserved — existing `agent.invoke(input)` and `agent.invokeSuspend(input)` go through the same internal path with a no-op emitter, byte-for-byte unchanged behavior. |
| 13 | +- **`AgentEvent<OUT>` sealed hierarchy** — eight subtypes covering the full lifecycle: `Token(skillName, text)`, `ToolCallStarted(callId, toolName)`, `ToolCallArgumentsDelta(callId, deltaJson)`, `ToolCallFinished(callId, toolName, arguments, result, isError)`, `SkillStarted(skillName)`, `SkillCompleted(skillName, tokensUsed)`, `Completed<OUT>(output, tokensUsed)`, `Failed(cause)`. Every event carries `agentId` so consumers can demultiplex composed streams. Only `Completed<OUT>` is parameterized on the typed output; the rest are `AgentEvent<Nothing>` and flow through any `AgentSession<OUT>`. |
| 14 | +- **`ModelClient.chatStream(messages): Flow<LlmChunk>`** as a default-implementing sibling of `chat`. Non-streaming providers keep working unchanged; the default wraps `chat()` and emits a chunk-equivalent sequence. |
| 15 | +- **`LlmChunk` sealed type** — provider-level chunks: `TextDelta`, `ToolCallStarted`, `ToolCallArgumentsDelta`, `ToolCallFinished`, `End(tokenUsage)`. Sits between adapters and `chatOrStream`, keeping provider quirks from leaking into `AgentEvent`. |
| 16 | +- **Cumulative `TokenUsage` on `SkillCompleted` and `Completed`** — summed across every LLM turn of one skill invocation (prompt and completion tokens summed independently). Null for `implementedBy` skills (no LLM round-trip). |
| 17 | + |
| 18 | +#### Native streaming adapters |
| 19 | +Three adapters override the default `chatStream` with real wire-level streaming: |
| 20 | +- **Ollama (NDJSON)** — `POST /api/chat` with `stream: true`. Line-by-line parser; tool calls land in the final chunk (Ollama limitation), emitted as the canonical `ToolCallStarted` / `ArgumentsDelta` / `ToolCallFinished` triple. Live integration: ~19 chunks per response, measurable timing gap between first and last. |
| 21 | +- **Anthropic SSE** — `POST /v1/messages` with `stream: true`. Indexed content-block aware: tracks `Map<Int, BlockState>` so interleaved `content_block_delta` events for text + tool_use can be routed to the right block. `tool_use` blocks carry the canonical Anthropic `toolu_*` id; we use it verbatim as `LlmChunk.ToolCallStarted.callId` (the case `ToolCall.callId` was designed for). Live integration verified against `claude-haiku-4-5-20251001`. |
| 22 | +- **OpenAI SSE** — `POST /v1/chat/completions` with `stream: true` + `stream_options.include_usage: true`. Per-index tool-call state (id from first delta, args accumulated across deltas). Terminator: `data: [DONE]`. Live integration verified against `gpt-4o-mini`. |
| 23 | + |
| 24 | +Cancellation contract verified by regression-guard tests on all three adapters: Kotlin Flow's channel-backed `emit` propagates collector cancellation back through `useLines` + `.use { stream }`, closing the underlying InputStream before the next blocking read. |
| 25 | + |
| 26 | +#### Composition session support |
| 27 | +Every composition operator now exposes a `.session(input)` entry point. Inner events from each contained agent flow with their own `agentId`s; the operator emits a single terminal `Completed`/`Failed`: |
| 28 | + |
| 29 | +- **`Pipeline.session(input)`** (#1745, #1746) — sequential composition. Each stage runs to completion (streaming its tokens), then the next starts with the typed `MID` value. Three-stage chains (`a then b then c`) emit events from all three. |
| 30 | +- **`wrap` (`teacher wrap student`)** (#1747) — teacher streams; its output becomes the student's prompt override; student streams. Consolidated `invokeSuspendForSession` to take an optional `promptOverride`, collapsing two near-identical entry points. |
| 31 | +- **`Branch.session(input)`** (#1748) — source agent streams, matched route streams. `BranchRoute` gains `sessionExecutor` and `routedAgentName` so terminal `Completed.agentId` points at the agent that actually produced the output. |
| 32 | +- **`Loop.session(input)`** (#1749) — bracket events emitted per iteration; same `agentId` repeated each iteration. |
| 33 | +- **`Parallel.session(input)`** (#1750) — branches run concurrently on `Dispatchers.Default`; their events interleave by arrival order in the shared Flow, demultiplexable by `agentId`. Terminal `Completed.agentId = "parallel"`. |
| 34 | +- **`Forum.session(input)`** (#1751) — participants stream concurrently, captain streams sequentially after. Preserves the `ForumReturnException` short-circuit. |
| 35 | +- **`Swarm.absorb(sibling)`** (#1752) — absorbed siblings stream their inner events into the captain's session, between the captain's own `ToolCallStarted` and `ToolCallFinished` brackets. `ToolDef` gains an optional `sessionExecutor` channel that any future sub-agent-wrapping tool can use. |
| 36 | + |
| 37 | +#### MCP-as-skills unification |
| 38 | +The conceptual point of v0.5.0: an MCP capability and an agent `Skill` share the same shape (named, described, typed unit of work). All three MCP capability surfaces now expose as `Skill<Map<String, Any?>, String>`: |
| 39 | + |
| 40 | +- **`mcp.toolSkills()`** (#1795) — every MCP-exposed tool wrapped as a Skill whose `implementedBy` invokes `mcp.call(toolName, args)`. Sits alongside the existing `mcp.toolDefs()` (tools as auxiliary functions a skill calls); consumers pick the shape that matches their agent design. |
| 41 | +- **`mcp.promptSkills()`** (#1796) — every server-side prompt template wrapped as a Skill whose `implementedBy` invokes `mcp.getPrompt(name, args)`. New `McpClient.listPrompts()` and `McpClient.getPrompt(name, args)` methods. |
| 42 | +- **`mcp.resourceSkills()`** (#1810) — every URI-addressable resource wrapped as a Skill whose `implementedBy` invokes `mcp.readResource(uri)`. Skill args are ignored — the URI is captured in the skill's closure. New `McpClient.listResources()` and `McpClient.readResource(uri)` methods. |
| 43 | + |
| 44 | +`McpServer` gains DSLs for the server side: |
| 45 | +```kotlin |
| 46 | +McpServer.from(agent) { |
| 47 | + port = 0 |
| 48 | + expose("skill-name") // tool (existing) |
| 49 | + prompt("greet", "Greeting template") { args -> "Hello ${args["name"]}" } // new |
| 50 | + resource("policy:///precision.md", "precision-policy", |
| 51 | + description = "...", mimeType = "text/markdown") { // new |
| 52 | + "Be precise. Cite sources." |
| 53 | + } |
| 54 | +} |
| 55 | +``` |
| 56 | +Handlers added for `prompts/list`, `prompts/get`, `resources/list`, `resources/read`. Initialize capabilities now declare prompts and resources when registered. |
| 57 | + |
| 58 | +- **`McpClient.snapshot: McpServerInfo`** (#1734) — immutable view of the connected server's full surface (identity, capabilities matrix, tools, prompts, resources, resource templates). Populated after `handshake()` + `loadTools()`. |
| 59 | + |
| 60 | +#### Test infrastructure |
| 61 | +- **Loopback MCP fixture (`LoopbackMcpAlgebraTest`, #1754)** — agent → `McpServer.from(...)` → `McpClient.connect(server.url)` → tool invocation, all in-JVM. Round-trip verified by computing `sqrt(π/e)` (digits-as-arrays + BigInteger) and checking the result with both a Math.sqrt sanity floor and a BigDecimal square-back proving `result² ≈ π/e` to 20 decimal places. |
| 62 | +- **Three pre-existing MCP tests converted to loopback** (#1794) — no more `MCP_REDMINE_URL` requirement. `./gradlew mcpIntegrationTest` runs fully out of the box. |
| 63 | +- **`./gradlew testAll`** task (#1720) aggregates unit + KSP + no-reflect smoke + live-llm integration + live-mcp integration into one command for pre-push verification. |
| 64 | +- **`docs/streaming.md`** (#1744) — consumer guide for the session API, native streaming status, cancellation contract, test coverage map, composition note. |
| 65 | +- **`docs/premortem-0.5.0-streaming.md`** (#1721) — design-before-code premortem listing the typed event hierarchy, cancellation contract, composition fidelity matrix, success criteria. Every claim in this release notes points at a criterion this premortem listed. |
| 66 | + |
| 67 | +### Roadmap updates |
| 68 | +- **Sandboxed tool execution** refined in `docs/roadmap.md` Phase 3 with concrete backends: `ProcessSandbox` (Seatbelt on macOS, bwrap on Linux), `WasmSandbox` (Chicory pure-Java), `DockerSandbox` (docker-java extras module). Scoped to subprocess-shaped tools only — `grants { }` covers in-process lambdas. |
| 69 | +- **Multimodal I/O** added — image/audio input (Phase 2) via `LlmContent` sealed-block evolution of `LlmMessage`; image generation (`ImageModelClient`) and TTS (`TTSModelClient`) in Phase 3. |
| 70 | +- **HTTP `sendAsync` migration** documented as the cancellation latency optimization deferred past v0.5.0 — correctness already holds via Flow semantics (verified by adapter regression-guard tests); `sendAsync` would tighten mid-line cancellation but is not blocking. |
| 71 | + |
| 72 | +### Migration notes |
| 73 | +v0.5.0 is **drop-in for v0.4.6** consumers. Every existing API still works: |
| 74 | +- `agent.invoke(input)` and `agent.invokeSuspend(input)` unchanged. |
| 75 | +- `agent.observe { PipelineEvent -> ... }` unchanged (the v0.4.x event surface for post-hoc skill/tool/error observability). |
| 76 | +- `model { ollama / claude / openai }` adapters unchanged; `chatStream` is a default-impl addition. |
| 77 | + |
| 78 | +To opt into streaming: |
| 79 | +```kotlin |
| 80 | +val session = myAgent.session(input) |
| 81 | +session.events.collect { event -> /* render Token, log ToolCall*, ... */ } |
| 82 | +val output: OUT = session.await() // typed terminal |
| 83 | +``` |
| 84 | + |
| 85 | +To consume an MCP server via the unified surface: |
| 86 | +```kotlin |
| 87 | +val mcp = McpClient.connect(url) |
| 88 | +val agent = agent<Map<String, Any?>, String>("wrapper") { |
| 89 | + skills { |
| 90 | + mcp.toolSkills().forEach { +it } |
| 91 | + mcp.promptSkills().forEach { +it } |
| 92 | + mcp.resourceSkills().forEach { +it } |
| 93 | + } |
| 94 | +} |
| 95 | +``` |
| 96 | + |
| 97 | +### Stats |
| 98 | +- **1,074+ unit tests** across root + KSP + no-reflect smoke subprojects — 0 failures |
| 99 | +- **54 live-LLM integration tests** — green on clean runs against `gpt-oss:120b-cloud`, `claude-haiku-4-5-20251001`, `gpt-4o-mini` |
| 100 | +- **7 live-MCP integration tests** — fully self-contained loopback coverage, no external infrastructure |
| 101 | +- v0.4.6 → v0.5.0: ~30 commits, ~25 new test files |
| 102 | + |
5 | 103 | ## [0.4.6] — 2026-05-15 |
6 | 104 |
|
7 | 105 | Follow-up to v0.4.5's open thread: actually make `kotlin-reflect` optional at runtime, and ship the smoke test that proves it. The premortem (`docs/premortem-0.4.6.md`) defined the success criteria; this release meets them. |
|
0 commit comments