CtrlK
BlogDocsLog inGet started
Tessl Logo

jbaruch/koog

Koog 1.0 idioms, gotchas, and scaffolding skills for Kotlin agents on the JVM

88

1.95x
Quality

88%

Does it follow best practices?

Impact

88%

1.95x

Average score across 43 eval scenarios

SecuritybySnyk

Passed

No known issues

Overview
Quality
Evals
Security
Files

SKILL.mdskills/model-planner-subtasks/

name:
model-planner-subtasks
description:
Model a problem domain as a tree of typed subtasks the Koog 1.0 planner can execute — the `PlannerNode` data model, parallel vs sequential composition, accessing in-flight subtasks through `AIAgentStorage`, retry-on-parse-failure edges, and TL;DR compression between phases. Goes deeper than `use-planner` (which only picks the planner factory). Use when the user asks to "decompose into subtasks", "model the agent's work as a tree", "compose parallel and sequential steps", "retry failed subtasks", or describes a richer planner usage than the basic factory.

Model Planner Subtasks Skill

Process steps in order. Do not skip ahead.

Step 1 — Confirm This Is the Right Depth

use-planner covers picking the planner type and basic factory wiring. This skill is for projects that need to model the plan itself — the tree of subtasks the planner builds, how its nodes compose, how to access in-flight subtasks during a run, how to retry failures gracefully.

If the user just needs "an LLM-based planner with default behavior", invoke Skill(skill: "use-planner"). If they describe a domain in terms of "parallel and sequential subtasks", "retrying a failed step", "inspecting the plan tree", continue here.

Proceed immediately to Step 2.

Step 2 — Understand the PlannerNode Tree

The planner represents a plan as a tree of PlannerNodes:

  • Leaf nodes — concrete actions the planner-driven agent executes (typically an LLM call or a tool invocation)
  • Sequential composite nodes — children run in order, each consuming the previous's output
  • Parallel composite nodes — children run concurrently and their outputs are merged

Building blocks (from agents-planner):

import ai.koog.agents.planner.PlannerNode

val plan = PlannerNode.sequential(
    PlannerNode.leaf("classify", description = "..."),
    PlannerNode.parallel(
        PlannerNode.leaf("lookup_user", description = "..."),
        PlannerNode.leaf("lookup_account", description = "..."),
    ),
    PlannerNode.leaf("respond", description = "..."),
)

For LLM-based planners (Planners.llmBased), the tree is generated by the LLM from the goal — your code doesn't build it explicitly. For static planners or when you want to pre-seed structure, build it directly and pass to the strategy.

Proceed immediately to Step 3.

Step 3 — Access In-Flight Subtasks Through Storage

Use AIAgentStorage to track in-flight planner subtasks across nodes. The pattern from the in-repo example:

import ai.koog.agents.core.agent.context.createStorageKey

val unfinishedNodesKey = createStorageKey<MutableList<PlannerNode.Builder.Reference>>("unfinishedNodes")
val currentNodeKey = createStorageKey<PlannerNode.Builder.Reference>("currentNode")

// inside a strategy node body, while iterating subtasks:
val unfinished = storage.get(unfinishedNodesKey) ?: mutableListOf()
val next = unfinished.removeFirstOrNull() ?: return null
storage.set(currentNodeKey, next)
storage.set(unfinishedNodesKey, unfinished)

PlannerNode.Builder.Reference is the planner's identifier for a subtask in the tree. Keep it in storage to maintain "where am I in the plan" across nodes.

In 1.0 storage is serialized into checkpoints — references survive a restart if checkpointing is on (add-persistence).

Proceed immediately to Step 4.

Step 4 — Add Retry-on-Parse-Failure Edges

When a subtask requires the LLM to produce structured output (a JSON plan revision, a typed result), parse failures should retry the subtask, not abort the whole plan:

val executeSubtask by nodeLLMRequest()
val parseResult by node<Message.User, ParsedSubtaskResult?>("parse") { msg ->
    runCatching { parseStructured<ParsedSubtaskResult>(msg.content) }.getOrNull()
}

edge(executeSubtask forwardTo parseResult)
edge(parseResult forwardTo continueWithResult onCondition { it != null })
edge(parseResult forwardTo executeSubtask onCondition { it == null } transformed { "Re-emit the plan revision as valid JSON." })

The retry edge feeds a corrective prompt back into the same subtask node. Cap retries — usually 2–3 — by counting attempts in storage; uncapped retries on a misformatted LLM response burn budget fast.

Proceed immediately to Step 5.

Step 5 — Compress History Between Phases

Long planner runs accumulate enormous history (every subtask's LLM round-trip lands in the conversation). Without compression you hit context limits halfway through. Compress at phase boundaries — between "planning" and "execution", or every N completed subtasks:

val phaseBoundary by node<Unit, Unit>("phase-boundary") {
    llm.writeSession {
        replaceHistoryWithTLDR()
    }
}

Pick the compression points by looking at the plan tree — boundaries at the end of a parallel block or between sequential phases. See manage-state for the broader compression vocabulary.

Proceed immediately to Step 6.

Step 6 — Inspect the Plan Tree at Runtime

For debugging and observability, expose the in-flight plan as a side channel:

  • Capture the tree (or its current cursor position) in AIAgentStorage at every node entry
  • Forward it to the event handler — handle-agent-events's onNodeEntry (or equivalent) callback can dump the plan state to stdout for live demos
  • Persist it via the trace feature (trace-agent-internals) for post-mortem analysis

For production, surface key counters (subtasks completed, subtasks failed, retries used) as OpenTelemetry custom metrics on top of the built-in gen_ai.* set.

Reference: examples/simple-examples/.../planner/PlannerAgentExample.kt — the richest demonstration of all six steps in working code. The example's inline parse is a TODO() skeleton; the rest of the plumbing (storage keys, retry edges, history compression) is real.

Finish here.

skills

model-planner-subtasks

README.md

tile.json