Watch
1
0
Fork
You've already forked souveraine
0

api+remote: ambient sensorium, cancel/interject verbs, SSE event mirror

SendMessageRequest gains ambient (surface sensorium note). RemoteBackend
gains spawn_cancel_watch + spawn_interject_pump so TUI remote mode has the
same interrupt/type-while-busy semantics as local. StreamEvent is now the
full wire mirror of BackendEvent — a new engine event is a compile error
at the SSE seam, not a silent skip.
This commit is contained in:
Fimeg 2026-07-12 14:56:48 -04:00
commit 165d814492
8 changed files with 439 additions and 79 deletions

View file

@ -214,9 +214,26 @@ pub struct SendMessageRequest {
pub messages: Vec<Message>,
#[serde(default)]
pub stream: bool,
/// Ambient context from the sending surface — what the environment
/// senses at the moment of speaking: active window, open apps, cursor
/// position, device sensors. Injected as a system note before the user
/// message so the agent perceives the room she is being spoken to in.
#[serde(default)]
pub ambient: Option<String>,
}
#[derive(Debug, Clone, Serialize)]
#[derive(Debug, Deserialize)]
pub struct InterjectRequest {
pub text: String,
}
/// The full wire mirror of [`crate::backend::BackendEvent`].
///
/// Every engine event crosses the SSE boundary — no silent skips. The
/// exhaustive `From` impls in both directions mean a new `BackendEvent`
/// variant is a compile error here, not an invisible hole in every
/// non-TUI surface. Tag values are the wire contract; never rename.
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "message_type")]
pub enum StreamEvent {
#[serde(rename = "assistant_message")]
@ -224,7 +241,11 @@ pub enum StreamEvent {
#[serde(rename = "reasoning_message")]
ReasoningMessage { content: String },
#[serde(rename = "tool_call_message")]
ToolCallMessage { tool_call: ToolCall },
ToolCallMessage {
tool_call: ToolCall,
#[serde(default)]
round: u32,
},
#[serde(rename = "tool_return_message")]
ToolReturnMessage { tool_return: ToolReturn },
#[serde(rename = "souveraine_surfacing")]
@ -233,6 +254,38 @@ pub enum StreamEvent {
Reflection { content: String },
#[serde(rename = "souveraine_archivist")]
Archivist { synthesis: String, pressure: f32 },
#[serde(rename = "compaction_warning")]
CompactionWarning { pressure: f32, tier: u8 },
#[serde(rename = "context_pressure")]
ContextPressure { pressure: f32, tokens: usize },
#[serde(rename = "inference_strain")]
InferenceStrain { attempt: u32, status: u16, model: String },
#[serde(rename = "schedule_active")]
ScheduleActive { name: String },
#[serde(rename = "schedule_complete")]
ScheduleComplete { name: String, silent: bool },
#[serde(rename = "subconscious_token")]
SubconsciousToken { content: String },
#[serde(rename = "subconscious_tool_call")]
SubconsciousToolCall { name: String, arguments: String },
#[serde(rename = "subconscious_tool_result")]
SubconsciousToolResult { name: String, output: String, is_error: bool },
#[serde(rename = "subconscious_halt")]
SubconsciousHalt { reason: String, severity: String },
#[serde(rename = "subconscious_pass")]
SubconsciousPass { active: bool },
#[serde(rename = "atmosphere")]
Atmosphere { preset: String },
#[serde(rename = "itinerary")]
Itinerary { route: String },
#[serde(rename = "outfit")]
Outfit { name: String },
#[serde(rename = "interstitial")]
Interstitial { text: String, register: crate::backend::Register },
#[serde(rename = "primary_complete")]
PrimaryComplete,
#[serde(rename = "done")]
Done,
#[serde(rename = "ping")]
Ping,
}
@ -247,15 +300,125 @@ impl StreamEvent {
StreamEvent::Surfacing { .. } => "souveraine_surfacing",
StreamEvent::Reflection { .. } => "souveraine_reflection",
StreamEvent::Archivist { .. } => "souveraine_archivist",
StreamEvent::CompactionWarning { .. } => "compaction_warning",
StreamEvent::ContextPressure { .. } => "context_pressure",
StreamEvent::InferenceStrain { .. } => "inference_strain",
StreamEvent::ScheduleActive { .. } => "schedule_active",
StreamEvent::ScheduleComplete { .. } => "schedule_complete",
StreamEvent::SubconsciousToken { .. } => "subconscious_token",
StreamEvent::SubconsciousToolCall { .. } => "subconscious_tool_call",
StreamEvent::SubconsciousToolResult { .. } => "subconscious_tool_result",
StreamEvent::SubconsciousHalt { .. } => "subconscious_halt",
StreamEvent::SubconsciousPass { .. } => "subconscious_pass",
StreamEvent::Atmosphere { .. } => "atmosphere",
StreamEvent::Itinerary { .. } => "itinerary",
StreamEvent::Outfit { .. } => "outfit",
StreamEvent::Interstitial { .. } => "interstitial",
StreamEvent::PrimaryComplete => "primary_complete",
StreamEvent::Done => "done",
StreamEvent::Ping => "ping",
}
}
}
#[derive(Debug, Clone, Serialize)]
impl From<crate::backend::BackendEvent> for StreamEvent {
fn from(be: crate::backend::BackendEvent) -> Self {
use crate::backend::BackendEvent as BE;
match be {
BE::Token(content) => Self::AssistantMessage { content },
BE::Reasoning(content) => Self::ReasoningMessage { content },
BE::Surfacing { source, content, priority } => Self::Surfacing { source, content, priority },
BE::Reflection(content) => Self::Reflection { content },
BE::Archivist { synthesis, pressure } => Self::Archivist { synthesis, pressure },
BE::CompactionWarning { pressure, tier } => Self::CompactionWarning { pressure, tier },
BE::ContextPressure(pressure, tokens) => Self::ContextPressure { pressure, tokens },
BE::InferenceStrain { attempt, status, model } => Self::InferenceStrain { attempt, status, model },
BE::ScheduleActive { name } => Self::ScheduleActive { name },
BE::ScheduleComplete { name, silent } => Self::ScheduleComplete { name, silent },
BE::ToolCall { id, name, arguments, round } => Self::ToolCallMessage {
tool_call: ToolCall { id, function: ToolFunction { name, arguments } },
round,
},
BE::ToolResult { id, name, output, is_error } => Self::ToolReturnMessage {
tool_return: ToolReturn {
status: if is_error { "error".into() } else { "success".into() },
output,
id,
name,
},
},
BE::SubconsciousToken(content) => Self::SubconsciousToken { content },
BE::SubconsciousToolCall { name, arguments } => Self::SubconsciousToolCall { name, arguments },
BE::SubconsciousToolResult { name, output, is_error } => {
Self::SubconsciousToolResult { name, output, is_error }
}
BE::SubconsciousHalt { reason, severity } => Self::SubconsciousHalt { reason, severity },
BE::SubconsciousPass(active) => Self::SubconsciousPass { active },
BE::Atmosphere(preset) => Self::Atmosphere { preset },
BE::Itinerary(route) => Self::Itinerary { route },
BE::Outfit(name) => Self::Outfit { name },
BE::Interstitial { text, register } => Self::Interstitial { text, register },
BE::Keepalive => Self::Ping,
BE::PrimaryComplete => Self::PrimaryComplete,
BE::Done => Self::Done,
}
}
}
impl From<StreamEvent> for crate::backend::BackendEvent {
fn from(se: StreamEvent) -> Self {
use crate::backend::BackendEvent as BE;
match se {
StreamEvent::AssistantMessage { content } => BE::Token(content),
StreamEvent::ReasoningMessage { content } => BE::Reasoning(content),
StreamEvent::Surfacing { source, content, priority } => BE::Surfacing { source, content, priority },
StreamEvent::Reflection { content } => BE::Reflection(content),
StreamEvent::Archivist { synthesis, pressure } => BE::Archivist { synthesis, pressure },
StreamEvent::CompactionWarning { pressure, tier } => BE::CompactionWarning { pressure, tier },
StreamEvent::ContextPressure { pressure, tokens } => BE::ContextPressure(pressure, tokens),
StreamEvent::InferenceStrain { attempt, status, model } => BE::InferenceStrain { attempt, status, model },
StreamEvent::ScheduleActive { name } => BE::ScheduleActive { name },
StreamEvent::ScheduleComplete { name, silent } => BE::ScheduleComplete { name, silent },
StreamEvent::ToolCallMessage { tool_call, round } => BE::ToolCall {
id: tool_call.id,
name: tool_call.function.name,
arguments: tool_call.function.arguments,
round,
},
StreamEvent::ToolReturnMessage { tool_return } => BE::ToolResult {
id: tool_return.id,
name: tool_return.name,
is_error: tool_return.status == "error",
output: tool_return.output,
},
StreamEvent::SubconsciousToken { content } => BE::SubconsciousToken(content),
StreamEvent::SubconsciousToolCall { name, arguments } => BE::SubconsciousToolCall { name, arguments },
StreamEvent::SubconsciousToolResult { name, output, is_error } => {
BE::SubconsciousToolResult { name, output, is_error }
}
StreamEvent::SubconsciousHalt { reason, severity } => BE::SubconsciousHalt { reason, severity },
StreamEvent::SubconsciousPass { active } => BE::SubconsciousPass(active),
StreamEvent::Atmosphere { preset } => BE::Atmosphere(preset),
StreamEvent::Itinerary { route } => BE::Itinerary(route),
StreamEvent::Outfit { name } => BE::Outfit(name),
StreamEvent::Interstitial { text, register } => BE::Interstitial { text, register },
StreamEvent::Ping => BE::Keepalive,
StreamEvent::PrimaryComplete => BE::PrimaryComplete,
StreamEvent::Done => BE::Done,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ToolReturn {
pub status: String,
pub output: String,
/// Tool-call id this return answers. Empty on frames from pre-widening servers.
#[serde(default)]
pub id: String,
/// Tool name. Empty on frames from pre-widening servers.
#[serde(default)]
pub name: String,
}
#[derive(Debug, Serialize)]