Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions config.example.toml
Original file line number Diff line number Diff line change
Expand Up @@ -745,6 +745,10 @@ apply_patch = true
mcp = true
exec_policy = true
# vision_model = false # enable vision model for image_analyze tool
# verify_tool = false # disable the agent-callable `verify` self-critique tool
# (#4196). On by default; the agent decides when to spend
# the extra reasoning, so cost is only incurred on demand.
# Set false to remove it from the model's tool catalog.

# ─────────────────────────────────────────────────────────────────────────────────
# Vision Model Configuration (optional)
Expand Down
1 change: 1 addition & 0 deletions crates/tui/src/core/engine/tool_setup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ impl Engine {
};
options.speech_output_dir = self.config.speech_output_dir.clone();
options.goal_state = Some(self.config.goal_state.clone());
options.verify_tool_enabled = self.config.features.enabled(Feature::Verify);
options
}

Expand Down
8 changes: 8 additions & 0 deletions crates/tui/src/features.rs
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ pub enum Feature {
ExecPolicy,
/// Enable vision model for image analysis.
VisionModel,
/// Enable the agent-callable `verify` adversarial self-critique tool (#4196).
Verify,
}

impl fmt::Display for Stage {
Expand Down Expand Up @@ -255,6 +257,12 @@ pub const FEATURES: &[FeatureSpec] = &[
stage: Stage::Beta,
default_enabled: false,
},
FeatureSpec {
id: Feature::Verify,
key: "verify_tool",
stage: Stage::Stable,
default_enabled: true,
},
];

#[cfg(test)]
Expand Down
1 change: 1 addition & 0 deletions crates/tui/src/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ pub mod truncate;
pub mod user_input;
pub mod validate_data;
pub mod verifier;
pub mod verify;
pub mod web_run;
pub mod web_search;
pub mod workflow;
Expand Down
73 changes: 73 additions & 0 deletions crates/tui/src/tools/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -501,6 +501,9 @@ pub struct AgentToolSurfaceOptions {
pub vision_config: Option<crate::config::VisionModelConfig>,
pub speech_output_dir: Option<PathBuf>,
pub goal_state: Option<SharedGoalState>,
/// Register the agent-callable `verify` self-critique tool (#4196).
/// Gated by `Feature::Verify` (`[features] verify_tool`), default on.
pub verify_tool_enabled: bool,
}

impl AgentToolSurfaceOptions {
Expand All @@ -514,6 +517,7 @@ impl AgentToolSurfaceOptions {
vision_config: None,
speech_output_dir: None,
goal_state: None,
verify_tool_enabled: true,
}
}
}
Expand Down Expand Up @@ -867,6 +871,15 @@ impl ToolRegistryBuilder {
self.with_tool(Arc::new(ReviewTool::new(client, model)))
}

/// Include the agent-callable `verify` self-critique tool (#4196). The
/// critic runs at elevated reasoning (default `Max`) independent of the
/// session tier and is given no tools, so it cannot recurse into `verify`.
#[must_use]
pub fn with_verify_tool(self, client: Option<DeepSeekClient>, model: String) -> Self {
use super::verify::VerifyTool;
self.with_tool(Arc::new(VerifyTool::new(client, model)))
}

/// Include note tool.
#[must_use]
pub fn with_note_tool(self) -> Self {
Expand Down Expand Up @@ -1012,6 +1025,8 @@ impl ToolRegistryBuilder {
plan_state: super::plan::SharedPlanState,
) -> Self {
let speech_client = client.clone();
let verify_client = client.clone();
let verify_model = model.clone();
let mut builder = self
.with_agent_tools_policy(options.shell_policy)
.with_todo_tool(todo_list)
Expand All @@ -1022,6 +1037,9 @@ impl ToolRegistryBuilder {
.with_fim_tool(client, model)
.with_speech_tools(speech_client, options.speech_output_dir.clone());

if options.verify_tool_enabled {
builder = builder.with_verify_tool(verify_client, verify_model);
}
if let Some(goal_state) = options.goal_state {
builder = builder.with_goal_tools(goal_state);
}
Expand Down Expand Up @@ -1737,6 +1755,61 @@ mod tests {
assert!(registry.contains("finance"));
}

#[test]
fn with_verify_tool_registers_and_exposes_verify() {
let tmp = tempdir().expect("tempdir");
let ctx = ToolContext::new(tmp.path().to_path_buf());

let registry = ToolRegistryBuilder::new()
.with_verify_tool(None, "test-model".to_string())
.build(ctx);

assert!(
registry.contains("verify"),
"verify tool should be registered"
);
let api_names = registry
.to_api_tools()
.into_iter()
.map(|tool| tool.name)
.collect::<Vec<_>>();
assert!(
api_names.iter().any(|name| name == "verify"),
"verify tool should be model-visible"
);
}

#[test]
fn agent_runtime_surface_gates_verify_on_option() {
use super::AgentToolSurfaceOptions;
use crate::worker_profile::ShellPolicy;

let build_surface = |verify_enabled: bool| {
let tmp = tempdir().expect("tempdir");
let ctx = ToolContext::new(tmp.path().to_path_buf());
let mut options = AgentToolSurfaceOptions::new(ShellPolicy::Full);
options.verify_tool_enabled = verify_enabled;
ToolRegistryBuilder::new()
.with_agent_runtime_surface(
None,
"test-model".to_string(),
options,
crate::tools::todo::new_shared_todo_list(),
crate::tools::plan::new_shared_plan_state(),
)
.build(ctx)
};

assert!(
build_surface(true).contains("verify"),
"verify should register when enabled"
);
assert!(
!build_surface(false).contains("verify"),
"verify should be absent when the opt-out disables it"
);
}

#[test]
fn test_builder_with_agent_tools_includes_finance() {
let tmp = tempdir().expect("tempdir");
Expand Down
Loading
Loading