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
231/// Node status for TUI display (mirrors NodeState but simplified)
232#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
233pub enum NodeStatus {
234 /// Queued, waiting for dependencies
235 Queued,
236 /// Planning phase (Architect)
237 Planning,
238 Pending,
239 /// Active coding / implementation phase
240 Coding,
241 Running,
242 Verifying,
243 /// PSP-5: Node is retrying after a failed verification
244 Retrying,
245 /// PSP-5 Phase 7: Sheaf consistency check underway
246 SheafCheck,
247 /// PSP-5 Phase 7: Committing stable state to ledger
248 Committing,
249 Completed,
250 Failed,
251 Escalated,
252}
253
254impl From<crate::types::NodeState> for NodeStatus {
255 fn from(state: crate::types::NodeState) -> Self {
256 use crate::types::NodeState;
257 match state {
258 NodeState::TaskQueued => NodeStatus::Queued,
259 NodeState::Planning => NodeStatus::Planning,
260 NodeState::Coding => NodeStatus::Coding,
261 NodeState::Verifying => NodeStatus::Verifying,
262 NodeState::Retry => NodeStatus::Retrying,
263 NodeState::SheafCheck => NodeStatus::SheafCheck,
264 NodeState::Committing => NodeStatus::Committing,
265 NodeState::Escalated => NodeStatus::Escalated,
266 NodeState::Completed => NodeStatus::Completed,
267 NodeState::Failed => NodeStatus::Failed,
268 NodeState::Aborted => NodeStatus::Failed,
269 }
270 }
271}
272
273/// Type of action requiring approval
274#[derive(Debug, Clone, Serialize, Deserialize)]
275pub enum ActionType {
276 /// File creation or modification
277 FileWrite { path: String },
278 /// Shell command execution
279 Command { command: String },
280 /// Multiple files in a sub-graph
281 SubGraph { node_count: usize },
282 /// Project initialization (with editable name)
283 ProjectInit {
284 command: String,
285 suggested_name: String,
286 },
287 /// PSP-5: Multi-file artifact bundle write
288 BundleWrite {
289 /// Node that produced the bundle
290 node_id: String,
291 /// Files being written or modified
292 files: Vec<String>,
293 },
294}
295
296/// Actions sent from TUI to Orchestrator
297#[derive(Debug, Clone, Serialize, Deserialize)]
298pub enum AgentAction {
299 /// Approve a pending request
300 Approve { request_id: String },
301 /// Approve with an edited value (e.g., project name)
302 ApproveWithEdit {
303 request_id: String,
304 edited_value: String,
305 },
306 /// Reject a pending request
307 Reject {
308 request_id: String,
309 reason: Option<String>,
310 },
311 /// Pause orchestration
312 Pause,
313 /// Resume orchestration
314 Resume,
315 /// Abort the entire session
316 Abort,
317 /// PSP-5 Phase 7: Request correction with structured user feedback
318 RequestCorrection {
319 request_id: String,
320 feedback: String,
321 },
322}
323
324/// Channel types for agent communication
325pub mod channel {
326 use super::{AgentAction, AgentEvent};
327 use tokio::sync::mpsc;
328
329 /// Sender for AgentEvents (Orchestrator → TUI)
330 pub type EventSender = mpsc::UnboundedSender<AgentEvent>;
331 /// Receiver for AgentEvents (Orchestrator → TUI)
332 pub type EventReceiver = mpsc::UnboundedReceiver<AgentEvent>;
333
334 /// Sender for AgentActions (TUI → Orchestrator)
335 pub type ActionSender = mpsc::UnboundedSender<AgentAction>;
336 /// Receiver for AgentActions (TUI → Orchestrator)
337 pub type ActionReceiver = mpsc::UnboundedReceiver<AgentAction>;
338
339 /// Create event channel (Orchestrator → TUI)
340 pub fn event_channel() -> (EventSender, EventReceiver) {
341 mpsc::unbounded_channel()
342 }
343
344 /// Create action channel (TUI → Orchestrator)
345 pub fn action_channel() -> (ActionSender, ActionReceiver) {
346 mpsc::unbounded_channel()
347 }
348}