This document describes the turn-based execution model of VT Code's agent runtime, focusing on the core processing loop that drives LLM interaction, tool execution, and response handling within a single turn.
Scope: This page covers the turn loop implementation (src/agent/runloop/unified/turn/turn_loop.rs), turn processing contexts, phase transitions, and the integration between LLM requests and tool execution. For broader agent execution orchestration, see Agent Runner. For context and prompt management, see Context Management. For tool execution details, see Tool System.
The turn processing loop is the core execution unit of the agent. Each turn represents a complete interaction cycle: building a system prompt, sending an LLM request, processing the response, executing any requested tool calls, and handling the outcome.
The turn loop architecture consists of three primary structures:
TurnLoopContext src/agent/runloop/unified/turn/turn_loop.rs45-162
InlineHandle, InlineSession), tool registries, LLM provider, configuration, and state trackersas_turn_processing_context() method to create refined context viewsTurnProcessingContext src/agent/runloop/unified/turn/context.rs151-245
TurnLoopContextToolContext, LLMContext, UIContext, TurnProcessingStatefrom_parts() constructor with TurnProcessingContextPartsHarnessTurnState src/agent/runloop/unified/run_loop_context.rs64-203
tool_calls, blocked_tool_calls, max_tool_calls, turn_started_attool_budget_exhausted(), wall_clock_exhausted(), remaining_tool_calls()Sources: src/agent/runloop/unified/turn/turn_loop.rs45-244 src/agent/runloop/unified/turn/context.rs88-299 src/agent/runloop/unified/run_loop_context.rs64-203
Each turn progresses through four distinct phases tracked by TurnPhase enum src/agent/runloop/unified/run_loop_context.rs28-33:
| Phase | Purpose | Key Operations |
|---|---|---|
Preparing | Pre-request validation and setup | Proactive guards, safety validator initialization, turn state reset |
Requesting | LLM request construction and execution | System prompt building, message history normalization, streaming response handling |
ExecutingTools | Tool call validation and execution | Permission checks, circuit breaker validation, tool execution pipeline |
Finalizing | Result aggregation and cleanup | Turn outcome emission, telemetry recording, history updates |
Sources: src/agent/runloop/unified/turn/turn_loop.rs319-323 src/agent/runloop/unified/turn/turn_loop.rs391-403 src/agent/runloop/unified/turn/turn_loop.rs593-602 src/agent/runloop/unified/turn/turn_loop.rs660-673
The run_turn_loop function src/agent/runloop/unified/turn/turn_loop.rs301-680 orchestrates the entire turn execution cycle:
Sources: src/agent/runloop/unified/turn/turn_loop.rs301-680 src/agent/runloop/unified/turn/guards.rs199-213 src/agent/runloop/unified/turn/turn_processing/llm_request.rs477-1084 src/agent/runloop/unified/turn/turn_processing/response_processing.rs src/agent/runloop/unified/turn/turn_processing/result_handler.rs
1. Initialization src/agent/runloop/unified/turn/turn_loop.rs314-346
Completedauto_exit_plan_mode_attempted flagLoopTracker for repetition detectionmax_tool_loops from turn config2. Steering Messages src/agent/runloop/unified/turn/turn_loop_helpers.rs
3. Pre-Request Action src/agent/runloop/unified/turn/turn_loop_helpers.rs
4. Plan Mode Exit Trigger src/agent/runloop/unified/turn/turn_loop_helpers.rs
5. Tool Loop Limit src/agent/runloop/unified/turn/turn_loop_helpers.rs
max_tool_loops per turn6. Proactive Guards src/agent/runloop/unified/turn/guards.rs199-213
7. LLM Request Execution src/agent/runloop/unified/turn/turn_processing/llm_request.rs
ContextManager8. Response Processing src/agent/runloop/unified/turn/turn_processing/response_processing.rs
9. Turn Result Handling src/agent/runloop/unified/turn/turn_processing/result_handler.rs
Continue or Break(outcome)10. Token Usage Update src/agent/runloop/unified/turn/turn_loop.rs644-653
ContextManager with provider-reported usageSources: src/agent/runloop/unified/turn/turn_loop.rs301-680 src/agent/runloop/unified/turn/guards.rs199-213
The execute_llm_request function src/agent/runloop/unified/turn/turn_processing/llm_request.rs477-1084 orchestrates LLM request construction:
Key optimizations:
Vec<uni::ToolDefinition> when config/plan mode unchanged via tool_catalog.get_or_build() src/agent/runloop/unified/turn/turn_processing/llm_request.rs648-697compact_tool_messages_for_retry() to max 1200 chars per message src/agent/runloop/unified/turn/turn_processing/llm_request.rs274-303STREAM_TIMEOUT_FALLBACK_PROVIDERS array after timeout src/agent/runloop/unified/turn/turn_processing/llm_request.rs42-51 src/agent/runloop/unified/turn/turn_processing/llm_request.rs253-264PromptCacheShapingMode via resolve_prompt_cache_shaping_mode() for Anthropic/MiniMax src/agent/runloop/unified/turn/turn_processing/llm_request.rs66-84vtcode_commons::classify_error_message() for structured retry decisions src/agent/runloop/unified/turn/turn_processing/llm_request.rs38-40Sources: src/agent/runloop/unified/turn/turn_processing/llm_request.rs477-1084 src/agent/runloop/unified/turn/turn_processing/llm_request.rs648-697 src/agent/runloop/unified/turn/turn_processing/llm_request.rs274-303 src/agent/runloop/unified/turn/turn_processing/llm_request.rs42-51
The process_llm_response function src/agent/runloop/unified/turn/turn_processing/response_processing.rs parses and validates LLM responses:
| Response Type | Validation | Output |
|---|---|---|
| Tool calls | validate_tool_args_security() | TurnProcessingResult::ToolCalls |
| Text with proposed plan | Extract plan from markdown blocks | TurnProcessingResult::TextResponse with proposed_plan |
| Plain text | Basic content extraction | TurnProcessingResult::TextResponse |
| Empty/whitespace | No actionable content | TurnProcessingResult::Empty |
Validation flow src/agent/runloop/unified/turn/guards.rs20-179:
Sources: src/agent/runloop/unified/turn/turn_processing/response_processing.rs src/agent/runloop/unified/turn/guards.rs20-179
Tool execution within a turn follows a multi-stage pipeline with extensive validation and safety checks:
Sources: src/agent/runloop/unified/turn/turn_processing/result_handler.rs src/agent/runloop/unified/turn/tool_outcomes/handlers.rs src/agent/runloop/unified/turn/tool_outcomes/handlers_batch.rs src/agent/runloop/unified/turn/tool_outcomes/execution_result.rs431-504 src/agent/runloop/unified/turn/guards.rs20-179 src/agent/runloop/unified/tool_pipeline.rs
Stage 1: Circuit Breaker Check src/agent/runloop/unified/turn/tool_outcomes/handlers.rs63-85
circuit_breaker.is_open(canonical_tool_name)try_interactive_circuit_recovery() which:
error_recovery.can_prompt_user() for cooldowndiagnostics and open circuitsexecute_recovery_prompt() with RecoveryAction enumResetAllCircuits, Continue, SkipStep, SaveAndExitcircuit_breaker_default_blocked() returns ValidationResult::BlockedStage 2: Argument Validation src/agent/runloop/unified/turn/guards.rs20-179
validation_cache.check(name, args_hash)tool_registry.preflight_validate_call(name, args) if registry available&["path"], &["command"], etc.paths::validate_path_safety(path) for path argumentscommands::validate_command_safety(cmd) for command argumentsvalidation_cache.insert(name, args_hash, is_valid)Stage 3: Rate Limiting src/agent/runloop/unified/turn/tool_outcomes/handlers.rs55-57
for attempt in 0..MAX_RATE_LIMIT_ACQUIRE_ATTEMPTS (4 attempts)rate_limiter.try_acquire(canonical_tool_name, 1)?tokio::time::sleep(backoff_duration).awaitbuild_rate_limit_error_content() if exhaustedStage 4: Permission Verification src/agent/runloop/unified/tool_routing.rs
tool_permission_cache.check_permission(tool_name, args)ensure_tool_permission() which:
tool_policy_manager.check_policy()ToolPermissionFlow::execute() for prompt/deny policiestool_permission_cacheapproval_policy_from_human_in_the_loop() for MCP toolsSources: src/agent/runloop/unified/turn/tool_outcomes/handlers.rs55-214 src/agent/runloop/unified/turn/guards.rs20-179 src/agent/runloop/unified/tool_routing.rs
Once validated, tools execute through a timeout-wrapped pipeline src/agent/runloop/unified/tool_pipeline.rs:
Timeout wrapper src/agent/runloop/unified/tool_pipeline.rs27-32
DEFAULT_TOOL_TIMEOUT = 180 secondsapply_timeout_with_warning() with MIN_TIMEOUT_WARNING_HEADROOM = 5 secondsmax_tool_timeout from tool policy configurationRetry logic src/agent/runloop/unified/tool_pipeline/execution_attempts.rs
max_tool_retries)RETRY_BACKOFF_BASE = 200ms, MAX_RETRY_BACKOFF = 3sis_retryable_tool_error() for retry decisionsSandbox application src/agent/runloop/unified/tool_pipeline/execution_plan_mode.rs
is_read_only_operation() for plan mode restrictionsSandboxManager transformations for high-risk commandsworkspace boundary constraintsOutput handling src/agent/runloop/unified/tool_pipeline/execution.rs
ToolOutputSpooler for outputs exceeding DEFAULT_SPOOL_THRESHOLDapply_tool_summarizer() for large resultshandle_pipeline_output_from_turn_ctx()Metric recording src/agent/runloop/unified/turn/tool_outcomes/execution_result.rs27-47
circuit_breaker.record_execution(tool_name, success, duration)tool_health_tracker.record_execution(tool_name, success, duration)autonomous_executor.record_execution(tool_name, success)telemetry.record_tool_usage(tool_name, success)Sources: src/agent/runloop/unified/tool_pipeline.rs27-32 src/agent/runloop/unified/tool_pipeline/execution_attempts.rs src/agent/runloop/unified/tool_pipeline/execution.rs src/agent/runloop/unified/turn/tool_outcomes/execution_result.rs27-47
The turn loop concludes with one of five possible outcomes:
Sources: src/agent/runloop/unified/turn/turn_loop.rs660-712 src/agent/runloop/unified/turn/context.rs32-39
Turn outcomes propagate through multiple layers:
| Layer | Structure | Purpose |
|---|---|---|
| Turn Loop | TurnLoopResult | Final turn status (Completed, Aborted, etc.) |
| Turn Handler | TurnHandlerOutcome | Intermediate control flow (Continue, Break) |
| Turn Processing | TurnProcessingResult | LLM response classification (ToolCalls, TextResponse) |
| Tool Batch | ToolBatchOutcome | Tool execution batch status |
| Tool Pipeline | ToolPipelineOutcome | Individual tool execution result |
Sources: src/agent/runloop/unified/turn/context.rs32-74 src/agent/runloop/unified/tool_pipeline.rs24
The turn loop includes recovery logic for post-tool LLM failures src/agent/runloop/unified/turn/turn_loop.rs252-297:
Recovery logic src/agent/runloop/unified/turn/turn_loop.rs260-297:
has_tool_response_since(working_history, turn_history_start_len)
false if no tool progress: abort recoveryvtcode_commons::classify_anyhow_error(err)
ErrorCategory with is_retryable() methodtrue to complete turn gracefully, preserving partial tool outputRecovery conditions:
turn_history_start_len and current lengtherr_cat.user_label() and recovery_suggestions()Call sites src/agent/runloop/unified/turn/turn_loop.rs:
execute_llm_request() failureprocess_llm_response() failurehandle_turn_processing_result() failureIf recovery succeeds, the turn completes with Completed status despite the LLM failure, preserving tool output. This prevents tool re-execution on retry.
Sources: src/agent/runloop/unified/turn/turn_loop.rs252-297 src/agent/runloop/unified/turn/turn_loop.rs246-250 src/agent/runloop/unified/turn/turn_loop.rs406-471 src/agent/runloop/unified/turn/turn_loop.rs527-542
The HarnessTurnState structure src/agent/runloop/unified/run_loop_context.rs64-203 enforces per-turn resource limits:
| Metric | Field | Check Method | Threshold Source | Action on Exhaustion |
|---|---|---|---|---|
| Tool calls | tool_calls: usize / max_tool_calls: usize | tool_budget_exhausted() line 99 | harness.max_tool_calls config | Emit budget warning at 75% via should_emit_tool_budget_warning(0.75) line 102 stop at 100% |
| Wall clock | turn_started_at: Instant + max_tool_wall_clock: Duration | wall_clock_exhausted() line 92 | harness.max_tool_wall_clock_secs config | Stop turn, return TurnLoopResult::Blocked with timeout reason |
| Blocked streak | consecutive_blocked_tool_calls: usize | record_blocked_tool_call() line 120 returns streak count | tools.max_consecutive_blocked_tool_calls_per_turn from config | Call enforce_blocked_tool_call_guard() which stops turn after limit exceeded |
| Spool reads | consecutive_spool_chunk_reads: usize | record_spool_chunk_read() line 159 | tools.max_sequential_spool_chunk_reads_per_turn | Apply offset hints via maybe_apply_spool_read_offset_hint() to auto-advance |
| Shell repeats | last_shell_command_signature: Option<String>, consecutive_same_shell_command_runs: usize | record_shell_command_run(signature) line 168 | tools.max_repeated_tool_calls from config | Block via build_repeated_shell_run_error_content() after limit |
Sources: src/agent/runloop/unified/run_loop_context.rs64-203 src/agent/runloop/unified/turn/tool_outcomes/handlers.rs294-343
The turn loop tracks repetitive patterns across three dimensions:
1. Tool Call Signatures src/agent/runloop/unified/turn/tool_outcomes/helpers.rs
"tool_name::normalized_args"2. Shell Command Signatures src/agent/runloop/unified/run_loop_context.rs168-183
3. Spool Chunk Reads src/agent/runloop/unified/run_loop_context.rs159-166
Sources: src/agent/runloop/unified/turn/tool_outcomes/helpers.rs src/agent/runloop/unified/run_loop_context.rs159-183
The turn loop emits structured metrics for observability:
Turn Lifecycle src/agent/runloop/unified/turn/turn_loop.rs320-322
turn_started_event(): Emitted at turn initializationturn_completed_event(): Emitted on success/exitturn_failed_event(): Emitted on abort/block/cancelTool Catalog Cache src/agent/runloop/unified/turn/turn_processing/llm_request.rs362-407
LLM Retry Outcome src/agent/runloop/unified/turn/turn_processing/llm_request.rs410-474
Tool Execution src/agent/runloop/unified/turn/tool_outcomes/execution_result.rs27-47
ToolHealthTrackerCircuitBreaker with success/failureTelemetryManagerSources: src/agent/runloop/unified/turn/turn_loop.rs320-322 src/agent/runloop/unified/turn/turn_processing/llm_request.rs362-474 src/agent/runloop/unified/turn/tool_outcomes/execution_result.rs27-47
Turn loop behavior is controlled by configuration from multiple sources:
The extract_turn_config function src/agent/runloop/unified/turn/turn_loop_helpers.rs consolidates configuration:
Configuration sources:
vtcode.toml [tools] sectionvtcode-config/constants)| Parameter | Path | Default | Impact |
|---|---|---|---|
max_tool_calls | harness.max_tool_calls | 25 | Tool budget per turn |
max_tool_wall_clock_secs | harness.max_tool_wall_clock_secs | 300 | Wall clock timeout per turn |
max_tool_retries | harness.max_tool_retries | 3 | Retry budget for failed tools |
max_repeated_tool_calls | tools.max_repeated_tool_calls | 3 | Loop detection threshold |
max_consecutive_blocked_tool_calls_per_turn | tools.max_consecutive_blocked_tool_calls_per_turn | 5 | Blocked streak limit |
max_sequential_spool_chunk_reads_per_turn | tools.max_sequential_spool_chunk_reads_per_turn | 8 | Spool read limit |
Sources: src/agent/runloop/unified/turn/turn_loop_helpers.rs vtcode-config/src/constants/defaults.rs
The turn loop is invoked by the session interaction loop src/agent/runloop/unified/turn/session/interaction_loop.rs172-177:
Sources: src/agent/runloop/unified/turn/session/interaction_loop.rs172-177
Steering messages enable external control over turn execution via tokio::sync::mpsc::UnboundedReceiver<SteeringMessage> vtcode-core/src/core/agent/steering.rs:
Processing src/agent/runloop/unified/turn/turn_loop_helpers.rs:
handle_steering_messages(&mut ctx, working_history, &mut result).await?ctx.steering_receiver via try_recv() (non-blocking)SwitchModel: Updates ctx.config.model and recreates ctx.provider_clientExit/Cancel: Sets result and returns early from turn loopIntegration points:
Sources: src/agent/runloop/unified/turn/turn_loop_helpers.rs vtcode-core/src/core/agent/steering.rs src/agent/runloop/unified/turn/turn_loop.rs349-351
In plan mode, the turn loop can synthesize interview questions from assistant responses src/agent/runloop/unified/turn/turn_processing/plan_mode.rs:
should_attempt_dynamic_interview_generation)request_user_input tool call with synthesized argsSources: src/agent/runloop/unified/turn/turn_processing/plan_mode.rs
For external integrations (e.g., Zed via ACP), the turn loop emits structured events src/agent/runloop/unified/inline_events/harness.rs:
turn_started_event()turn_completed_event()turn_failed_event()Sources: src/agent/runloop/unified/turn/turn_loop.rs320-322 src/agent/runloop/unified/turn/turn_loop.rs660-672
Refresh this wiki
This wiki was recently refreshed. Please wait 1 day to refresh again.