Phase 4.2 of the roadmap — deterministic workflow planning from user intent.
The Skill Planner transforms user intent (natural language) into an execution plan (a structured list of skills to use and why).
It is responsible for:
- Understanding what the user is asking for (intent analysis)
- Matching those requests against available skills (capability matching)
- Selecting appropriate skills (skill selection)
- Ordering skills for logical execution (dependency-aware ordering)
- Producing an explainable plan (with reasoning for every decision)
The planner is NOT responsible for:
- Executing skills
- Invoking tools
- Generating prompts
- Calling models
- Mutating state
These belong to the Workflow Runtime (Phase 4.3).
The planner consists of three layers:
User Intent
↓
Intent Analysis (analyzeIntent)
↓
Capability Matching (matchCapabilityAgainstRegistry)
↓
Skill Selection & Ordering (selectSkills, buildExecutionSteps)
↓
Execution Plan (ExecutionPlan struct)
Converts natural language into a list of capabilities the user is requesting.
Method: Simple, deterministic heuristic splitting:
- Split on
,,;,and,or,then - Normalize to lowercase
- Remove filler words (
a,the,for, etc.)
Why not ML? Determinism is more important than accuracy at this stage. The planner's decisions must be explainable and reproducible. If semantic analysis becomes necessary, it can be added as an extension without breaking existing workflows.
Matches each requested capability against the Skill Registry using similarity scoring.
Method: Token-based Jaccard similarity:
- Extract words from both the requested capability and each skill's declared capabilities
- Calculate overlap / union (Jaccard)
- Keep matches scoring > 0.3
- Rank by score (ties broken alphabetically)
Match types:
- Direct: Exact word overlap between requested capability and skill's stated capabilities
- Partial: Semantic similarity (Jaccard score) suggests the skill might help
Chooses which skills to include in the plan based on matched capabilities.
Algorithm:
- For each matched capability, select the top-ranked skill
- Add all dependencies of selected skills (recursively)
- Add related skills if plan is under-specified (< 8 skills)
- Limit depth to prevent bloat (max 8 skills)
Sorts selected skills using topological sort (Kahn's algorithm) to ensure dependencies always execute first.
Properties:
- Deterministic (same input always produces same order)
- Respects all dependency constraints
- Breaks circular dependencies (defensive) by taking the first remaining skill
interface ExecutionPlan {
// User's normalized intent
intent: string;
// Extracted capabilities from intent
requestedCapabilities: string[];
// How each capability matched against registry
capabilityMatches: CapabilityMatch[];
// Ordered list of skills to execute
steps: ExecutionStep[];
// Human-readable summary
summary: string;
// Complexity assessment: simple, moderate, complex
complexity: 'simple' | 'moderate' | 'complex';
// Optional warnings or notes
notes?: string[];
}interface CapabilityMatch {
capability: string;
matches: Array<{
skillId: string;
matchType: 'direct' | 'partial' | 'related';
score: number;
explanation: string;
}>;
isMatched: boolean;
unmatchedReason?: string;
}interface ExecutionStep {
skillId: string;
order: number;
reason: 'direct-capability-match' | 'alias-match' | 'domain-expert' |
'dependency-requirement' | 'recommended-pairing' | 'related-skill';
rationale?: string;
dependencies: string[];
contextInputs?: Record<string, unknown>;
}The reason field is crucial for explainability — it tells future developers (and the user) exactly why this skill was included.
Generate a plan and save to execution-plan.json:
bun run plan "create interview questions for a senior manager"Output includes:
- Matching analysis (which capabilities matched which skills)
- Execution steps (ordered with reasoning)
- Validation results (any issues detected)
- Suggestions (improvements the user might consider)
See examples/planner-runtime/from-intent-to-execution.md
for a full worked example, including sample plan output for a real hiring
intent.
import { generateExecutionPlan } from './planner.js';
import { buildRegistry } from './registry.js';
import { validateExecutionPlan } from './validate-planner.js';
const registry = await buildRegistry();
const plan = generateExecutionPlan('create an onboarding plan', registry);
const validation = validateExecutionPlan(plan, registry);
if (!validation.isValid) {
console.error('Plan has issues:', validation.issues);
}validateExecutionPlan() detects common issues:
- Duplicate steps — skill appears twice in plan
- Dangling references — step references skill not in registry
- Dependency order violations — dependency executes after dependent
- Circular dependencies — cycle in skill dependencies
- Unmatched capabilities — requested capability has no match (warning)
- Empty plans — no steps generated (warning)
- Order field inconsistency — step order fields not sequential
suggestPlanImprovements() offers non-binding suggestions:
- Plan is too simple or too complex for the request
- Unmatched capabilities might need refinement
- Independent skills could potentially be parallelized
The planner is fully deterministic:
- Same user intent → same execution plan
- Same registry state → same matching results
- No randomness, no external ranking signals, no ML models
This makes plans reproducible, testable, and debuggable.
The planner consumes registry/skills.json generated by Phase 4.1.
Never:
- Parse
SKILL.mddirectly - Maintain parallel metadata
- Hardcode skill information
Always:
- Read from the registry
- Leverage existing metadata (domain, tags, capabilities, dependencies)
- Validate against registry schema
The planner produces ExecutionPlan — the runtime's input.
The planner provides:
- What skills to execute
- Why each skill was selected
- Execution order with dependencies
- Estimated complexity
The runtime will handle:
- Actually executing skills
- Context propagation between steps
- Retry and failure handling
- Runtime state management
If you want to derive more metadata for matching (e.g. keywords in SKILL.md tips):
- Add extraction logic to
buildRegistry()inregistry.ts - Store in
RegistryEntry - Use in
matchCapabilityAgainstRegistry()to improve matching
Replace analyzeIntent() with a more sophisticated approach:
- Semantic tokenization
- Synonym expansion
- Context-aware parsing
The interface (intent: string → capabilities: string[]) doesn't change — swapping implementations is safe.
Replace matchCapabilityAgainstRegistry() if needed:
- BM25 scoring
- Word embeddings
- Semantic similarity
- Keyword boosting
Keep the output signature (capability: string → CapabilityMatch) stable.
selectSkills() is where you'd add:
- User preference for skill tier (full > partial > bare)
- Domain expertise weighting
- Feedback from previous executions
- Manual override hints in the registry
Run planner tests:
bun test packages/hr-skills-build/test/planner/planner.test.tsTests cover:
- Intent analysis (basic, with filler words, empty input)
- Capability matching (direct match, partial, unmatched)
- Plan generation (simple and complex scenarios)
- Validation (correct plans, common issues)
- Suggestions (complexity warnings, coverage gaps)
Potential enhancements beyond the initial implementation:
- Semantic intent analysis — move beyond token splitting to understand domain-specific language
- Multi-turn planning — refine plans based on user feedback iteratively
- User preference modeling — learn which skills users prefer for similar requests
- Capability versioning — track how skill capabilities change over time
- Execution metrics — collect success/failure data to improve matching
- Skill recommendations — suggest new skills that would improve coverage
- Parallel execution — identify independent steps for concurrent execution
- Branching plans — support conditional execution (
if...then...else) - Fallback strategies — suggest alternative skills if primary ones fail
Last updated: July 23, 2026