Skip to main content

brainos_terminal/
graph.rs

1//! Optional graph-mirror hook for the Terminal Bridge.
2//!
3//! When wired with a `TerminalGraphSink`, every session lifecycle
4//! emits two graph events: a `record_open` at session creation that
5//! returns the node handles needed to wire the close edge, and a
6//! `record_close` that lands the final `terminal_event(close)` node
7//! and the second `causal_produced` edge.
8//!
9//! Defining the trait here (rather than pulling in `hippocampus`)
10//! keeps this crate's dep surface storage-free. The concrete
11//! `EpisodicGraph`-backed impl lives in `brainos-signal` where both
12//! crates are already in scope.
13
14use async_trait::async_trait;
15use identity::Principal;
16
17/// Node ids produced by [`TerminalGraphSink::record_open`]. Kept on
18/// the [`crate::SessionRegistry`] so the matching `record_close` can
19/// wire its edge back to the open event.
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct TerminalGraphHandles {
22    /// `node_kind = "tool_call"`, body describes the
23    /// `terminal.open` invocation.
24    pub tool_call_node_id: String,
25    /// `node_kind = "terminal_event"`, body describes the open-side
26    /// event for this `session_id`. Acts as the parent of the close
27    /// event so traversal can reconstruct the full lifecycle.
28    pub open_event_node_id: String,
29}
30
31/// Errors a [`TerminalGraphSink`] impl can surface.
32#[derive(Debug, thiserror::Error)]
33pub enum MirrorError {
34    #[error("graph mirror error: {0}")]
35    Backend(String),
36}
37
38/// The bridge's hook into a graph store. Failures are caller-visible
39/// because a graph-write failure during a terminal lifecycle is a
40/// data-loss event the operator wants to see — `TerminalSvc` logs at
41/// `warn!` and continues so the PTY itself keeps working.
42#[async_trait]
43pub trait TerminalGraphSink: Send + Sync {
44    async fn record_open(
45        &self,
46        session_id: &str,
47        program: &str,
48        args: &[String],
49        cwd: Option<&str>,
50        principal: Option<&Principal>,
51    ) -> Result<TerminalGraphHandles, MirrorError>;
52
53    async fn record_close(
54        &self,
55        handles: &TerminalGraphHandles,
56        session_id: &str,
57        exit_code: i32,
58        was_killed: bool,
59    ) -> Result<(), MirrorError>;
60}