How DeepSeek-Reasonix Two-Model Collaboration Works: Planner and Executor Architecture
DeepSeek-Reasonix runs two isolated language-model agents—a read-only Planner and a full-capability Executor—coordinated by the Coordinator type to optimize costs while preserving cache efficiency.
DeepSeek-Reasonix supports an advanced two-model collaboration feature that enables running separate planner and executor models within a single conversation. This architecture, implemented in the internal/agent/coordinator.go file, allows developers to use a lightweight model for planning and a powerful model for execution, reducing API costs while maintaining stable model-prefix caches.
Architecture Overview
The collaboration centers on two distinct agents orchestrated by the Coordinator type. When the configuration key agent.planner_model names a different provider than the executor, the CLI creates a Coordinator that wires a dedicated planner provider to an executor Agent.
The Planner model operates as a low-frequency, read-only agent that produces concise, ordered plans without mutating the workspace. It can browse documentation and inspect files but cannot execute tools that write or run shell commands.
The Executor model is a full-capability agent that performs actual work using all registered tools. It runs in an isolated session separate from the planner, ensuring that heavy-weight operations do not contaminate the planning context.
Session Isolation and Cache Optimization
Session isolation is fundamental to the design. The planner runs in its own plannerSess while the executor maintains a separate Session. Both sessions are prepend-only, meaning the model-prefix cache remains stable throughout the conversation.
This isolation prevents the "cache-reset" that would occur if a single session switched models mid-conversation. By keeping each model’s system prompt and history in dedicated sessions, DeepSeek-Reasonix reconciles its cache-first philosophy with the need for distinct model capabilities, as specified in §3.5 of SPEC.md.
The Collaboration Workflow
The Coordinator.Run method manages the hand-off between models through a structured sequence of checks and transformations.
Planning Phase
When Coordinator.Run receives input, it first evaluates shouldPlan. If planning is required, the coordinator emits a planning phase event and invokes plan(ctx, input). This streams the planner’s response, appends the raw plan text to plannerSess, and returns the plan string for the next stage.
The planner sees only read-only tools during this phase, guaranteeing safety while it analyzes the workspace and formulates the execution strategy.
No-Op Detection
After receiving the plan, isNoOpPlan examines the text for phrases like "no changes needed". If the planner determines the task is already satisfied, persistExecutorNoOp records the plan in the executor’s session and skips the execution phase entirely.
This saves a provider call and associated costs when no actual work is required.
Hand-Off to Executor
If the plan is not a no-op, Coordinator.Run calls formatHandoff to create a self-contained instruction set embedding the original task, the planner’s output, and any executor-tool context. This formatted string is fed to executor.Run, which treats the planner’s output as context rather than as its own role description.
The executor then runs exactly like a normal Agent, invoking tools, editing files, and executing commands while its session grows independently, preserving its own cache prefix.
Configuration and Code Implementation
The following Go example demonstrates how to instantiate the two-model collaboration programmatically:
// Create a planner provider (e.g. a cheap model) and an executor provider (full model)
plannerProv := provider.NewOpenAI("deepseek-planner", cfg.PlannerAPIKey)
executorProv := provider.NewOpenAI("deepseek-executor", cfg.ExecutorAPIKey)
// Planner session is isolated from the executor’s session
plannerSess := internal.Agent.NewSession("planner")
executor := internal.Agent.New(executorProv, tool.NewRegistry(), nil, internal.Agent.Options{}, nil)
// Wire them together via Coordinator
coord := internal.Agent.NewCoordinator(
plannerProv, // planner provider
plannerSess, // planner session
nil, // pricing (optional)
nil, // planner tools (read-only only)
internal.Agent.Options{}, // planner options
executor, // executor agent
0.0, // temperature
event.Discard, // event sink
nil, // shouldPlan (nil = always plan)
)
// Run a task – the coordinator will first ask the planner, then hand off to the executor
ctx := context.Background()
if err := coord.Run(ctx, "Refactor the `utils` package to use Go 2 generics"); err != nil {
log.Fatalf("run failed: %v", err)
}
When the user toggles plan-only mode via the UI, Coordinator.SetPlanMode propagates the flag to both the planner agent and the executor, ensuring consistent permission handling across both models.
Key Source Files
internal/agent/coordinator.go– Core implementation of the two-modelCoordinator(session handling, planning, hand-off, and no-op detection).docs/SPEC.md– Architectural specification describing the two-model collaboration intent and cache-first design principles.docs/GUIDE.md– User-facing documentation explaining how to enable the feature from the CLI or UI.internal/agent/agent.go– The standard single-modelAgentimplementation used as the executor side of the collaboration.internal/control/controller_test.go– Unit tests exercisingCoordinatorlogic including planner-executor hand-off and no-op handling.
Summary
- Two-model collaboration in DeepSeek-Reasonix uses a
Coordinatorto manage a read-only Planner and a full-capability Executor. - Session isolation via prepend-only sessions keeps each model’s cache prefix stable, avoiding expensive cache resets.
- No-op detection through
isNoOpPlanandpersistExecutorNoOpeliminates unnecessary executor calls when the planner determines no changes are needed. - Safe hand-off via
formatHandoffensures the executor receives clean, self-contained instructions without role confusion. - Plan mode propagation via
SetPlanModekeeps permission states synchronized across both models.
Frequently Asked Questions
What is the purpose of separating planner and executor models?
Separating the models allows you to use a cheaper, lightweight model for planning while reserving expensive, powerful models for actual execution. This reduces API costs and latency while ensuring that complex reasoning about strategy (planning) remains distinct from the heavy lifting of implementation (execution).
How does the Coordinator handle cache efficiency?
The Coordinator maintains separate Session instances for the planner and executor, both configured as prepend-only. This prevents the cache invalidation that would occur if a single session alternated between models, ensuring that each model’s system prompt and conversation history remain stable in the provider’s kv-cache.
What happens when the planner determines no action is needed?
When the planner output contains phrases like "no changes needed", the isNoOpPlan function returns true, triggering persistExecutorNoOp. This records the plan in the executor’s session for context but skips the execution phase entirely, saving a provider call and associated costs.
Can I use the same model for both planner and executor?
While technically possible by configuring identical providers, doing so defeats the architectural purpose of the feature. The design assumes distinct capabilities and costs; using the same model would not provide the cost optimization or safety isolation benefits, though the Coordinator would still function as a pass-through layer.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →