Reasonix Two-Model Architecture: Separating Planning from Execution

Reasonix implements a dual-model design where a lightweight planner drafts a read-only strategy and a separate executor carries it out with full tool access, enforced by a Coordinator that maintains cache-stable sessions for each model.

The DeepSeek-Reasonix project solves the problem of context contamination and inefficient token usage by splitting cognitive work between two specialized language models. Instead of forcing a single model to switch between high-level planning and low-level tool execution, the Reasonix two-model architecture isolates these concerns into distinct runtime sessions managed by the Coordinator component.

How the Coordinator Orchestrates Two Models

The Coordinator type in internal/agent/coordinator.go acts as the runtime bridge between the planner and executor. When initialized via NewCoordinator, it constructs two independent Session objects—one for the planner and one for the executor—ensuring that system prompt prefixes remain immutable during a single turn.

This separation preserves the cache-first design documented in docs/SPEC.md. Because the planner’s prepend-only system prompt never mixes with execution tool calls, and the executor never inherits the planner’s prefix, both models maintain high cache hit rates throughout the workflow.

The handoff between models is explicit: the planner outputs a structured plan (or a [no_changes] marker) that the Coordinator passes to the executor using the executorHandoffMarker constant defined at line 87 of coordinator.go.

The Planner Model: Low-Frequency, Read-Only

Configuration via planner_model

You enable the two-model mode by setting the planner_model key in reasonix.toml. This specifies which provider handles the planning phase—typically a cheaper "flash" model—while the default agent.model handles execution.


# reasonix.toml

[agent]
planner_model = "deepseek-v4-flash"   # low-cost model used only for planning

The configuration loader parses this value during Coordinator initialization, as specified in the configuration examples within docs/SPEC.md.

Tool Restrictions and Prompt Stability

The planner operates with severe capability constraints. According to the DefaultPlannerPrompt defined at line 36 of coordinator.go, the planner only receives read-only research tools and the use_capability proxy for non-destructive MCP operations.

This restriction serves two purposes:

  • Safety: The planner cannot accidentally modify files or execute commands before the plan is reviewed.
  • Cache stability: The planner’s system prompt remains static (prepend-only) throughout the turn, avoiding cache invalidation that would occur if tool results were interleaved into its context.

The Executor Model: Full Tool Access and Plan Validation

Plan Handoff and Execution

Once the planner produces a strategy, the Coordinator validates the output and transfers control to the executor. The executor is a standard Agent instance constructed with the full tool registry—including bash, file writers, and destructive MCP tools.

The executor receives the plan text prefixed with the handoff marker, allowing it to validate steps before invoking tools. If the planner fails or returns malformed output, the Coordinator falls back to executor-only mode with a plannerFallbackNotice, ensuring the user request is never blocked by planning errors.

Runtime Execution Loop

The executor runs the standard Agent loop defined in internal/agent/agent.go, handling tool-call deltas and session updates until the plan completes or maxSteps is reached. Unlike the planner, the executor’s session accumulates tool results and file changes, building the persistent context required for implementation tasks.

Implementing the Two-Model Flow

Creating a Coordinator Programmatically

To manually instantiate the architecture, use NewCoordinator with separate providers for each role:

package main

import (
    "context"
    "reasonix/internal/agent"
    "reasonix/internal/provider"
    "reasonix/internal/tool"
)

func main() {
    // Separate providers for each cognitive role
    var plannerProv provider.Provider   // e.g., a cheap "flash" model
    var executorProv provider.Provider // e.g., the default "pro" model

    // Planner only gets read-only tools
    plannerTools := tool.NewRegistry()
    plannerTools.EnableReadOnlyTools()

    // Executor gets the full registry
    executorAgent := agent.NewAgent(
        executorProv, 
        tool.DefaultRegistry(), 
        nil, 
        agent.Options{},
    )

    // Construct the Coordinator
    coord := agent.NewCoordinator(
        plannerProv,
        agent.NewSession(""),   // fresh planner session
        nil,                    // optional pricing
        plannerTools,
        agent.Options{},        // planner options (temperature, etc.)
        executorAgent,
        0.0,                    // temperature
        nil,                    // event sink
        nil,                    // legacy gate - nil means always plan
    )

    // Run the coordinated turn
    err := coord.Run(context.Background(), "Refactor the auth middleware")
    if err != nil {
        panic(err)
    }
}

The Coordinator type definition and constructor logic reside in internal/agent/coordinator.go at line 47.

Adding Context to the Planner

You can inject project-specific context into the planner’s system prompt using PlannerPromptWithContext:

plannerPrompt := agent.PlannerPromptWithContext(`

# Project context

- The project is a Go microservice.
- Existing routes are defined in ./internal/api/routes.go
`)

This helper function is implemented at line 111 of coordinator.go and merges user context with the DefaultPlannerPrompt.

Planner Policies and Approval Flows

The Coordinator respects a PlannerPolicy (defined at line 135 of coordinator.go) that determines when planning occurs:

  • Plan-first: Always run the planner before execution.
  • Plan-for-approval: Run the planner but pause for user confirmation via [planner_requires_approval].
  • Executor-only: Skip planning and route directly to the executor.

Interactive clarification is supported through <planner-ask>…</planner-ask> markers, which the Coordinator converts into interactive prompts when the planner needs additional information before drafting a strategy.

Summary

  • Reasonix two-model architecture splits work between a read-only planner and a full-access executor to optimize both cost and context window usage.
  • The Coordinator in internal/agent/coordinator.go manages separate sessions for each model, ensuring cache-stable system prompts.
  • Configure the planner via planner_model in reasonix.toml, typically using a cheaper model than the executor.
  • The planner only accesses read-only tools, while the executor handles destructive operations after receiving the validated plan.
  • PlannerPolicy controls when planning happens, supporting automatic execution, mandatory approval, or bypass modes.

Frequently Asked Questions

What happens if the planner model fails or times out?

If the planner returns an error or malformed plan, the Coordinator catches the failure and falls back to executor-only mode, appending a plannerFallbackNotice to the context. This ensures user requests proceed even when the planning model is unavailable.

Can I use the same model provider for both planner and executor?

Yes. While the architecture is designed to let you use a cheaper "flash" model for planning and a stronger "pro" model for execution, you can configure both planner_model and the default agent.model to use the same provider. The separation remains valuable for prompt isolation and tool restriction.

Where is the handoff marker defined between planner and executor?

The handoff marker is defined as the constant executorHandoffMarker at line 87 of internal/agent/coordinator.go. This marker signals to the executor that the following text contains the structured plan prepared by the planner session.

How does this architecture improve performance?

By maintaining separate sessions, neither model’s system prompt prefix changes mid-turn. This preserves the KV cache on the inference server, reducing latency and token costs. Additionally, using a cheaper model for the high-frequency planning phase reduces API expenses compared to running all reasoning through the execution-grade model.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →