Skip to content

Releases: Deep-CodeAI/Agents.KT

0.5.0

16 May 20:19

Choose a tag to compare

Changelog

All notable changes to Agents.KT are documented here. The format follows Keep a Changelog, and the project adheres to Semantic Versioning. Pre-1.0, minor bumps may add new public API; existing API surface is preserved.

[0.5.0] — 2026-05-16

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.

Added

Streaming runtime

  • 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.
  • 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>.
  • 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.
  • LlmChunk sealed type — provider-level chunks: TextDelta, ToolCallStarted, ToolCallArgumentsDelta, ToolCallFinished, End(tokenUsage). Sits between adapters and chatOrStream, keeping provider quirks from leaking into AgentEvent.
  • 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).

Native streaming adapters

Three adapters override the default chatStream with real wire-level streaming:

  • 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.
  • Anthropic SSEPOST /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.
  • OpenAI SSEPOST /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.

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.

Composition session support

Every composition operator now exposes a .session(input) entry point. Inner events from each contained agent flow with their own agentIds; the operator emits a single terminal Completed/Failed:

  • 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.
  • 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.
  • 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.
  • Loop.session(input) (#1749) — bracket events emitted per iteration; same agentId repeated each iteration.
  • 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".
  • Forum.session(input) (#1751) — participants stream concurrently, captain streams sequentially after. Preserves the ForumReturnException short-circuit.
  • 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.

MCP-as-skills unification

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>:

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

McpServer gains DSLs for the server side:

McpServer.from(agent) {
    port = 0
    expose("skill-name")                                          // tool (existing)
    prompt("greet", "Greeting template") { args -> "Hello ${args["name"]}" }  // new
    resource("policy:///precision.md", "precision-policy",
             description = "...", mimeType = "text/markdown") {   // new
        "Be precise. Cite sources."
    }
}

Handlers added for prompts/list, prompts/get, resources/list, resources/read. Initialize capabilities now declare prompts and resources when registered.

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

Test infrastructure

  • 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.
  • Three pre-existing MCP tests converted to loopback (#1794) — no more MCP_REDMINE_URL requirement. ./gradlew mcpIntegrationTest runs fully out of the box.
  • ./gradlew testAll task (#1720) aggregates unit + KSP + no-reflect smoke + live-llm integration + live-mcp integration into one command for pre-push verification.
  • docs/streaming.md (#1744) — consumer guide for the session API, native streaming status, cancellation contract, test coverage map, composition note.
  • 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.

Roadmap updates

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

v0.4.4

13 May 01:19

Choose a tag to compare

Agents.KT v0.4.4 — KSP, wrap, and reflect-free runtime

implementation("ai.deep-code:agents-kt:0.4.4")

v0.4.2

12 May 13:29

Choose a tag to compare

🆕 Three model providers, one ModelClient

model { ollama("qwen2.5:7b"); host = "localhost"; port = 11434 }   // since 0.1                                                                                                                                                        
model { claude("claude-opus-4-7"); apiKey = System.getenv("ANTHROPIC_API_KEY") }   // new
model { openai("gpt-4o"); apiKey = System.getenv("OPENAI_API_KEY") }   // new                                                                                                                                                          

Each adapter handles its provider's wire conventions internally — Anthropic's tool_use/tool_result blocks, OpenAI's stringified function.arguments + synthesized tool_call_ids, Ollama's flat shape with inline-JSON fallback.
The agentic loop and LlmMessage/LlmResponse types are unchanged. Provider switching is a one-line DSL change.

Boundary contract preserved: provider error envelopes surface as LlmProviderException for all three (#702, #1644, #1656).

🆕 Fail-fast REPL startup

LiveShowBuilder.precheck: (() -> Unit)? runs after argument parsing and before banner / --once / REPL prompt. Throw to abort; the runner prints error: <msg> and returns exit code 2. No more mid-spinner
java.net.ConnectException on the first turn.

LiveRunner.serve(captain, args) {
    prompt = "fib> "                                                                                                                                                                                                                   
    precheck = OllamaPreflight(host = "localhost", port = 11434)::check       
}                                                        

Generic hook — config validation, env checks, even DB pings can run before the user types (#1132).

🆕 Live typed-args integration coverage

TypedArgsLiveIntegrationTest proves the typed tool<Args, Result> path round-trips correctly through schema → wire → response parse → KClass.constructFromMap → typed executor against real Ollama / Claude / OpenAI models. Each
test skips cleanly when its provider isn't reachable (#1675).

🐛 Ollama Cloud bug: content: null on assistant tool-call turns

External bug report — multi-turn agentic loops against Ollama Cloud gpt-oss:120b-cloud / gpt-oss:20b-cloud were hitting 500 Internal Server Error. Root cause: assistant messages with tool_calls and no text were
wire-serialized as content: "", but the OpenAI / Ollama spec says content should be null (or omitted) when tool_calls is present. Local Ollama tolerated it; cloud's strict validator rejected. Six regression cases pin the
wire shape, including the reporter's exact two-tool-call PlanMaster sequence (#1694).

🔒 apiKey no longer leaks through toString

ModelConfig is a Kotlin data class; its auto-generated toString() was dumping the raw API key, one log.info("config = $cfg") away from a credential leak. Now overridden to mask: apiKey=sk-ant…108chars. equals/hashCode
still consider apiKey — masking is observation-only. SECURITY.md gained a "Handling LLM provider credentials" section (.secrets/ convention, chmod 0600/0700, the masking contract, "key was committed → rotate first" runbook)
(#1665).

🔒 Refreshed deps

  • kotlinx-coroutines-core and kotlinx-coroutines-test 1.10.2 → 1.11.0
  • Gradle wrapper 9.4.1 → 9.5.0
  • Lockfile + gradle/verification-metadata.xml regenerated
  • BouncyCastle 1.84 pin now visible to Dependabot via explicit compileOnly nodes (no transitive leak to consumers — runtime classpath unchanged from 0.3.0)

Supersedes the open dependabot PRs (#47, #48, #39).

Binary compatibility with 0.3.0

Source-compatible. Every new public API has defaults; existing 0.3.0 code compiles unchanged on 0.4.2.

ModelProvider enum gained ANTHROPIC and OPENAI. ModelConfig carries new optional fields (`api...

Read more

v0.2.2

05 May 01:38
c95ee6d

Choose a tag to compare

A feature-heavy patch release — REPL deployment, multi-agent JAR composition (Swarm), four new observability hooks, two new budget controls, classpath-resource prompt loading, and a slimmer README. Pre-1.0 patch bump — no breaking changes; all existing API surface preserved.

Highlights
LiveShow / LiveRunner — REPL deployment surface mirroring MCP's two-layer split (LiveShow.from(x).start() / LiveRunner.serve(x, args)). Six factory overloads cover Agent / Pipeline / Forum / Parallel / Loop / Branch — any String-input structure becomes interactively chattable. ANSI color theme, full-resolution ASCII Agents.KT banner, in-place cat spinner during inference, lifecycle hooks (onTurnStart / onTurnEnd / onErrorReported), renderOutput post-processor, string-concatenated conversation history with --- user --- / --- assistant --- delimiters, slash commands (/quit, /clear, /help plus user-extensible slash(name) { }), --once "" for non-interactive single-turn use.
Swarm — multi-agent JAR composition. Drop sibling agent JARs into a folder, ServiceLoader-discover them, me.absorb(sibling) exposes each as a tool with full agent personality preserved (prompt, skills, knowledge, memory, observability hooks). In-JVM, no IPC overhead, no static-typing-across-JARs limitation MCP-stdio would impose. Captain-capable: any agent JAR can be elected by running its main.
Four new observability hooks. onError { Throwable } for infrastructure failures (LLM transport, parse, budget). Agent.observe { event } bridges the four legacy hooks into one sealed PipelineEvent stream. onBudgetThreshold(threshold) { reason, used } fires once per BudgetReason when cumulative usage crosses a fraction (pre-cap warning). LiveShow.onTurnStart / onTurnEnd / onErrorReported for REPL-side telemetry.
Two new budget controls. maxTokens (cumulative across turns when the provider reports usage; new BudgetReason.TOKENS) and maxConsecutiveSameTool (catches LLM retry loops on a broken tool; new BudgetReason.CONSECUTIVE_TOOL). LlmResponse.tokenUsage: TokenUsage? — Ollama's prompt_eval_count + eval_count plumbed through the agentic loop.
loadResource(path) for classpath prompts. prompt(loadResource("prompts/coder.md")) loads UTF-8 from the classpath; fail-fast at agent construction with a helpful error if the path is missing. loadResourceOrNull(path) for the optional case.
README split. Down from 1243 → 203 lines. Topical sections moved to docs/{skills, model-and-tools, mcp, error-recovery, memory, generation, composition, roadmap}.md with cross-back links.
Added
REPL / runtime
LiveShow.from(agent | pipeline | forum | parallel | loop | branch).start().runUntilTerminated() — programmatic REPL host. Six factory overloads collapse to one private constructor taking suspend (String) -> Any? (#981).
LiveRunner.serve(structure, args, configure) — picocli-shaped main shim mirroring McpRunner.serve. Six overloads, --once "", --max-history N, -h, -V. JVM shutdown hook + blocking until SIGTERM, returns int exit code (#981).
LiveShowBuilder configurables: prompt, maxHistoryTurns, historyDelimiter, input, output, plus UI polish: colors, theme, renderOutput, banner, spinner (#983).
LiveShowTheme.DEFAULT / LiveShowTheme.NONE color presets binding AnsiColor to roles (prompt / agentOutput / error / slashOutput / banner) (#983).
Spinner.CAT / Spinner.NONE — in-place cat-face spinner during inference, suppressed on non-TTY (#983).
Default banner — full-resolution ASCII rendering of the Agents.KT logo (angular cat face with pink crown accents, block-letter wordmark) (#983).
Swarm.discover() and Swarm.discover(classLoader) — ServiceLoader-walk for AgentProvider impls (#984).
interface AgentProvider { fun build(): Agent<*, > } — single-method SPI for sibling JARs (#984).
Agent<
, >.absorb(sibling: Agent<, *>) — wraps the sibling as a tool on the captain; auto-enables across all skills; fails fast on name collision / typed-input siblings (#984).
Observability
Agent.onError { Throwable -> } — infrastructure-error observability hook (LLM transport, response parse, budget). Pure observability — original exception always rethrows; listener exceptions attached as suppressed (#962).
Agent.observe { event -> } — sealed PipelineEvent (SkillChosen / ToolCalled / KnowledgeLoaded / ErrorOccurred) bridges the four hooks into one typed stream; composes additively with prior listeners (#965).
Agent.onBudgetThreshold(threshold) { reason, usedPercent -> } — pre-cap warning hook; fires once per BudgetReason when cumulative usage crosses the fraction (#966).
Budget
BudgetConfig.maxTokens: Int? + BudgetReason.TOKENS — cumulative token cap; counts only when the provider reports tokenUsage on the response (#963).
BudgetConfig.maxConsecutiveSameTool: Int? + BudgetReason.CONSECUTIVE_TOOL — catches retry loops on a broken tool (#969).
LlmResponse.tokenUsage: TokenUsage? (promptTokens, completionTokens, total) — Ollama's prompt_eval_count + eval_count plumbed end-to-end (#963).
DX
loadResource(path: String): String — read agent prompts from src/main/resources/.... Fail-fast at agent construction; UTF-8 decoded; leading-slash normalized (#980).
loadResourceOrNull(path: String): String? — null-returning variant for optional resources (#980).
Agent.toString() — single-line Agent form replacing the JVM identity-hash default (#970).
Agent.describe(): String — multi-line debug summary of name + OUT type, prompt (truncated at 80), model config, budget (overrides only), skills, tools, memory bank presence (#970).

0.2.0

03 May 05:47

Choose a tag to compare

What's new

MCP

  • HTTP / stdio / TCP transports, Bearer auth, namespaced tools, mock servers for tests
  • McpServer.from(agent) with explicit tools/listChanged: false capability
  • McpRunner standalone main

Typed tools

  • tool<Args, Result>(...) with reflection-built JSON Schema
  • @generable / @Guide annotations
  • Sealed-args boundaries: rejected at the typed tool<> builder (separate untyped path remains)

Runtime hardening

  • ForumTranscript deliberation pattern (transcriptCaptain)
  • BranchRoute sealed type with onNull / onElse; sealed-completeness validation at construction
  • SkillRoute(name, confidence, rationale) structured router output
  • Untrusted tool-output wrapping — model can't impersonate framework messages
  • Reserved tool names (memory_*) protected from shadowing
  • Encapsulated toolMap / skills (read-only Map views; mutation only via DSL)
  • Strict typed args — additionalProperties: false, sealed type discriminator must match constructed variant, repaired-args revalidation

Provider integration

  • LlmProviderException — provider-boundary errors surface distinctly from output-parse errors
  • Inline-tool fallback for Ollama models without native tool support
  • Per-instance latch skips redundant native tool attempt after capability error observed

Suspend refactor

  • invokeSuspend(input) on Agent + every composition operator
  • executeAgentic and selectSkillByLlm are now suspend
  • client.chat(...) wrapped in withContext(Dispatchers.IO) so cancellation interrupts HTTP I/O
  • Parallel and Forum use coroutineScope for structured concurrency

Fixed

  • Ollama provider error envelopes were silently passed through as LlmResponse.Text(rawJson), causing user transformOutput to fail with a misleading
    "could not parse" error (#702)
  • Agent.mcp { } could mutate the tool registry post-construction because registerTool didn't checkNotFrozen() (#708)
  • Agentic loop accepted repaired tool args without re-validating them through the typed schema (#658)
  • constructFromMap accepted extra keys for plain data classes; sealed variants didn't verify the type discriminator matched (#665, #699)
  • Tool name typos in tools(...) silently dropped instead of failing fast at construction (#631)
  • Default budget was unbounded — agents could loop indefinitely without an explicit maxTurns (#633)

Migration

Existing code keeps working unchanged. For coroutine-scope callers, the new suspend entry points propagate cancellation cleanly:

runBlocking {
val result = myAgent.invokeSuspend("input") // no nested runBlocking
val out = (a then b).invokeSuspend("input") // suspend composition
val bounded = withTimeoutOrNull(2.seconds) { // works now
slowParallel.invokeSuspend("input")
}
}

No deprecations — the blocking shims are the documented back-compat surface.

0.1.1

28 Mar 23:41

Choose a tag to compare

Agents.KT v0.1.1 — Tool Error Recovery

Release date: 2026-03-29

The fixer is an agent.

What's new

Tool Error Recovery System

Every agent framework hits the same wall: tools fail at runtime. Malformed arguments, network errors, flaky APIs, type mismatches. The standard response is a dedicated parser class or a callback function. Agents.KT takes a different position: the fixer is an Agent<String, String> — same type system, same composition, same telemetry as everything else. Deterministic agents (implementedBy) cost zero LLM calls.

onError inside the tool block

Error handling lives where the tool lives:

tool("calculateNumberOfKeys") {
    description("Count top-level keys in a JSON object")
    executor { args ->
        val json = args["json"]?.toString() ?: throw IllegalArgumentException("Missing json")
        Regex(""""([^"]+)"\s*:""").findAll(json).count()
    }
    onError {
        executionError { _ -> fix(agent = jsonFixer, retries = 2) }
        invalidArgs { _, _ -> fix(agent = jsonFixer) }
    }
}

Three placement options with clear priority:

  1. Tool block onError {} — highest priority
  2. Agent-level onToolError("name") {} — middle
  3. defaults { onError {} } — lowest, applies to all tools

The fixer is always an agent

No lambda callbacks. Repair uses Agent<String, String> — deterministic or LLM-driven:

// Deterministic — zero LLM calls
val jsonFixer = agent<String, String>("json-fixer") {
    skills {
        skill<String, String>("cleanup", "Fix JSON") {
            implementedBy { input -> input.replace(",}", "}").replace(",]", "]") }
        }
    }
}

// LLM-driven — uses a model to analyze and fix
val smartFixer = agent<String, String>("smart-fixer") {
    prompt("Fix malformed JSON. If structural error, call escalate.")
    model { ollama("gpt-4o-mini"); temperature = 0.0 }
    skills {
        skill<String, String>("fix", "Analyze and fix JSON errors") {
            tools("escalate")
        }
    }
}

Built-in escalate and throwException tools

Every agent has two framework-provided tools registered at construction time — inactive by default, activated when a skill references them in tools(...).

  • escalate — soft failure. The error is fed back to the parent LLM as a tool result, giving it a chance to retry with corrected arguments. The fixer can include corrected data in the escalation reason.
  • throwException — hard failure. ToolExecutionException propagates immediately. No retries.
// LLM-driven fixer calls escalate → error fed back → parent LLM retries
LLM calls parseJson(json = "{name: world}")
  → tool throws: "unquoted keys"
  → fixer invoked → fixer calls escalate("Corrected: {\"name\":\"world\"}")
    → error fed back to parent LLM
      → parent retries with corrected JSON → succeeds

ToolError sealed hierarchy

Four error types for programmatic handling:

sealed interface ToolError {
    data class InvalidArgs(val rawArgs: String, val parseError: String, val expectedSchema: Map<String, Any?>)
    data class DeserializationError(val rawValue: String, val targetType: KType, val cause: Throwable)
    data class ExecutionError(val args: Map<String, Any?>, val cause: Throwable)
    data class EscalationError(val source: String, val reason: String, val severity: Severity, val originalError: ToolError, val attempts: Int)
}

enum class Severity { LOW, MEDIUM, HIGH, CRITICAL }

Tool Definition Block DSL

New ToolDefBuilder for richer tool definitions:

tools {
    tool("fetch") {
        description("Fetch a URL")
        executor { args -> httpGet(args["url"].toString()) }
        onError {
            executionError { _ -> retry(maxAttempts = 3) }
        }
    }
}

All existing tool("name", "description") { args -> ... } forms continue to work.

New files

File Purpose
model/ToolError.kt ToolError sealed hierarchy, Severity, EscalationException, ToolExecutionException
model/OnErrorBuilder.kt RepairResult, RepairScope, ToolErrorHandler, OnErrorBuilder, executeAgentFix

Modified files

File Change
model/ToolDef.kt ToolDefBuilder block DSL, ToolDefaultsBuilder, buildBuiltInTools() (escalate/throwException)
model/AgenticLoop.kt executeToolWithRecovery() — error handler dispatch with retry, agent repair, escalation feedback
core/Agent.kt onToolError(), getToolErrorHandler(), built-in tool auto-registration

Tests

78 new tests across 10 test files:

File Tests Coverage
ToolErrorTest 6 Sealed hierarchy construction, exhaustive when
OnErrorDSLTest 10 invalidArgs, deserializationError, executionError handlers
ToolErrorDefaultsTest 3 Defaults apply to all tools, per-tool overrides
ToolErrorAgentRepairTest 4 Agent-based fix, retries, escalation, throwException
ToolErrorAgenticLoopTest 6 Retry recovery, retry exhaustion, escalation feedback, defaults in loop
ToolLevelOnErrorTest 16 onError via onError= param, priority chain, agentic loop, escalation, throwException
ToolBlockOnErrorTest 9 tool {} block DSL, priority over defaults/agent-level, agentic loop
EscalateToolTest 10 Built-in tools in every agent, activation via tools(...), severity parsing
JsonParseEscalationIntegrationTest 3 Full escalation flow: malformed JSON → fixer escalates → LLM retries → succeeds
ThrowExceptionIntegrationTest 5 Hard failure: throwException kills pipeline, doesn't fire onToolUse, ignores remaining retries

Integration tests (live LLM via Ollama):

  • Flaky tool retry recovery with real LLM
  • Retry exhaustion → ToolExecutionException
  • Escalation → LLM reads corrected data from error → retries → succeeds
  • Agent-based repair with real LLM
  • Defaults across multiple tools with real LLM
  • Tool block onError with escalation and real LLM
  • throwException stops pipeline with real LLM

Breaking changes

None. All existing APIs and tests continue to work unchanged.

Upgrade

// build.gradle.kts
dependencies {
    implementation("ai.deep-code:agents-kt:0.1.1")
}

Agents.KT — Define Freely. Compose Strictly. Ship Reliably.

0.1.0

24 Mar 11:07

Choose a tag to compare

First version good enough to make strict agent pipelines