Install
$ agentstack add mcp-deep-codeai-agents-kt ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ● Shell / process execution Used
- ● Environment & secrets Used
- ● Dynamic code execution Used
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
The typed agent runtime for the JVM. Compiler-enforced boundaries. Least-privilege tools. Audit evidence by construction. MCP-native.
Built for teams that need to say exactly what an AI system is allowed to do — with the in-runtime boundaries it does enforce, and the non-goals it doesn't, stated up front: see Security Model and the threat model.
Agents.KT is built for teams that need to know exactly what an AI system is allowed to do. Every agent is Agent: one input type, one output type, one job. Type mismatches and wrong compositions are caught by the compiler where composition is purely type-driven, and structural misuses fail fast at construction time.
The 0.6–0.7 line turns those boundaries into reviewable evidence: deterministic permission manifests, runtime manifestHash correlation, JSONL audit export, a tamper-evident ToolAuditLedger, OTel/LangSmith/Langfuse bridge adapters, before-interceptor policy hooks, declarative tool policy metadata, reasoning/thinking stream, vendor-neutral prompt caching with prefix-stability guard, snapshot/resume with manifest-hash restore guard, and typed audit events for hallucinated tool calls. It is not a compliance product, and it does not OS-sandbox arbitrary tool code — [Security Model](#security-model) is precise about which boundaries are kernel-enforced versus deployer-owned. Agents.KT is the runtime behind agents-kt.dev.
First 10 Minutes
Requirements: JDK 21+, Kotlin 2.x, Gradle
// build.gradle.kts
dependencies {
implementation("ai.deep-code:agents-kt:0.8.2")
}
Or clone and build from source:
git clone https://github.com/Deep-CodeAI/Agents.KT.git
cd Agents.KT
./gradlew test
# #2807 — static analysis. Baseline freezes existing violations;
# new code is held to the rules in `detekt.yml`.
./gradlew detekt
Building an agent
An Agent has one input type, one output type, one or more skills, and an optional model for the agentic path. Skills come in two shapes:
implementedBy { input -> output }— deterministic Kotlin lambda. No LLM. Fastest and fully testable.tools(...)with amodel { }block — agentic. The framework runs a multi-turn loop where the LLM picks tools from the skill's allowlist; the runtime refuses anything outside it (authorization, not just prompting).
val coder = agent("coder") {
model { claude("claude-opus-4-7"); apiKey = System.getenv("ANTHROPIC_API_KEY") }
lateinit var writeFile: Tool, Any?>
lateinit var compile: Tool, Any?>
tools {
writeFile = tool("write_file", "Write a source file") { args -> writeFile(args) }
compile = tool("compile", "Compile the bundle") { args -> compile(args) }
}
skills {
skill("write-code", "Implement endpoints") { tools(writeFile, compile) }
}
}
When multiple skills can take the same input type, the LLM (or a manual skillSelection { }) routes between them.
Composing agents
Composition is purely type-driven — the compiler enforces that boundaries line up. Six primitives ship today:
| Primitive | Shape | What it does | |---|---|---| | a then b | Pipeline | Sequential. a.OUT must equal b.IN, enforced at compile time. | | a / b | Parallel> | Run both branches concurrently against the same input; collect results. | | agent.branch { … } | Branch | Route per source-output shape (onClass then …, onElse then …); sealed sources are exhaustiveness-checked. | | teacher wrap student | Pipeline | Teacher-student: teacher.OUT (a String) becomes student's per-call system prompt. | | forum { members(…); captain = … } | Forum | Council of members with a captain that emits the verdict. | | triage handoff { … } | Branch | Named hand-off to specialists on the source's typed output (#3871). Same routing as branch + an audit signal (onHandoff / HandoffPerformed); the target never sees the source's history — typed input only, unlike Swarm-style handoff. |
A single agent instance can only be placed in one composition — wiring it into two spots fails fast at construction. See [docs/composition.md](docs/composition.md) for the operator reference, [docs/patterns.md](docs/patterns.md) for the Anthropic "Building Effective Agents" catalog mapped 1:1 onto these primitives, and [docs/comparison.md](docs/comparison.md) for the release narrative.
One typed pipeline
val parse = agent("parse") {
skills {
skill("parse-spec", "Splits raw text into a structured specification") {
implementedBy { input -> Specification(input.text.split(",").map { it.trim() }) }
}
}
}
val generate = agent("generate") {
skills {
skill("gen-code", "Generates stub functions for each endpoint") {
implementedBy { spec -> CodeBundle(spec.endpoints.joinToString("\n") { "fun $it() {}" }) }
}
}
}
val review = agent("review") {
skills {
skill("review-code", "Approves code if it is non-empty") {
implementedBy { code -> ReviewResult(approved = code.source.isNotBlank()) }
}
}
}
// Compiler checks every boundary
val pipeline = parse then generate then review
// Pipeline
val result = pipeline(RawText("getUsers, createUser, deleteUser"))
// ReviewResult(approved=true)
Testing details — task names, integration test setup, mutation testing, and how to write tests with a stub ModelClient — are in [docs/testing.md](docs/testing.md). Build prerequisites are on the Building From Source wiki page. macOS contributors: the Linux sandbox tests (bwrap/firejail) need a Linux kernel, so they auto-skip on macOS — CI runs them on a native Ubuntu runner; see [docs/testing.md → Linux sandbox tests](docs/testing.md#linux-sandbox-tests-bwrap--firejail).
What Agents.KT Owns
Agents.KT owns the runtime boundary model:
- Typed
Agentcontracts and composition operators. - Per-skill tool authorization and typed tool handles.
- MCP client/server surfaces that share the same tool/skill shape.
- Permission manifests, declarative tool policies, and runtime audit correlation.
- JSONL audit export plus OTel, LangSmith, and Langfuse adapters through
ObservabilityBridge. - Local-first JVM execution with Ollama by default and cloud providers when you choose them.
These are the pieces the framework can make deterministic, testable, and reviewable in code. Start with [permission manifests](docs/permission-manifest.md), the [threat model](docs/threat-model.md), the [regulated deployment guide](docs/regulated-deployment.md), and the [comparison page](docs/comparison.md) for the release narrative. The manifest is also reachable outside Gradle via the standalone [agents-kt CLI](docs/cli.md) (generate / inspect / verify) — a drop-in CI gate that fails when a change widens a capability boundary (#1923).
What Agents.KT Does Not Own
Agents.KT emits evidence and enforces in-runtime boundaries; it does not replace your deployment controls:
- It is not a legal compliance product. It produces compliance-supporting artifacts and audit-ready evidence; your counsel and compliance team still classify the use case.
- It does not yet OS-sandbox arbitrary Kotlin lambdas. A tool's declared
ToolPolicyis now enforced in-JVM for filesystem-path arguments (Layer 1, #2890 — a declared write/read glob blocks out-of-policy absolute paths before the executor runs; see [docs/tool-policy-enforcement.md](docs/tool-policy-enforcement.md)), but a lambda can still touch paths it doesn't take as arguments, andnetwork/environmentisolation needs the Layer-2 OS/container sandbox (#1916), which remains a deployer responsibility for now. - It does not rate-limit public MCP ingress. Use
McpServerauth/policy plus your gateway. - It does not ship a universal prompt-injection classifier. Wire your chosen detector through
onBeforeTurn. - It does not try to be a vector-store, eval-suite, or hosted orchestration platform. It is the typed JVM runtime boundary underneath those integrations.
Why Agents.KT
Most agent frameworks let you wire anything to anything. Agents.KT says no.
| Problem | Agents.KT answer | |---------|-----------------| | God-agents with unlimited responsibilities | Agent — one type contract, compiler-enforced SRP | | Runtime type mismatches between agents | then requires A.OUT == B.IN — compile error otherwise | | The same agent instance wired into two places | Single-placement rule — IllegalArgumentException at construction time | | LLM doesn't know which skill to use | Manual skillSelection {} routing or automatic LLM routing — descriptions sell each skill to the router | | LLM doesn't know what context to load | knowledge("key", "description") { } entries — LLM reads descriptions before deciding to call | | Flat pipelines only | Composition operators covering sequential, forum, parallel, iterative, and branching patterns | | LLM output is an untyped string | @Generable + @Guide — JSON Schema, provider constrained decoding, prompt fragments, lenient deserializer, and PartiallyGenerated; KSP-generated metadata avoids runtime reflection when present | | MCP tools are wrappers, not first-class | McpClient.tools() returns first-class McpTool handles, while toolSkills() keeps the prompt-style skill adapter; agents can also be exposed as MCP servers via McpServer.from(agent) | | Permission model is stringly-typed | grants { tools(writeFile, compile) } — actual Tool references, compiler-validated (planned Phase 2) | | No testing story | AgentUnit — deterministic through semantic assertions (planned) | | JVM frameworks require Java installed | Native CLI binary via GraalVM (planned Phase 2 Priority) |
What's Shipped
This section is the index — every claim below points to working code in main, with the issue number that established it. Topical detail lives in [docs/](docs/).
Implemented today
These APIs work in main, are unit-tested, and are exercised by integration tests (./gradlew test for default suite, ./gradlew integrationTest for live-LLM):
- Typed agents —
Agentwith at least one skill producingOUT, validated at construction. See [docs/skills.md](docs/skills.md). - Skills with knowledge —
skill { knowledge("key", "...") { } }, lazy-loaded per call. See [docs/skills.md#shared-knowledge](docs/skills.md#shared-knowledge). - Agentic loop with tool calling — multi-turn
chat ↔ toolsdriven by the model. See [docs/model-and-tools.md](docs/model-and-tools.md). - Eight model providers —
model { ollama(...) }for local/cloud Ollama,model { claude("claude-opus-4-7"); apiKey = ... }for Anthropic's Messages API,model { openai("gpt-4o"); apiKey = ... }for OpenAI Chat Completions,model { deepseek("deepseek-v4-flash"); apiKey = ... }for DeepSeek's OpenAI-compatible API,model { kimi("moonshot-v1-8k"); apiKey = ... }for Kimi (Moonshot AI) — long-context (-32k/-128k) variants via the model string (#2697),model { openrouter("anthropic/claude-3.5-sonnet"); apiKey = ...; openRouterHttpReferer = "https://your.app"; openRouterXTitle = "Your App" }for OpenRouter — the OpenAI-compatible multi-provider aggregator that fronts hundreds of upstream models behind one API, with optionalHTTP-Referer/X-Titleattribution headers honored across the catalog (#2701),model { perplexity("sonar"); apiKey = ... }for Perplexity's Sonar web-grounded API (OpenAI-compatible;sonar/sonar-pro/sonar-reasoning-pro/sonar-deep-research, #3675), andmodel { gemini("gemini-2.5-flash"); apiKey = ... }for Google Gemini's Generative Language API — a full from-scratch adapter (not OpenAI-compatible):contents/partswithuser/modelroles,systemInstruction,functionDeclarationstool calling, native SSE streaming,responseJsonSchemaconstrained decoding, thought-summary reasoning, andinlineDatavision (#1917). All eight go through oneModelClientinterface —LlmMessage/LlmResponseare provider-agnostic, tools/system/role mapping is per-adapter (#1644, #1656). - Typed tools via
@Generable—tool(...)with reflection-built JSON Schema;additionalProperties: false; sealed-discriminator validation (#658, #661, #699). - Provider-neutral tool handles — local typed tool handles and MCP-discovered tools share
Tool;McpClient.tools()returnsMcpTool, String>for grants/manifests/policy work whiletoolSkills()remains available for primary-skill use (#1948). - Provider constrained decoding for
@Generableoutputs — agentic skills returning@Generabletypes pass their JSON Schema to supporting providers automatically: OpenAIresponse_format.json_schema, Ollamaformat, and Anthropic's forced structured-output tool pattern (#1949). - Reasoning/thinking stream — opt-in
model { reasoning(budgetTokens = ..., effort = ...) }surfaces a model's reasoning asAgentEvent.Reasoning, a channel separate from the answerTokenstream, so a production UI can render live reasoning instead of a spinner. Claude (extended thinking), DeepSeek (reasoning_content), Ollama (thinking), and Gemini (thought summaries viathinkingConfig.includeThoughts) emit reasoning text; OpenAI Chat Completions reportsreasoning_effort+ reasoning token counts only (no text). Off by default (#2406). See [docs/streaming.md](docs/streaming.md). - Typed tool refs in skill allowlists —
tool(...)returns aToolhandle;skill { tools(writeFile, compile) }accepts handles, the IDE catches typos (#1015–#1017). The legacytools("name")string form remains for built-in tools and runtime-discovered MCP names but produces a deprecation warning. - Declarative tool policies + in-JVM filesystem enforcement —
tool { policy { risk = ToolRisk.Medium; filesystem { write("/uploads/**") }; network { denyAll() } } }records expected filesystem/network/environment scope for manifests and audit events, and the declared filesystem path-arguments are now enforced at runtime by default (Layer 1, #2890): an out-of-policy absolute path is denied before the executor runs, surfacing viaonToolDenied/PipelineEvent.ToolDenied. Opt-in by declaration;enforceToolPolicies = falseopts out. OS/container sandboxing of executors (network/env, subprocess isolation) is the separate Layer-2 work (#1916). See [docs/tool-policy-enforcement.md](docs/tool-policy-enforcement.md). - Permission manifests —
agent.permissionManifest()andpipeline.permissionManifest()emit deterministic JSON/YAML capability graphs with agents, skills, tools, memory, MCP, providers, budgets, guardrails, composition structure, masked secrets, and a SHA-256 hash that is attached to runtime events (#1912). See [docs/permission-manifest.md](docs/permission-manifest.md). - 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).
- Before interceptors —
onBeforeSkill,onBeforeTurn, andonBeforeToolCallreturnDecision(Proceed,ProceedWith,Deny,Substitute) for dynamic policy, prompt filtering, argument mutation, and synthetic results (#1907). See [docs/interceptors.md](docs/interceptors.md). - 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). - **Compositi
…
Source & license
This open-source MCP server is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Deep-CodeAI
- Source: Deep-CodeAI/Agents.KT
- License: MIT
- Homepage: https://deep-code.ai
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.