Tree-of-Thought Reasoning
Explore several approaches to a problem with the ThoughtTreeExecutor, score branches, prune weak ones and return the best reasoning path.
Overview
Tree-of-Thought (ToT) reasoning lets an agent explore several approaches to a problem, score each one, drop the weak ones and keep the best result. Instead of committing to a single chain of thought, the ThoughtTreeExecutor builds a tree of candidate approaches and runs the promising ones as ordinary agent runs.
This page is about that executor. A model's own built-in thinking (OpenAI reasoning effort, Claude extended thinking, Gemini thinking budgets) is set with the reasoning option of an agent or a run — see Reasoning under LLM backends. The two combine: the runs inside a tree use the agent's reasoning setting like any other run.
Basic Usage
import { Agent, Cogitator, ThoughtTreeExecutor } from '@cogitator-ai/core';
const cogitator = new Cogitator({
llm: {
defaultProvider: 'openai',
providers: { openai: { apiKey: process.env.OPENAI_API_KEY! } },
},
});
const executor = new ThoughtTreeExecutor(cogitator, {
maxDepth: 4,
branchFactor: 3,
beamWidth: 2,
confidenceThreshold: 0.3,
terminationConfidence: 0.9,
maxTotalNodes: 50,
});
const agent = new Agent({
name: 'problem-solver',
instructions: 'You solve complex problems by exploring multiple approaches.',
model: 'openai/gpt-5.5',
});
const result = await executor.explore(agent, 'Design a caching strategy for a real-time dashboard');
console.log('Success:', result.success);
console.log('Output:', result.output);
console.log('Best path depth:', result.bestPath.length);
console.log('Nodes explored:', result.stats.exploredNodes);The executor makes its own model calls — branch generation, branch evaluation and the final synthesis — on the agent's model, routed like a normal run: a provider/model id, a bare name served by llm.defaultProvider and agent.config.provider all work.
How It Works
- Root — the goal becomes the root node.
- Branch generation — the
BranchGeneratorasks the model forbranchFactorcandidate approaches for the node, each athoughtwith a proposed action: a tool call, a sub-goal or a direct response. - Branch evaluation — the
BranchEvaluatorscores every candidate on confidence, progress and novelty, combined into acompositescore. - Pruning — candidates with a composite score below
confidenceThresholdare dropped. Every other candidate becomes a pending child node. - Queueing — the
beamWidthbest children are queued for execution; the rest wait as alternatives for backtracking. - Execution — a direct response is taken as is; a tool call or sub-goal runs the agent with
cogitator.run()(without memory) on a prompt built from the goal and the approach. A completed node belowmaxDepthis queued again to be expanded; a failed one backtracks. - Termination — exploration stops as soon as an executed node's branch confidence reaches
terminationConfidence, or when the frontier is empty,maxTotalNodesnodes have been executed, thetimeoutpasses or the abort signal fires. - Result — the completed node with the highest cumulative score (the sum of composite scores along its path) wins. Its answer is the output; without one, the path is synthesized into a final answer with one more model call.
Exploration Strategies
explorationStrategy decides which queued node runs next:
| Strategy | Order |
|---|---|
beam (default) | Level by level: the shallowest node first, pending nodes before completed ones at the same depth, then by cumulative score. A whole level runs before the next is generated. |
best-first | The node whose own branch has the highest composite score. |
dfs | Last in, first out, with the best child of the latest expansion on top, so the search goes deep before it goes wide. |
Configuration
const executor = new ThoughtTreeExecutor(cogitator, {
maxDepth: 5, // how deep the tree can grow (default 5)
branchFactor: 3, // candidates generated per node (default 3)
beamWidth: 2, // best candidates queued per expanded node (default 2)
explorationStrategy: 'beam', // 'beam' | 'best-first' | 'dfs' (default 'beam')
confidenceThreshold: 0.3, // minimum composite score to keep a candidate (default 0.3)
terminationConfidence: 0.8, // branch confidence that stops the search (default 0.8)
maxTotalNodes: 50, // hard cap on executed nodes (default 50)
maxIterationsPerBranch: 3, // iteration cap for each branch run (default 3)
timeout: 60_000, // ms per explore() call (no limit by default)
});maxIterationsPerBranch caps the iterations of each branch's cogitator.run(); it never raises the cap above the agent's own maxIterations. timeout is the default time limit of every explore() call; explore(agent, goal, { timeout }) overrides it for one call (see Progress Monitoring).
Higher branchFactor explores more possibilities but costs more tokens. Lower beamWidth focuses the search but risks missing good paths. DEFAULT_TOT_CONFIG holds the defaults.
Callbacks
const executor = new ThoughtTreeExecutor(cogitator, {
onBranchGenerated: (node, branches) =>
console.log(`${branches.length} ideas at depth ${node.depth}`),
onBranchEvaluated: (branch, score) => console.log(branch.thought, score.composite),
onNodeExplored: (node) => console.log(node.status, node.result?.response),
onBacktrack: (from, to) => console.log(`Dead end at depth ${from.depth}`, to?.depth ?? 'none'),
});onBacktrack fires when every candidate of a node scores below the threshold, or when running a node fails; the node is marked failed. to is the best deferred alternative — a pending sibling, or a pending sibling of the nearest ancestor that has one — which is queued next, or null when none is left.
Branch Generation
The BranchGenerator can be used on its own. It takes a backend and the model name to send to it:
import { BranchGenerator } from '@cogitator-ai/core';
const generator = new BranchGenerator(llmBackend, 'gpt-6.1-sol');
const branches = await generator.generate(
currentNode, // null for the root
'Optimize database query performance',
3,
agentContext,
exploredThoughts
);Each branch has a thought, a proposedAction and the parent node's messages. Previously explored thoughts are passed in to encourage novelty. When the model's answer cannot be parsed, generic fallback branches are returned.
Branch Evaluation
The BranchEvaluator scores branches on three dimensions:
- Confidence — how likely is this approach to succeed?
- Progress — how much closer does it get to the goal?
- Novelty — how different is it from sibling branches?
import { BranchEvaluator } from '@cogitator-ai/core';
const evaluator = new BranchEvaluator({
llm: llmBackend,
model: 'gpt-6.1-sol',
reflectionEngine, // optional
confidenceWeight: 0.5,
progressWeight: 0.3,
noveltyWeight: 0.2,
});
const scores = await evaluator.evaluateBatch(branches, goal, agentContext);The weights above are the defaults for the composite score. When a ReflectionEngine is given, tool-call branches are evaluated with the agent's past insights. Inside explore(), the Cogitator's reflection engine is used when reflection is enabled and has been initialized by an earlier run.
Progress Monitoring
const controller = new AbortController();
const result = await executor.explore(agent, goal, {
timeout: 30_000,
abortSignal: controller.signal,
onProgress: (stats) => {
console.log(`Explored: ${stats.exploredNodes}/${stats.totalNodes}`);
console.log(`Depth: ${stats.maxDepthReached}, Backtracks: ${stats.backtrackCount}`);
console.log(`LLM calls: ${stats.llmCalls}`);
},
});onProgress is called after every step of the search — each node execution and each expansion. On timeout or abort, the best node found so far is returned.
Result Structure
interface ToTResult {
success: boolean;
output: string;
tree: ThoughtTree;
bestPath: ThoughtNode[];
stats: ToTStats;
runId: string;
agentId: string;
usage: {
inputTokens: number;
outputTokens: number;
totalTokens: number;
cost: number;
duration: number;
};
}bestPath holds every node from the root to the winning node, so you can trace how the agent reached its conclusion; the full tree (with nodes as a Map) is there for visualization or debugging. success is true when the winning node completed. usage and stats.tokenUsage count the tokens of the branch runs and of the executor's own generation, evaluation and synthesis calls; usage.cost adds the cost of the branch runs to the cost of those calls, priced from the model registry. In stats, llmCalls counts the executor's own model calls (not those inside branch runs), prunedNodes the candidates dropped below confidenceThreshold, and totalNodes every child node created, including pending alternatives that never ran.
When to Use ToT
Tree-of-Thought reasoning is most effective for:
- Problems with multiple valid approaches (architecture decisions, optimization)
- Tasks where the first attempt often fails (complex debugging, multi-step reasoning)
- Situations where exploring alternatives has high value (research, strategy)
For straightforward tasks, a single run — possibly with the model's own reasoning turned up — is faster and cheaper. Use ToT when the cost of exploring is worth the improvement in output quality.
Reflection Engine
Enable agents to reflect on their own outputs, extract insights from past executions, and continuously improve through self-evaluation loops.
Agent Learning & Optimization
Improve agents over time with execution traces, DSPy-style compilation of instructions and demos, A/B tests, degradation monitoring and automated optimization.