feat: implement Crosscheck Work experiment
Add the 'Crosscheck Work' experiment to the Agents settings. When enabled, a reviewer sub-agent is spawned after the main agent finishes a turn (with no pending tool calls). The reviewer critiques the output using a dedicated system prompt focused on correctness, simplicity, and code quality. If the reviewer does not respond with 'LGTM!', its feedback is injected as a synthetic user query back to the main agent, which must address it. This loop continues until the reviewer approves or max iterations is reached. Components: - Feature flag: CrosscheckWork (enabled in DOGFOOD_FLAGS) - Settings: agents.experiments.crosscheck_enabled, agents.experiments.crosscheck_model_id, agents.experiments.crosscheck_max_iterations - Settings UI: new 'Experiments' subpage under Agents - Crosscheck module: app/src/ai/crosscheck/ with prompt, reviewer model - Controller integration: hooks into AfterStreamFinished when no actions are queued, triggers reviewer, handles feedback injection - Provider support: OpenAI-compatible and Bedrock direct invocation - Safety: max iteration guard, error handling, reset on new user query
This commit is contained in:
@@ -92,13 +92,14 @@ use crate::settings::{
|
||||
AgentModeCodingPermissionsType, AgentModeCommandExecutionDenylist,
|
||||
AgentModeCommandExecutionPredicate, AgentModeQuerySuggestionsEnabled, BedrockAutoLogin,
|
||||
BedrockEnabled, CanUseWarpCreditsForFallback, CodeSettings, CodebaseContextEnabled,
|
||||
FileBasedMcpEnabled, GitOperationsAutogenEnabled, IncludeAgentCommandsInHistory, InputSettings,
|
||||
IntelligentAutosuggestionsEnabled, LongRunningCommandSubmissionMode, MemoryEnabled,
|
||||
NLDInTerminalEnabled, NaturalLanguageAutosuggestionsEnabled, OpenAIEnabled,
|
||||
OrchestrationMessageDisplayMode, PromptSubmissionMode, RuleSuggestionsEnabled,
|
||||
SharedBlockTitleGenerationEnabled, ShouldRenderCLIAgentToolbar,
|
||||
ShouldRenderUseAgentToolbarForUserCommands, ShowAgentTips, ShowConversationHistory,
|
||||
ShowHintText, ThinkingDisplayMode, VoiceInputEnabled, WarpDriveContextEnabled,
|
||||
CrosscheckEnabled, FileBasedMcpEnabled, GitOperationsAutogenEnabled,
|
||||
IncludeAgentCommandsInHistory, InputSettings, IntelligentAutosuggestionsEnabled,
|
||||
LongRunningCommandSubmissionMode, MemoryEnabled, NLDInTerminalEnabled,
|
||||
NaturalLanguageAutosuggestionsEnabled, OpenAIEnabled, OrchestrationMessageDisplayMode,
|
||||
PromptSubmissionMode, RuleSuggestionsEnabled, SharedBlockTitleGenerationEnabled,
|
||||
ShouldRenderCLIAgentToolbar, ShouldRenderUseAgentToolbarForUserCommands, ShowAgentTips,
|
||||
ShowConversationHistory, ShowHintText, ThinkingDisplayMode, VoiceInputEnabled,
|
||||
WarpDriveContextEnabled,
|
||||
};
|
||||
use crate::terminal::session_settings::{SessionSettings, SessionSettingsChangedEvent};
|
||||
use crate::terminal::CLIAgent;
|
||||
@@ -128,6 +129,8 @@ pub enum AISubpage {
|
||||
Bedrock,
|
||||
/// OpenAI-compatible (LiteLLM) provider configuration.
|
||||
OpenAI,
|
||||
/// Experimental features.
|
||||
Experiments,
|
||||
}
|
||||
|
||||
impl AISubpage {
|
||||
@@ -139,6 +142,7 @@ impl AISubpage {
|
||||
SettingsSection::ThirdPartyCLIAgents => Some(Self::ThirdPartyCLIAgents),
|
||||
SettingsSection::Bedrock => Some(Self::Bedrock),
|
||||
SettingsSection::OpenAI => Some(Self::OpenAI),
|
||||
SettingsSection::Experiments => Some(Self::Experiments),
|
||||
// AgentMCPServers renders the standalone MCPServers page, not an AI subpage.
|
||||
_ => None,
|
||||
}
|
||||
@@ -2866,6 +2870,9 @@ impl AISettingsPageView {
|
||||
let title: Option<&str> = None;
|
||||
return (PageType::new_uncategorized(widgets, title), None);
|
||||
}
|
||||
Some(AISubpage::Experiments) => {
|
||||
widgets.push(Box::new(ExperimentsWidget::default()));
|
||||
}
|
||||
}
|
||||
|
||||
// Subpage widgets render their own subheader-sized titles internally,
|
||||
@@ -3656,6 +3663,7 @@ pub enum AISettingsPageAction {
|
||||
agent: Option<CLIAgent>,
|
||||
},
|
||||
ToggleCloudAgentComputerUse,
|
||||
ToggleCrosscheckEnabled,
|
||||
}
|
||||
|
||||
impl From<&AISettingsPageAction> for LoginGatedFeature {
|
||||
@@ -4356,6 +4364,12 @@ impl TypedActionView for AISettingsPageView {
|
||||
});
|
||||
ctx.notify();
|
||||
}
|
||||
AISettingsPageAction::ToggleCrosscheckEnabled => {
|
||||
AISettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||
report_if_error!(settings.crosscheck_enabled.toggle_and_save_value(ctx));
|
||||
});
|
||||
ctx.notify();
|
||||
}
|
||||
AISettingsPageAction::ToggleBedrockEnabled => {
|
||||
AISettings::handle(ctx).update(ctx, |settings, ctx| {
|
||||
report_if_error!(settings.bedrock_enabled.toggle_and_save_value(ctx));
|
||||
@@ -9790,6 +9804,77 @@ impl SettingsWidget for CustomModelRoutersWidget {
|
||||
}
|
||||
}
|
||||
|
||||
// ── Experiments widget ──────────────────────────────────────────────────────
|
||||
|
||||
#[derive(Default)]
|
||||
struct ExperimentsWidget {
|
||||
crosscheck_toggle: SwitchStateHandle,
|
||||
}
|
||||
|
||||
impl SettingsWidget for ExperimentsWidget {
|
||||
type View = AISettingsPageView;
|
||||
|
||||
fn search_terms(&self) -> &str {
|
||||
"experiments crosscheck review reviewer sub-agent feedback loop lgtm"
|
||||
}
|
||||
|
||||
fn should_render(&self, _app: &AppContext) -> bool {
|
||||
FeatureFlag::CrosscheckWork.is_enabled()
|
||||
}
|
||||
|
||||
fn render(
|
||||
&self,
|
||||
view: &Self::View,
|
||||
appearance: &Appearance,
|
||||
app: &AppContext,
|
||||
) -> Box<dyn Element> {
|
||||
let ai_settings = AISettings::as_ref(app);
|
||||
let is_any_ai_enabled = ai_settings.is_any_ai_enabled(app);
|
||||
|
||||
let header = build_sub_header(
|
||||
appearance,
|
||||
"Experiments",
|
||||
Some(styles::header_font_color(is_any_ai_enabled, app)),
|
||||
)
|
||||
.with_margin_bottom(HEADER_PADDING)
|
||||
.finish();
|
||||
|
||||
let crosscheck_toggle = render_ai_setting_toggle::<CrosscheckEnabled>(
|
||||
"Crosscheck Work",
|
||||
AISettingsPageAction::ToggleCrosscheckEnabled,
|
||||
*ai_settings.crosscheck_enabled,
|
||||
is_any_ai_enabled,
|
||||
self.crosscheck_toggle.clone(),
|
||||
&view.local_only_icon_tooltip_states,
|
||||
app,
|
||||
);
|
||||
|
||||
let crosscheck_description = render_ai_setting_description(
|
||||
"When enabled, a reviewer agent critiques the main agent's output after each turn. \
|
||||
The feedback loop continues until the reviewer responds with \"LGTM!\" or the \
|
||||
maximum iteration count is reached.",
|
||||
is_any_ai_enabled,
|
||||
app,
|
||||
);
|
||||
|
||||
let model_description = render_ai_setting_description(
|
||||
"Configure the reviewer model and max iterations in settings.toml under \
|
||||
[agents.experiments].",
|
||||
is_any_ai_enabled,
|
||||
app,
|
||||
);
|
||||
|
||||
let column = Flex::column()
|
||||
.with_child(header)
|
||||
.with_child(crosscheck_toggle)
|
||||
.with_child(crosscheck_description)
|
||||
.with_child(model_description)
|
||||
.finish();
|
||||
|
||||
column
|
||||
}
|
||||
}
|
||||
|
||||
mod styles {
|
||||
use galaxy_core::ui::appearance::Appearance;
|
||||
use galaxy_core::ui::theme::Fill;
|
||||
|
||||
@@ -272,6 +272,7 @@ pub enum SettingsSection {
|
||||
ThirdPartyCLIAgents,
|
||||
Bedrock,
|
||||
OpenAI,
|
||||
Experiments,
|
||||
/// Internal backing-page identifier for CodeSettingsPageView. Multiple subpages
|
||||
/// (CodeIndexing, EditorAndCodeReview) share this single backing page,
|
||||
/// so this variant is needed as the key in `settings_pages`.
|
||||
@@ -306,6 +307,7 @@ impl Display for SettingsSection {
|
||||
SettingsSection::ThirdPartyCLIAgents => write!(f, "Third party CLI agents"),
|
||||
SettingsSection::Bedrock => write!(f, "AWS Bedrock"),
|
||||
SettingsSection::OpenAI => write!(f, "OpenAI / LiteLLM"),
|
||||
SettingsSection::Experiments => write!(f, "Experiments"),
|
||||
SettingsSection::Warpify => write!(f, "Wormhole"),
|
||||
SettingsSection::CodeIndexing => write!(f, "Indexing and projects"),
|
||||
SettingsSection::EditorAndCodeReview => write!(f, "Editor and Code Review"),
|
||||
@@ -363,6 +365,7 @@ impl SettingsSection {
|
||||
Self::ThirdPartyCLIAgents,
|
||||
Self::Bedrock,
|
||||
Self::OpenAI,
|
||||
Self::Experiments,
|
||||
]
|
||||
}
|
||||
|
||||
@@ -405,6 +408,7 @@ impl FromStr for SettingsSection {
|
||||
"CloudEnvironments" | "Environments" => Ok(Self::CloudEnvironments),
|
||||
"OzCloudAPIKeys" => Ok(Self::OzCloudAPIKeys),
|
||||
"SharedBlocks" | "Shared Blocks" => Ok(Self::SharedBlocks),
|
||||
"Experiments" => Ok(Self::Experiments),
|
||||
_ => Err(()),
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user