Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
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