vtcode-config 0.175.2

Config loader components shared across VT Code and downstream adopters
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
//! Built-in primary and subagent specifications.

use std::collections::BTreeMap;
use std::sync::LazyLock;

use vtcode_commons::reasoning::ReasoningEffortLevel;

use crate::constants::tools;
use crate::constants::ui;
use crate::core::permissions::{AgentPermissionsConfig, PermissionDefault};
use crate::core::tools::ToolPolicy;

use super::{AgentMode, SubagentSource, SubagentSpec};

const BUILTIN_DEFAULT_AGENT: &str = r#"You are the default VT Code execution subagent.

Work directly, keep context isolated from the parent session, and return concise summaries.
Match the repository's local patterns, verify changes, and avoid unrelated edits.
If a file is referenced, read it before answering; base claims about code on what you have read.
Only make changes that are directly requested or clearly necessary. Keep solutions simple and focused.
Do not add features, refactor code, or make improvements beyond what was asked.
Verify your work by running the smallest relevant check before reporting completion."#;

const BUILTIN_EXPLORER_AGENT: &str = r#"You are a fast read-only exploration subagent.

Search the codebase, inspect relevant files, and return concise findings with file references.
Do not modify files or take mutating actions.
Read files before making claims about their contents.
Use structural search and grep over shell exploration when possible.
Return findings with file paths and line numbers for easy navigation."#;

const BUILTIN_WORKER_AGENT: &str = r#"You are a write-capable worker subagent.

Handle bounded implementation work, verify results, and return a concise outcome summary with
any important risks or follow-up items.
Read files before editing them or describing what they contain.
Only make changes that are directly requested. Keep solutions simple and focused.
Do not add features, refactor surrounding code, or make improvements beyond the scope.
Verify your changes by running relevant tests or checks before reporting completion.
If calls repeat without progress, re-plan instead of retrying identically."#;

const BUILTIN_BUILD_PRIMARY_AGENT: &str = r#"You are the build agent.

Understand the user's request, then use available tools to inspect context and make changes.
Read files before editing. Search before guessing at code structure.
Edit, write, and run commands directly when the request is clear.
Keep changes focused on what was asked. Do not add unrelated features or refactors.
When planning is needed, state the plan briefly before implementation. When the user only wants
discussion or review, do not edit files.
Report changed files, validation, and remaining risks clearly."#;

const BUILTIN_AUTO_PRIMARY_AGENT: &str = r#"You are the auto agent.

Work autonomously within the active permission policy, taking direct action when the request is clear.
Inspect the relevant repository context before editing, keep changes focused, and verify with the
narrowest useful checks before reporting completion.
Pause for user input when the scope is ambiguous, risky, or outside the requested work."#;

const BUILTIN_COORDINATOR_PRIMARY_AGENT: &str = r#"You are the coordinator agent.

Define explicit matrix tasks, dependencies, resource capacities, timeouts, and verification commands.
Delegate all shell execution, file changes, and verification to scheduler-owned matrix workers.
Use matrix create to persist a specification, then start to freeze it and dispatch work.
Delegate discovery to a read-only explorer before making repository claims, while the matrix is idle.
During active execution, use matrix status and control actions; do not spawn independent workers.
Resolve failed checks, permission denials, exhausted budgets, and unsafe retries with the user.
Worker summaries are not verification evidence. Report success only after matrix final verification.
Pause stops dispatch; cancel is terminal. Preserve sandbox, approvals, and existing budgets."#;

const DISCUSSION_FIRST_GUIDANCE: &str = r#"Be discussion-first. Clarify scope, constraints, contradictions, and options before implementation.
Resolve ordinary ambiguity from repository evidence when possible; ask the user directly only when material ambiguity is critical.
Stop researching when existing evidence supports a decision."#;

const BUILTIN_PLAN_AGENT_ROLE: &str = r#"You are a read-only planning agent.

Use repository-grounded, read-only discovery to gather the minimum context needed to support a plan or design decision.
Return findings, risks, and constraints clearly, with specific code references and file paths.
Read relevant files before making claims about the codebase.
Use structural search to find patterns across the repository.
When ready, emit exactly one final <proposed_plan> block for review.
Never write the plan file with shell or file-editing tools; the runtime persists the plan and tracker artifacts.
When the user asks for implementation, present the plan and wait for explicit user approval before implementation instead of suggesting an immediate edit."#;

const BUILTIN_DUCK_PRIMARY_AGENT_ROLE: &str = r#"You are the duck agent.

Do not edit files; you are for rubber-ducking only.
If the user asks for edits, suggest pressing Tab to switch to the Build agent for implementation."#;
/// Cached built-in subagent specifications.
///
/// Built once on first access and reused for every subsequent discovery call.
/// The constructors below (`builtin_primary_build_agent`, etc.) each allocate
/// a `SubagentSpec` with a `BTreeMap` of policy overrides and a cloned prompt
/// string; reconstructing all of them on every `discover_subagents` call is the
/// expensive part, so the result is memoized here. Callers still receive an
/// owned `Vec` (cloned from this cache) so they can mutate freely without
/// touching the shared canonical specs.
static BUILTIN_SUBAGENTS: LazyLock<Vec<SubagentSpec>> = LazyLock::new(builtin_subagents_inner);

pub fn builtin_subagents() -> Vec<SubagentSpec> {
    BUILTIN_SUBAGENTS.clone()
}

fn builtin_subagents_inner() -> Vec<SubagentSpec> {
    vec![
        builtin_primary_build_agent(),
        builtin_primary_auto_agent(),
        builtin_primary_coordinator_agent(),
        builtin_primary_duck_agent(),
        builtin_plan_agent(),
        SubagentSpec {
            name: "default".to_string(),
            description: "Default inheriting subagent for general delegated work.".to_string(),
            prompt: BUILTIN_DEFAULT_AGENT.to_string(),
            tools: None,
            disallowed_tools: Vec::new(),
            model: Some("inherit".to_string()),
            color: Some("blue".to_string()),
            reasoning_effort: None,
            permissions: mutating_agent_permissions(),
            skills: Vec::new(),
            mcp_servers: Vec::new(),
            hooks: None,
            background: false,
            mode: AgentMode::Subagent,
            max_turns: None,
            nickname_candidates: vec!["default".to_string()],
            initial_prompt: None,
            memory: None,
            isolation: None,
            aliases: Vec::new(),
            source: SubagentSource::Builtin,
            file_path: None,
            warnings: Vec::new(),
            tool_policy_overrides: BTreeMap::new(),
        },
        SubagentSpec {
            name: "explorer".to_string(),
            description: "Read-only exploration specialist. Use proactively for code search, file discovery, and repository understanding.".to_string(),
            prompt: BUILTIN_EXPLORER_AGENT.to_string(),
            tools: Some(builtin_readonly_tool_ids()),
            disallowed_tools: builtin_readonly_disallowed_tool_ids(),
            model: Some("small".to_string()),
            color: Some("cyan".to_string()),
            reasoning_effort: Some(ReasoningEffortLevel::Low),
            permissions: readonly_agent_permissions(),
            skills: Vec::new(),
            mcp_servers: Vec::new(),
            hooks: None,
            background: false,
            mode: AgentMode::Subagent,
            max_turns: None,
            nickname_candidates: vec!["explore".to_string(), "search".to_string()],
            initial_prompt: None,
            memory: None,
            isolation: None,
            aliases: vec!["explore".to_string()],
            source: SubagentSource::Builtin,
            file_path: None,
            warnings: Vec::new(),
            tool_policy_overrides: BTreeMap::new(),
        },
        SubagentSpec {
            name: "worker".to_string(),
            description: "Write-capable execution subagent for bounded implementation or multi-step action.".to_string(),
            prompt: BUILTIN_WORKER_AGENT.to_string(),
            tools: None,
            disallowed_tools: Vec::new(),
            model: Some("inherit".to_string()),
            color: Some("magenta".to_string()),
            reasoning_effort: None,
            permissions: mutating_agent_permissions(),
            skills: Vec::new(),
            mcp_servers: Vec::new(),
            hooks: None,
            background: false,
            mode: AgentMode::Subagent,
            max_turns: None,
            nickname_candidates: vec!["general".to_string(), "worker".to_string()],
            initial_prompt: None,
            memory: None,
            isolation: None,
            aliases: vec!["general".to_string(), "general-purpose".to_string()],
            source: SubagentSource::Builtin,
            file_path: None,
            warnings: Vec::new(),
            tool_policy_overrides: BTreeMap::new(),
        },
    ]
}

pub fn builtin_primary_build_agent() -> SubagentSpec {
    SubagentSpec {
        name: "build".to_string(),
        description: "Built-in implementation agent for the main session.".to_string(),
        prompt: BUILTIN_BUILD_PRIMARY_AGENT.to_string(),
        tools: None,
        disallowed_tools: Vec::new(),
        model: Some("inherit".to_string()),
        color: Some(ui::AGENT_COLOR_BUILD.to_string()),
        reasoning_effort: None,
        permissions: mutating_agent_permissions(),
        skills: Vec::new(),
        mcp_servers: Vec::new(),
        hooks: None,
        background: false,
        mode: AgentMode::Primary,
        max_turns: None,
        nickname_candidates: vec!["build".to_string(), "builder".to_string()],
        initial_prompt: None,
        memory: None,
        isolation: None,
        aliases: vec!["builder".to_string()],
        source: SubagentSource::Builtin,
        file_path: None,
        warnings: Vec::new(),
        tool_policy_overrides: {
            let mut m = BTreeMap::new();
            m.insert("exec_command".to_string(), ToolPolicy::Allow);
            m
        },
    }
}

pub fn builtin_primary_auto_agent() -> SubagentSpec {
    SubagentSpec {
        name: "auto".to_string(),
        description: "Built-in autonomous implementation agent for the main session.".to_string(),
        prompt: BUILTIN_AUTO_PRIMARY_AGENT.to_string(),
        tools: None,
        disallowed_tools: Vec::new(),
        model: Some("inherit".to_string()),
        color: Some(ui::AGENT_COLOR_AUTO.to_string()),
        reasoning_effort: None,
        permissions: auto_agent_permissions(),
        skills: Vec::new(),
        mcp_servers: Vec::new(),
        hooks: None,
        background: false,
        mode: AgentMode::Primary,
        max_turns: None,
        nickname_candidates: vec!["auto".to_string()],
        initial_prompt: None,
        memory: None,
        isolation: None,
        aliases: vec!["autonomous".to_string()],
        source: SubagentSource::Builtin,
        file_path: None,
        warnings: Vec::new(),
        tool_policy_overrides: {
            let mut m = BTreeMap::new();
            m.insert("exec_command".to_string(), ToolPolicy::Allow);
            m
        },
    }
}

/// Opt-in orchestration role; worker permissions remain inherited from the session.
pub fn builtin_primary_coordinator_agent() -> SubagentSpec {
    let mut spec = builtin_primary_build_agent();
    spec.name = "coordinator".to_string();
    spec.description = "Deterministic local matrix orchestration for the main session.".to_string();
    spec.prompt = BUILTIN_COORDINATOR_PRIMARY_AGENT.to_string();
    spec.tools = Some(
        [
            "matrix",
            tools::REQUEST_USER_INPUT,
            "agent",
            tools::RECORD_DECISION,
            tools::TASK_TRACKER,
        ]
        .into_iter()
        .map(str::to_string)
        .collect(),
    );
    spec.nickname_candidates = vec!["coordinator".to_string()];
    spec.aliases.clear();
    spec.tool_policy_overrides.clear();
    spec
}

pub fn builtin_plan_agent() -> SubagentSpec {
    SubagentSpec {
        name: "plan".to_string(),
        description: "Built-in read-only planning agent definition.".to_string(),
        prompt: format!("{DISCUSSION_FIRST_GUIDANCE}\n\n{BUILTIN_PLAN_AGENT_ROLE}"),
        tools: Some(builtin_primary_readonly_tool_ids()),
        disallowed_tools: builtin_readonly_disallowed_tool_ids(),
        model: Some("inherit".to_string()),
        color: Some(ui::AGENT_COLOR_PLAN.to_string()),
        reasoning_effort: None,
        permissions: plan_agent_permissions(),
        skills: Vec::new(),
        mcp_servers: Vec::new(),
        hooks: None,
        background: false,
        mode: AgentMode::Primary,
        max_turns: None,
        nickname_candidates: vec!["plan".to_string(), "planner".to_string()],
        initial_prompt: None,
        memory: None,
        isolation: None,
        aliases: vec!["planner".to_string()],
        source: SubagentSource::Builtin,
        file_path: None,
        warnings: Vec::new(),
        tool_policy_overrides: BTreeMap::new(),
    }
}

pub fn builtin_primary_duck_agent() -> SubagentSpec {
    SubagentSpec {
        name: "duck".to_string(),
        description: "Built-in discussion-first agent for the main session.".to_string(),
        prompt: format!("{DISCUSSION_FIRST_GUIDANCE}\n\n{BUILTIN_DUCK_PRIMARY_AGENT_ROLE}"),
        tools: Some(builtin_primary_readonly_tool_ids()),
        disallowed_tools: builtin_readonly_disallowed_tool_ids(),
        model: Some("inherit".to_string()),
        color: Some(ui::AGENT_COLOR_DUCK.to_string()),
        reasoning_effort: None,
        permissions: readonly_interview_agent_permissions(),
        skills: Vec::new(),
        mcp_servers: Vec::new(),
        hooks: None,
        background: false,
        mode: AgentMode::Primary,
        max_turns: None,
        nickname_candidates: vec!["duck".to_string()],
        initial_prompt: None,
        memory: None,
        isolation: None,
        aliases: Vec::new(),
        source: SubagentSource::Builtin,
        file_path: None,
        warnings: Vec::new(),
        tool_policy_overrides: BTreeMap::new(),
    }
}

fn builtin_readonly_tool_ids() -> Vec<String> {
    // The direct read tools must be listed explicitly: read-only agents deny
    // `bash`, so the wire shaper hides `exec_command` for them — without
    // grep_file/read_file/list_files their effective catalog collapses to
    // bare `code_search` (the turn_912/913 planning failure shape). The
    // planning profile and Interactive surface intersect this list further
    // (e.g. read_file/list_files stay hidden on Interactive).
    vec![
        tools::CODE_SEARCH.to_string(),
        tools::EXEC_COMMAND.to_string(),
        tools::GREP_FILE.to_string(),
        tools::READ_FILE.to_string(),
        tools::LIST_FILES.to_string(),
    ]
}

/// Readonly tools for primary agents that interact with the user directly.
/// Extends the base readonly set with `request_user_input` so plan/duck can
/// ask clarifying questions.
fn builtin_primary_readonly_tool_ids() -> Vec<String> {
    let mut ids = builtin_readonly_tool_ids();
    ids.push(tools::REQUEST_USER_INPUT.to_string());
    ids.push(tools::RECORD_DECISION.to_string());
    ids
}

fn builtin_readonly_disallowed_tool_ids() -> Vec<String> {
    Vec::new()
}

pub(super) fn mutating_agent_permissions() -> AgentPermissionsConfig {
    AgentPermissionsConfig::new(PermissionDefault::Ask)
}

fn auto_agent_permissions() -> AgentPermissionsConfig {
    AgentPermissionsConfig::new(PermissionDefault::Auto)
}

pub(super) fn readonly_agent_permissions() -> AgentPermissionsConfig {
    let mut permissions = AgentPermissionsConfig::new(PermissionDefault::Deny);
    permissions.allow = vec!["read".to_string()];
    permissions
}

/// Read-only agents that talk to the user (duck, plan) also list
/// `request_user_input` and `record_decision` in their tool set. Allow both
/// explicitly: the default-deny fallback otherwise rejects their semantic
/// kind "other" and hides them from the wire catalog.
pub(super) fn readonly_interview_agent_permissions() -> AgentPermissionsConfig {
    let mut permissions = readonly_agent_permissions();
    permissions.allow.push("request_user_input".to_string());
    permissions.allow.push(tools::RECORD_DECISION.to_string());
    permissions
}

/// Plan-agent permissions extend the read-only interview set with `bash` so
/// `exec_command` stays on the wire and read-only shell inspection (`rg`,
/// `cat`, `wc`, ...) works while planning. This stays read-only in practice:
/// selecting the plan agent always activates the planning workflow, whose
/// dispatch gate (`ToolRegistry::is_planning_active_allowed` +
/// `assess_plan_mode_read_only_bash_command`) hard-blocks every mutating
/// command before execution — the bash permission only admits the tool, the
/// planning gate keeps it read-only. Explorer/duck stay bash-denied via
/// [`readonly_agent_permissions`]/[`readonly_interview_agent_permissions`].
fn plan_agent_permissions() -> AgentPermissionsConfig {
    let mut permissions = readonly_interview_agent_permissions();
    permissions.allow.push("bash".to_string());
    permissions
}