Skip to content

Commit 0f247f2

Browse files
authored
Merge pull request #44 from Deep-CodeAI/feat/1018-agents-kt-ksp-module
feat(#1018): :agents-kt-ksp Gradle module skeleton + Maven Central pu…
2 parents c200747 + 295c5e4 commit 0f247f2

56 files changed

Lines changed: 755 additions & 285 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CHANGELOG.md

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,33 @@
22

33
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.
44

5+
## [Unreleased] — targeting 0.3.0
6+
7+
First leg of the **KSP / compile-time-validation initiative** described in `docs/ksp-design.md`. This release ships **typed tool refs** — Kotlin's type system catches `tools("typo")` mistakes that previously bombed at agent `validate()` (or in CI test runs). Plus the `:agents-kt-ksp` module skeleton, ready for the Phase 2 codegen work.
8+
9+
### Binary compatibility
10+
11+
**Source-compatible** with 0.2.x — your code compiles unchanged (you'll see deprecation warnings on `tools("name")` calls, with a `ReplaceWith` hint to the typed form).
12+
13+
**NOT binary-compatible.** `tool(...)` builders changed return type `Unit → Tool<Args, Result>`. Consumers who upgrade the `agents-kt` jar without recompiling will hit `NoSuchMethodError` at first tool registration. Recompile against 0.3.0; no source changes required. If you depend on `agents-kt` from a published library, that library must also republish against 0.3.0. This is why the bump goes 0.2.x → 0.3.0 and not 0.2.x → 0.2.3.
14+
15+
### Added
16+
- `Tool<Args, Result>` typed handle returned by every `tool(...)` builder overload. Phantom-typed wrapper around `ToolDef` whose type parameters propagate through the agent build (#1015).
17+
- `Skill.tools(first: Tool<*, *>, vararg rest: Tool<*, *>)` — typed overload alongside the legacy stringly-typed form. Tool typos become red squiggles in IntelliJ instead of runtime errors at `validate()` (#1016).
18+
- `Skill.tools()` — explicit no-argument overload that marks a skill agentic with no allowlisted tools (the model gets only memory + built-in tools). Disambiguates from the deprecated string-vararg form.
19+
- `docs/ksp-design.md` — initiative roadmap, runtime-checks inventory (72 sites bucketed), three-phase plan.
20+
- **`:agents-kt-ksp` Gradle module** — new sibling artifact `ai.deep-code:agents-kt-ksp` published to Maven Central. Empty `SymbolProcessorProvider` skeleton; consumers can wire it via `ksp("ai.deep-code:agents-kt-ksp:VERSION")` but it does no work yet. Phase 2 of the KSP initiative (#1018). The validation pass (#1019) and schema-generation pass (#1020) plug into the processor in subsequent issues.
21+
- Multi-module Gradle setup: `settings.gradle.kts` includes `:agents-kt-ksp`; same Maven Central + Sonatype publishing wiring as the runtime artifact; same in-memory PGP signing.
22+
- Depends on `com.google.devtools.ksp:symbol-processing-api:2.3.7` (KSP2, decoupled from Kotlin compiler version).
23+
- Reads runtime annotations via `compileOnly(project(":"))` — never lands on the consumer's runtime classpath.
24+
25+
### Changed
26+
- README + `docs/model-and-tools.md` examples now show typed-ref form first; string form is documented only for built-in tools (`escalate`, `throwException`, `memory_*`).
27+
- Internal test fixtures migrated to typed refs across 35+ files (#1017).
28+
29+
### Deprecated
30+
- `Skill.tools(vararg names: String)` — soft-deprecated at warning level. Stays for built-in tools (`escalate`, `throwException`, `memory_*`) and runtime-discovered tool names (MCP); no removal planned pre-1.0.
31+
532
## [0.2.3] — 2026-05-04
633

734
Hotfix patch — single bug.

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@ These APIs work in `main`, are unit-tested, and are exercised by integration tes
8484
- **Skills with knowledge**`skill { knowledge("key", "...") { } }`, lazy-loaded per call. See [docs/skills.md#shared-knowledge](docs/skills.md#shared-knowledge).
8585
- **Agentic loop with tool calling** — multi-turn `chat ↔ tools` driven by the model. See [docs/model-and-tools.md](docs/model-and-tools.md).
8686
- **Typed tools via `@Generable`**`tool<Args, Result>(...)` with reflection-built JSON Schema; `additionalProperties: false`; sealed-discriminator validation (#658, #661, #699).
87+
- **Typed tool refs in skill allowlists**`tool(...)` returns a `Tool<Args, Result>` handle; `skill { tools(writeFile, compile) }` accepts handles, the IDE catches typos (#1015#1017). The legacy `tools("name")` string form remains for built-in tools and runtime-discovered MCP names but produces a deprecation warning.
8788
- **Per-skill tool authorization** — runtime allowlist; the prompt's "Available tools" listing is descriptive, the security boundary is the runtime check (#630). See [docs/model-and-tools.md#tool-authorization-model](docs/model-and-tools.md#tool-authorization-model).
8889
- **Inline tool-call fallback** — auto-recovery when an Ollama model rejects native `tools` (e.g. `gemma3:4b`) — strips the field, injects inline JSON format prompt, retries (#702, #706). See [docs/model-and-tools.md#inline-tool-call-fallback-ollama-models-without-native-tool-support](docs/model-and-tools.md#inline-tool-call-fallback-ollama-models-without-native-tool-support).
8990
- **Composition operators**`then`, `/` (parallel), `*` and `forum { }` (multi-agent), `.loop {}`, `.branch {}` on sealed types. See [docs/composition.md](docs/composition.md).

agents-kt-ksp/build.gradle.kts

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
plugins {
2+
kotlin("jvm")
3+
`maven-publish`
4+
signing
5+
}
6+
7+
group = "ai.deep-code"
8+
version = rootProject.version
9+
10+
repositories {
11+
mavenCentral()
12+
}
13+
14+
dependencyLocking {
15+
lockAllConfigurations()
16+
}
17+
18+
dependencies {
19+
// KSP processor API. KSP2 (2.x) is decoupled from the bundled Kotlin
20+
// compiler version, so the same KSP release works across a range of
21+
// Kotlin versions. See https://github.com/google/ksp.
22+
implementation("com.google.devtools.ksp:symbol-processing-api:2.3.7")
23+
24+
// Read annotations defined in the runtime library (e.g. @Generable).
25+
// compileOnly — never end up on the consumer's runtime classpath; the
26+
// consumer already has the runtime jar via their own implementation(...).
27+
compileOnly(project(":"))
28+
29+
testImplementation(kotlin("test"))
30+
}
31+
32+
kotlin {
33+
jvmToolchain(21)
34+
}
35+
36+
java {
37+
withSourcesJar()
38+
withJavadocJar()
39+
}
40+
41+
publishing {
42+
publications {
43+
create<MavenPublication>("mavenCentral") {
44+
from(components["java"])
45+
46+
artifactId = "agents-kt-ksp"
47+
48+
pom {
49+
name.set("Agents.KT KSP processor")
50+
description.set("Compile-time KSP processor for Agents.KT — validates @Generable shape and (in later releases) generates JSON Schema + lenient parser code.")
51+
url.set("https://github.com/Deep-CodeAI/Agents.KT")
52+
53+
licenses {
54+
license {
55+
name.set("MIT License")
56+
url.set("https://opensource.org/licenses/MIT")
57+
}
58+
}
59+
60+
developers {
61+
developer {
62+
id.set("kskobeltsyn")
63+
name.set("Konstantin Skobeltsyn")
64+
email.set("konstantin@deep-code.ai")
65+
}
66+
}
67+
68+
scm {
69+
url.set("https://github.com/Deep-CodeAI/Agents.KT")
70+
connection.set("scm:git:git://github.com/Deep-CodeAI/Agents.KT.git")
71+
developerConnection.set("scm:git:ssh://git@github.com/Deep-CodeAI/Agents.KT.git")
72+
}
73+
}
74+
}
75+
}
76+
77+
repositories {
78+
maven {
79+
name = "sonatype"
80+
url = uri("https://ossrh-staging-api.central.sonatype.com/service/local/staging/deploy/maven2/")
81+
credentials {
82+
username = findProperty("sonatypeUsername") as String? ?: ""
83+
password = findProperty("sonatypePassword") as String? ?: ""
84+
}
85+
}
86+
}
87+
}
88+
89+
signing {
90+
val signingKey = findProperty("signing.key") as String?
91+
val signingPassword = findProperty("signing.password") as String?
92+
if (signingKey != null) {
93+
useInMemoryPgpKeys(signingKey, signingPassword ?: "")
94+
}
95+
sign(publishing.publications["mavenCentral"])
96+
}
97+
98+
tasks.withType<Sign>().configureEach {
99+
onlyIf { findProperty("signing.key") != null }
100+
}

agents-kt-ksp/gradle.lockfile

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# This is a Gradle generated file for dependency locking.
2+
# Manual edits can break the build and are not advised.
3+
# This file is expected to be part of source control.
4+
com.google.devtools.ksp:symbol-processing-api:2.3.7=compileClasspath
5+
org.jetbrains.kotlin:kotlin-build-tools-api:2.3.21=kotlinBuildToolsApiClasspath
6+
org.jetbrains.kotlin:kotlin-build-tools-compat:2.3.21=kotlinBuildToolsApiClasspath
7+
org.jetbrains.kotlin:kotlin-build-tools-cri-impl:2.3.21=kotlinBuildToolsApiClasspath
8+
org.jetbrains.kotlin:kotlin-build-tools-impl:2.3.21=kotlinBuildToolsApiClasspath
9+
org.jetbrains.kotlin:kotlin-compiler-embeddable:2.3.21=kotlinBuildToolsApiClasspath
10+
org.jetbrains.kotlin:kotlin-compiler-runner:2.3.21=kotlinBuildToolsApiClasspath
11+
org.jetbrains.kotlin:kotlin-daemon-client:2.3.21=kotlinBuildToolsApiClasspath
12+
org.jetbrains.kotlin:kotlin-daemon-embeddable:2.3.21=kotlinBuildToolsApiClasspath
13+
org.jetbrains.kotlin:kotlin-reflect:1.6.10=kotlinBuildToolsApiClasspath
14+
org.jetbrains.kotlin:kotlin-script-runtime:2.3.21=kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain
15+
org.jetbrains.kotlin:kotlin-scripting-common:2.3.21=kotlinCompilerPluginClasspathMain
16+
org.jetbrains.kotlin:kotlin-scripting-compiler-embeddable:2.3.21=kotlinCompilerPluginClasspathMain
17+
org.jetbrains.kotlin:kotlin-scripting-compiler-impl-embeddable:2.3.21=kotlinCompilerPluginClasspathMain
18+
org.jetbrains.kotlin:kotlin-scripting-jvm:2.3.21=kotlinCompilerPluginClasspathMain
19+
org.jetbrains.kotlin:kotlin-stdlib:2.3.21=compileClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain
20+
org.jetbrains.kotlin:kotlin-tooling-core:2.3.21=kotlinBuildToolsApiClasspath
21+
org.jetbrains.kotlinx:kotlinx-coroutines-core-jvm:1.8.0=kotlinBuildToolsApiClasspath
22+
org.jetbrains:annotations:13.0=compileClasspath,kotlinBuildToolsApiClasspath,kotlinCompilerPluginClasspathMain
23+
empty=kotlinScriptDefExtensions
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package agents_engine.ksp
2+
3+
import com.google.devtools.ksp.processing.Resolver
4+
import com.google.devtools.ksp.processing.SymbolProcessor
5+
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
6+
import com.google.devtools.ksp.symbol.KSAnnotated
7+
8+
/**
9+
* KSP processor entry point for Agents.KT (#1018, P2.1).
10+
*
11+
* Currently a no-op skeleton — exists so the `:agents-kt-ksp` artifact can be
12+
* published, applied via the KSP plugin in consumer projects, and exercised
13+
* end-to-end without doing any work yet. The validation pass (#1019) and
14+
* schema-generation pass (#1020) plug into [process] in subsequent issues.
15+
*/
16+
class AgentsKtSymbolProcessor(
17+
@Suppress("unused") private val env: SymbolProcessorEnvironment,
18+
) : SymbolProcessor {
19+
20+
override fun process(resolver: Resolver): List<KSAnnotated> {
21+
// #1019 will walk every @Generable class here and emit compile-time
22+
// validation errors via env.logger.error(...).
23+
// #1020 will then generate per-class *_GeneratedSchema.kt files using
24+
// env.codeGenerator.createNewFile(...).
25+
return emptyList()
26+
}
27+
}
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
package agents_engine.ksp
2+
3+
import com.google.devtools.ksp.processing.SymbolProcessor
4+
import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
5+
import com.google.devtools.ksp.processing.SymbolProcessorProvider
6+
7+
/**
8+
* Service-loader entry point. KSP picks this up via
9+
* `META-INF/services/com.google.devtools.ksp.processing.SymbolProcessorProvider`.
10+
*
11+
* Consumers apply the KSP plugin and add `ksp("ai.deep-code:agents-kt-ksp:0.3.0")`
12+
* to their dependencies; KSP discovers this provider at compile time and runs
13+
* [AgentsKtSymbolProcessor.process] over their source tree.
14+
*/
15+
class AgentsKtSymbolProcessorProvider : SymbolProcessorProvider {
16+
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor =
17+
AgentsKtSymbolProcessor(environment)
18+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
agents_engine.ksp.AgentsKtSymbolProcessorProvider

docs/model-and-tools.md

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,22 @@ val calculator = agent<String, String>("calculator") {
99
prompt("You are a calculator. Use the provided tools to evaluate expressions step by step.")
1010
model { ollama("gpt-oss:120b-cloud"); host = "localhost"; port = 11434; temperature = 0.0 }
1111

12+
lateinit var add: Tool<Map<String, Any?>, Any?>
13+
lateinit var subtract: Tool<Map<String, Any?>, Any?>
14+
lateinit var multiply: Tool<Map<String, Any?>, Any?>
15+
lateinit var divide: Tool<Map<String, Any?>, Any?>
16+
lateinit var power: Tool<Map<String, Any?>, Any?>
1217
tools {
13-
tool("add", "Add two numbers. Args: a, b") { args -> num(args, "a") + num(args, "b") }
14-
tool("subtract", "Subtract b from a. Args: a, b") { args -> num(args, "a") - num(args, "b") }
15-
tool("multiply", "Multiply two numbers. Args: a, b") { args -> num(args, "a") * num(args, "b") }
16-
tool("divide", "Divide a by b. Args: a, b") { args -> num(args, "a") / num(args, "b") }
17-
tool("power", "Raise base to exponent. Args: base, exp") { args -> Math.pow(num(args, "base"), num(args, "exp")) }
18+
add = tool("add", "Add two numbers. Args: a, b") { args -> num(args, "a") + num(args, "b") }
19+
subtract = tool("subtract", "Subtract b from a. Args: a, b") { args -> num(args, "a") - num(args, "b") }
20+
multiply = tool("multiply", "Multiply two numbers. Args: a, b") { args -> num(args, "a") * num(args, "b") }
21+
divide = tool("divide", "Divide a by b. Args: a, b") { args -> num(args, "a") / num(args, "b") }
22+
power = tool("power", "Raise base to exponent. Args: base, exp") { args -> Math.pow(num(args, "base"), num(args, "exp")) }
1823
}
1924

2025
skills {
2126
skill<String, String>("solve", "Evaluate arithmetic expressions using tools") {
22-
tools("add", "subtract", "multiply", "divide", "power")
27+
tools(add, subtract, multiply, divide, power)
2328
}
2429
}
2530

@@ -100,8 +105,9 @@ A per-instance latch records the model's incapability, so subsequent `chat()` ca
100105
val a = agent<String, String>("calc") {
101106
// gemma3:4b doesn't support native tools — the fallback drives it via inline JSON
102107
model { ollama("gemma3:4b"); host = "localhost"; port = 11434 }
103-
tools { tool("evaluate", "Evaluate an arithmetic expression") { args -> eval(args["expression"]!!) } }
104-
skills { skill<String, String>("calc", "Compute") { tools("evaluate") } }
108+
lateinit var evaluate: Tool<Map<String, Any?>, Any?>
109+
tools { evaluate = tool("evaluate", "Evaluate an arithmetic expression") { args -> eval(args["expression"]!!) } }
110+
skills { skill<String, String>("calc", "Compute") { tools(evaluate) } }
105111
}
106112
a("Compute (2+3)*4") // works — agent invokes evaluate via inline tool call, returns "20"
107113
```
@@ -194,12 +200,14 @@ assistant("Translate this to French: Hello world")
194200
```kotlin
195201
val compute = agent<String, Int>("calculator") {
196202
model { ollama("gpt-oss:120b-cloud"); host = "localhost"; port = 11434; temperature = 0.0 }
203+
lateinit var add: Tool<Map<String, Any?>, Any?>
204+
lateinit var power: Tool<Map<String, Any?>, Any?>
197205
tools {
198-
tool("add", "Add two numbers. Args: a, b") { args -> num(args, "a") + num(args, "b") }
199-
tool("power", "Raise base to exponent. Args: base, exp") { args -> Math.pow(num(args, "base"), num(args, "exp")) }
206+
add = tool("add", "Add two numbers. Args: a, b") { args -> num(args, "a") + num(args, "b") }
207+
power = tool("power", "Raise base to exponent. Args: base, exp") { args -> Math.pow(num(args, "base"), num(args, "exp")) }
200208
}
201209
skills { skill<String, Int>("solve", "Evaluate arithmetic expressions") {
202-
tools("add", "power")
210+
tools(add, power)
203211
transformOutput { it.trim().toIntOrNull() ?: Regex("-?\\d+").find(it)?.value?.toInt() ?: error("No int in: $it") }
204212
}}
205213
}

0 commit comments

Comments
 (0)