NOETIC
Framework
Memory

Plan Memory

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

Overview

Plan Memory 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 a FlowNode execution tree, then exits to execute the plan with full tool access.

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

Usage

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

const layer = planMemory();

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

Configuration

interface PlanMemoryConfig {
  scope?: MemoryScope;
  additionalAllowedTools?: string[];
  maxPrdLength?: number;
  maxTreeDepth?: number;
  additionalPlanInstructions?: string;
  onEnterSession?: () => Promise<{ slug: string }>;
  onExit?: (state: PlanState) => Promise<{ approved: boolean }>;
}
FieldTypeDefaultPurpose
scopeMemoryScope'thread'Persistence scope
additionalAllowedToolsstring[]--Extra tools allowed during plan mode
maxPrdLengthnumber50000Maximum PRD content length
maxTreeDepthnumber5Maximum plan tree nesting depth
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

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 and plan tree.
  3. executing — Full tool access. Plan context injected into each LLM 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.

LLM Tools

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

ToolDescription
plan/enterPlanModeEnter plan mode. Accepts optional goal string.
plan/updatePrdReplace PRD content with new markdown.
plan/setPlanTreeSet the optional FlowNode execution tree (validated against FlowSchema).
plan/exitPlanModeExit plan mode (execute or cancel).
status (data)Read-only: { phase, hasPrd, hasPlanTree, version }

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 Tree is a FlowNode

plan/setPlanTree stores a flow — a JSON-serialisable subset of Noetic's Step shape defined by FlowSchema (exported from @noetic-tools/core):

type FlowNode =
  | { kind: 'llm'; id: string; instructions: string; model?: string; tools?: string[] }
  | { kind: 'subagent'; id: string; preset: string; prompt: string }
  | { kind: 'fork'; id: string; mode: 'all' | 'race' | 'settle'; paths: FlowNode[] }
  | { kind: 'spawn'; id: string; child: FlowNode }
  | { kind: 'sequence'; id: string; steps: FlowNode[] };

Structural nodes (sequence, fork, spawn) nest child nodes; leaf nodes (llm, subagent) carry execution instructions. Tool references inside llm nodes are name strings, resolved against the harness's live tool registry when the flow runs. The runtime expands each node into a concrete Step via the existing builders (step.llm, fork, spawn) at execute time; validateFlow(), walkFlow(), and flowDepth() are the supporting utilities.

This is a different shape from PlanNode ({ id, description, assignee, execution, children }), which belongs to the standalone compilePlan() pattern for compiling task-assignment trees into step graphs — the plan layer's tree cannot be passed to compilePlan().

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

On this page