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
283/// Type of action requiring approval
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub enum ActionType {
286 /// File creation or modification
287 FileWrite { path: String },
288 /// Shell command execution
289 Command { command: String },
290 /// Multiple files in a sub-graph
291 SubGraph { node_count: usize },
292 /// Project initialization (with editable name)
293 ProjectInit {
294 command: String,
295 suggested_name: String,
296 },
297 /// PSP-5: Multi-file artifact bundle write
298 BundleWrite {
299 /// Node that produced the bundle
300 node_id: String,
301 /// Files being written or modified
302 files: Vec<String>,
303 },
304}
305
306/// Actions sent from TUI to Orchestrator
307#[derive(Debug, Clone, Serialize, Deserialize)]
308pub enum AgentAction {
309 /// Approve a pending request
310 Approve { request_id: String },
311 /// Approve with an edited value (e.g., project name)
312 ApproveWithEdit {
313 request_id: String,
314 edited_value: String,
315 },
316 /// Reject a pending request
317 Reject {
318 request_id: String,
319 reason: Option<String>,
320 },
321 /// Pause orchestration
322 Pause,
323 /// Resume orchestration
324 Resume,
325 /// Abort the entire session
326 Abort,
327 /// PSP-5 Phase 7: Request correction with structured user feedback
328 RequestCorrection {
329 request_id: String,
330 feedback: String,
331 },
332}
333
334/// Channel types for agent communication
335pub mod channel {
336 use super::{AgentAction, AgentEvent};
337 use tokio::sync::mpsc;
338
339 /// Sender for AgentEvents (Orchestrator → TUI)
340 pub type EventSender = mpsc::UnboundedSender<AgentEvent>;
341 /// Receiver for AgentEvents (Orchestrator → TUI)
342 pub type EventReceiver = mpsc::UnboundedReceiver<AgentEvent>;
343
344 /// Sender for AgentActions (TUI → Orchestrator)
345 pub type ActionSender = mpsc::UnboundedSender<AgentAction>;
346 /// Receiver for AgentActions (TUI → Orchestrator)
347 pub type ActionReceiver = mpsc::UnboundedReceiver<AgentAction>;
348
349 /// Create event channel (Orchestrator → TUI)
350 pub fn event_channel() -> (EventSender, EventReceiver) {
351 mpsc::unbounded_channel()
352 }
353
354 /// Create action channel (TUI → Orchestrator)
355 pub fn action_channel() -> (ActionSender, ActionReceiver) {
356 mpsc::unbounded_channel()
357 }
358}