Imported from vinhnx/VTCode (
crates/codegen/vtcode-core/AGENTS.md). Install upstream withnpx skills add vinhnx/VTCode --skill vtcode-core. Copyright stays with the author.
vtcode-core
Root AGENTS.md | Agent loop, tools, LLM, safety, UI.
Module map
- Execution explanations:
core/agent/events/context.rsannotates canonical task/item facts and native delegation observations;SessionStoreSinkorders snapshot/evidence queries and decision validation after accepted events, and builds bounded pages from shared cached projections. Projection lives invtcode-memory, never another history database.git::capture_workspace_diffkeeps current state separate and disables Git extensions. Builtin declarations and runtime tool packs must share registrations; verify new tools through public execution, not only factory metadata. See guide. core/agent/: runtime, event recording, runner, progress, harness artifacts;request_envelopefreezes each segment's prompt and ordered catalog.result_reducers::project_model_tool_resultis the shared interactive/headless boundary for compact indexed tracker updates; keep full results for events and persistence.turn/tool_outcomes/execution_result/failure_diagnosis/keeps bounded evidence, deterministic classification, auxiliary routing, and presentation behind small internal interfaces.tools/: handlers, registry, policy, execution;registry/patch_recovery.rsowns one bounded uncached context-mismatch read per path/user turn; begin its turn at interactive/headless task boundaries, preserve typed patch errors before string-based configuration detection;ToolRegistry::new_for_schemabuilds offline metadata registries without persistent workspace policy state;output_spooler.rsowns bounded model previews and spool-reference metadata; startup spool pruning is age-expiry only — themax_filescount budget stays on the in-session periodic cleanup because pinning is per-process and a startup count prune could evict a concurrent session's young spools whileregistry/spool_processing.rsowns pure spool decisions/preview caps;tool_intent/activity.rsowns inspection/verification classification plus truncation-only verifier elision for truthful exit statuses;read_file/batch.rsowns batch admission whileread_file.rsowns single-file semantics;exec_session.rsowns the unified PTY/pipe registry, bounded background slots, and Ctrl+B promotion; exit-driven terminators (terminate_*_for_exit_async) close PTYs inPtyCloseMode::Immediate(group SIGKILL, no grace window) while in-session closes stayGraceful;registry/executors/is split intoexec_command.rs,exec_output.rs,exec_sessions.rs,exec_support.rs;grep_file.rsdelegates togrep_backend.rs;handlers/task_tracker.rsowns the renderer-independent compact task-tree formatter; workspace-aware path responses usevtcode_commons::workspace_relative_display.llm/: re-export facade; providers live invtcode-llm.exec/events.rsre-exports canonicalvtcode-exec-events::ThreadEvent;prompts/owns cached static profiles, compiled runtime guidance, and bounded resource caches (prompts/system/splits composition/constants/cache/tokens/types).utils/ansi/splits the renderer:mod.rsstruct,core.rsconstruction/state,compact.rscommand groups,output.rsplain lines,markdown.rsmarkdown,sink.rsinline sink,links.rsopener/links.mcp/plugin_providers.rs: discovers Agent Plugin MCP servers and maps them toMcpProviderConfig../-prefixed stdiocommand/cwdare canonicalized at discovery so symlink escapes cannot bypass the plugin-root sandbox.skills/skill_policy.rsowns skill-scoped tool/network/sandbox policy;skills/executor.rsowns sub-LLM/fork execution.skills/loader.rsloadsSKILL.mdmanifests from pluginskills/*/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 inturn_loop/notifications.rs. No centralThreadEvent→notification bridge — each domain outcome owns its own call site to avoid duplicates.
Rules
- Public pipe runs default to null stdin;
stdin: trueopts into later input. Sharewrite_stdindispatch across interactive/headless paths; session cleanup retains permission checks and does not represent a workspace edit. - Re-export public APIs from
lib.rs; consumers must not reach into submodules, and keep constants inconfig::constantsrather than inline. Keep WebMCP transport/runtime logic invtcode-webmcp; core only owns the CLI/config facade and prompt guidance. Layer resets go throughConfigServiceso 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. Keepexec_policyandcommand_safetyas 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.SessionStoreSinkwrites the canonical workspace store; recovery/final assistant messages must use the canonicalThreadEventpath 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, while verified checkpoint metadata must reach both normal and quiet handoff writers.ModelsManagermust 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 throughSpooledOutputReference; binary consumers must not reopen spool paths to reconstruct previews. Provider-facing tool previews use per-result bounds (64 KiB execution, 96 KiB planning) in registry and runloop layers; history compaction owns accumulated context, and legacy exhaustion markers are diagnostics only; complete spool output is retained only for the current-session viewer. Direct and interceptedapply_patchmust 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 intocore/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; optional Markdown lint may be skipped explicitly only if unavailable (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; canonical metadata markers delimit steps, while prose/verification arrows remain content). Add providers withadding-llm-providersand update model enumeration, presets, and thellm/providersfacade. 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 finiteProviderenum.
Gotchas
- Import
RetryPolicyCoreExtfor domain retry methods; misconfiguration-first (detect_misconfiguration→ fail-closedGiveUpwith config guidance) runs before any backoff; budget decisions belong tollm/usage_cost.rsandBudgetStatus::classify, not call sites. Execwait/inspectcalls are turn-budget-exempt (tool_intent::is_turn_budget_exempt_call): theSafetyGatewaycounts them in a separate control-plane bucket (MAX_CONTROL_PLANE_TOOL_CALLS_PER_TURN, still bounded by the session fuse and rate limits) and the runloop skips both the exhaustion rejection and thetool_callsincrement for them in all three admission paths. Do not re-couple these calls to the per-turn work budget — that recreates the long-build budget starvation the exemption exists to prevent. Cross-turn resume:append_transient_turn_notesinjects a bounded exec-session resume hint at every turn start from the live registry's foreground-only view (in_progress_foreground_exec_sessions, backend-checked completion); retained background sessions remain asynchronous, the hint must stay bounded, and it must never auto-execute the wait.SnapshotTurnDiagnosticsandturn.completedshareMAX_IN_PROGRESS_EXEC_SESSIONS(4) for ATIF correlation (schema 0.16.0). Backgroundexit 0isStoppedwith no auto-restore; spontaneous non-zero exit isError, but user-requested managed/raw termination remainsStopped; completion watchers make a bounded final-output drain before notification, and pruning must retain an exited background session until its watcher finishes delivery; managed delivery persistsBackgroundRecordfirst and replays bounded pre-parent-subscription completions, while raw user-background exec sessions use the shared parent-loop completion subscription; launch finalization may promote only the matchingStartingrecord so a fast terminal event or replacement session cannot be resurrected; the idle loop owns bounded continuation;VTCodeExecSession::status_label/command_labelown drawer/inspect/transcript display. Plugin MCPcommand/cwdstarting with./are canonicalized and containment-checked byvtcode_agent_plugins::validate_plugin_relativebefore 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.rsintentionally discards it. Automatic thresholds use the effective provider/session budget minus reserved output, and explicit harness thresholds are clamped to that budget;effective_context_budgetis the shared runtime/preflight/status denominator. Native provider output is a canonical replay window: do not locally prune or inject envelopes into the returned history.AgentSessionState.messagesisArc<Vec<Message>>; mutate viamessages_mut()/Arc::make_mutso request and continuation histories stay shared. Request assembly should keep normalized history shared too; persisted editor/few-shot context (appended once, never rewritten atmessages[0]) and provider compaction are the intentional copy boundaries.core::agent::state::normalize_history_for_requestis 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.clear_old_tool_resultsis its sibling fortool_result_clearingon requests that will not carry Anthropic native context edits — non-Anthropic providers, Anthropic routes without the capability, and the headlessAgentRunner, which never attachescontext_management(request-only stubs; never mutates durable history or ThreadEvents; the gate must not consume the provider capability at the headless call site). Workspace-controlled lifecycle hook content (vtcode.toml/.vtcodelayers or workspace agent-spec hooks) gates the whole engine: nothing spawns until the exact command-set digest is approved. Build engines withnew_with_session_gated, restore persisted approvals withrestore_workspace_hook_approval, and never add a bypass;new/new_with_sessionare 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_blockingfor 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 oneVtCodePathssnapshot; legacy and paged text reads share the bounded line reader. - Evaluator
generalization_notesare bounded, validated, task-scoped evidence; replans must preserve scopes and add falsifiers to the tracker without promoting notes to global memory.SessionProgressSinkcoalesces snapshots through a bounded writer, so progress mutations must not perform synchronous filesystem I/O. SessionToolCatalogprojection 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_waitingis for existing-file GUI opens; preserve adapter-specific reuse/location flags, keep temporary-file/editflows on the waiting API, and keepeditor_args_with_wait_policyinjecting the adapter wait flag (--wait/-w/-W) when a waiting launch's command omitted it.- 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; empty output-search pipelines are progress signals only, excluding quiet probes and hidden captures; standalone grep classification must preserve exit-status ownership and reject redirected diagnostics; keep optional SQLite on rusqlite 0.39 until stable cfg_select support is available.
web_fetchaccepts remote HTTP(S) URLs only; local reads must useread_file/unified_file.AnsiRendererowns 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 mergedoutput/stream aliases must not duplicate payloads. Bound previews per result; earlier output volume must never hide fresh evidence or disable tools. PTY decoding retains only an incomplete UTF-8 suffix (at most three bytes), never rejects a valid chunk solely for its size. Active names stay read-only;write_stdin/apply_patchremain 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 honoragent.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 setno_spoolso inspecting a spool never creates a recursive model-visible spool reference. Registryapply_patchandwrite_fileresults include bounded canonical diff previews (with legacy compatibility fields); keep lifecycle payloads and TUI rendering paths visible when changing that response shape.ToolResponseBuilder::build_jsondeduplicates at the single build point:metadata.datakeys already surfaced as top-levelfield()s are dropped from the metadata copy, so pick one surface per key and never re-add the duplicate. Completed spools expose exactspool_line_countfrom committed sanitized bytes, never preview or pending output. Tracker create/bulk sync replaces the checklist; shared item parsers reject encoded update commands after status-prefix normalization and before mutation. Task-trackerfiles,outcome, andverifyremain 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 optionalcode_searchfilters (neverpath: ""), 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-modeldiagnosisdata before tool-free recovery; exec capture errors propagate; peeks replace bounded snapshots while drains append chunks; missing handles use typed deterministic recovery without implying command reruns; collapsed output uses one provider-neutral typed turn-scoped marker after tool results, with Anthropicclear_atonly on eligible wires and ordinary system/history mapping elsewhere; update prompt golden/compactness tests with changes. Skipped/suppressed diff previews keep stablereasoncodes; renderdiff_preview_user_messageinstead of the code. - Build and Auto are authority-parity destinations: only confirmation policy differs, and both pass the same blocked-call guard, command/path safety, verification gate, budgets, and recovery limits. Dispatch-level blocked mutations must return a distinct handled state when the guard has not tripped so a rejected call cannot fall through to execution.
