Koog 1.3 idioms, gotchas, and scaffolding skills for Kotlin agents on the JVM
72
91%
Does it follow best practices?
Run evals on this skill
Adds up to 20 points to the overall score
Low
Low-risk findings worth noting
Process steps in order. Do not skip ahead.
Agent Skills are runtime-discovered capability bundles, scanned when
discoverSkills is called. Reach for them when 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.3.0-beta") // discoverSkills, generateSkillsPrompt
implementation("ai.koog:agents-ext:1.3.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 prevents writes through these tools. It does not restrict reads to the
discovery roots. ReadFileTool accepts arbitrary absolute paths accessible to the process.
The example discovers once at startup. Restart the process to load a new catalog,
or call discoverSkills again and rebuild the catalog prompt and agent before the
next run. Adding a file alone does not refresh an existing agent's system prompt.
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 appears after restart or explicit rediscovery and catalog
regeneration, with no recompileSkill bodies are instructions the model will follow, and they come from the filesystem. Treat a skills root exactly like any other untrusted input:
For a trusted local filesystem with a confined reader, replace the default
ReadFileTool / ListDirectoryTool registrations with the tools in:
skills/use-agent-skills/references/confined-file-tools.md.tessl-plugin
skills
add-observability
add-persistence
references
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
references
test-koog-agents
trace-agent-internals
use-agent-skills
references
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