Agent Loop Contract
June 26, 2026 ยท View on GitHub
VT Code keeps its existing harness-first runtime, but its external loop contract now lines up more closely with SDK-style agent runtimes.
This guide describes the public lifecycle semantics shared by interactive runs,
vtcode exec, harness logs, and Open Responses extension events.
Message and Event Mapping
VT Code does not expose Claude-specific SDK structs. The canonical stream stays
vtcode_exec_events::ThreadEvent.
The closest concept mapping is:
| Agent SDK concept | VT Code event |
|---|---|
SystemMessage(init) | thread.started |
AssistantMessage | item.* with agent_message, reasoning, tool_invocation |
Tool-result UserMessage | item.* with tool_output or command_execution |
StreamEvent | item.updated plus Open Responses stream events |
ResultMessage | thread.completed |
compact_boundary | thread.compact_boundary |
turn.started, turn.completed, and turn.failed remain VT Code turn
wrappers around the inner item lifecycle.
Terminal Thread Result
VT Code now emits thread.completed at the end of a session or exec run.
Fields:
thread_id: stable event-stream thread identifiersession_id: stable VT Code session identifiersubtype:success,error_max_turns,error_max_budget_usd,error_during_execution, orcancelledoutcome_code: VT Code-specific terminal coderesult: final assistant summary text on successful completion onlystop_reason: provider stop reason when availableusage: aggregate token usage for the full threadtotal_cost_usd: aggregate estimated cost when pricing metadata existsnum_turns: total turn count
For vtcode exec, outcome_code comes from TaskOutcome::code(). Interactive
sessions preserve the corresponding VT Code session end semantics.
Compaction Boundary
Whenever VT Code compacts history itself or via a provider-native compaction
path, it emits thread.compact_boundary.
Fields:
thread_idtrigger:manualorautomode:localorprovideroriginal_message_countcompacted_message_counthistory_artifact_path: optional archived history path
This is emitted for manual /compact flows and for automatic local fallback
compaction. When Open Responses is enabled, VT Code surfaces these as VT Code
custom extension events without changing the core Open Responses response model.
Budget and Limits
agent.harness.max_budget_usd is the shared budget setting for interactive and
exec sessions.
- VT Code estimates cost from aggregate usage via
ModelResolver::estimate_cost. - If pricing metadata is unavailable for the active model, VT Code does not enforce the budget.
- In that case
total_cost_usdstaysnulland VT Code emits one warning.
Turn limits still surface through thread.completed.subtype = "error_max_turns".
Hooks
VT Code now supports hooks.lifecycle.pre_compact.
pre_compact runs before VT Code records a compaction boundary. Its payload
includes:
session_idcwdhook_event_name = "PreCompact"triggermodeoriginal_message_countcompacted_message_counthistory_artifact_pathtranscript_path
session_start with source compact remains supported for compatibility, but
pre_compact is the first-class hook for compaction-aware automation.
Related Controls
These VT Code settings line up with common agent-loop controls:
- Tool allow and deny rules:
[permissions].allow,[permissions].deny, tool policy config - Permission policy: workspace trust, human-in-the-loop settings, granular agent rules, and full automation allow-lists
- Effort: provider/model reasoning settings
- Tool discovery: MCP and tool catalog flows
- Resume and fork continuity: session archives, thread bootstrap, and compaction envelopes
Loop Engineering Additions
The subagent layer now supports loop-engineering primitives:
- Worktree isolation: set
isolation = "worktree"on an agent spec to run the child in a git worktree under.vtcode/worktrees/. The child's file mutations stay in its own working tree until explicitly merged. - Propose/verify separation:
SubagentController::verify_proposed_change()spawns a read-only verifier sub-agent that re-reads affected files and approves or rejects the change. The verifier has no shared context with the proposer. - Loop run state:
vtcode-core/src/loop_state.rspersists step index, cumulative cost, and status to.vtcode/state/loop-<id>.jsonso a scheduler can resume across invocations. - Loop memory:
vtcode-core/src/loop_memory.rsprovides an append-only store for agent notes and decisions in.vtcode/state/notes.mdanddecisions.md. - Cost guardrails:
CostBudgetinloop_state.rstracks token/cost/step limits and reportsBudgetStatus(Ok/TokenLimitReached/CostLimitReached/StepLimitReached).
See docs/project/PLAN-loop-engineering.md for the full design.