Synced from monorepo

Synced from monorepo

Changes:
- Workspace server: report `/ready` as failed with dwell on hub connect failure
- Refresh OIDC token for the Grok agent in the shell
- ACP terminal output recorder
- Cross-platform provider auth commands in the shell
- Default `/resume` to Grok sessions with a hint for hidden external sessions
- Resume sessions by title with `--resume`
- Limit app-builder archive size
- Data-driven tag labels for slash commands
- Doctor fixes for tmux
- Custom provider gateways and subprocess environment policy in the shell
- `/tutorial` — opt-in onboarding tour of Grok Build
- Soft and required CLI version checks in the shell
- Privacy banner env overrides survive live settings updates
- Add remote flag to override the image-edit model
- Return profile fields from auth info even when the access token is expired
- Add edit control on queued prompt rows
- Keep fail-closed policy when clearing orphans with no team
- Setting to disable the Ctrl+Space/F8 voice shortcut
- Pass `--raw` to pw-record so Linux dictation works on older PipeWire
- Validate git URLs when adding marketplace entries
- Stop shipping stale tool-doc parameter and tool names
- Re-point dashboard attach after `/fork` only when the parent was attached
- Surface Grok Computer media-generation results as file-path chunks
- Clear web background-task tray on kill and keep the task description
- Show privacy upsell banner in agent view until acted on
- Add tools-server client callback surface
- Protect persistent global hook sources

Source-Revision: 95d84f443eddcbed6cbfd6eed22e2eafe6b3939d
This commit is contained in:
grokkybara[bot] 2026-07-23 17:12:33 +00:00
commit 69f0ba880a
286 changed files with 22939 additions and 9624 deletions

View file

@ -3,22 +3,25 @@ license = "Apache-2.0"
name = "xai-grok-subagent-resolution"
version = "0.1.0"
edition.workspace = true
description = "Subagent configuration resolution: merges persona, role, and spawn-time overrides into a resolved spec"
description = "Shared subagent definition, runtime, prompt, and resume resolution"
[dependencies]
chrono = { workspace = true }
serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true }
thiserror = { workspace = true }
tracing = { workspace = true }
xai-grok-agent = { path = "../xai-grok-agent" }
xai-grok-sampling-types = { path = "../xai-grok-sampling-types" }
xai-grok-tools = { path = "../xai-grok-tools" }
xai-tool-types.workspace = true
# TODO(phase2): add these when resolve_subagent_spec() composition is implemented:
# xai-grok-agent = { path = "../xai-grok-agent" } # AgentDefinition lookup
# xai-fast-worktree = { path = "../xai-fast-worktree" } # worktree creation
[features]
default = []
[dev-dependencies]
tempfile = { workspace = true }
tokio = { workspace = true, features = ["macros", "rt"] }
toml = { workspace = true }
[lints]

View file

@ -0,0 +1,408 @@
//! Production subagent definition discovery and tool-policy resolution.
use crate::config::{SubagentPersona, SubagentRole};
use crate::types::{EffectiveRuntimeConfig, ResolutionError};
use std::collections::HashMap;
use std::path::Path;
use xai_grok_agent::config::{AgentDefinition, IsolationMode};
use xai_grok_agent::plugins::PluginRegistry;
use xai_grok_agent::prompt::context::{PromptAudience, PromptContext};
use xai_grok_tools::implementations::grok_build::task::types::{
SubagentCapabilityModeExt, SubagentRuntimeOverrides, prune_orphaned_background_task_tools,
};
use xai_grok_tools::registry::types::ToolConfig;
use xai_grok_tools::types::compat::CompatConfig;
use xai_grok_tools::types::template_renderer::TemplateRenderer;
use xai_grok_tools::types::tool::ToolKind;
use xai_tool_types::{SubagentCapabilityMode, SubagentIsolationMode};
/// Inputs that affect definition discovery and spawn permission.
pub struct DefinitionResolutionContext<'a> {
pub cwd: &'a Path,
pub plugins: Option<&'a PluginRegistry>,
pub cli_agents: &'a [AgentDefinition],
pub toggles: &'a HashMap<String, bool>,
pub allowed_types: Option<&'a [String]>,
}
/// Inputs for validating a type when only session CLI names are available.
pub struct DefinitionValidationContext<'a> {
pub cwd: &'a Path,
pub plugins: Option<&'a PluginRegistry>,
pub cli_agent_names: &'a [String],
pub toggles: &'a HashMap<String, bool>,
pub allowed_types: Option<&'a [String]>,
}
/// Parent/runtime inputs that choose the production child harness flavor.
pub struct HarnessToolsetContext<'a> {
pub harness_override: Option<&'a str>,
pub parent_agent_name: Option<&'a str>,
pub parent_model_agent_type: Option<&'a str>,
pub file_tool_overrides: Option<&'a [ToolConfig]>,
}
/// `false` twin: the alternate flavors re-select toolset presets and
/// templates, so none is representable when the optional harness is compiled
/// out. Keeps ungated call sites compiling.
pub fn subagent_harness_flavor_is_representable(_agent_type: &str) -> bool {
false
}
/// Apply the production parent/harness-dependent child toolset selection.
pub fn apply_harness_toolset(
#[allow(unused_variables)] subagent_type: &str,
context: &HarnessToolsetContext<'_>,
definition: &mut AgentDefinition,
) {
let flavor_agent = context.harness_override.or_else(|| {
context
.parent_agent_name
.filter(|name| subagent_harness_flavor_is_representable(name))
.or(context.parent_model_agent_type)
});
if flavor_agent.is_some_and(subagent_harness_flavor_is_representable) {
} else if let Some(file_tools) = context.file_tool_overrides {
definition.override_file_tools(file_tools.to_vec());
}
}
/// Discover the same project/builtin/user/plugin definition used by production,
/// with session CLI definitions as the final fallback.
pub fn discover_agent_definition(
subagent_type: &str,
context: &DefinitionResolutionContext<'_>,
) -> Option<AgentDefinition> {
xai_grok_agent::discovery::by_name_in_cwd_with_plugins(
subagent_type,
context.cwd,
context.plugins,
)
.or_else(|| {
context
.cli_agents
.iter()
.find(|definition| definition.name == subagent_type)
.cloned()
})
}
/// Sorted model-facing names available under the current discovery context.
pub fn available_agent_names(context: &DefinitionResolutionContext<'_>) -> Vec<String> {
let mut available: Vec<String> = xai_grok_agent::discovery::all_subagents_with_plugins(
context.cwd,
context.toggles,
context.plugins,
)
.into_iter()
.map(|entry| entry.name)
.collect();
for definition in context.cli_agents {
if context
.toggles
.get(&definition.name)
.copied()
.unwrap_or(true)
&& !available.contains(&definition.name)
{
available.push(definition.name.clone());
}
}
available.sort();
available
}
/// Apply the production toggle and parent allow-list gates.
pub fn gate_agent_definition(
subagent_type: &str,
context: &DefinitionResolutionContext<'_>,
) -> Result<(), ResolutionError> {
if !context.toggles.get(subagent_type).copied().unwrap_or(true) {
return Err(ResolutionError::Disabled {
subagent_type: subagent_type.to_string(),
});
}
if let Some(allowed) = context.allowed_types
&& !allowed
.iter()
.any(|candidate| candidate.eq_ignore_ascii_case(subagent_type))
{
return Err(ResolutionError::NotAllowed {
subagent_type: subagent_type.to_string(),
allowed: allowed.to_vec(),
});
}
Ok(())
}
/// Validate discovery, toggle, and allow-list gates without cloning definitions.
pub fn validate_agent_name(
subagent_type: &str,
context: &DefinitionValidationContext<'_>,
) -> Result<(), ResolutionError> {
let resolves = context
.cli_agent_names
.iter()
.any(|name| name == subagent_type)
|| xai_grok_agent::discovery::by_name_in_cwd_with_plugins(
subagent_type,
context.cwd,
context.plugins,
)
.is_some();
if !resolves {
let mut available: Vec<String> = xai_grok_agent::discovery::all_subagents_with_plugins(
context.cwd,
context.toggles,
context.plugins,
)
.into_iter()
.map(|entry| entry.name)
.collect();
for name in context.cli_agent_names {
if context.toggles.get(name).copied().unwrap_or(true) && !available.contains(name) {
available.push(name.clone());
}
}
available.sort();
return Err(ResolutionError::Unknown {
subagent_type: subagent_type.to_owned(),
available,
});
}
let gate_context = DefinitionResolutionContext {
cwd: context.cwd,
plugins: context.plugins,
cli_agents: &[],
toggles: context.toggles,
allowed_types: context.allowed_types,
};
gate_agent_definition(subagent_type, &gate_context)
}
/// Discover and gate one production agent definition.
pub fn resolve_agent_definition(
subagent_type: &str,
context: &DefinitionResolutionContext<'_>,
) -> Result<AgentDefinition, ResolutionError> {
let definition = discover_agent_definition(subagent_type, context).ok_or_else(|| {
ResolutionError::Unknown {
subagent_type: subagent_type.to_string(),
available: available_agent_names(context),
}
})?;
gate_agent_definition(subagent_type, context)?;
Ok(definition)
}
/// Resolve the role selected by production: type-specific first, then persona.
pub fn select_role<'a>(
subagent_type: &str,
overrides: &SubagentRuntimeOverrides,
roles: &'a HashMap<String, SubagentRole>,
) -> (Option<&'a SubagentRole>, Option<String>) {
if let Some(role) = roles.get(subagent_type) {
return (Some(role), Some(subagent_type.to_string()));
}
let Some(persona) = overrides.persona.as_deref() else {
return (None, None);
};
match roles.get(persona) {
Some(role) => (Some(role), Some(persona.to_string())),
None => (None, None),
}
}
/// Fill runtime values whose defaults live on the resolved agent definition.
pub fn apply_definition_runtime_defaults(
runtime: &mut EffectiveRuntimeConfig,
definition: &AgentDefinition,
) {
if runtime.capability_mode.is_none() {
runtime.capability_mode = definition.capability_mode;
}
if runtime.reasoning_effort.is_none() {
runtime.reasoning_effort = definition
.effort
.map(|effort| <&str>::from(effort).to_string());
}
if runtime.isolation == SubagentIsolationMode::None
&& definition.isolation == Some(IsolationMode::Worktree)
{
runtime.isolation = SubagentIsolationMode::Worktree;
}
}
/// Apply capability filtering and recursion depth to the exact production
/// definition toolset.
pub fn apply_child_tool_policy(
definition: &mut AgentDefinition,
capability_mode: Option<SubagentCapabilityMode>,
allow_nested_subagents: bool,
) {
if let Some(mode) = capability_mode {
mode.filter_tool_config(&mut definition.tool_config);
}
if !allow_nested_subagents {
definition
.tool_config
.tools
.retain(|tool| tool.kind != Some(ToolKind::Task));
prune_orphaned_background_task_tools(&mut definition.tool_config);
}
}
/// Resolve runtime overrides and definition defaults in the production order.
pub fn resolve_runtime_config(
subagent_type: &str,
overrides: &SubagentRuntimeOverrides,
roles: &HashMap<String, SubagentRole>,
personas: &HashMap<String, SubagentPersona>,
cwd: Option<&Path>,
definition: &AgentDefinition,
) -> EffectiveRuntimeConfig {
let (role, role_name) = select_role(subagent_type, overrides, roles);
let mut runtime = crate::resolve_effective_overrides(overrides, role, personas, cwd, role_name);
apply_definition_runtime_defaults(&mut runtime, definition);
runtime
}
/// Render the same full subagent base template + definition body used by the
/// production `AgentBuilder`, for runtimes that expose only finalized tool
/// names rather than a complete `ToolBridge`.
pub fn render_subagent_system_prompt(
definition: &AgentDefinition,
runtime: &EffectiveRuntimeConfig,
renderer: &TemplateRenderer,
working_directory: &Path,
) -> Option<String> {
let context = PromptContext {
prompt_mode: definition.prompt_mode.clone(),
audience: PromptAudience::Subagent,
prompt_body: definition.prompt_body.clone(),
system_prompt: definition.system_prompt.clone(),
role_instructions: runtime.role_prompt.clone(),
persona_instructions: runtime.persona_instructions.clone(),
os_name: Some(format!(
"{} {}",
std::env::consts::OS,
std::env::consts::ARCH
)),
shell_path: std::env::var("SHELL").ok(),
working_directory: Some(working_directory.to_string_lossy().into_owned()),
current_date: Some(chrono::Local::now().format("%Y-%m-%d").to_string()),
is_non_interactive: true,
..Default::default()
};
context.render_with_renderer(renderer)
}
/// Render project instructions as the child's prepended user message.
pub async fn render_subagent_initial_user_message(
definition: &AgentDefinition,
working_directory: &Path,
compat: CompatConfig,
) -> Option<String> {
if !definition.agents_md {
return None;
}
let agents_md_files = xai_grok_agent::prompt::agents_md::read_agents_config_with_paths(
&working_directory.to_string_lossy(),
compat,
)
.await;
PromptContext {
audience: PromptAudience::Subagent,
system_prompt: definition.system_prompt.clone(),
agents_md_files,
..Default::default()
}
.agents_md_user_reminder()
}
#[cfg(test)]
mod tests {
use super::*;
fn context<'a>(
cwd: &'a Path,
toggles: &'a HashMap<String, bool>,
) -> DefinitionResolutionContext<'a> {
DefinitionResolutionContext {
cwd,
plugins: None,
cli_agents: &[],
toggles,
allowed_types: None,
}
}
#[test]
fn builtin_explore_uses_production_read_only_toolset() {
let cwd = tempfile::tempdir().unwrap();
let toggles = HashMap::new();
let mut definition =
resolve_agent_definition("explore", &context(cwd.path(), &toggles)).unwrap();
apply_child_tool_policy(&mut definition, None, false);
let kinds: Vec<Option<ToolKind>> = definition
.tool_config
.tools
.iter()
.map(|tool| tool.kind)
.collect();
assert!(kinds.contains(&Some(ToolKind::Read)));
assert!(kinds.contains(&Some(ToolKind::Search)));
assert!(!kinds.contains(&Some(ToolKind::Execute)));
assert!(!kinds.contains(&Some(ToolKind::Task)));
}
#[test]
fn gates_disabled_and_not_allowed_definitions() {
let cwd = tempfile::tempdir().unwrap();
let toggles = HashMap::from([("explore".to_string(), false)]);
let disabled = context(cwd.path(), &toggles);
assert!(matches!(
resolve_agent_definition("explore", &disabled),
Err(ResolutionError::Disabled { .. })
));
let allowed = ["plan".to_string()];
let toggles = HashMap::new();
let restricted = DefinitionResolutionContext {
allowed_types: Some(&allowed),
..context(cwd.path(), &toggles)
};
assert!(matches!(
resolve_agent_definition("explore", &restricted),
Err(ResolutionError::NotAllowed { .. })
));
}
#[test]
fn definition_defaults_fill_runtime_without_overwriting_explicit_values() {
let cwd = tempfile::tempdir().unwrap();
let toggles = HashMap::new();
let mut definition =
resolve_agent_definition("explore", &context(cwd.path(), &toggles)).unwrap();
definition.isolation = Some(IsolationMode::Worktree);
let mut runtime = EffectiveRuntimeConfig::default();
apply_definition_runtime_defaults(&mut runtime, &definition);
assert_eq!(runtime.isolation, SubagentIsolationMode::Worktree);
}
#[test]
fn full_prompt_uses_production_subagent_template_and_body() {
let cwd = tempfile::tempdir().unwrap();
let toggles = HashMap::new();
let definition =
resolve_agent_definition("explore", &context(cwd.path(), &toggles)).unwrap();
let renderer = TemplateRenderer::new(
HashMap::from([
(ToolKind::Read, "read_x".to_string()),
(ToolKind::List, "list_x".to_string()),
(ToolKind::Search, "search_x".to_string()),
]),
HashMap::new(),
);
let prompt = render_subagent_system_prompt(
&definition,
&EffectiveRuntimeConfig::default(),
&renderer,
cwd.path(),
)
.unwrap();
assert!(prompt.contains("<project_instructions_spec>"));
assert!(prompt.contains("read-only codebase exploration agent"));
assert!(prompt.contains(&format!("Workspace Path: {}", cwd.path().display())));
assert!(!prompt.contains("${{"));
}
#[tokio::test]
async fn initial_user_message_contains_project_instructions() {
let cwd = tempfile::tempdir().unwrap();
std::fs::write(cwd.path().join("AGENTS.md"), "Use the project contract.").unwrap();
let toggles = HashMap::new();
let definition =
resolve_agent_definition("explore", &context(cwd.path(), &toggles)).unwrap();
let message =
render_subagent_initial_user_message(&definition, cwd.path(), CompatConfig::default())
.await
.unwrap();
assert!(message.contains("Use the project contract."));
}
}

View file

@ -14,24 +14,27 @@
//! Designed to be consumed by local hosts (e.g. `xai-grok-shell`) and any
//! future remote spawn path that only needs pure resolution logic.
//!
//! ## Planned composition API
//!
//! Future work may add a higher-level composition helper once shell call sites
//! are refactored onto this crate:
//!
//! - `resolve_subagent_spec()` composition function
//! - `SubagentSpec`, `ResolveSubagentRequest`, `ResolutionContext` boundary types
//! - Optional deps for `AgentDefinition` lookup and worktree creation
//! - Model override resolution chain (global > per-type > role > parent)
//! - Capability mode filtering (delegates to `SubagentCapabilityMode::filter_tool_config()`)
//! Definition discovery, gating, prompt context, runtime defaults, and
//! capability/depth tool policy are shared here. Model catalog selection and
//! workspace materialization remain host adapters.
pub mod config;
pub mod context;
pub mod definition;
pub mod overrides;
pub mod resume;
pub mod types;
pub use config::{PersonaIOField, SubagentPersona, SubagentRole};
pub use definition::{
DefinitionResolutionContext, DefinitionValidationContext, HarnessToolsetContext,
apply_child_tool_policy, apply_definition_runtime_defaults, apply_harness_toolset,
available_agent_names, discover_agent_definition, gate_agent_definition,
render_subagent_initial_user_message, render_subagent_system_prompt, resolve_agent_definition,
resolve_runtime_config, select_role, subagent_harness_flavor_is_representable,
validate_agent_name,
};
pub use overrides::{intersect_capability_modes, resolve_effective_overrides};
pub use resume::{ResumeValidationError, validate_resume_identity};
pub use types::{ContextSource, EffectiveRuntimeConfig, ResolutionError, ResumeSourceData};
pub use xai_grok_agent::config::AgentDefinition;

View file

@ -82,6 +82,24 @@ pub struct ResumeSourceData {
/// Errors that can occur during subagent resolution.
#[derive(Debug, thiserror::Error)]
pub enum ResolutionError {
/// No production or session CLI definition has this name.
#[error("unknown subagent type \"{subagent_type}\"; available: {available:?}")]
Unknown {
subagent_type: String,
available: Vec<String>,
},
/// The definition exists but is disabled by the session toggle.
#[error("subagent \"{subagent_type}\" is disabled")]
Disabled { subagent_type: String },
/// The parent session restricts which child types may run.
#[error("subagent \"{subagent_type}\" is not allowed; allowed: {allowed:?}")]
NotAllowed {
subagent_type: String,
allowed: Vec<String>,
},
/// Persona was explicitly requested but could not be resolved.
#[error("persona resolution failed: {0}")]
PersonaResolution(String),