session-plan

SkillProductivity

Creates a structured wave execution plan with role-based assignment after user alignment. Decomposes agreed tasks into waves resolved from the session mode by `scripts/session-shape.mjs`, with optimal agent assignment, dependency ordering, and inter-wave checkpoints. Activated by session-start after Q&A phase completes.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the session-plan skill

What this skill tells your AI

The instructions your AI receives, as published by kanevry/session-orchestrator in skills/session-plan/SKILL.md and read by ahel’s review.

Platform Note: Project agents live in <state-dir>/agents/ where <state-dir> is .claude/ (Claude Code), .codex/ (Codex CLI), .cursor/ (Cursor IDE), or .pi/ (Pi). On Cursor IDE and Pi v1, parallel agent dispatch is not available — present wave tasks as a sequential execution list instead. See skills/_shared/platform-tools.md.

Session Plan Skill

Project-instruction file resolution: CLAUDE.md and AGENTS.md (Codex CLI) are transparent aliases — see skills/_shared/instruction-file-resolution.md. Wherever this skill mentions CLAUDE.md, the alias rule applies.

Phase 0.5: Parallel-Aware Preamble

Skip silently when persistence: false in Session Config.

Before any Phase 1 work, run the parallel-aware preamble per skills/_shared/parallel-aware-preamble.md. The preamble detects other active sessions in the worktree-family via findPeers(repoRoot, { mySessionId }), classifies the caller's mode via classifyMode(callerMode) against the exclusivity-matrix, and either:

  • Returns PASS_THROUGH (no other session / always-ok mode) → continue to Phase 1
  • Returns EXCLUSIVE_BLOCKED → fires Exclusive-Conflict AUQ from skills/_shared/parallel-aware-auq.md
  • Returns PROMOTION_OFFER → fires Worktree-Promotion AUQ (via enterWorktree() from scripts/lib/autopilot/worktree-pipeline.mjs — see parallel-aware-auq.md outcome-handling)

On any non-PASS_THROUGH outcome that does not result in immediate exit, append a Deviation to STATE.md via appendDeviationOnDisk(repoRoot, isoTimestamp, message) from scripts/lib/state-md.mjs.

Implementation reference: skills/_shared/parallel-aware-preamble.md § Implementation. AUQ reference: skills/_shared/parallel-aware-auq.md.

Purpose

Transform the agreed session scope (from session-start Q&A) into an executable wave plan (using role-based assignment) with specific agent assignments, file scopes, and acceptance criteria per task.

Input: Session Scope

This skill receives the agreed session scope from session-start. The scope includes:

  • Issue list: VCS issue numbers and titles selected by the user
  • Session type: housekeeping, feature, or deep
  • Recommended focus: the option the user selected in session-start Phase 7
  • Session Config: parsed JSON from parse-config.mjs
  • Express-path signal (optional): session-start Phase 8.5 may set EXPRESS_PATH=true in the handoff context when the activation conditions are met.

These are passed via the conversation context (not a file). Parse the preceding session-start output to extract the agreed scope.

Optional private capability context

Before either the express path or task decomposition, apply Private capability context when the owner explicitly supplies or authorizes a local catalog lookup for a known private/internal planning audience. Reuse the bounded findings already supplied by /plan new when applicable; do not repeat the same lookup. This step does not require persistence. With no authorized context, or a public/unknown audience, skip it without a prompt or lookup and continue the existing flow. Eligible source references inform reuse alternatives and verification tasks; a catalog match does not expand the agreed implementation scope or disable the express path.

Express Path Short-Circuit (#214)

Check this before Step 0. If the express path is active, this skill emits a minimal 1-wave plan and exits — no role decomposition, no wave splitting, no agent count computation.

Phase 8.5 of session-start hands off here NORMALLY when the express path activates — it does not skip session-plan (#1146). The banner below is printed by node scripts/express-path.mjs, and the 1-wave plan this section emits is the artifact /go detects.

Detect express-path activation: Search the conversation context for the banner line:

Express path activated — <N> tasks, coordinator-direct, no inter-wave checks.

If found AND express-path.enabled is true in Session Config (read via Step 0 below — skip only that field check if config read is needed):

Emit this 1-wave plan and exit the skill immediately (do not continue to Step 1 or beyond):

## Wave Plan (Session: housekeeping, 1 wave, isolation: none) [Express Path]

### Wave 1: Coordinator-Direct (<N> tasks)
- All agreed tasks executed sequentially by the coordinator — no subagents dispatched.
- Tasks: [list agreed issues/tasks]
- Isolation: none (coord-direct)
- Max-turns: N/A (coordinator executes directly)

### Execution Config
- Waves: 1 | Agents-per-wave: 0 (coordinator-direct) | Isolation: none
- Express path: active (housekeeping + scope ≤ 3 + no parallel agents needed)
- Total agents planned: 0

Express path — no inter-wave checks. Use /go to begin.

The express path's 1-wave plan is the same shape housekeeping resolves to — one wave with coordinatorDirect: true and no dispatched agents (scripts/session-shape.mjs --session-type housekeeping). The express path stays as written above; it does not need to call the shape resolver to know that.

When express-path banner is absent or express-path.enabled: false: Proceed to Step 0 and the full planning flow as normal.

Step 0: Read Session Config

Read and parse Session Config per skills/_shared/config-reading.md. Store result as $CONFIG.

Extract these fields for planning:

  • waves — number of execution waves; resolved by scripts/session-shape.mjs (totalWaves), do not compute by hand. The shape reports in wavesConfigHonored whether the configured value was used at all, and says why in notes.
  • agents-per-wave (may have session-type overrides per config-reading.md) — the operator's ceiling; the per-wave cap that actually binds is resolved by scripts/session-shape.mjs (waves[].agentCap), do not compute by hand.
  • isolation — Session Config input (worktree / none / auto) that feeds configIsolation into the graduated per-wave rule (resolveIsolation, issue #194, in scripts/lib/wave-sizing.mjs: an explicit config value always wins; otherwise ≤2 agents → none, ≥5 agents → worktree, 3-4 agents → none for housekeeping else worktree). The RESOLVED value for a given wave is waves[].isolation in the shape's JSON output (scripts/session-shape.mjs) — a wave with coordinatorDirect: true, or a read-only wave, resolves none without calling resolveIsolation at all. Do not compute by hand; the plan header's Isolation: line is copied straight from that wave entry.
  • enforcement (default: warn) — Session Config input (strict / warn / off) that feeds configEnforcement into resolveEnforcement (same module); the resolved per-wave value is waves[].enforcement. Isolation none auto-promotes warn to strict, since the scope-enforcement hook is then the only barrier left.
  • max-turns — agent turn budget; resolved by scripts/session-shape.mjs (waves[].maxTurns), do not compute by hand.
  • agent-mapping (optional) — explicit role-to-agent bindings
  • persistence (default: true) — whether to use STATE.md and learnings

Fallback: If session-start already output a ## Session Config (active) block in the conversation context, extract values from there to avoid a redundant parse. If not present in context, parse independently.

Step 1: Task Decomposition

  1. Check for resume context: > Skip if persistence is false in Session Config. If <state-dir>/STATE.md exists with status: active or status: paused, read it to understand:
    • Which waves were completed in the prior session
    • Which agents completed, which were partial/failed
    • What deviations were logged
    • Use this to avoid re-doing completed work and to prioritize carryover tasks If no STATE.md or status: completed, proceed with fresh planning.

0.5. Read project intelligence: > Skip if persistence is false in Session Config. If .orchestrator/metrics/learnings.jsonl exists, read active learnings (confidence > 0.3, not expired). Sort by confidence DESC (tiebreaker: created_at DESC) and slice to the first learnings-surface-top-n entries (default 15) before applying the four categories below. If the top-N slice is empty, skip the categories.

  • Fragile files: if any planned task touches a known fragile file, note it as a warning in the agent spec
  • Effective sizing: use historical sizing data to inform Step 3 complexity scoring
  • Recurring issues: pre-populate risk mitigation with known issue patterns
  • Scope guidance: validate planned scope against historical session capacity
  • Over-delivery sizing (#730/H4): read the over_delivery_ratio of recent same-session_type waves — from effective-sizing learnings if present, else directly from the last ~5 sessions.jsonl records' waves[].over_delivery_ratio (skip records lacking the field — pre-#730; also skip Discovery/Finalization waves, whose planned set is empty by design). If the median ratio R > 1.3, the fleet historically under-briefs file scope: inflate the Step 3 "Files to change" estimate by R before scoring the complexity tier; note it under Project Intelligence Applied.

For each agreed task/issue:

  1. Read the VCS issue description and acceptance criteria (if session-start Phase 7.1 emitted a ### Premise Verification Result entry for this issue, treat its verdict as binding — re-scope or drop tasks whose verdict is FALSCH-PRÄMISSE/SHIPPED before decomposing; do not re-run the greps, session-start already did)
  2. Identify affected files by searching the codebase (Grep/Glob — don't guess)
  3. Map dependencies: which tasks must complete before others can start
  4. Estimate complexity: small (1 agent), medium (2-3 agents), large (dedicated wave)
  5. Identify synergies: tasks that touch the same files → same wave, same agent

Step 1.5: Agent Discovery

Before assigning tasks to waves, discover available agents for this session:

  1. Scan for project-level agents: Glob <state-dir>/agents/*.md (.claude/agents/*.md for Claude Code, .codex/agents/*.md for Codex CLI, .cursor/agents/*.md for Cursor IDE, .pi/agents/*.md for Pi)

    • Read each file's YAML frontmatter: extract name and description
    • Filter out non-agent reference files (skip files with description containing "Reference documentation" or "NOT an executable agent")
    • Build a list of available project agents with their names and capabilities
  2. Read agent-mapping from Session Config (optional):

    • Field: agent-mapping — a JSON object mapping role keys to agent names
    • Role keys: impl, test, db, ui, security, compliance, docs, perf
    • Example: agent-mapping: { impl: code-editor, test: test-specialist, db: database-architect }
    • If present, these explicit mappings take priority over auto-matching
    • A value MAY carry a channel prefix: session-orchestrator:<plugin-agent> or cursor:<model> (foreign model, #1150). An unknown prefix is rejected fail-loud by scripts/lib/config.mjs at parse time — see docs/session-config-reference.md § agent-mapping values.

    Validation: If agent-mapping specifies an agent name, verify the agent exists:

    • For project agents: check <state-dir>/agents/<name>.md exists
    • For plugin agents: check the agent is registered (contains : separator)
    • For cursor:<model> (foreign channel): the existence check is on the CHANNEL, not the model — cursor-agent on PATH and logged in (cursor-agent status). The model string is free-form and is validated only at dispatch time, because the model catalogue lives outside this repo.
    • If the agent doesn't exist — or the cursor channel is unavailable (binary missing / not logged in) — warn the user and fall back to auto-discovery for that role (same fallback shape in both cases; never hard-fail the plan)
    • Two constraints the plan must carry into the wave, both owned by skills/wave-executor/wave-loop.md § Third branch: foreign-model dispatch (one place owns the contract — do not restate it here): a cursor:<model> mapping is INERT for any never_foreign role (impl-core, security-review, migration, release, secrets, incident, refactor-crosscut — the adapter refuses it), and every foreign run requires a MANDATORY Claude semantic diff-review before merge-back. Plan the review as work, not as a formality.
  3. Build Agent Registry (resolution priority):

    • Priority 1: Project agents (from <state-dir>/agents/ — see Platform Note) — matched by name
    • Priority 2: Plugin agents (session-orchestrator:code-implementer, session-orchestrator:test-writer, session-orchestrator:ui-developer, session-orchestrator:db-specialist, session-orchestrator:security-reviewer)
    • Priority 3: general-purpose (fallback)
  4. Match tasks to agents: For each task from Step 1:

    • If agent-mapping config specifies a mapping for the task's domain → use that agent. For Docs-role tasks specifically, check agent-mapping.docs first; if set, use that agent name instead of the default below.

    • Docs-role fast path (high-priority — runs before keyword matching): If the task's role is classified as Docs (per Step 1.8) AND docs-orchestrator.enabled: true in Session Config → resolve subagent_type: "docs-writer". The docs-writer project agent is discovered at <state-dir>/agents/docs-writer.md during the Priority 1 scan above. No colon prefix — it is a project agent, not a plugin agent. If agent-mapping.docs is set, use that name instead of "docs-writer".

    • Else, match task description against agent descriptions using the content-based routing table below. Match any keyword from the pattern column (case-insensitive) against the task title and description. Use the first matching row; rows are checked top to bottom.

      Keyword patternResolved agent
      migration, schema, RLS, index, query, ORM, supabase, postgres, database, dbsession-orchestrator:db-specialist
      component, tsx, css, tailwind, page, layout, a11y, wcag, responsive, UI, frontend, stylesession-orchestrator:ui-developer
      security, auth, csrf, csp, injection, XSS, sanitize, OWASP, vulnerability, pen testsession-orchestrator:security-reviewer
      test, coverage, vitest, jest, playwright, spec, fixture, assertionsession-orchestrator:test-writer
      (none of the above match)session-orchestrator:code-implementer
    • Else, use role-based default: Impl-Core/Impl-Polish → code-implementer, Quality → test-writer

    • Record the resolved subagent_type for each task

No agents found? If no project agents exist and plugin agents are available, use plugin agents. If neither, fall back to general-purpose for all tasks. The system works at every level.

Step 1.8: Task-to-Role Classification

Assigns exactly one role (Discovery/Impl-Core/Impl-Polish/Docs/Quality/Finalization) to each Step 1 task via the signal-to-role mapping table, the disambiguation rules, the Docs-role Phase 2.5 emission-block parsing, and the housekeeping short-circuit. Also emits the Docs Tasks and Wave-Plan Mission Status machine-readable blocks (SSOT for wave-executor + session-end) and the Mission-Status Enum (#340). See references/session-plan-task-classification.md. Read WHEN: after Step 1.5, before Step 2.

Step 2: Wave Assignment

Distribute tasks across the waves the session shape returned; each wave carries its own role. Which roles exist, and how many waves there are, is resolved by scripts/session-shape.mjs — see § Role-to-Wave Mapping below.

Wave Roles

RolePurposeAgents modify code?
DiscoveryUnderstand the current state before changing anythingNo (read-only)
Impl-CorePrimary implementation — core feature code, APIs, DB changesYes
Impl-PolishFix issues from Impl-Core, secondary tasks, integration, edge casesYes
QualityTests, typecheck, lint, security reviewYes (tests only). Lint MUST use the canonical {lint-command} unscoped — never domain-split (e.g., pnpm lint src/ hides errors in tests/). See quality-gates § Scope Policy.
FinalizationDocumentation, issue cleanup, commit preparationMinimal

Role-to-Wave Mapping

The wave list is not derived here. Resolve it ONCE at plan time from the session mode:

node scripts/session-shape.mjs --repo-root "$PWD" --session-type <housekeeping|feature|deep> \
  [--profile ultradeep] [--known-scope true|false] --task-count <N>

Run it with event emission (no --no-event) — that record (orchestrator.session.shape_resolved in .orchestrator/metrics/events.jsonl) is the canonical record of this session's shape. Use --no-event only for a throwaway planning dry-run.

It prints one JSON line carrying:

  • totalWaves — the wave count
  • waves[] — one record per wave: n, role, agentCap, agentCapRaw, coordinatorDirect, writes, maxTurns, verification, qualityEarned, allowedPaths
  • discovery — whether a Discovery wave is part of the shape
  • wavesConfigHonored — whether the Session Config waves value was used
  • notes — human-readable reasons for any of the above

The plan's wave list IS that output. The coordinator fills tasks into the returned waves and NEVER adds, removes, or renumbers a wave — the sole exception is the empty-role rule below (and its coordinator-direct carve-out). --known-scope true is what drops the Discovery wave on a deep session; --profile ultradeep is what selects the ultradeep shape, and it applies ONLY when STATE.md frontmatter carries session-profile: ultradeep (written by the /session ultradeep argument alias — see commands/session.md). session-type stays deep; the profile changes the wave SHAPE, nothing else, and it ignores the Session Config waves value (the shape says so in wavesConfigHonored / notes). Spec: docs/prd/2026-09-06-ultradeep-session-profile.md § 5.

Ultradeep agent counts per wave: take each wave's cap from that wave's agentCap in the shape — there is no second table here to disagree with it. The caps are ceilings, not targets, and the Quality wave's cap is still EARNED per the Step 3 rule (the shape marks it qualityEarned: true); Research and Code-Discovery share wave 1's cap across their two separately-scoped groups; the Synthesis-Gate wave carries agentCap: 0 with coordinatorDirect: true and writes only the coordinator's own artifacts (audit report, STATE.md, plan).

Wave 1 splits into two disjointly-scoped groups: Research agents (web-enabled, see skills/wave-executor/SKILL.md § Ultradeep Profile) and Code-Discovery agents (repo-only). Both are read-only. Wave 2 dispatches NO agents — the coordinator consolidates wave 1, writes docs/audits/<YYYY-MM-DD>-<slug>.md, and asks ONE blocking AskUserQuestion before wave 3.

When roles are combined into a single wave, agents from both roles execute in that wave.

Docs role dispatch rule (conditional — docs-orchestrator.enabled: true only):

When docs-orchestrator.enabled: true, apply the following concrete dispatch rule based on the count of synthesized Docs tasks from Step 1.8:

  • len(docs-tasks) == 0skip Docs role entirely. Apply the empty-role rule: do not create a Docs wave slot, do not dispatch any docs-writer agent.
  • len(docs-tasks) == 1inline with Finalization wave. Dispatch one docs-writer agent alongside the Finalization agent in the Finalization wave. The docs-writer agent's file scope must not overlap the Finalization agent's files (deconflict per Step 3.5).
  • len(docs-tasks) >= 2dedicated Impl-Polish sub-slot or dedicated wave slot. Options in priority order:
    1. If Impl-Polish wave has remaining agent capacity (below agents-per-wave): add docs-writer agents to the Impl-Polish wave as a sub-slot. The docs-writer agents MUST NOT share file scopes with any code-implementer agents in the same wave — verify via Step 3.5 deconfliction.
    2. If Impl-Polish is at capacity: add a dedicated Docs slot within the closest wave with capacity (prefer the wave immediately before Finalization).
  • NEVER add a 6th wave for Docs. Docs always occupies an existing wave slot.
  • When docs-orchestrator.enabled is false (default), this rule has no effect — the Docs role does not exist.

Cross-role constraint in combined waves: Tasks from different roles within a combined wave (the feature shape's Impl-Polish+Quality is the one today) CANNOT be merged into a single agent — the roles carry different scope permissions. If the combined wave's tasks exceed its agentCap, defer the lower-priority role's tasks: in Impl-Polish+Quality, defer Quality tasks to a separate phase within the same wave.

A combined wave's verification field in the shape already carries the more restrictive of its two roles' levels — read it, do not re-derive it.

Empty roles: If a role has 0 tasks, skip its wave entirely. Do NOT dispatch an empty wave. Remaining waves retain their original role names but are renumbered sequentially, and total-waves in the plan output is updated to reflect the actual wave count. This rule never applies to Discovery. Discovery is dropped exactly once, at shape-resolution time, by passing --known-scope true to scripts/session-shape.mjs (§ Role-to-Wave Mapping above) — the shape itself renumbers the remaining waves and reports the new count as totalWaves in its JSON output, before the coordinator ever sees a wave list to assign tasks into. Applying this rule to Discovery by hand, after the fact, would be a second, competing renumbering of a decision the shape already made. The empty-role rule below is for the roles that stay ON the wave list after the shape is fixed (e.g., Docs, Quality) and whose task count can still fall to 0 during Step 1/1.8 classification.

Exception — a wave declared coordinator-direct: true is NEVER removed by the empty-role rule. The rule's premise is "0 tasks means nothing to dispatch, so the wave is dead weight". For a coordinator-direct wave that premise is inverted: dispatching zero agents is the wave's PURPOSE, not evidence of its emptiness. Its plan item therefore carries BOTH markers and is emitted verbatim:

- wave: 2
  role: Synthesis-Gate
  coordinator-direct: true
  agents: 0
  • agents: 0 on such an item is a DECLARATION, never a defect — do not "fix" it upward, and do not let the Step 3.5 constraint check or the Step 3 tier table raise it.
  • The wave still counts toward total-waves and still occupies its wave number; the renumbering above skips over it, it does not absorb it.
  • The ultradeep Synthesis-Gate (wave 2) is the only such wave today. Without this exception the empty-role rule deletes it — and it is the one wave whose entire job is to stop and ask before any code is written (docs/prd/2026-09-06-ultradeep-session-profile.md AC-4).
  • The exception is scoped to the MARKER, not to the profile: any future coordinator-direct wave inherits it without another edit here.

Role Details

Shortened here. Read the whole file on GitHub.

Signals

GitHub stars
51
Forks
7
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
session-plan
Source
github.com/kanevry/session-orchestrator