swarm-engine-core 0.1.6

Core types and orchestration for SwarmEngine
Documentation
//! ManagerAgent - 観察・判断・指示を行う上位Agent
//!
//! # 型の正規定義
//!
//! 以下の型はこのモジュールで正規定義されています:
//! - `ManagerId`
//! - `BatchDecisionRequest`
//! - `WorkerDecisionRequest`
//! - `ManagementDecision`
//! - `ManagementStrategy`
//!
//! これらは `batch.rs` からも re-export されます。
//!
//! # 設計変更(v2)
//!
//! ManagerAgent は TaskContext を受け取り、Request を生成する。
//! SwarmState からの分析は Analyzer が担当。
//!
//! ```text
//! SwarmState → [Analyzer] → TaskContext → [Manager] → BatchDecisionRequest
//!//!                                     [BatchInvoker] → Response
//!//!                                   [Manager.finalize] → ManagementDecision
//! ```

use std::collections::HashMap;

use crate::types::{LoraConfig, WorkerId};

use super::batch::DecisionResponse;
use super::worker::Guidance;
use crate::context::TaskContext;

// ============================================================================
// Management Strategy
// ============================================================================

/// ManagerAgent の起動タイミング戦略
#[derive(Debug, Clone)]
pub enum ManagementStrategy {
    /// 毎 Tick 起動(LLM 不要なフロー向け)
    ///
    /// V2 ExplorationSpace など、LLM 呼び出しなしで Guidance を生成できる場合に使用。
    /// Worker がアクションを完了したら即座に次のノードを割り当てられる。
    EveryTick,

    /// N Tick ごとに ManagerAgent を起動
    FixedInterval { interval: u64 },

    /// 全 WorkerAgent の完了を待って起動
    CompletionBased {
        /// 最大待機Tick(タイムアウト)
        max_wait_ticks: u64,
    },

    /// ハイブリッド: 完了待ちだが、最大N Tickで強制起動
    Hybrid {
        preferred_interval: u64,
        force_after_ticks: u64,
    },

    /// Escalation ベース: 定期 + Escalation 発生時に即時起動
    EscalationBased {
        /// 定期起動間隔
        interval: u64,
        /// Escalation 発生時に即時起動するか
        immediate_on_escalation: bool,
    },
}

impl Default for ManagementStrategy {
    fn default() -> Self {
        ManagementStrategy::FixedInterval { interval: 10 }
    }
}

// ============================================================================
// Manager ID
// ============================================================================

/// Manager ID
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct ManagerId(pub usize);

// ============================================================================
// Batch Request
// ============================================================================

/// Batch リクエスト(1 Manager が 1 Tick で送る全リクエスト)
///
/// Manager が各 Worker に対して判断を求めるリクエストをまとめたもの。
/// ContextStore → ContextResolver → ResolvedContext の流れで生成される。
#[derive(Debug, Clone)]
pub struct BatchDecisionRequest {
    /// 発行元 Manager ID
    pub manager_id: ManagerId,
    /// 各 Worker への判断リクエスト
    pub requests: Vec<WorkerDecisionRequest>,
}

use crate::context::ResolvedContext;

/// 個別 Worker への判断リクエスト
///
/// Worker が判断に必要な情報を含む。
/// `context` は構造化データとして保持し、文字列化は LLM 層で行う。
#[derive(Debug, Clone)]
pub struct WorkerDecisionRequest {
    /// 対象 Worker ID
    pub worker_id: WorkerId,
    /// タスク目標(query)
    pub query: String,
    /// Worker 固有のコンテキスト(構造化データ)
    pub context: ResolvedContext,
    /// LoRA アダプター設定(オプション)
    ///
    /// 指定すると、このリクエストに対して特定の LoRA アダプターを適用する。
    /// llama.cpp の per-request LoRA 機能を使用。
    pub lora: Option<LoraConfig>,
}

// ============================================================================
// Management Decision
// ============================================================================

/// 非同期タスクリクエスト
#[derive(Debug, Clone, Default)]
pub struct AsyncTaskRequest {
    pub task_type: String,
    pub params: HashMap<String, String>,
}

/// ManagerAgent の決定結果
#[derive(Default, Clone)]
pub struct ManagementDecision {
    /// WorkerAgent への Guidance(Worker ID -> Guidance)
    pub guidances: HashMap<WorkerId, Guidance>,
    /// 戦略の動的変更(オプション)
    pub strategy_update: Option<ManagementStrategy>,
    /// 非同期タスクの発行(重いLLM呼び出し等)
    pub async_tasks: Vec<AsyncTaskRequest>,
}

impl ManagementDecision {
    /// 単一 Worker への Guidance を設定
    pub fn with_guidance(mut self, worker_id: WorkerId, guidance: Guidance) -> Self {
        self.guidances.insert(worker_id, guidance);
        self
    }

    /// 全 Worker に同じ Guidance を設定
    pub fn broadcast_guidance(mut self, worker_ids: &[WorkerId], guidance: Guidance) -> Self {
        for &id in worker_ids {
            self.guidances.insert(id, guidance.clone());
        }
        self
    }
}

// ============================================================================
// ManagerAgent Trait
// ============================================================================

/// 観察・判断・指示を行う上位Agent(Batch 対応)
///
/// # 設計
///
/// ManagerAgent は TaskContext を受け取り、Request を生成する。
/// SwarmState からの分析は Analyzer が担当。
///
/// ```text
/// ┌─────────────────────────────────────────────────────────────┐
/// │                      Tick N                                  │
/// │                                                              │
/// │  SwarmState → [Analyzer] → TaskContext                      │
/// │                                 ↓                            │
/// │  Manager[0].prepare(ctx) ─→ N Requests ─┐                   │
/// │  Manager[1].prepare(ctx) ─→ N Requests ─┼─→ Batch           │
/// │  ...                                    │                    │
/// │  Manager[M].prepare(ctx) ─→ N Requests ─┘                   │
/// │                                                              │
/// │                    ↓ LLM Batch Call (~1秒)                  │
/// │                                                              │
/// │  M×N Responses → 各 Manager.finalize(ctx, responses)        │
/// │                    ↓                                         │
/// │  Worker Instructions 配布                                    │
/// └─────────────────────────────────────────────────────────────┘
/// ```
pub trait ManagerAgent: Send + Sync {
    /// TaskContext を見て Batch リクエストを生成(軽量、同期)
    ///
    /// どの Worker に何を聞くかを決定する。
    /// Orchestrator がこれらを集約して LLM Batch Call を実行する。
    fn prepare(&self, context: &TaskContext) -> BatchDecisionRequest;

    /// Batch 結果受取後: 指示を生成(軽量、同期)
    ///
    /// LLM Batch Call の結果を受けて、Worker への具体的な指示を生成する。
    fn finalize(
        &self,
        context: &TaskContext,
        responses: Vec<(WorkerId, DecisionResponse)>,
    ) -> ManagementDecision;

    /// Manager ID を取得
    fn id(&self) -> ManagerId;

    /// 名前を取得
    fn name(&self) -> &str;
}

/// Blanket implementation for Box<dyn ManagerAgent>
impl ManagerAgent for Box<dyn ManagerAgent> {
    fn prepare(&self, context: &TaskContext) -> BatchDecisionRequest {
        (**self).prepare(context)
    }

    fn finalize(
        &self,
        context: &TaskContext,
        responses: Vec<(WorkerId, DecisionResponse)>,
    ) -> ManagementDecision {
        (**self).finalize(context, responses)
    }

    fn id(&self) -> ManagerId {
        (**self).id()
    }

    fn name(&self) -> &str {
        (**self).name()
    }
}