Koog 1.2 idioms, gotchas, and scaffolding skills for Kotlin agents on the JVM
71
89%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
View guide
Low
Low-risk findings worth noting
Process steps in order. Do not skip ahead.
Agent Skills are runtime-discovered capability bundles, read off disk on every run. Reach for them when the set of capabilities should change without recompiling — a directory a non-developer drops files into, a skills repo shared across agents.
If the capability is fixed at build time and typed, that is a tool, not a skill —
use Skill(skill: "add-tool") instead. Finish here.
If the work is a multi-stage pipeline with typed handoffs, use
Skill(skill: "domain-model-subtask-pipeline"). Finish here.
Continue to Step 1 only when the capability set genuinely needs to change without a recompile.
The umbrella does not pull either of these. Both are on the beta version line.
Path: build.gradle.kts
implementation("ai.koog:skills:1.2.0-beta") // discoverSkills, generateSkillsPrompt
implementation("ai.koog:agents-ext:1.2.0-beta") // ReadFileTool, ListDirectoryToolagents-ext is required, not optional: without file tools the agent can see the
catalog but cannot read any skill body.
Proceed immediately to Step 2.
Discovery expects one directory per skill, each containing a SKILL.md whose
YAML frontmatter carries name and description.
Path: skills/<skill-name>/SKILL.md
---
name: <skill-name>
description: What this does and when to use it. The model sees this in the catalog and picks on it, so write a trigger, not a title.
---
# <skill-name>
Instructions, rules, vocabulary, examples — whatever the model needs to apply it.Four hard requirements, each of which silently drops the skill when violated:
name must equal the parent directory namename and description must be present and non-blankProceed immediately to Step 3.
Path: Main.kt
import ai.koog.rag.base.files.JVMFileSystemProvider
import ai.koog.skills.discovery.discoverSkills
val skillsRoot = "/absolute/path/to/skills"
val discovered = discoverSkills(JVMFileSystemProvider.ReadOnly, listOf(skillsRoot))Use JVMFileSystemProvider.ReadOnly — a skills directory is input, and a read-only
provider means a prompt-injected instruction inside a SKILL.md cannot rewrite it.
Pass absolute paths. Under a Gradle run task the working directory is the
module directory, not the project root, so a relative root silently discovers nothing.
Wire it through explicitly:
// build.gradle.kts
tasks.named<JavaExec>("run") {
systemProperty("skills.root", rootProject.layout.projectDirectory.dir("skills").asFile.absolutePath)
}Proceed immediately to Step 4.
Path: Main.kt
import ai.koog.skills.prompt.SkillsPromptFormat
import ai.koog.skills.prompt.generateSkillsPrompt
val skillsPrompt = generateSkillsPrompt(discovered, SkillsPromptFormat.XML)SkillsPromptFormat also offers YAML and JSON. Default to XML — the catalog
nests, and XML degrades most gracefully when a description contains markup.
Proceed immediately to Step 5.
Path: Main.kt
val agent = AIAgent(
promptExecutor = executor,
systemPrompt = """
Before using a skill, disclose it: list the skill directory and read the
SKILL.md, then apply it.
$skillsPrompt
""".trimIndent(),
llmModel = model,
toolRegistry = ToolRegistry {
tool(ListDirectoryTool(JVMFileSystemProvider.ReadOnly))
tool(ReadFileTool(JVMFileSystemProvider.ReadOnly))
},
)Instruct the agent to disclose before applying. The tool trace is then a readable audit of which skill fired, and a mis-selected skill becomes visible instead of silent.
Proceed immediately to Step 6.
Confirm all four, in order:
println(discovered.joinToString { it.name }). Empty means
a relative path, a name/directory mismatch, or malformed frontmatter — check the
warning log before touching anything elseSKILL.md and re-running picks it up with no recompile. If it does
not, the root is wrongSkill bodies are instructions the model will follow, and they come from the filesystem. Treat a skills root exactly like any other untrusted input:
.tessl-plugin
skills
add-observability
add-persistence
add-rag
add-structured-output
add-token-budgeting
add-tool
cache-llm-calls
define-prompt
domain-model-subtask-pipeline
references
enable-prompt-caching
handle-agent-events
manage-state
migrate-from-0-x
model-planner-subtasks
persist-chat-history
query-sql-from-agent
scaffold-agent
snapshot-and-restore
test-koog-agents
trace-agent-internals
use-agent-skills
use-attachments
use-cli-agents
use-functional-agent
use-llm-node-variants
use-planner
wire-a2a
wire-acp-server
wire-ktor-server
wire-mcp-server
wire-spring-boot