vtcode-core

September 14, 2026 · View on GitHub

Root AGENTS.md | Agent loop, tools, LLM, safety, UI.

Module map

  • core/agent/: runtime, event recording, runner, progress, harness artifacts; request_envelope freezes each segment's prompt and ordered catalog.
  • tools/: handlers, registry, policy, execution; ToolRegistry::new_for_schema builds offline metadata registries without persistent workspace policy state; output_spooler.rs owns bounded model previews and spool-reference metadata while registry/spool_processing.rs owns pure spool decisions/preview caps; tool_intent/activity.rs owns inspection/verification classification; read_file/batch.rs owns batch admission while read_file.rs owns single-file semantics; registry/executors/ is split into exec_command.rs, exec_output.rs, exec_sessions.rs, exec_support.rs; grep_file.rs delegates to grep_backend.rs; task_tracking.rs owns the renderer-independent compact task-tree formatter; workspace-aware path responses use vtcode_commons::workspace_relative_display.
  • turn/tool_outcomes/execution_result/failure_diagnosis/ keeps bounded evidence, deterministic classification, auxiliary routing, and presentation behind small internal interfaces.
  • llm/: re-export facade; providers live in vtcode-llm. exec/events.rs re-exports canonical vtcode-exec-events::ThreadEvent; prompts/ owns cached static profiles, compiled runtime guidance, and bounded resource caches.
  • mcp/plugin_providers.rs: discovers Agent Plugin MCP servers and maps them to McpProviderConfig. ./-prefixed stdio command/cwd are canonicalized at discovery so symlink escapes cannot bypass the plugin-root sandbox.
  • skills/skill_policy.rs owns skill-scoped tool/network/sandbox policy; skills/executor.rs owns sub-LLM/fork execution. skills/loader.rs loads SKILL.md manifests from plugin skills/*/ directories into the session skill catalog; repository plugin roots are metadata-only and native loading is explicit and approval-gated.
  • notifications/: maps important runtime events to OS/desktop alerts via dedicated call-site helpers (send_global_notification). Session completion is surfaced at session finalization in the binary (session_loop_runner/notifications.rs); turn completion in turn_loop/notifications.rs. No central ThreadEvent→notification bridge — each domain outcome owns its own call site to avoid duplicates.

Rules

  • Re-export public APIs from lib.rs; consumers must not reach into submodules, and keep constants in config::constants rather than inline. Keep WebMCP transport/runtime logic in vtcode-webmcp; core only owns the CLI/config facade and prompt guidance. Layer resets go through ConfigService so path validation, cache invalidation, and effective-config reload stay shared by CLI and TUI callers; repository policy persistence must use the repository-safe config writer. Keep exec_policy and command_safety as separate policy layers.
  • Session event sinks are authoritative: preserve ordering, use one bounded non-blocking handoff with count/byte limits and fail-closed saturation, and await the shared one-shot drain result before reporting task success. SessionStoreSink writes the canonical workspace store; recovery/final assistant messages must use the canonical ThreadEvent path rather than history-only writes. Snapshot files migrate independently through V2 while unrelated durable state remains on V1. Final session archives retain only a lightweight last-turn diagnostics block; full progress history remains checkpoint-only. Blocker archives are session-owned, path-contained, and append a durable resolution before their live pointer is cleared; archive-less runner handoffs must not claim a session-archive resume ID. ModelsManager must read provider-scoped and legacy unscoped cache snapshots, with canonical entries taking precedence; approval and installer backoff caches must likewise recover legacy files.
  • Trajectory logging is best effort: retain line/byte bounds and expose drops/failures through diagnostics. Spool references are consumed through SpooledOutputReference; binary consumers must not reopen spool paths to reconstruct previews. Provider-facing tool previews share a planning-aware per-turn budget (32 KiB execution, 96 KiB planning via turn_preview_budget_bytes); exhausted previews retain bounded scalar outcome/control metadata, record suppression diagnostics, and never invite equivalent retry calls; complete spool output is retained only for the current-session viewer. Direct and intercepted apply_patch must validate the turn cwd and every target against the session workspace with symlink-aware containment before mutation.

Workflows

  • Add tools under tools/, register/classify them, wire them into core/agent/; approved-plan task trackers deduplicate normalized step descriptions while preserving first-seen order, approval must fail closed if the tracker cannot be persisted, and plan artifacts must validate concrete targets plus command/observable verification markers before writing (leading shell env assignments are allowed only before a concrete command; lists need at least one concrete target; bracket lists split on commas outside single/double quotes). Add providers with adding-llm-providers and update model enumeration, presets, and the llm/providers facade. NVIDIA-style first-class providers also need startup defaults, the secret enum, lightweight routing, and generated docs metadata; gateway providers may additionally need explicit route pass-through validation. Custom provider registration stays in the factory and delegates protocol selection to the profile-aware router; do not add arbitrary custom names to the finite Provider enum.

Gotchas

  • Import RetryPolicyCoreExt for domain retry methods; budget decisions belong to llm/usage_cost.rs and BudgetStatus::classify, not call sites.
  • Plugin MCP command/cwd starting with ./ are canonicalized and containment-checked by vtcode_agent_plugins::validate_plugin_relative before spawn. Never re-resolve them relative to the working directory at spawn time — that defeats the containment check.
  • Compaction preserves conversation and starts a new immutable request segment before replacing history; context_reset.rs intentionally discards it. Automatic thresholds use the effective provider/session budget minus reserved output, and explicit harness thresholds are clamped to that budget; effective_context_budget is the shared runtime/preflight/status denominator. AgentSessionState.messages is Arc<Vec<Message>>; mutate via messages_mut()/Arc::make_mut so request and continuation histories stay shared. Request assembly should keep normalized history shared too; editor/few-shot injection and provider compaction are the intentional copy boundaries.
  • core::agent::state::normalize_history_for_request is request-only and idempotent: repair tool-result pairing/order for the provider wire without mutating durable session history; retain the borrowed fast path for clean histories.
  • Workspace-controlled lifecycle hook content (vtcode.toml/.vtcode layers or workspace agent-spec hooks) gates the whole engine: nothing spawns until the exact command-set digest is approved. Build engines with new_with_session_gated, restore persisted approvals with restore_workspace_hook_approval, and never add a bypass; new/new_with_session are test-only.
  • Async plugin, skill, file-tool, planning, persistent-memory, durable-scheduler, prompt-resource cache-miss, context-reset manifest, and trajectory setup paths must use Tokio filesystem APIs or spawn_blocking for recursive/synchronous scans, record loading, claim files, persistence, or Git work; registry workspace settings, including persistent-memory config, are startup snapshots. Global user paths come from one VtCodePaths snapshot; legacy and paged text reads share the bounded line reader.
  • Evaluator generalization_notes are bounded, validated, task-scoped evidence; replans must preserve scopes and add falsifiers to the tracker without promoting notes to global memory. SessionProgressSink coalesces snapshots through a bounded writer, so progress mutations must not perform synchronous filesystem I/O.
  • SessionToolCatalog projection caches are private and lazy per entry/documentation mode; rebuild the catalog when registrations change. Basic directory-list caches are keyed by canonical workspace and response shape.
  • TerminalAppLauncher::launch_editor_target_non_waiting is for existing-file GUI opens; preserve adapter-specific reuse/location flags and keep temporary-file /edit flows on the waiting API.
  • Token/catalog deferral thresholds are correct behavior; warn only when deferral is disabled but beneficial. Shell safety must preserve raw command text when classifying dynamic syntax; activity and read-only intent share the static shell parser, allowing compound sequences only when every segment is independently read-only while dynamic or unknown shapes fail closed as mutating; keep optional SQLite on rusqlite 0.39 until stable cfg_select support is available.
  • web_fetch accepts remote HTTP(S) URLs only; local reads must use read_file/unified_file. AnsiRenderer owns the session-local tool-summary display mode; Markdown table widths must be reduced for transcript indentation and framing before rendering. Compact mode groups only contiguous successful command calls; PTY live previews stay bounded while complete command output is retained in the current-session viewer, and merged output/stream aliases must not duplicate payloads. Active names stay read-only; write_stdin/apply_patch remain blocked while internal plan persistence is available. Pipe/PTy and MCP stdio children inherit filtered environment policy; use the sandbox-aware launch APIs rather than bypassing them. Batch file ranges remain under the absolute line cap, and duplicate tool calls may batch only after preflight marks each call parallel-safe; unified and runner dispatch honor agent.harness.max_parallel_tool_calls. Persistent-memory remember planning may receive only one explicitly selected immediate assistant reply for deictic requests; keep that reference separate from supplemental answers, tool output, and wider conversation history, and preserve planner validation, confirmation, and post-write candidate read-back gates.
  • Read-only shell commands targeting .vtcode/context/tool_outputs/ must set no_spool so inspecting a spool never creates a recursive model-visible spool reference. Registry apply_patch and write_file results include bounded canonical diff previews (with legacy compatibility fields); keep lifecycle payloads and TUI rendering paths visible when changing that response shape. Task-tracker files, outcome, and verify remain structured metadata; compact view rows must not reintroduce them as visible detail lines. Planning prompt guidance is part of the runtime contract: keep plan steps concrete and verifiable, omit unused optional code_search filters (never path: ""), reserve <plan> for approval artifacts while structured reasoning uses its distinct marker, require one plan block without policy echo, and keep rejected drafts in assistant history. Planning empty-response recovery allows one strict tool-free synthesis after two empties; failure is a resumable blocked handoff, never another interactive prompt. Live-preview queue pressure must remain visible through bounded coalesced diagnostics; compact PTY calls suppress transient live rows, expanded calls keep bounded previews, and complete command output stays in the current-session viewer. User-facing progress prompts may offer concise phase updates when useful and must finish with a standalone evidence/verification recap without narrating calls or hidden reasoning; structured tool-call events are the status source of truth. Terminal OSC palette probes must wait for all requested replies before their settle window; failure-like results retain bounded evidence and attach deterministic or lightweight-model diagnosis data before tool-free recovery; collapsed output uses one provider-neutral typed turn-scoped marker after tool results, with Anthropic clear_at only on eligible wires and ordinary system/history mapping elsewhere; update prompt golden/compactness tests with changes. Skipped/suppressed diff previews keep stable reason codes; render diff_preview_user_message instead of the code.