1use std::collections::HashMap;
4use std::time::Duration;
5
6use crate::state::SwarmState;
7use crate::types::{Action, ActionOutput, ActionResult, WorkerId};
8
9use super::escalation::EscalationReason;
10use super::manager::AsyncTaskRequest;
11
12#[derive(Debug, Clone, Default, PartialEq, Eq)]
29pub enum WorkerScope {
30 #[default]
32 Minimal,
33 SelfDetail,
35 WithTeamSummary,
37 WithTeamDetail,
39 Idle,
41}
42
43use crate::context::TaskContext;
48
49pub trait ScopeStrategy: Send + Sync {
66 fn determine_scope(&self, context: &TaskContext, worker_id: WorkerId) -> WorkerScope;
71}
72
73#[derive(Debug, Clone)]
78pub struct FixedScopeStrategy {
79 scope: WorkerScope,
80}
81
82impl FixedScopeStrategy {
83 pub fn new(scope: WorkerScope) -> Self {
85 Self { scope }
86 }
87
88 pub fn minimal() -> Self {
90 Self::new(WorkerScope::Minimal)
91 }
92
93 pub fn self_detail() -> Self {
95 Self::new(WorkerScope::SelfDetail)
96 }
97
98 pub fn with_team_detail() -> Self {
100 Self::new(WorkerScope::WithTeamDetail)
101 }
102}
103
104impl Default for FixedScopeStrategy {
105 fn default() -> Self {
106 Self::minimal()
107 }
108}
109
110impl ScopeStrategy for FixedScopeStrategy {
111 fn determine_scope(&self, _context: &TaskContext, _worker_id: WorkerId) -> WorkerScope {
112 self.scope.clone()
113 }
114}
115
116#[derive(Debug, Clone)]
124pub struct AdaptiveScopeStrategy {
125 pub default: WorkerScope,
127 pub on_escalation: WorkerScope,
129 pub on_high_failure: WorkerScope,
131 pub failure_threshold: u32,
133}
134
135impl AdaptiveScopeStrategy {
136 pub fn new() -> Self {
138 Self::default()
139 }
140
141 pub fn with_default(mut self, scope: WorkerScope) -> Self {
143 self.default = scope;
144 self
145 }
146
147 pub fn with_on_escalation(mut self, scope: WorkerScope) -> Self {
149 self.on_escalation = scope;
150 self
151 }
152
153 pub fn with_on_high_failure(mut self, scope: WorkerScope) -> Self {
155 self.on_high_failure = scope;
156 self
157 }
158
159 pub fn with_failure_threshold(mut self, threshold: u32) -> Self {
161 self.failure_threshold = threshold;
162 self
163 }
164}
165
166impl Default for AdaptiveScopeStrategy {
167 fn default() -> Self {
168 Self {
169 default: WorkerScope::Minimal,
170 on_escalation: WorkerScope::SelfDetail,
171 on_high_failure: WorkerScope::SelfDetail,
172 failure_threshold: 3,
173 }
174 }
175}
176
177impl ScopeStrategy for AdaptiveScopeStrategy {
178 fn determine_scope(&self, context: &TaskContext, worker_id: WorkerId) -> WorkerScope {
179 if context.has_escalation_for(worker_id) {
181 return self.on_escalation.clone();
182 }
183
184 if let Some(summary) = context.workers.get(&worker_id) {
186 if summary.consecutive_failures >= self.failure_threshold {
187 return self.on_high_failure.clone();
188 }
189 }
190
191 self.default.clone()
193 }
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)]
202pub enum Priority {
203 Low,
204 #[default]
205 Normal,
206 High,
207 Critical,
208}
209
210#[derive(Debug, Clone)]
212pub struct TaskDescription {
213 pub name: String,
214 pub details: String,
215}
216
217#[derive(Debug, Clone)]
219pub struct Issue {
220 pub description: String,
221 pub severity: Priority,
222}
223
224#[derive(Debug, Clone)]
226pub struct ProposedOption {
227 pub description: String,
228 pub pros: Vec<String>,
229 pub cons: Vec<String>,
230}
231
232#[derive(Debug, Clone, Default)]
234pub struct RelevantState {
235 pub data: Vec<u8>,
236}
237
238#[derive(Debug, Clone, Default)]
258pub struct Guidance {
259 pub actions: Vec<Action>,
261 pub content: Option<String>,
263 pub props: HashMap<String, Vec<u8>>,
265 pub exploration_target: Option<crate::exploration::ExplorationTarget>,
267 pub scope: WorkerScope,
269}
270
271impl Guidance {
272 pub fn action(action: Action) -> Self {
274 Self {
275 actions: vec![action],
276 content: None,
277 props: HashMap::new(),
278 exploration_target: None,
279 scope: WorkerScope::Minimal,
280 }
281 }
282
283 pub fn with_candidates(actions: Vec<Action>, content: impl Into<String>) -> Self {
285 Self {
286 actions,
287 content: Some(content.into()),
288 props: HashMap::new(),
289 exploration_target: None,
290 scope: WorkerScope::Minimal,
291 }
292 }
293
294 pub fn hint(content: impl Into<String>) -> Self {
296 Self {
297 actions: Vec::new(),
298 content: Some(content.into()),
299 props: HashMap::new(),
300 exploration_target: None,
301 scope: WorkerScope::Minimal,
302 }
303 }
304
305 pub fn idle() -> Self {
307 Self {
308 actions: Vec::new(),
309 content: None,
310 props: HashMap::new(),
311 exploration_target: None,
312 scope: WorkerScope::Idle,
313 }
314 }
315
316 pub fn is_idle(&self) -> bool {
318 matches!(self.scope, WorkerScope::Idle)
319 }
320
321 pub fn with_scope(mut self, scope: WorkerScope) -> Self {
323 self.scope = scope;
324 self
325 }
326
327 pub fn with_prop(mut self, key: impl Into<String>, value: Vec<u8>) -> Self {
329 self.props.insert(key.into(), value);
330 self
331 }
332
333 pub fn with_exploration_target(
335 mut self,
336 target: crate::exploration::ExplorationTarget,
337 ) -> Self {
338 self.exploration_target = Some(target);
339 self
340 }
341}
342
343impl<S: crate::exploration::map::MapState>
348 From<&crate::exploration::MapNode<crate::exploration::ActionNodeData, S>> for Guidance
349{
350 fn from(node: &crate::exploration::MapNode<crate::exploration::ActionNodeData, S>) -> Self {
361 use crate::exploration::{ExplorationTarget, NodeId};
362
363 let action = Action::from(&node.data);
364
365 let hint = node.data.discovery.as_ref().map(|d| match d {
367 serde_json::Value::String(s) => s.clone(),
368 other => other.to_string(),
369 });
370
371 let target = ExplorationTarget::new(NodeId::from(node.id)).with_action(action.clone());
373 let target = if let Some(h) = hint {
374 target.with_hint(h)
375 } else {
376 target
377 };
378
379 Guidance::action(action).with_exploration_target(target)
380 }
381}
382
383#[derive(Debug, Clone, Default)]
400pub struct ManagerInstruction {
401 pub instruction: Option<String>,
403 pub suggested_action: Option<String>,
405 pub suggested_target: Option<String>,
407 pub exploration_hint: Option<String>,
409}
410
411impl ManagerInstruction {
412 pub fn new() -> Self {
414 Self::default()
415 }
416
417 pub fn with_instruction(mut self, instruction: impl Into<String>) -> Self {
419 self.instruction = Some(instruction.into());
420 self
421 }
422
423 pub fn with_suggested_action(mut self, action: impl Into<String>) -> Self {
425 self.suggested_action = Some(action.into());
426 self
427 }
428
429 pub fn with_suggested_target(mut self, target: impl Into<String>) -> Self {
431 self.suggested_target = Some(target.into());
432 self
433 }
434
435 pub fn with_exploration_hint(mut self, hint: impl Into<String>) -> Self {
437 self.exploration_hint = Some(hint.into());
438 self
439 }
440
441 pub fn from_guidance(guidance: &Guidance) -> Self {
443 let mut mi = Self::new();
444
445 if let Some(ref content) = guidance.content {
447 mi.instruction = Some(content.clone());
448 }
449
450 if let Some(action) = guidance.actions.first() {
452 mi.suggested_action = Some(action.name.clone());
453 if let Some(ref target) = action.params.target {
454 mi.suggested_target = Some(target.clone());
455 }
456 }
457
458 if let Some(ref target) = guidance.exploration_target {
460 let hint = format!(
461 "Node {} ({})",
462 target.node_id.0,
463 target.hint.as_deref().unwrap_or("no hint")
464 );
465 mi.exploration_hint = Some(hint);
466 }
467
468 mi
469 }
470
471 pub fn has_content(&self) -> bool {
473 self.instruction.is_some()
474 || self.suggested_action.is_some()
475 || self.exploration_hint.is_some()
476 }
477}
478
479#[derive(Debug)]
485pub enum WorkResult {
486 Acted {
488 action_result: ActionResult,
490 state_delta: Option<WorkerStateDelta>,
492 },
493 Continuing { progress: f32 },
495 NeedsGuidance {
497 reason: String,
498 context: GuidanceContext,
499 },
500 Escalate {
502 reason: EscalationReason,
503 context: Option<String>,
504 },
505 Idle,
507 Done {
509 success: bool,
511 message: Option<String>,
513 },
514}
515
516impl WorkResult {
517 pub fn acted(action_result: ActionResult) -> Self {
519 Self::Acted {
520 action_result,
521 state_delta: None,
522 }
523 }
524
525 pub fn acted_with_delta(action_result: ActionResult, state_delta: WorkerStateDelta) -> Self {
527 Self::Acted {
528 action_result,
529 state_delta: Some(state_delta),
530 }
531 }
532
533 pub fn done_success(message: impl Into<String>) -> Self {
535 Self::Done {
536 success: true,
537 message: Some(message.into()),
538 }
539 }
540
541 pub fn done_failure(message: impl Into<String>) -> Self {
543 Self::Done {
544 success: false,
545 message: Some(message.into()),
546 }
547 }
548
549 pub fn is_done(&self) -> bool {
551 matches!(self, Self::Done { .. })
552 }
553
554 pub fn env_success(message: impl Into<String>) -> Self {
560 Self::Acted {
561 action_result: ActionResult {
562 success: true,
563 output: Some(ActionOutput::Text(message.into())),
564 error: None,
565 duration: Duration::ZERO,
566 discovered_targets: Vec::new(),
567 },
568 state_delta: None,
569 }
570 }
571
572 pub fn env_success_with_data(_message: impl Into<String>, data: impl Into<String>) -> Self {
574 Self::Acted {
575 action_result: ActionResult {
576 success: true,
577 output: Some(ActionOutput::Text(data.into())),
578 error: None,
579 duration: Duration::ZERO,
580 discovered_targets: Vec::new(),
581 },
582 state_delta: None,
583 }
584 }
585
586 pub fn env_success_structured(data: serde_json::Value) -> Self {
588 Self::Acted {
589 action_result: ActionResult {
590 success: true,
591 output: Some(ActionOutput::Structured(data)),
592 error: None,
593 duration: Duration::ZERO,
594 discovered_targets: Vec::new(),
595 },
596 state_delta: None,
597 }
598 }
599
600 pub fn env_success_with_discoveries(
605 _message: impl Into<String>,
606 data: impl Into<String>,
607 discovered_targets: Vec<String>,
608 ) -> Self {
609 Self::Acted {
610 action_result: ActionResult {
611 success: true,
612 output: Some(ActionOutput::Text(data.into())),
613 error: None,
614 duration: Duration::ZERO,
615 discovered_targets,
616 },
617 state_delta: None,
618 }
619 }
620
621 pub fn env_failure(message: impl Into<String>) -> Self {
623 Self::Acted {
624 action_result: ActionResult {
625 success: false,
626 output: None,
627 error: Some(message.into()),
628 duration: Duration::ZERO,
629 discovered_targets: Vec::new(),
630 },
631 state_delta: None,
632 }
633 }
634
635 pub fn unsupported(action_name: &str) -> Self {
637 Self::env_failure(format!("Unsupported action: {}", action_name))
638 }
639}
640
641#[derive(Debug, Clone, Default)]
651pub struct WorkerStateDelta {
652 pub cache_updates: Vec<CacheUpdate>,
654 pub shared_updates: Vec<SharedUpdate>,
656 pub async_tasks: Vec<AsyncTaskRequest>,
658}
659
660impl WorkerStateDelta {
661 pub fn new() -> Self {
662 Self::default()
663 }
664
665 pub fn with_cache(mut self, key: impl Into<String>, value: Vec<u8>, ttl_ticks: u64) -> Self {
667 self.cache_updates.push(CacheUpdate {
668 key: key.into(),
669 value,
670 ttl_ticks,
671 });
672 self
673 }
674
675 pub fn with_shared(mut self, key: impl Into<String>, value: Vec<u8>) -> Self {
677 self.shared_updates.push(SharedUpdate {
678 key: key.into(),
679 value,
680 });
681 self
682 }
683
684 pub fn with_async_task(
686 mut self,
687 task_type: impl Into<String>,
688 params: HashMap<String, String>,
689 ) -> Self {
690 self.async_tasks.push(AsyncTaskRequest {
691 task_type: task_type.into(),
692 params,
693 });
694 self
695 }
696}
697
698#[derive(Debug, Clone)]
700pub struct CacheUpdate {
701 pub key: String,
703 pub value: Vec<u8>,
705 pub ttl_ticks: u64,
707}
708
709#[derive(Debug, Clone)]
711pub struct SharedUpdate {
712 pub key: String,
714 pub value: Vec<u8>,
716}
717
718#[derive(Debug, Clone)]
724pub struct GuidanceContext {
725 pub issue: Issue,
727 pub options: Vec<ProposedOption>,
729 pub relevant_state: RelevantState,
731}
732
733#[derive(Debug)]
739pub struct ScheduledAction {
740 pub agent_id: WorkerId,
742 pub action: Action,
744 pub priority: Priority,
746}
747
748pub trait WorkerAgent: Send + Sync {
776 fn think_and_act(&self, state: &SwarmState, guidance: Option<&Guidance>) -> WorkResult;
795
796 fn id(&self) -> WorkerId;
798
799 fn name(&self) -> &str;
801}