git:20261002.c7ea326 to git:20261005.f557833

4 added, 4 removed. Audit A to A.

<!-- markdownlint-disable MD013 -->
# vtcode-core
[Root AGENTS.md](../../../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. `result_reducers::project_model_tool_result` is 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.rs` owns 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_schema` builds offline metadata registries without persistent workspace policy state; `output_spooler.rs` owns bounded model previews and spool-reference metadata; startup spool pruning is age-expiry only — the `max_files` count 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 while `registry/spool_processing.rs` owns pure spool decisions/preview caps; `tool_intent/activity.rs` owns inspection/verification classification plus truncation-only verifier elision for truthful exit statuses; `read_file/batch.rs` owns batch admission while `read_file.rs` owns single-file semantics; `exec_session.rs` owns the unified PTY/pipe registry, bounded background slots, and Ctrl+B promotion; `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`; `handlers/task_tracker.rs` owns the renderer-independent compact task-tree formatter; workspace-aware path responses use `vtcode_commons::workspace_relative_display`. `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.
+ - Execution explanations: `core/agent/events/context.rs` annotates canonical task/item facts and native delegation observations; `SessionStoreSink` orders snapshot/evidence queries and decision validation after accepted events, and builds bounded pages from shared cached projections. Projection lives in `vtcode-memory`, never another history database. `git::capture_workspace_diff` keeps 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](../../../docs/development/execution-explanations.md).
+ - `core/agent/`: runtime, event recording, runner, progress, harness artifacts; `request_envelope` freezes each segment's prompt and ordered catalog. `result_reducers::project_model_tool_result` is 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/execution_attempts.rs` owns structured-request safety and retries behind the public facade; keep its policy snapshot, attempt metadata, and dispatch authority intact (see [pipeline](../../../docs/development/tool-execution-pipeline.md)); `registry/reentrancy.rs` owns task/thread recursion frames and drop cleanup; its facade retains admission order and error/history contracts (see [guide](../../../docs/development/tool-reentrancy.md)); `registry/execution_history.rs` owns shared records and its facade; private `execution_history/` modules own replay/invalidation, task telemetry, and loop detection; `registry/patch_recovery.rs` owns 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_schema` builds offline metadata registries without persistent workspace policy state; `output_spooler.rs` owns bounded model previews and spool-reference metadata; startup spool pruning is age-expiry only — the `max_files` count 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 while `registry/spool_processing.rs` owns pure spool decisions/preview caps; `tool_intent/activity.rs` owns inspection/verification classification plus truncation-only verifier elision for truthful exit statuses; `read_file/batch.rs` owns batch admission while `read_file.rs` owns single-file semantics; `exec_session.rs` owns the unified PTY/pipe registry, bounded background slots, and Ctrl+B promotion; exit-driven terminators (`terminate_*_for_exit_async`) close PTYs in `PtyCloseMode::Immediate` (group SIGKILL, no grace window) while in-session closes stay `Graceful`; `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`; `handlers/planning_workflow/artifacts.rs` preserves the artifact API over `artifacts/{sections,steps,tracker,validation,verification}.rs`; section/step parsing is shared by validation and tracker conversion, and repair feedback stays validator-owned; `handlers/task_tracker.rs` owns the renderer-independent compact task-tree formatter; workspace-aware path responses use `vtcode_commons::workspace_relative_display`. `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 (`prompts/system/` splits composition/constants/cache/tokens/types). `utils/ansi/` splits the renderer: `mod.rs` struct, `core.rs` construction/state, `compact.rs` command groups, `output.rs` plain lines, `markdown.rs` markdown, `sink.rs` inline sink, `links.rs` opener/links. `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
- - Public pipe runs default to null stdin; `stdin: true` opts into later input. Share `write_stdin` dispatch 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 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, while verified checkpoint metadata must reach both normal and quiet handoff writers. `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 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 intercepted `apply_patch` must validate the turn cwd and every target against the session workspace with symlink-aware containment before mutation.
+ - Public pipe runs default to null stdin; `stdin: true` opts into later input. Share `write_stdin` dispatch 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 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, while verified checkpoint metadata must reach both normal and quiet handoff writers. `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 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 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; 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 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; misconfiguration-first (`detect_misconfiguration` → fail-closed `GiveUp` with config guidance) runs before any backoff; budget decisions belong to `llm/usage_cost.rs` and `BudgetStatus::classify`, not call sites. Exec `wait`/`inspect` calls are turn-budget-exempt (`tool_intent::is_turn_budget_exempt_call`): the `SafetyGateway` counts 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 the `tool_calls` increment 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_notes` injects 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. `SnapshotTurnDiagnostics` and `turn.completed` share `MAX_IN_PROGRESS_EXEC_SESSIONS` (4) for ATIF correlation (schema 0.16.0). Background `exit 0` is `Stopped` with no auto-restore; spontaneous non-zero exit is `Error`, but user-requested managed/raw termination remains `Stopped`; 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 persists `BackgroundRecord` first 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 matching `Starting` record so a fast terminal event or replacement session cannot be resurrected; the idle loop owns bounded continuation; `VTCodeExecSession::status_label/command_label` own drawer/inspect/transcript display. 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. Native provider output is a canonical replay window: do not locally prune or inject envelopes into the returned history. `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; persisted editor/few-shot context (appended once, never rewritten at `messages[0]`) 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. `clear_old_tool_results` is its sibling for `tool_result_clearing` on requests that will not carry Anthropic native context edits — non-Anthropic providers, Anthropic routes without the capability, and the headless `AgentRunner`, which never attaches `context_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`/`.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, keep temporary-file `/edit` flows on the waiting API, and keep `editor_args_with_wait_policy` injecting 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.
+ - 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; learned approval family keys (`shell-pattern:...`) are opaque, so `derived_shell_approval_prefixes` must never tokenize them into bare-program prefixes (that would widen a scoped family approval across families); 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. 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_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. `ToolResponseBuilder::build_json` deduplicates at the single build point: `metadata.data` keys already surfaced as top-level `field()`s are dropped from the metadata copy, so pick one surface per key and never re-add the duplicate. Completed spools expose exact `spool_line_count` from 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-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; 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 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.
- 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.