Skip to main content

swarm_engine_core/events/
action.rs

1//! Action Event - 行動イベントの定義
2//!
3//! Worker が実行した行動を記録するイベント構造体。
4//! オンライン統計・学習・永続化の基盤となる。
5
6use std::time::Duration;
7
8use crate::types::{LoraConfig, WorkerId};
9
10/// 行動イベント
11///
12/// Worker が実行した行動の完全な記録。
13/// オンライン統計や学習パイプラインに使用される。
14#[derive(Debug, Clone)]
15pub struct ActionEvent {
16    /// イベントID(ユニーク)
17    pub id: ActionEventId,
18    /// 発生した Tick
19    pub tick: u64,
20    /// 実行した Worker
21    pub worker_id: WorkerId,
22    /// アクション名
23    pub action: String,
24    /// ターゲット(オプション)
25    pub target: Option<String>,
26    /// 実行結果
27    pub result: ActionEventResult,
28    /// 実行時間
29    pub duration: Duration,
30    /// コンテキスト情報
31    pub context: ActionContext,
32}
33
34/// イベントID
35#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
36pub struct ActionEventId(pub u64);
37
38impl ActionEventId {
39    /// 新しいIDを生成(tick + worker_id + sequence でユニーク性を保証)
40    pub fn new(tick: u64, worker_id: WorkerId, sequence: u32) -> Self {
41        // tick: 40bit, worker_id: 16bit, sequence: 8bit = 64bit
42        let id = (tick << 24) | ((worker_id.0 as u64) << 8) | (sequence as u64 & 0xFF);
43        Self(id)
44    }
45}
46
47/// 行動結果
48#[derive(Debug, Clone)]
49pub struct ActionEventResult {
50    /// 成功/失敗
51    pub success: bool,
52    /// 出力(テキスト表現)
53    pub output: Option<String>,
54    /// エラーメッセージ
55    pub error: Option<String>,
56    /// 発見したノード数(探索の場合)
57    pub discoveries: u32,
58}
59
60impl ActionEventResult {
61    pub fn success() -> Self {
62        Self {
63            success: true,
64            output: None,
65            error: None,
66            discoveries: 0,
67        }
68    }
69
70    pub fn success_with_output(output: impl Into<String>) -> Self {
71        Self {
72            success: true,
73            output: Some(output.into()),
74            error: None,
75            discoveries: 0,
76        }
77    }
78
79    pub fn failure(error: impl Into<String>) -> Self {
80        Self {
81            success: false,
82            output: None,
83            error: Some(error.into()),
84            discoveries: 0,
85        }
86    }
87
88    pub fn with_discoveries(mut self, count: u32) -> Self {
89        self.discoveries = count;
90        self
91    }
92}
93
94/// 行動のコンテキスト情報
95///
96/// なぜその行動が選択されたかの情報を保持。
97/// 学習時の特徴量として使用可能。
98#[derive(Debug, Clone, Default)]
99pub struct ActionContext {
100    /// 選択に使用されたロジック(UCB1, Thompson, Greedy 等)
101    pub selection_logic: Option<String>,
102    /// 探索空間でのノードID
103    pub exploration_node_id: Option<u64>,
104    /// Guidance からの指示だったか
105    pub from_guidance: bool,
106    /// 前回の行動(シーケンス分析用)
107    pub previous_action: Option<String>,
108    /// 使用した LoRA アダプター設定
109    pub lora: Option<LoraConfig>,
110    /// 追加のメタデータ
111    pub metadata: std::collections::HashMap<String, String>,
112}
113
114impl ActionContext {
115    pub fn new() -> Self {
116        Self::default()
117    }
118
119    pub fn with_selection_logic(mut self, logic: impl Into<String>) -> Self {
120        self.selection_logic = Some(logic.into());
121        self
122    }
123
124    pub fn with_exploration_node(mut self, node_id: u64) -> Self {
125        self.exploration_node_id = Some(node_id);
126        self
127    }
128
129    pub fn with_guidance(mut self) -> Self {
130        self.from_guidance = true;
131        self
132    }
133
134    pub fn with_previous_action(mut self, action: impl Into<String>) -> Self {
135        self.previous_action = Some(action.into());
136        self
137    }
138
139    pub fn with_lora(mut self, lora: LoraConfig) -> Self {
140        self.lora = Some(lora);
141        self
142    }
143
144    pub fn with_metadata(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
145        self.metadata.insert(key.into(), value.into());
146        self
147    }
148}
149
150/// ActionEvent のビルダー
151pub struct ActionEventBuilder {
152    tick: u64,
153    worker_id: WorkerId,
154    sequence: u32,
155    action: String,
156    target: Option<String>,
157    result: ActionEventResult,
158    duration: Duration,
159    context: ActionContext,
160}
161
162impl ActionEventBuilder {
163    pub fn new(tick: u64, worker_id: WorkerId, action: impl Into<String>) -> Self {
164        Self {
165            tick,
166            worker_id,
167            sequence: 0,
168            action: action.into(),
169            target: None,
170            result: ActionEventResult::success(),
171            duration: Duration::ZERO,
172            context: ActionContext::default(),
173        }
174    }
175
176    pub fn sequence(mut self, seq: u32) -> Self {
177        self.sequence = seq;
178        self
179    }
180
181    pub fn target(mut self, target: impl Into<String>) -> Self {
182        self.target = Some(target.into());
183        self
184    }
185
186    pub fn result(mut self, result: ActionEventResult) -> Self {
187        self.result = result;
188        self
189    }
190
191    pub fn duration(mut self, duration: Duration) -> Self {
192        self.duration = duration;
193        self
194    }
195
196    pub fn context(mut self, context: ActionContext) -> Self {
197        self.context = context;
198        self
199    }
200
201    pub fn build(self) -> ActionEvent {
202        ActionEvent {
203            id: ActionEventId::new(self.tick, self.worker_id, self.sequence),
204            tick: self.tick,
205            worker_id: self.worker_id,
206            action: self.action,
207            target: self.target,
208            result: self.result,
209            duration: self.duration,
210            context: self.context,
211        }
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use super::*;
218
219    #[test]
220    fn test_action_event_id_uniqueness() {
221        let id1 = ActionEventId::new(100, WorkerId(0), 0);
222        let id2 = ActionEventId::new(100, WorkerId(0), 1);
223        let id3 = ActionEventId::new(100, WorkerId(1), 0);
224        let id4 = ActionEventId::new(101, WorkerId(0), 0);
225
226        assert_ne!(id1, id2);
227        assert_ne!(id1, id3);
228        assert_ne!(id1, id4);
229    }
230
231    #[test]
232    fn test_action_event_builder() {
233        let event = ActionEventBuilder::new(10, WorkerId(1), "CheckStatus")
234            .target("user-service")
235            .result(ActionEventResult::success_with_output("Service is running"))
236            .duration(Duration::from_millis(50))
237            .context(
238                ActionContext::new()
239                    .with_selection_logic("UCB1")
240                    .with_guidance(),
241            )
242            .build();
243
244        assert_eq!(event.tick, 10);
245        assert_eq!(event.worker_id, WorkerId(1));
246        assert_eq!(event.action, "CheckStatus");
247        assert_eq!(event.target, Some("user-service".to_string()));
248        assert!(event.result.success);
249        assert_eq!(event.duration, Duration::from_millis(50));
250        assert_eq!(event.context.selection_logic, Some("UCB1".to_string()));
251        assert!(event.context.from_guidance);
252    }
253}