Planner

How Hermes decomposes a goal into a typed step graph.

The planner takes a Goal and returns a Plan. The plan is a flat array of typed steps:

type Step =
  | { kind: "thought"; text: string }
  | { kind: "tool"; name: string; args: Record<string, unknown> }
  | { kind: "answer"; text: string };

Why flat steps and not a graph?

Local models reliably emit flat arrays. They struggle with arbitrary DAGs. The executor builds branching dynamically by inspecting observations and asking the planner to extend the plan when needed.

Prompting

The planner system prompt makes three things very explicit:

  1. The tools available (names + JSON schemas).
  2. The current memory context (recent recall).
  3. The constraint that the output must be a single JSON array of steps.

If the model fails to produce valid JSON, the executor retries up to two times with a self-correction prompt, then surfaces the failure to the UI honestly.

Replanning

The executor will call planner.extend(plan, observations) when:

  • A tool returned an unexpected shape.
  • A step needs information that wasn't available initially.
  • The user manually edited a step mid-plan.

Replans are capped at three per session.