NOETIC
Framework
Context Layers

Plan

PRD authoring and plan execution lifecycle with tool restrictions during planning.

Overview

The plan layer manages a structured planning workflow: the agent enters a restricted "plan mode" where only read-only tools are allowed, writes a PRD document, optionally structures the plan as a JSON WorkflowDocument, then exits to execute the plan with full tool access.

A plan's tree IS a workflow document (JSON Workflow Runtime). Plans additionally store named workflows referenced from the tree via subflow nodes — the reviewed tree stays small while detailed mechanics live in separately-authored, separately-reviewable workflows.

  • Slot: 240 (Slot.PROCEDURAL - 10)
  • Scope: thread (default)
  • Default budget: { min: 100, max: 3000 }

Usage

import { plan } from '@noetic-tools/core';

const layer = plan();

The layer is included by default in the Noetic CLI. Users type /plan to enter plan mode.

Configuration

interface PlanConfig {
  scope?: ContextScope;
  additionalAllowedTools?: string[];
  maxPrdLength?: number;
  maxDepth?: number;
  maxWorkflows?: number;
  maxWorkflowChars?: number;
  allowedNodeKinds?: WorkflowNode['kind'][];
  style?: PlanStyle;
  subAgentTool?: string;
  additionalPlanInstructions?: string;
  onEnterSession?: () => Promise<{ slug: string }>;
  onExit?: (state: PlanState) => Promise<{ approved: boolean }>;
}
FieldTypeDefaultPurpose
scopeContextScope'thread'Persistence scope
additionalAllowedToolsstring[]--Extra tools allowed during plan mode
maxPrdLengthnumber50000Maximum PRD content length
maxDepthnumber5Maximum structural depth (workflowDepth) of the plan tree and each named workflow
maxWorkflowsnumber20Maximum number of named workflows
maxWorkflowCharsnumber20000Maximum serialized size of each workflow document
allowedNodeKindsWorkflowNode['kind'][]all kindsOptional profile restricting which node kinds a plan may use. Hosts that set this must include 'subflow'
stylePlanStyle'phased'How the planning briefing shapes the turn — see Planning styles
subAgentToolstring--Name of the host's sub-agent tool. Unset, the briefing omits all sub-agent guidance — see Sub-agents
additionalPlanInstructionsstring--Extra free-form instructions appended to the planning-phase recall payload
onEnterSession() => Promise<{ slug: string }>--Host callback invoked once on the Idle → Planning transition. Returns a session identifier the host owns (e.g. an on-disk plan directory slug), stored on the state as planSlug
onExit(state: PlanState) => Promise<{ approved: boolean }>--Host callback invoked when the model requests plan/exitPlanMode with action: 'execute'. Return { approved: false } to keep the layer in Planning — the model is told the user rejected the plan and should revise it before requesting approval again

Planning styles

A planning turn can be spent two ways, and which one fits depends on whether the missing information is in the codebase or in the user's head.

import { plan, PlanStyle } from '@noetic-tools/core';

const layer = plan({ style: PlanStyle.Interview });
StyleShape of the turnReach for it when
'phased' (default)Understand → design → review → write → exitThe work's shape is known and the job is getting the details right
'interview'Explore → write → ask, on repeat. The PRD starts as a skeleton on the first turn and fills in as questions get answered.Requirements are still vague and the user is the missing input

Both styles enforce the same turn-ending rule: a turn ends with AskUserQuestion or with plan/exitPlanMode, never with a prose question. Asking "does this plan look right?" IS exitPlanMode — the briefing says so explicitly, because a model that asks in prose leaves the plan in limbo with no approval to act on.

Sub-agents

The layer ships no sub-agent tool. Set subAgentTool to the name of yours to add the parallel-exploration guidance to the briefing — how many explorers to launch (usually one, never more than three), when a second perspective is worth it, and what background to hand each agent so Phase 1's work is not thrown away at the Phase 2 boundary.

plan({ subAgentTool: 'agent' });

Left unset, the briefing tells the model to explore directly. This is deliberate: instructing a model to call a tool that is not registered costs it a turn and teaches it nothing.

What the briefing says you may use

The "What you may use" section is rendered from the layer's own allow-set, so it always names exactly what beforeToolCall will let through — including anything added via additionalAllowedTools. The node-kind guidance is filtered by allowedNodeKinds the same way, and plan/setPlanTree's tool description is built from the same table, so the briefing and the tool cannot disagree about what a plan may contain.

Budget

The briefing and the plan state share the layer's budget ({ min: 100, max: 3000 } tokens by default). State gives way first: the largest of the PRD draft, the tree JSON, or the workflow summaries is trimmed to the remaining headroom rather than dropped whole, since a truncated draft is worth more than the blank space dropping it would leave. The model can read state back with plan/getWorkflow.

Rules are never cut mid-sentence. If they will not fit, a compact briefing replaces them — the restriction, where the plan goes, and how the turn ends — and if even that overflows, the layer contributes nothing that turn rather than half a rule.

Phase Lifecycle

The layer manages a state machine with five phases:

  1. idle — No active plan. recall returns nothing.
  2. planning — Read-only tools only. LLM writes PRD, plan tree, and named workflows.
  3. executing — Full tool access. The plan is injected into the context of each model call.
  4. completed — Execution succeeded. Outcome recorded.
  5. failed — Execution failed or was aborted. Outcome recorded.

When onExit is configured, the Planning → Executing transition requires approval: if onExit returns { approved: false } (e.g. the user rejected the plan in the UI), the layer stays in planning and the model is prompted to address the feedback and call plan/exitPlanMode again.

Before onExit is ever invoked, the layer validates the plan structurally: every subflow ref (in the tree and inside stored workflows) must name a stored workflow, and named workflows must not reference each other in a cycle. The user is never asked to approve a plan that cannot hydrate.

LLM Tools

The layer exposes seven LLM tools and one data projection via its provides map:

ToolDescription
plan/enterPlanModeEnter plan mode. Accepts optional goal string. Resets workflows from any prior plan.
plan/updatePrdReplace PRD content with new markdown.
plan/setPlanTreeSet the plan as { document: WorkflowDocument }. Validates schema, depth, node kinds, and subflow-ref name syntax. Refs to not-yet-defined workflows are allowed — the result message enumerates them.
plan/setWorkflowCreate or replace a named workflow ({ name, document }, upsert). Names are lowercase slugs, max 64 chars.
plan/removeWorkflowDelete a named workflow. Warns when the tree or another workflow still references it.
plan/getWorkflowRead back a stored workflow's JSON (recall shows summaries, not bodies).
plan/exitPlanModeExit plan mode (execute or cancel). execute validates refs and cycles before requesting approval.
status (data)Read-only: { phase, hasPrd, hasPlanTree, workflowNames, version }

Documents may be passed as objects or JSON strings (a common LLM tool-call shape); validation happens inside the tool with readable error messages, against the published schema at https://noetic.tools/schema/noetic-workflow.schema.json.

Tool Restrictions

During the planning phase, beforeToolCall restricts tool usage:

  • Allowed: Read, Grep, Find, Ls, AskUserQuestion, activateSkill, the sub-agent coordination tools (agent, checkAgent, sendMessage), requestPlanApproval, and all plan/* tools
  • Denied: All other tools (Write, Edit, Bash, etc.)

Additional tools can be allowed via additionalAllowedTools in the config.

The Plan is a WorkflowDocument

plan/setPlanTree stores a WorkflowDocument — the same portable JSON format the JSON Workflow Runtime executes, with node kinds like callModel, invokeTool, sequence, inParallel, conditional, loop, spawn, subflow, and the coding-agent kinds (claude-code, codex, opencode, pi).

The planning-phase instructions steer the model to keep the tree small (~7 top-level nodes a reviewer can scan) and factor mechanics into named workflows:

{
  "version": 1,
  "root": {
    "kind": "sequence",
    "id": "plan",
    "steps": [
      { "kind": "callModel", "id": "implement", "instructions": "Apply the PRD changes." },
      { "kind": "subflow", "id": "verify", "ref": "verify-loop" }
    ]
  }
}

with verify-loop defined separately via plan/setWorkflow. Named workflows may reference other named workflows, but cycles are rejected at exit.

Executing an Approved Plan

PlanState.planTree and PlanState.workflows map directly onto the workflow runtime:

const onExit: PlanExitCallback = async (state) => {
  const approved = await ui.requestApproval(state.prd, state.planTree, state.workflows);
  if (approved) {
    void parseAndRunWorkflow({
      json: state.planTree,
      workflows: new Map(Object.entries(state.workflows)),
      harness, ctx, tools, layers,
    });
  }
  return { approved };
};

The default CLI flow uses context injection instead — the plan is recalled into the LLM's view as an <active_plan> block and the model executes by making tool calls directly; parseAndRunWorkflow is the programmatic path.

On this page