Skip to main content

swarm_engine_core/agent/
worker.rs

1//! WorkerAgent - 毎 Tick 実行する Agent
2
3use 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// ============================================================================
13// WorkerScope - Worker に渡す情報のスコープ
14// ============================================================================
15
16/// Worker に渡す情報のスコープ
17///
18/// Manager が Guidance で指定し、ContextResolver がこれに応じて
19/// ResolvedContext を生成する。
20///
21/// # スコープレベル
22///
23/// - `Minimal`: 自分の last_output のみ(最小限)
24/// - `SelfDetail`: 自分の詳細情報(履歴、失敗数など)
25/// - `WithTeamSummary`: 自分 + チームのサマリー
26/// - `WithTeamDetail`: 自分 + チームの詳細(フル情報)
27/// - `Idle`: 何もしない(Worker は即座に Idle を返す)
28#[derive(Debug, Clone, Default, PartialEq, Eq)]
29pub enum WorkerScope {
30    /// 最小: 自分の last_output のみ
31    #[default]
32    Minimal,
33    /// 自分の詳細(履歴、失敗数など)
34    SelfDetail,
35    /// 自分 + チームのサマリー(ID + 最新アクションのみ)
36    WithTeamSummary,
37    /// 自分 + チームの詳細(フル情報)
38    WithTeamDetail,
39    /// 何もしない
40    Idle,
41}
42
43// ============================================================================
44// ScopeStrategy - Scope 決定戦略
45// ============================================================================
46
47use crate::context::TaskContext;
48
49/// Scope 決定戦略
50///
51/// Manager が Worker に渡す情報のスコープを決定する戦略。
52/// 外部から注入可能で、状況に応じた動的なスコープ制御を実現する。
53///
54/// # 使用例
55///
56/// ```ignore
57/// // 固定スコープ(全 Worker に同じスコープ)
58/// let strategy = FixedScopeStrategy::new(WorkerScope::Minimal);
59///
60/// // 適応的スコープ(状況に応じて変化)
61/// let strategy = AdaptiveScopeStrategy::new()
62///     .with_default(WorkerScope::Minimal)
63///     .with_on_escalation(WorkerScope::SelfDetail);
64/// ```
65pub trait ScopeStrategy: Send + Sync {
66    /// Worker の Scope を決定
67    ///
68    /// TaskContext と WorkerId を受け取り、その Worker に適用する
69    /// WorkerScope を返す。
70    fn determine_scope(&self, context: &TaskContext, worker_id: WorkerId) -> WorkerScope;
71}
72
73/// 固定 Scope 戦略
74///
75/// 全ての Worker に同じスコープを適用する最もシンプルな戦略。
76/// デフォルトは `WorkerScope::Minimal`。
77#[derive(Debug, Clone)]
78pub struct FixedScopeStrategy {
79    scope: WorkerScope,
80}
81
82impl FixedScopeStrategy {
83    /// 新しい FixedScopeStrategy を作成
84    pub fn new(scope: WorkerScope) -> Self {
85        Self { scope }
86    }
87
88    /// Minimal スコープの戦略を作成
89    pub fn minimal() -> Self {
90        Self::new(WorkerScope::Minimal)
91    }
92
93    /// SelfDetail スコープの戦略を作成
94    pub fn self_detail() -> Self {
95        Self::new(WorkerScope::SelfDetail)
96    }
97
98    /// WithTeamDetail スコープの戦略を作成
99    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/// 適応的 Scope 戦略
117///
118/// Worker の状態に応じて動的にスコープを変更する戦略。
119///
120/// - 通常時: `default` スコープ
121/// - Escalation 中: `on_escalation` スコープ
122/// - 連続失敗時: `on_high_failure` スコープ
123#[derive(Debug, Clone)]
124pub struct AdaptiveScopeStrategy {
125    /// 通常時のスコープ
126    pub default: WorkerScope,
127    /// Escalation 時のスコープ
128    pub on_escalation: WorkerScope,
129    /// 連続失敗時のスコープ
130    pub on_high_failure: WorkerScope,
131    /// 失敗閾値(この回数以上連続失敗すると on_high_failure を適用)
132    pub failure_threshold: u32,
133}
134
135impl AdaptiveScopeStrategy {
136    /// 新しい AdaptiveScopeStrategy を作成
137    pub fn new() -> Self {
138        Self::default()
139    }
140
141    /// 通常時のスコープを設定
142    pub fn with_default(mut self, scope: WorkerScope) -> Self {
143        self.default = scope;
144        self
145    }
146
147    /// Escalation 時のスコープを設定
148    pub fn with_on_escalation(mut self, scope: WorkerScope) -> Self {
149        self.on_escalation = scope;
150        self
151    }
152
153    /// 連続失敗時のスコープを設定
154    pub fn with_on_high_failure(mut self, scope: WorkerScope) -> Self {
155        self.on_high_failure = scope;
156        self
157    }
158
159    /// 失敗閾値を設定
160    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        // Escalation 中の場合
180        if context.has_escalation_for(worker_id) {
181            return self.on_escalation.clone();
182        }
183
184        // 連続失敗が閾値以上の場合
185        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        // 通常時
192        self.default.clone()
193    }
194}
195
196// ============================================================================
197// Basic Types
198// ============================================================================
199
200/// 優先度
201#[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/// Task 説明
211#[derive(Debug, Clone)]
212pub struct TaskDescription {
213    pub name: String,
214    pub details: String,
215}
216
217/// Issue - Worker が困っていること
218#[derive(Debug, Clone)]
219pub struct Issue {
220    pub description: String,
221    pub severity: Priority,
222}
223
224/// 提案オプション
225#[derive(Debug, Clone)]
226pub struct ProposedOption {
227    pub description: String,
228    pub pros: Vec<String>,
229    pub cons: Vec<String>,
230}
231
232/// 関連 State のスナップショット
233#[derive(Debug, Clone, Default)]
234pub struct RelevantState {
235    pub data: Vec<u8>,
236}
237
238// ============================================================================
239// Guidance
240// ============================================================================
241
242/// ManagerAgent から Worker への指示・方針
243///
244/// RuleBased / LLMBased 両方のパターンに対応するシンプルな構造。
245///
246/// # 使用パターン
247///
248/// ## RuleBased: 具体的な Action を1つ指示
249/// ```ignore
250/// Guidance::action(read_action)
251/// ```
252///
253/// ## LLMBased: 複数の Action 候補 + 方針テキスト
254/// ```ignore
255/// Guidance::with_candidates(vec![read, grep, write], "ファイル探索を優先")
256/// ```
257#[derive(Debug, Clone, Default)]
258pub struct Guidance {
259    /// 実行すべき/候補となる Action のリスト
260    pub actions: Vec<Action>,
261    /// 方針・ヒント・追加コンテキスト(オプション)
262    pub content: Option<String>,
263    /// 拡張用プロパティ(将来の追加データ用)
264    pub props: HashMap<String, Vec<u8>>,
265    /// 探索ターゲット(Manager からの探索指示)
266    pub exploration_target: Option<crate::exploration::ExplorationTarget>,
267    /// Worker が見る情報のスコープ
268    pub scope: WorkerScope,
269}
270
271impl Guidance {
272    /// 単一 Action の指示を作成(RuleBased 向け)
273    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    /// 複数 Action 候補 + 方針テキストを作成(LLMBased 向け)
284    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    /// 方針テキストのみ(Action なし)
295    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    /// Idle 指示を作成(何もしない)
306    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    /// Idle 指示かどうか
317    pub fn is_idle(&self) -> bool {
318        matches!(self.scope, WorkerScope::Idle)
319    }
320
321    /// スコープを設定
322    pub fn with_scope(mut self, scope: WorkerScope) -> Self {
323        self.scope = scope;
324        self
325    }
326
327    /// プロパティを追加
328    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    /// 探索ターゲットを設定
334    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
343// ============================================================================
344// From<&MapNode<ActionNodeData, S>> for Guidance
345// ============================================================================
346
347impl<S: crate::exploration::map::MapState>
348    From<&crate::exploration::MapNode<crate::exploration::ActionNodeData, S>> for Guidance
349{
350    /// MapNode から Guidance への変換
351    ///
352    /// V2 ExplorationSpace の select_nodes() 結果を直接 Guidance に変換する。
353    /// Manager はこれをそのまま Worker に渡せる。
354    ///
355    /// # 変換内容
356    ///
357    /// - `node.data` → `Action` (ActionNodeData から変換)
358    /// - `node.data.discovery` → `hint` (ExplorationTarget に設定)
359    /// - `node.id` → `exploration_target.node_id`
360    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        // discovery があれば hint として設定
366        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        // ExplorationTarget を構築
372        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// ============================================================================
384// ManagerInstruction - Manager 指示の軽量表現(Prompt 埋め込み用)
385// ============================================================================
386
387/// Manager から Worker への指示(Prompt 埋め込み用の軽量版)
388///
389/// 前回の Manager 判断を次回の Worker Prompt に埋め込むために使用。
390/// Guidance から必要な情報のみを抽出した構造体。
391///
392/// # 使用フロー
393///
394/// ```text
395/// Tick N: Manager.finalize() → Guidance → ManagerInstruction::from(&guidance)
396/// Tick N+1: Manager.prepare() → ResolvedContext.manager_instruction = Some(...)
397///           → PromptBuilder が "Manager's Instruction" セクションを生成
398/// ```
399#[derive(Debug, Clone, Default)]
400pub struct ManagerInstruction {
401    /// Manager からの指示テキスト(Guidance.content)
402    pub instruction: Option<String>,
403    /// 推奨されたアクション名(Guidance.actions[0].name)
404    pub suggested_action: Option<String>,
405    /// 推奨されたアクションの対象(Guidance.actions[0].params.target)
406    pub suggested_target: Option<String>,
407    /// 探索ヒント(ExplorationTarget からの情報)
408    pub exploration_hint: Option<String>,
409}
410
411impl ManagerInstruction {
412    /// 新しい ManagerInstruction を作成
413    pub fn new() -> Self {
414        Self::default()
415    }
416
417    /// 指示テキストを設定
418    pub fn with_instruction(mut self, instruction: impl Into<String>) -> Self {
419        self.instruction = Some(instruction.into());
420        self
421    }
422
423    /// 推奨アクションを設定
424    pub fn with_suggested_action(mut self, action: impl Into<String>) -> Self {
425        self.suggested_action = Some(action.into());
426        self
427    }
428
429    /// 推奨ターゲットを設定
430    pub fn with_suggested_target(mut self, target: impl Into<String>) -> Self {
431        self.suggested_target = Some(target.into());
432        self
433    }
434
435    /// 探索ヒントを設定
436    pub fn with_exploration_hint(mut self, hint: impl Into<String>) -> Self {
437        self.exploration_hint = Some(hint.into());
438        self
439    }
440
441    /// Guidance から ManagerInstruction を生成
442    pub fn from_guidance(guidance: &Guidance) -> Self {
443        let mut mi = Self::new();
444
445        // content → instruction
446        if let Some(ref content) = guidance.content {
447            mi.instruction = Some(content.clone());
448        }
449
450        // actions[0] → suggested_action, suggested_target
451        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        // exploration_target → exploration_hint
459        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    /// 情報があるかどうか
472    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// ============================================================================
480// WorkResult
481// ============================================================================
482
483/// Worker の実行結果
484#[derive(Debug)]
485pub enum WorkResult {
486    /// 自律的に行動した結果
487    Acted {
488        /// Action の実行結果
489        action_result: ActionResult,
490        /// 状態変更リクエスト(Runtime が Phase 6 でマージ)
491        state_delta: Option<WorkerStateDelta>,
492    },
493    /// 継続中(自分で判断して次Tickも続ける)
494    Continuing { progress: f32 },
495    /// ManagerAgent の判断を仰ぎたい
496    NeedsGuidance {
497        reason: String,
498        context: GuidanceContext,
499    },
500    /// Agent 自身が Escalation を要求(Manager への介入依頼)
501    Escalate {
502        reason: EscalationReason,
503        context: Option<String>,
504    },
505    /// 待機中(やることがない、または他の Worker を待っている)
506    Idle,
507    /// タスク完了(Environment から done=true を受け取った)
508    Done {
509        /// 成功/失敗
510        success: bool,
511        /// 完了メッセージ
512        message: Option<String>,
513    },
514}
515
516impl WorkResult {
517    /// ActionResult から簡易的に Acted を生成(state_delta なし)
518    pub fn acted(action_result: ActionResult) -> Self {
519        Self::Acted {
520            action_result,
521            state_delta: None,
522        }
523    }
524
525    /// ActionResult と state_delta から Acted を生成
526    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    /// 成功で完了
534    pub fn done_success(message: impl Into<String>) -> Self {
535        Self::Done {
536            success: true,
537            message: Some(message.into()),
538        }
539    }
540
541    /// 失敗で完了
542    pub fn done_failure(message: impl Into<String>) -> Self {
543        Self::Done {
544            success: false,
545            message: Some(message.into()),
546        }
547    }
548
549    /// タスク完了かどうか
550    pub fn is_done(&self) -> bool {
551        matches!(self, Self::Done { .. })
552    }
553
554    // ------------------------------------------------------------------------
555    // Environment 向けヘルパー
556    // ------------------------------------------------------------------------
557
558    /// Environment からの成功結果
559    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            },
567            state_delta: None,
568        }
569    }
570
571    /// Environment からの成功結果(データ付き)
572    pub fn env_success_with_data(_message: impl Into<String>, data: impl Into<String>) -> Self {
573        Self::Acted {
574            action_result: ActionResult {
575                success: true,
576                output: Some(ActionOutput::Text(data.into())),
577                error: None,
578                duration: Duration::ZERO,
579            },
580            state_delta: None,
581        }
582    }
583
584    /// Environment からの成功結果(構造化データ付き)
585    pub fn env_success_structured(data: serde_json::Value) -> Self {
586        Self::Acted {
587            action_result: ActionResult {
588                success: true,
589                output: Some(ActionOutput::Structured(data)),
590                error: None,
591                duration: Duration::ZERO,
592            },
593            state_delta: None,
594        }
595    }
596
597    /// Environment からの失敗結果
598    pub fn env_failure(message: impl Into<String>) -> Self {
599        Self::Acted {
600            action_result: ActionResult {
601                success: false,
602                output: None,
603                error: Some(message.into()),
604                duration: Duration::ZERO,
605            },
606            state_delta: None,
607        }
608    }
609
610    /// 未サポートアクション
611    pub fn unsupported(action_name: &str) -> Self {
612        Self::env_failure(format!("Unsupported action: {}", action_name))
613    }
614}
615
616// ============================================================================
617// WorkerStateDelta
618// ============================================================================
619
620/// Worker の状態変更リクエスト(Runtime が Phase 6 でマージ)
621///
622/// Worker は `&self` で実行されるため、直接 State を変更できない。
623/// 代わりに変更リクエストを `WorkResult` に含めて返し、
624/// Runtime が Phase 6 で一括適用する。
625#[derive(Debug, Clone, Default)]
626pub struct WorkerStateDelta {
627    /// ローカルキャッシュ更新リクエスト
628    pub cache_updates: Vec<CacheUpdate>,
629    /// SharedData 更新リクエスト
630    pub shared_updates: Vec<SharedUpdate>,
631    /// 非同期タスク発行リクエスト
632    pub async_tasks: Vec<AsyncTaskRequest>,
633}
634
635impl WorkerStateDelta {
636    pub fn new() -> Self {
637        Self::default()
638    }
639
640    /// キャッシュ更新を追加
641    pub fn with_cache(mut self, key: impl Into<String>, value: Vec<u8>, ttl_ticks: u64) -> Self {
642        self.cache_updates.push(CacheUpdate {
643            key: key.into(),
644            value,
645            ttl_ticks,
646        });
647        self
648    }
649
650    /// SharedData 更新を追加
651    pub fn with_shared(mut self, key: impl Into<String>, value: Vec<u8>) -> Self {
652        self.shared_updates.push(SharedUpdate {
653            key: key.into(),
654            value,
655        });
656        self
657    }
658
659    /// 非同期タスク発行を追加
660    pub fn with_async_task(
661        mut self,
662        task_type: impl Into<String>,
663        params: HashMap<String, String>,
664    ) -> Self {
665        self.async_tasks.push(AsyncTaskRequest {
666            task_type: task_type.into(),
667            params,
668        });
669        self
670    }
671}
672
673/// ローカルキャッシュ更新リクエスト
674#[derive(Debug, Clone)]
675pub struct CacheUpdate {
676    /// キャッシュキー
677    pub key: String,
678    /// 値
679    pub value: Vec<u8>,
680    /// TTL(Tick数)
681    pub ttl_ticks: u64,
682}
683
684/// SharedData 更新リクエスト
685#[derive(Debug, Clone)]
686pub struct SharedUpdate {
687    /// キー
688    pub key: String,
689    /// 値
690    pub value: Vec<u8>,
691}
692
693// ============================================================================
694// GuidanceContext
695// ============================================================================
696
697/// NeedsGuidance 時に ManagerAgent に渡すコンテキスト
698#[derive(Debug, Clone)]
699pub struct GuidanceContext {
700    /// 何に困っているか
701    pub issue: Issue,
702    /// 自分で考えた選択肢(あれば)
703    pub options: Vec<ProposedOption>,
704    /// 関連する State のスナップショット
705    pub relevant_state: RelevantState,
706}
707
708// ============================================================================
709// ScheduledAction
710// ============================================================================
711
712/// スケジュールされたアクション
713#[derive(Debug)]
714pub struct ScheduledAction {
715    /// 対象Agent
716    pub agent_id: WorkerId,
717    /// 実行するAction
718    pub action: Action,
719    /// 優先度
720    pub priority: Priority,
721}
722
723// ============================================================================
724// WorkerAgent Trait
725// ============================================================================
726
727/// 毎 Tick 実行する Agent
728///
729/// RuleBased・LLMBased 両方のパターンを想定した汎用的な Worker trait です。
730/// SwarmApp は登録された全 WorkerAgent を毎 Tick 並列実行します。
731///
732/// # 設計方針
733///
734/// この trait は LLMRouter 等の使用を強制しません。
735/// システムの要件に応じて、以下のいずれかのパターンで実装できます:
736///
737/// - **RuleBased**: 固定ロジックで高速実行(< 1ms)
738/// - **LLMBased**: ActionRouter/LLM を内部で使用し自律判断
739///
740/// # ライフサイクル
741///
742/// ```text
743/// SwarmApp.run()
744///     └─ loop {
745///            for worker in workers {
746///                worker.think_and_act(state, guidance)  // ← 毎 Tick 呼ばれる
747///            }
748///        }
749/// ```
750pub trait WorkerAgent: Send + Sync {
751    /// 自律的に判断して行動(毎 Tick 呼ばれる)
752    ///
753    /// # 並列実行のための設計
754    ///
755    /// `&self` で実行し、状態変更は `WorkResult` で返す。
756    /// これにより `par_iter()` での並列実行が可能。
757    ///
758    /// # Arguments
759    ///
760    /// * `state` - 現在の Swarm 状態(ReadOnly)
761    /// * `guidance` - Manager からの方針・ヒント(オプション)
762    ///
763    /// # Returns
764    ///
765    /// * [`WorkResult::Acted`] - Action を実行した
766    /// * [`WorkResult::Continuing`] - 処理継続中(次 Tick も続ける)
767    /// * [`WorkResult::NeedsGuidance`] - Manager の判断を仰ぎたい
768    /// * [`WorkResult::Idle`] - やることがない
769    fn think_and_act(&self, state: &SwarmState, guidance: Option<&Guidance>) -> WorkResult;
770
771    /// Worker ID を取得
772    fn id(&self) -> WorkerId;
773
774    /// 名前を取得
775    fn name(&self) -> &str;
776}