perspt_core/events.rs
1//! Agent Events
2//!
3//! Event types for communication between the SRBN Orchestrator and TUI.
4//! Enables async, decoupled control flow for interactive agent sessions.
5
6use serde::{Deserialize, Serialize};
7
8/// PSP-5 Phase 4: Per-plugin readiness summary for session-start reporting
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct PluginReadiness {
11 /// Plugin name (e.g. "rust", "python")
12 pub plugin_name: String,
13 /// Verifier stages that have at least one available tool
14 pub available_stages: Vec<String>,
15 /// Verifier stages where only a fallback or no tool is available
16 pub degraded_stages: Vec<String>,
17 /// LSP status description
18 pub lsp_status: String,
19}
20
21/// Events emitted by the Orchestrator for TUI consumption
22#[derive(Debug, Clone, Serialize, Deserialize)]
23pub enum AgentEvent {
24 /// Task status changed
25 TaskStatusChanged { node_id: String, status: NodeStatus },
26
27 /// Plan generated by Architect
28 PlanGenerated(crate::types::TaskPlan),
29
30 /// Lyapunov energy updated
31 EnergyUpdated { node_id: String, energy: f32 },
32
33 /// Log message for display
34 Log(String),
35
36 /// Node completed successfully
37 NodeCompleted { node_id: String, goal: String },
38
39 /// Approval required before proceeding
40 ApprovalRequest {
41 request_id: String,
42 node_id: String,
43 action_type: ActionType,
44 description: String,
45 diff: Option<String>,
46 },
47
48 /// Orchestration finished
49 Complete { success: bool, message: String },
50
51 /// Error occurred
52 Error(String),
53
54 // =========================================================================
55 // PSP-5: Lifecycle Events
56 // =========================================================================
57 /// PSP-5: Plan ready after sheafification with detected plugins and execution mode
58 PlanReady {
59 nodes: usize,
60 plugins: Vec<String>,
61 execution_mode: String,
62 },
63
64 /// PSP-5: Node selected for execution
65 NodeSelected {
66 node_id: String,
67 goal: String,
68 node_class: String,
69 },
70
71 /// PSP-5: Deterministic fallback planner activated
72 FallbackPlanner { reason: String },
73
74 /// PSP-5: Verification completed for a node
75 VerificationComplete {
76 node_id: String,
77 syntax_ok: bool,
78 build_ok: bool,
79 tests_ok: bool,
80 lint_ok: bool,
81 diagnostics_count: usize,
82 tests_passed: usize,
83 tests_failed: usize,
84 energy: f32,
85 /// PSP-5 Phase 7: Full energy component breakdown
86 energy_components: crate::types::EnergyComponents,
87 /// PSP-5 Phase 7: Per-stage verification outcomes with sensor status
88 stage_outcomes: Vec<crate::types::StageOutcome>,
89 /// PSP-5 Phase 7: Whether verification ran in degraded mode
90 degraded: bool,
91 /// PSP-5 Phase 7: Human-readable reasons for each degraded stage
92 degraded_reasons: Vec<String>,
93 /// PSP-5 Phase 7: Summary suitable for display
94 summary: String,
95 /// PSP-5 Phase 7: Node class for display context
96 node_class: String,
97 },
98
99 /// PSP-5: Artifact bundle applied to workspace
100 BundleApplied {
101 node_id: String,
102 files_created: Vec<String>,
103 files_modified: Vec<String>,
104 /// PSP-5 Phase 7: Number of write (new file) operations
105 writes_count: usize,
106 /// PSP-5 Phase 7: Number of diff (patch) operations
107 diffs_count: usize,
108 /// PSP-5 Phase 7: Node class for display context
109 node_class: String,
110 },
111
112 /// PSP-5 Phase 4: A sensor fell back to an alternative tool
113 SensorFallback {
114 node_id: String,
115 stage: String,
116 primary: String,
117 actual: String,
118 reason: String,
119 },
120
121 /// PSP-5 Phase 4: Verification completed with degraded stages
122 DegradedVerification {
123 node_id: String,
124 degraded_stages: Vec<String>,
125 stability_blocked: bool,
126 },
127
128 /// PSP-5 Phase 5: Non-convergence classified with a repair action
129 EscalationClassified {
130 node_id: String,
131 category: String,
132 action: String,
133 },
134
135 /// PSP-5 Phase 5: Sheaf validation completed for a node
136 SheafValidationComplete {
137 node_id: String,
138 validators_run: usize,
139 failures: usize,
140 v_sheaf: f32,
141 },
142
143 /// PSP-5 Phase 5: Graph rewrite applied (split, interface insertion, replan)
144 GraphRewriteApplied {
145 trigger_node: String,
146 action: String,
147 nodes_affected: usize,
148 },
149
150 /// PSP-5 Phase 6: Provisional branch created for speculative child work
151 BranchCreated {
152 branch_id: String,
153 node_id: String,
154 parent_node_id: String,
155 },
156
157 /// PSP-5 Phase 6: Interface sealed for a node (dependents may proceed)
158 InterfaceSealed {
159 node_id: String,
160 sealed_paths: Vec<String>,
161 seal_hash: String,
162 },
163
164 /// PSP-5 Phase 6: Provisional branches flushed due to parent failure
165 BranchFlushed {
166 parent_node_id: String,
167 flushed_branch_ids: Vec<String>,
168 reason: String,
169 },
170
171 /// PSP-5 Phase 6: Blocked dependent unblocked after parent seal
172 DependentUnblocked {
173 child_node_id: String,
174 parent_node_id: String,
175 },
176
177 /// PSP-5 Phase 6: Provisional branch merged into committed state
178 BranchMerged { branch_id: String, node_id: String },
179
180 /// PSP-5 Phase 3: Context assembly degraded (budget exceeded or missing artifacts)
181 ContextDegraded {
182 node_id: String,
183 budget_exceeded: bool,
184 missing_owned_files: Vec<String>,
185 included_file_count: usize,
186 total_bytes: usize,
187 reason: String,
188 },
189
190 /// PSP-5 Phase 3: Context blocked — required structural context is untrustworthy.
191 /// The node SHALL NOT proceed silently (PSP-5 §3 requirement).
192 ContextBlocked {
193 node_id: String,
194 missing_owned_files: Vec<String>,
195 reason: String,
196 },
197
198 /// PSP-5 Phase 3: Structural dependency pre-check failed — a required
199 /// dependency only has prose summaries, no machine-verifiable digests.
200 StructuralDependencyMissing {
201 node_id: String,
202 dependency_node_id: String,
203 reason: String,
204 },
205
206 /// PSP-5 Phase 1/4: Model fallback triggered for a tier after structured-output failure
207 ModelFallback {
208 node_id: String,
209 tier: String,
210 primary_model: String,
211 fallback_model: String,
212 reason: String,
213 },
214
215 /// PSP-5 Phase 3: Context provenance drift detected on resume
216 ProvenanceDrift {
217 node_id: String,
218 missing_files: Vec<String>,
219 reason: String,
220 },
221
222 /// PSP-5 Phase 4: Tool readiness snapshot captured at session start
223 ToolReadiness {
224 /// Per-plugin readiness: (plugin_name, available_stages, degraded_stages, lsp_status)
225 plugins: Vec<PluginReadiness>,
226 /// Verifier strictness mode in effect
227 strictness: String,
228 },
229
230 /// PSP-5 Phase 8: Budget envelope updated (step/cost consumed)
231 BudgetUpdated {
232 steps_used: u32,
233 max_steps: Option<u32>,
234 cost_used_usd: f64,
235 max_cost_usd: Option<f64>,
236 revisions_used: u32,
237 max_revisions: Option<u32>,
238 },
239
240 /// PSP-5 Phase 8: Plan revision superseded by a new revision
241 PlanRevised {
242 revision_id: String,
243 sequence: u32,
244 reason: String,
245 node_count: usize,
246 },
247
248 /// PSP-5 Phase 10: File deleted via safe delete operation
249 FileDeleted { node_id: String, path: String },
250
251 /// PSP-5 Phase 10: File moved/renamed via safe move operation
252 FileMoved {
253 node_id: String,
254 from: String,
255 to: String,
256 },
257}
258
259/// Node status for TUI display (mirrors NodeState but simplified)
260#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
261pub enum NodeStatus {
262 /// Queued, waiting for dependencies
263 Queued,
264 /// Planning phase (Architect)
265 Planning,
266 Pending,
267 /// Active coding / implementation phase
268 Coding,
269 Running,
270 Verifying,
271 /// PSP-5: Node is retrying after a failed verification
272 Retrying,
273 /// PSP-5 Phase 7: Sheaf consistency check underway
274 SheafCheck,
275 /// PSP-5 Phase 7: Committing stable state to ledger
276 Committing,
277 Completed,
278 Failed,
279 Escalated,
280 Aborted,
281}
282
283impl From<crate::types::NodeState> for NodeStatus {
284 fn from(state: crate::types::NodeState) -> Self {
285 use crate::types::NodeState;
286 match state {
287 NodeState::TaskQueued => NodeStatus::Queued,
288 NodeState::Planning => NodeStatus::Planning,
289 NodeState::Coding => NodeStatus::Coding,
290 NodeState::Verifying => NodeStatus::Verifying,
291 NodeState::Retry => NodeStatus::Retrying,
292 NodeState::SheafCheck => NodeStatus::SheafCheck,
293 NodeState::Committing => NodeStatus::Committing,
294 NodeState::Escalated => NodeStatus::Escalated,
295 NodeState::Completed => NodeStatus::Completed,
296 NodeState::Failed => NodeStatus::Failed,
297 NodeState::Aborted => NodeStatus::Aborted,
298 NodeState::Superseded => NodeStatus::Completed,
299 }
300 }
301}
302
303/// Type of action requiring approval
304#[derive(Debug, Clone, Serialize, Deserialize)]
305pub enum ActionType {
306 /// File creation or modification
307 FileWrite { path: String },
308 /// Shell command execution
309 Command { command: String },
310 /// Multiple files in a sub-graph
311 SubGraph { node_count: usize },
312 /// Project initialization (with editable name)
313 ProjectInit {
314 command: String,
315 suggested_name: String,
316 },
317 /// PSP-5: Multi-file artifact bundle write
318 BundleWrite {
319 /// Node that produced the bundle
320 node_id: String,
321 /// Files being written or modified
322 files: Vec<String>,
323 },
324}
325
326/// Actions sent from TUI to Orchestrator
327#[derive(Debug, Clone, Serialize, Deserialize)]
328pub enum AgentAction {
329 /// Approve a pending request
330 Approve { request_id: String },
331 /// Approve with an edited value (e.g., project name)
332 ApproveWithEdit {
333 request_id: String,
334 edited_value: String,
335 },
336 /// Reject a pending request
337 Reject {
338 request_id: String,
339 reason: Option<String>,
340 },
341 /// Pause orchestration
342 Pause,
343 /// Resume orchestration
344 Resume,
345 /// Abort the entire session
346 Abort,
347 /// PSP-5 Phase 7: Request correction with structured user feedback
348 RequestCorrection {
349 request_id: String,
350 feedback: String,
351 },
352}
353
354/// Channel types for agent communication
355pub mod channel {
356 use super::{AgentAction, AgentEvent};
357 use tokio::sync::mpsc;
358
359 /// Sender for AgentEvents (Orchestrator → TUI)
360 pub type EventSender = mpsc::UnboundedSender<AgentEvent>;
361 /// Receiver for AgentEvents (Orchestrator → TUI)
362 pub type EventReceiver = mpsc::UnboundedReceiver<AgentEvent>;
363
364 /// Sender for AgentActions (TUI → Orchestrator)
365 pub type ActionSender = mpsc::UnboundedSender<AgentAction>;
366 /// Receiver for AgentActions (TUI → Orchestrator)
367 pub type ActionReceiver = mpsc::UnboundedReceiver<AgentAction>;
368
369 /// Create event channel (Orchestrator → TUI)
370 pub fn event_channel() -> (EventSender, EventReceiver) {
371 mpsc::unbounded_channel()
372 }
373
374 /// Create action channel (TUI → Orchestrator)
375 pub fn action_channel() -> (ActionSender, ActionReceiver) {
376 mpsc::unbounded_channel()
377 }
378}