diff --git a/detekt-baseline.xml b/detekt-baseline.xml
index ef7f9c7..f7e7108 100644
--- a/detekt-baseline.xml
+++ b/detekt-baseline.xml
@@ -48,11 +48,6 @@
MagicNumber:McpRunner.kt$McpRunner$50
MagicNumber:McpServer.kt$McpServer$200
MagicNumber:McpServer.kt$McpServer$202
- MagicNumber:McpServer.kt$McpServer$32600
- MagicNumber:McpServer.kt$McpServer$32601
- MagicNumber:McpServer.kt$McpServer$32602
- MagicNumber:McpServer.kt$McpServer$32603
- MagicNumber:McpServer.kt$McpServer$32700
MagicNumber:McpServer.kt$McpServer$400
MagicNumber:McpServer.kt$McpServer$403
MagicNumber:McpServer.kt$McpServer$405
@@ -69,8 +64,6 @@
MagicNumber:ModelConfig.kt$ModelConfig$8
MagicNumber:PrefixStabilityGuard.kt$PrefixStabilityGuard$4
MagicNumber:StdioMcpTransport.kt$StdioMcpTransport.Companion$500
- MagicNumber:ToolPolicy.kt$ManifestJson$16
- MagicNumber:ToolPolicy.kt$ManifestJson$4
MagicNumber:ToolResult.kt$12
MaxLineLength:Agent.kt$Agent$"Router uncertain (confidence=${route.confidence}, threshold=$skillSelectionConfidenceThreshold). "
MaxLineLength:Agent.kt$Agent$var
@@ -207,6 +200,8 @@
MaxLineLength:JsonParseEscalationIntegrationTest.kt$JsonParseEscalationIntegrationTest$assertTrue(toolUses.size >= 2, "onToolUse must fire at least twice (escalation error + success), got: ${toolUses.size}")
MaxLineLength:JsonParseEscalationIntegrationTest.kt$JsonParseEscalationIntegrationTest$description("Count top-level keys in a JSON object. Args: json (string — valid JSON with double-quoted keys)")
MaxLineLength:JsonParseEscalationIntegrationTest.kt$JsonParseEscalationIntegrationTest$throw IllegalArgumentException("No valid keys found — JSON may have unquoted keys. All keys must be double-quoted.")
+ MaxLineLength:JsonRpc.kt$JsonRpc$"""{"${JsonRpcWire.KEY_JSONRPC}":"${JsonRpcWire.VERSION}","${JsonRpcWire.KEY_ID}":${McpJson.encode(id)},"${JsonRpcWire.KEY_RESULT}":${McpJson.encode(result)}}"""
+ MaxLineLength:JsonRpc.kt$JsonRpc$return """{"${JsonRpcWire.KEY_JSONRPC}":"${JsonRpcWire.VERSION}","${JsonRpcWire.KEY_ID}":${McpJson.encode(id)},"${JsonRpcWire.KEY_ERROR}":${McpJson.encode(errorObj)}}"""
MaxLineLength:KnowledgeToolDescriptionTest.kt$KnowledgeToolDescriptionTest$knowledge("checklist", "Self-verification steps before returning output") { "1. Check length\n2. Check tone" }
MaxLineLength:KoogRegressionMcpCleanTextOutputTest.kt$KoogRegressionMcpCleanTextOutputTest$val request = """{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"$toolName","arguments":$argsJson}}"""
MaxLineLength:KoogRegressionUnknownToolTest.kt$KoogRegressionUnknownToolTest$?:
@@ -228,10 +223,10 @@
MaxLineLength:LoopWhilePatternTest.kt$LoopWhilePatternTest$val normalize = agent<Signal, Signal>("normalize") { skills { skill<Signal, Signal>("normalize") { implementedBy { Signal(it.value + 1.0) } } } }
MaxLineLength:McpAuthRedactionTest.kt$McpAuthRedactionTest$assertTrue(rendered.contains("redacted", ignoreCase = true), "toString must indicate redaction; got: '$rendered'")
MaxLineLength:McpClient.kt$McpClient$/** Server-reported capabilities map from the initialize handshake; raw shape so we can refine later without re-fetching. */
- MaxLineLength:McpClient.kt$McpClient$fun
MaxLineLength:McpRunnerMutationTest.kt$McpRunnerMutationTest$assertTrue(!thread.isAlive, "Runner thread must exit within 2s of server.stop() — stop-watcher start() removal would block here")
MaxLineLength:McpServer.kt$ExposedSkill.Companion$?:
- MaxLineLength:McpServer.kt$McpServer$"""{"jsonrpc":"2.0","id":${McpJson.encode(id)},"error":${McpJson.encode(mapOf("code" to code, "message" to message))}}"""
+ MaxLineLength:McpServer.kt$McpServer$jsonRpcError(id, JsonRpcErrorCode.INTERNAL_ERROR, "Prompt '$name' rendering failed: ${e.message ?: e.toString()}")
+ MaxLineLength:McpServer.kt$McpServer$jsonRpcError(id, JsonRpcErrorCode.INTERNAL_ERROR, "Resource '$uri' read failed: ${e.message ?: e.toString()}")
MaxLineLength:McpServer.kt$McpServer.Companion$"Skill \"$name\" is agentic — McpServer only exposes non-agentic skills (implementedBy { }) in this slice."
MaxLineLength:McpServerBeforeInterceptorTest.kt$McpServerBeforeInterceptorTest$"""{"jsonrpc":"2.0","id":1,"method":"tools/call","params":{"name":"echo","arguments":{"input":"original"}}}"""
MaxLineLength:McpServerConformanceTest.kt$McpServerConformanceTest$"""{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"$MCP_PROTOCOL_VERSION","capabilities":{},"clientInfo":{"name":"t","version":"0"}}}"""
@@ -476,6 +471,7 @@
PackageNaming:GuessingGameLiveTest.kt$package agents_engine.composition.wrap
PackageNaming:GuessingGameTest.kt$package agents_engine.composition.wrap
PackageNaming:HttpMcpTransport.kt$package agents_engine.mcp
+ PackageNaming:HttpModelClientSupport.kt$package agents_engine.model
PackageNaming:HumanApproval.kt$package agents_engine.core
PackageNaming:HumanApprovalTest.kt$package agents_engine.core
PackageNaming:InlineToolCallParser.kt$package agents_engine.model
@@ -487,9 +483,10 @@
PackageNaming:Interrupt.kt$package agents_engine.core
PackageNaming:InterruptResumeTest.kt$package agents_engine.core
PackageNaming:InvokeSuspendResumingTest.kt$package agents_engine.core
- PackageNaming:JsonEscape.kt$package agents_engine.model
+ PackageNaming:JsonEscape.kt$package agents_engine.internal
PackageNaming:JsonEscapeTest.kt$package agents_engine.model
PackageNaming:JsonParseEscalationIntegrationTest.kt$package agents_engine.model
+ PackageNaming:JsonRpc.kt$package agents_engine.mcp
PackageNaming:KnowledgeToolDescriptionTest.kt$package agents_engine.core
PackageNaming:KoogRegressionEnumSchemaTest.kt$package agents_engine.generation
PackageNaming:KoogRegressionLoopProtectionTest.kt$package agents_engine.model
@@ -637,6 +634,7 @@
PackageNaming:ReservedMemoryToolNamesTest.kt$package agents_engine.model
PackageNaming:Resources.kt$package agents_engine.core
PackageNaming:SealedSchemaGenerationTest.kt$package agents_engine.generation
+ PackageNaming:SessionCancellationTest.kt$package agents_engine.runtime.events
PackageNaming:SessionHistory.kt$package agents_engine.runtime.events
PackageNaming:SharedKnowledgeTest.kt$package agents_engine.core
PackageNaming:SignInAgentTest.kt$package agents_engine.model
diff --git a/src/main/kotlin/agents_engine/composition/branch/BranchSessionExtension.kt b/src/main/kotlin/agents_engine/composition/branch/BranchSessionExtension.kt
index bbb8726..93f3d73 100644
--- a/src/main/kotlin/agents_engine/composition/branch/BranchSessionExtension.kt
+++ b/src/main/kotlin/agents_engine/composition/branch/BranchSessionExtension.kt
@@ -95,14 +95,17 @@ fun Branch.session(input: IN): AgentSession {
channel.send(AgentEvent.Completed(terminalAgentId, output, null))
channel.close()
result.complete(output)
+ } catch (timeout: TimeoutCancellationException) {
+ // #2863 — caught BEFORE CancellationException (subtype).
+ channel.send(AgentEvent.Failed(terminalAgentId, timeout))
+ channel.close()
+ result.completeExceptionally(timeout)
+ } catch (cancel: CancellationException) {
+ // #2863 — bare cancellation propagates per structured concurrency.
+ result.completeExceptionally(cancel)
+ channel.close(cancel)
+ throw cancel
} catch (t: Throwable) {
- // #2863 — propagate bare cancellation; keep timeout on the
- // Failed path (real budget/timeout failures are user-visible).
- if (t is CancellationException && t !is TimeoutCancellationException) {
- result.completeExceptionally(t)
- channel.close(t)
- throw t
- }
channel.send(AgentEvent.Failed(terminalAgentId, t))
channel.close()
result.completeExceptionally(t)
diff --git a/src/main/kotlin/agents_engine/composition/forum/ForumSessionExtension.kt b/src/main/kotlin/agents_engine/composition/forum/ForumSessionExtension.kt
index 90616e2..528a34f 100644
--- a/src/main/kotlin/agents_engine/composition/forum/ForumSessionExtension.kt
+++ b/src/main/kotlin/agents_engine/composition/forum/ForumSessionExtension.kt
@@ -107,13 +107,17 @@ fun Forum.session(input: IN): AgentSession {
channel.trySend(AgentEvent.Completed(captain.name, verdict, null))
channel.close()
result.complete(verdict)
+ } catch (timeout: TimeoutCancellationException) {
+ // #2863 — caught BEFORE CancellationException (subtype).
+ channel.trySend(AgentEvent.Failed(captain.name, timeout))
+ channel.close()
+ result.completeExceptionally(timeout)
+ } catch (cancel: CancellationException) {
+ // #2863 — bare cancellation propagates per structured concurrency.
+ result.completeExceptionally(cancel)
+ channel.close(cancel)
+ throw cancel
} catch (t: Throwable) {
- // #2863 — propagate bare cancellation; keep timeout on Failed.
- if (t is CancellationException && t !is TimeoutCancellationException) {
- result.completeExceptionally(t)
- channel.close(t)
- throw t
- }
channel.trySend(AgentEvent.Failed(captain.name, t))
channel.close()
result.completeExceptionally(t)
diff --git a/src/main/kotlin/agents_engine/composition/loop/LoopSessionExtension.kt b/src/main/kotlin/agents_engine/composition/loop/LoopSessionExtension.kt
index 1b33c1a..1a6427e 100644
--- a/src/main/kotlin/agents_engine/composition/loop/LoopSessionExtension.kt
+++ b/src/main/kotlin/agents_engine/composition/loop/LoopSessionExtension.kt
@@ -77,13 +77,17 @@ fun Loop.session(input: IN): AgentSession {
channel.trySend(AgentEvent.Completed(terminalAgentId, current, null))
channel.close()
result.complete(current)
+ } catch (timeout: TimeoutCancellationException) {
+ // #2863 — caught BEFORE CancellationException (subtype).
+ channel.trySend(AgentEvent.Failed(terminalAgentId, timeout))
+ channel.close()
+ result.completeExceptionally(timeout)
+ } catch (cancel: CancellationException) {
+ // #2863 — bare cancellation propagates per structured concurrency.
+ result.completeExceptionally(cancel)
+ channel.close(cancel)
+ throw cancel
} catch (t: Throwable) {
- // #2863 — propagate bare cancellation; keep timeout on Failed.
- if (t is CancellationException && t !is TimeoutCancellationException) {
- result.completeExceptionally(t)
- channel.close(t)
- throw t
- }
channel.trySend(AgentEvent.Failed(terminalAgentId, t))
channel.close()
result.completeExceptionally(t)
diff --git a/src/main/kotlin/agents_engine/composition/parallel/ParallelSessionExtension.kt b/src/main/kotlin/agents_engine/composition/parallel/ParallelSessionExtension.kt
index fc0b35f..34e9c24 100644
--- a/src/main/kotlin/agents_engine/composition/parallel/ParallelSessionExtension.kt
+++ b/src/main/kotlin/agents_engine/composition/parallel/ParallelSessionExtension.kt
@@ -93,15 +93,18 @@ fun Parallel.session(input: IN): AgentSession> {
channel.send(AgentEvent.Completed("parallel", outputs, null))
channel.close()
result.complete(outputs)
+ } catch (timeout: TimeoutCancellationException) {
+ // #2863 — caught BEFORE CancellationException (subtype). Real
+ // failure path: emit Failed.
+ channel.send(AgentEvent.Failed("parallel", timeout))
+ channel.close()
+ result.completeExceptionally(timeout)
+ } catch (cancel: CancellationException) {
+ // #2863 — bare cancellation propagates per structured concurrency.
+ result.completeExceptionally(cancel)
+ channel.close(cancel)
+ throw cancel
} catch (t: Throwable) {
- // #2863 — bare cancellation propagates per structured-concurrency
- // contract. TimeoutCancellationException is a real failure
- // and stays on the Failed path.
- if (t is CancellationException && t !is TimeoutCancellationException) {
- result.completeExceptionally(t)
- channel.close(t)
- throw t
- }
channel.send(AgentEvent.Failed("parallel", t))
channel.close()
result.completeExceptionally(t)
diff --git a/src/main/kotlin/agents_engine/composition/pipeline/PipelineSessionExtension.kt b/src/main/kotlin/agents_engine/composition/pipeline/PipelineSessionExtension.kt
index af91c20..76d93a1 100644
--- a/src/main/kotlin/agents_engine/composition/pipeline/PipelineSessionExtension.kt
+++ b/src/main/kotlin/agents_engine/composition/pipeline/PipelineSessionExtension.kt
@@ -78,15 +78,18 @@ fun Pipeline.session(input: IN): AgentSession {
channel.send(AgentEvent.Completed(terminalAgentId, output, null))
channel.close()
result.complete(output)
+ } catch (timeout: TimeoutCancellationException) {
+ // #2863 — TimeoutCancellationException is a real failure.
+ // Must be caught BEFORE CancellationException (subtype).
+ channel.send(AgentEvent.Failed(terminalAgentId, timeout))
+ channel.close()
+ result.completeExceptionally(timeout)
+ } catch (cancel: CancellationException) {
+ // #2863 — bare cancellation propagates per structured concurrency.
+ result.completeExceptionally(cancel)
+ channel.close(cancel)
+ throw cancel
} catch (t: Throwable) {
- // #2863 — see AgentSessionExtension for the rationale. Bare
- // cancellation propagates per structured concurrency;
- // TimeoutCancellationException stays on the Failed path.
- if (t is CancellationException && t !is TimeoutCancellationException) {
- result.completeExceptionally(t)
- channel.close(t)
- throw t
- }
channel.send(AgentEvent.Failed(terminalAgentId, t))
channel.close()
result.completeExceptionally(t)
diff --git a/src/main/kotlin/agents_engine/runtime/events/AgentSessionExtension.kt b/src/main/kotlin/agents_engine/runtime/events/AgentSessionExtension.kt
index 3588843..d7777ec 100644
--- a/src/main/kotlin/agents_engine/runtime/events/AgentSessionExtension.kt
+++ b/src/main/kotlin/agents_engine/runtime/events/AgentSessionExtension.kt
@@ -88,20 +88,25 @@ fun Agent.session(input: IN): AgentSession {
channel.send(completed)
channel.close()
result.complete(output)
+ } catch (timeout: TimeoutCancellationException) {
+ // #2863 — TimeoutCancellationException must be caught BEFORE
+ // CancellationException (it extends it). A budget / withTimeout
+ // breach is a real failure consumers must hear about, so it
+ // rides the Failed path.
+ val failed = AgentEvent.Failed(agent.name, timeout, runtimeContext)
+ agent.fireAgentEvent(failed)
+ channel.send(failed)
+ channel.close()
+ result.completeExceptionally(timeout)
+ } catch (cancel: CancellationException) {
+ // #2863 — bare CancellationException means the collector / scope
+ // was cancelled. Propagate per structured-concurrency contract
+ // and close the channel WITH the cancel; do NOT emit a
+ // synthetic Failed event.
+ result.completeExceptionally(cancel)
+ channel.close(cancel)
+ throw cancel
} catch (t: Throwable) {
- // #2863 — distinguish cancellation from failure. A bare
- // CancellationException means the collector / surrounding
- // scope was cancelled; propagate per structured-concurrency
- // contract and close the channel WITH the cancel so the
- // consumer's flow surfaces a CancellationException, not a
- // synthetic Failed event. TimeoutCancellationException is
- // still a real failure (budget / per-op timeout) and rides
- // the Failed path so consumers and audit logs see it.
- if (t is CancellationException && t !is TimeoutCancellationException) {
- result.completeExceptionally(t)
- channel.close(t)
- throw t
- }
val failed = AgentEvent.Failed(agent.name, t, runtimeContext)
agent.fireAgentEvent(failed)
channel.send(failed)