use std::collections::HashMap;
use std::time::Duration;
use crate::state::SwarmState;
use crate::types::{Action, ActionOutput, ActionResult, WorkerId};
use super::escalation::EscalationReason;
use super::manager::AsyncTaskRequest;
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub enum WorkerScope {
#[default]
Minimal,
SelfDetail,
WithTeamSummary,
WithTeamDetail,
Idle,
}
use crate::context::TaskContext;
pub trait ScopeStrategy: Send + Sync {
fn determine_scope(&self, context: &TaskContext, worker_id: WorkerId) -> WorkerScope;
}
#[derive(Debug, Clone)]
pub struct FixedScopeStrategy {
scope: WorkerScope,
}
impl FixedScopeStrategy {
pub fn new(scope: WorkerScope) -> Self {
Self { scope }
}
pub fn minimal() -> Self {
Self::new(WorkerScope::Minimal)
}
pub fn self_detail() -> Self {
Self::new(WorkerScope::SelfDetail)
}
pub fn with_team_detail() -> Self {
Self::new(WorkerScope::WithTeamDetail)
}
}
impl Default for FixedScopeStrategy {
fn default() -> Self {
Self::minimal()
}
}
impl ScopeStrategy for FixedScopeStrategy {
fn determine_scope(&self, _context: &TaskContext, _worker_id: WorkerId) -> WorkerScope {
self.scope.clone()
}
}
#[derive(Debug, Clone)]
pub struct AdaptiveScopeStrategy {
pub default: WorkerScope,
pub on_escalation: WorkerScope,
pub on_high_failure: WorkerScope,
pub failure_threshold: u32,
}
impl AdaptiveScopeStrategy {
pub fn new() -> Self {
Self::default()
}
pub fn with_default(mut self, scope: WorkerScope) -> Self {
self.default = scope;
self
}
pub fn with_on_escalation(mut self, scope: WorkerScope) -> Self {
self.on_escalation = scope;
self
}
pub fn with_on_high_failure(mut self, scope: WorkerScope) -> Self {
self.on_high_failure = scope;
self
}
pub fn with_failure_threshold(mut self, threshold: u32) -> Self {
self.failure_threshold = threshold;
self
}
}
impl Default for AdaptiveScopeStrategy {
fn default() -> Self {
Self {
default: WorkerScope::Minimal,
on_escalation: WorkerScope::SelfDetail,
on_high_failure: WorkerScope::SelfDetail,
failure_threshold: 3,
}
}
}
impl ScopeStrategy for AdaptiveScopeStrategy {
fn determine_scope(&self, context: &TaskContext, worker_id: WorkerId) -> WorkerScope {
if context.has_escalation_for(worker_id) {
return self.on_escalation.clone();
}
if let Some(summary) = context.workers.get(&worker_id) {
if summary.consecutive_failures >= self.failure_threshold {
return self.on_high_failure.clone();
}
}
self.default.clone()
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
pub enum Priority {
Low,
#[default]
Normal,
High,
Critical,
}
#[derive(Debug, Clone)]
pub struct TaskDescription {
pub name: String,
pub details: String,
}
#[derive(Debug, Clone)]
pub struct Issue {
pub description: String,
pub severity: Priority,
}
#[derive(Debug, Clone)]
pub struct ProposedOption {
pub description: String,
pub pros: Vec<String>,
pub cons: Vec<String>,
}
#[derive(Debug, Clone, Default)]
pub struct RelevantState {
pub data: Vec<u8>,
}
#[derive(Debug, Clone, Default)]
pub struct Guidance {
pub actions: Vec<Action>,
pub content: Option<String>,
pub props: HashMap<String, Vec<u8>>,
pub exploration_target: Option<crate::exploration::ExplorationTarget>,
pub scope: WorkerScope,
}
impl Guidance {
pub fn action(action: Action) -> Self {
Self {
actions: vec![action],
content: None,
props: HashMap::new(),
exploration_target: None,
scope: WorkerScope::Minimal,
}
}
pub fn with_candidates(actions: Vec<Action>, content: impl Into<String>) -> Self {
Self {
actions,
content: Some(content.into()),
props: HashMap::new(),
exploration_target: None,
scope: WorkerScope::Minimal,
}
}
pub fn hint(content: impl Into<String>) -> Self {
Self {
actions: Vec::new(),
content: Some(content.into()),
props: HashMap::new(),
exploration_target: None,
scope: WorkerScope::Minimal,
}
}
pub fn idle() -> Self {
Self {
actions: Vec::new(),
content: None,
props: HashMap::new(),
exploration_target: None,
scope: WorkerScope::Idle,
}
}
pub fn is_idle(&self) -> bool {
matches!(self.scope, WorkerScope::Idle)
}
pub fn with_scope(mut self, scope: WorkerScope) -> Self {
self.scope = scope;
self
}
pub fn with_prop(mut self, key: impl Into<String>, value: Vec<u8>) -> Self {
self.props.insert(key.into(), value);
self
}
pub fn with_exploration_target(
mut self,
target: crate::exploration::ExplorationTarget,
) -> Self {
self.exploration_target = Some(target);
self
}
}
impl<S: crate::exploration::map::MapState>
From<&crate::exploration::MapNode<crate::exploration::ActionNodeData, S>> for Guidance
{
fn from(node: &crate::exploration::MapNode<crate::exploration::ActionNodeData, S>) -> Self {
use crate::exploration::{ExplorationTarget, NodeId};
let action = Action::from(&node.data);
let hint = node.data.discovery.as_ref().map(|d| match d {
serde_json::Value::String(s) => s.clone(),
other => other.to_string(),
});
let target = ExplorationTarget::new(NodeId::from(node.id)).with_action(action.clone());
let target = if let Some(h) = hint {
target.with_hint(h)
} else {
target
};
Guidance::action(action).with_exploration_target(target)
}
}
#[derive(Debug, Clone, Default)]
pub struct ManagerInstruction {
pub instruction: Option<String>,
pub suggested_action: Option<String>,
pub suggested_target: Option<String>,
pub exploration_hint: Option<String>,
}
impl ManagerInstruction {
pub fn new() -> Self {
Self::default()
}
pub fn with_instruction(mut self, instruction: impl Into<String>) -> Self {
self.instruction = Some(instruction.into());
self
}
pub fn with_suggested_action(mut self, action: impl Into<String>) -> Self {
self.suggested_action = Some(action.into());
self
}
pub fn with_suggested_target(mut self, target: impl Into<String>) -> Self {
self.suggested_target = Some(target.into());
self
}
pub fn with_exploration_hint(mut self, hint: impl Into<String>) -> Self {
self.exploration_hint = Some(hint.into());
self
}
pub fn from_guidance(guidance: &Guidance) -> Self {
let mut mi = Self::new();
if let Some(ref content) = guidance.content {
mi.instruction = Some(content.clone());
}
if let Some(action) = guidance.actions.first() {
mi.suggested_action = Some(action.name.clone());
if let Some(ref target) = action.params.target {
mi.suggested_target = Some(target.clone());
}
}
if let Some(ref target) = guidance.exploration_target {
let hint = format!(
"Node {} ({})",
target.node_id.0,
target.hint.as_deref().unwrap_or("no hint")
);
mi.exploration_hint = Some(hint);
}
mi
}
pub fn has_content(&self) -> bool {
self.instruction.is_some()
|| self.suggested_action.is_some()
|| self.exploration_hint.is_some()
}
}
#[derive(Debug)]
pub enum WorkResult {
Acted {
action_result: ActionResult,
state_delta: Option<WorkerStateDelta>,
},
Continuing { progress: f32 },
NeedsGuidance {
reason: String,
context: GuidanceContext,
},
Escalate {
reason: EscalationReason,
context: Option<String>,
},
Idle,
Done {
success: bool,
message: Option<String>,
},
}
impl WorkResult {
pub fn acted(action_result: ActionResult) -> Self {
Self::Acted {
action_result,
state_delta: None,
}
}
pub fn acted_with_delta(action_result: ActionResult, state_delta: WorkerStateDelta) -> Self {
Self::Acted {
action_result,
state_delta: Some(state_delta),
}
}
pub fn done_success(message: impl Into<String>) -> Self {
Self::Done {
success: true,
message: Some(message.into()),
}
}
pub fn done_failure(message: impl Into<String>) -> Self {
Self::Done {
success: false,
message: Some(message.into()),
}
}
pub fn is_done(&self) -> bool {
matches!(self, Self::Done { .. })
}
pub fn env_success(message: impl Into<String>) -> Self {
Self::Acted {
action_result: ActionResult {
success: true,
output: Some(ActionOutput::Text(message.into())),
error: None,
duration: Duration::ZERO,
discovered_targets: Vec::new(),
},
state_delta: None,
}
}
pub fn env_success_with_data(_message: impl Into<String>, data: impl Into<String>) -> Self {
Self::Acted {
action_result: ActionResult {
success: true,
output: Some(ActionOutput::Text(data.into())),
error: None,
duration: Duration::ZERO,
discovered_targets: Vec::new(),
},
state_delta: None,
}
}
pub fn env_success_structured(data: serde_json::Value) -> Self {
Self::Acted {
action_result: ActionResult {
success: true,
output: Some(ActionOutput::Structured(data)),
error: None,
duration: Duration::ZERO,
discovered_targets: Vec::new(),
},
state_delta: None,
}
}
pub fn env_success_with_discoveries(
_message: impl Into<String>,
data: impl Into<String>,
discovered_targets: Vec<String>,
) -> Self {
Self::Acted {
action_result: ActionResult {
success: true,
output: Some(ActionOutput::Text(data.into())),
error: None,
duration: Duration::ZERO,
discovered_targets,
},
state_delta: None,
}
}
pub fn env_failure(message: impl Into<String>) -> Self {
Self::Acted {
action_result: ActionResult {
success: false,
output: None,
error: Some(message.into()),
duration: Duration::ZERO,
discovered_targets: Vec::new(),
},
state_delta: None,
}
}
pub fn unsupported(action_name: &str) -> Self {
Self::env_failure(format!("Unsupported action: {}", action_name))
}
}
#[derive(Debug, Clone, Default)]
pub struct WorkerStateDelta {
pub cache_updates: Vec<CacheUpdate>,
pub shared_updates: Vec<SharedUpdate>,
pub async_tasks: Vec<AsyncTaskRequest>,
}
impl WorkerStateDelta {
pub fn new() -> Self {
Self::default()
}
pub fn with_cache(mut self, key: impl Into<String>, value: Vec<u8>, ttl_ticks: u64) -> Self {
self.cache_updates.push(CacheUpdate {
key: key.into(),
value,
ttl_ticks,
});
self
}
pub fn with_shared(mut self, key: impl Into<String>, value: Vec<u8>) -> Self {
self.shared_updates.push(SharedUpdate {
key: key.into(),
value,
});
self
}
pub fn with_async_task(
mut self,
task_type: impl Into<String>,
params: HashMap<String, String>,
) -> Self {
self.async_tasks.push(AsyncTaskRequest {
task_type: task_type.into(),
params,
});
self
}
}
#[derive(Debug, Clone)]
pub struct CacheUpdate {
pub key: String,
pub value: Vec<u8>,
pub ttl_ticks: u64,
}
#[derive(Debug, Clone)]
pub struct SharedUpdate {
pub key: String,
pub value: Vec<u8>,
}
#[derive(Debug, Clone)]
pub struct GuidanceContext {
pub issue: Issue,
pub options: Vec<ProposedOption>,
pub relevant_state: RelevantState,
}
#[derive(Debug)]
pub struct ScheduledAction {
pub agent_id: WorkerId,
pub action: Action,
pub priority: Priority,
}
pub trait WorkerAgent: Send + Sync {
fn think_and_act(&self, state: &SwarmState, guidance: Option<&Guidance>) -> WorkResult;
fn id(&self) -> WorkerId;
fn name(&self) -> &str;
}