Ingest pair-scoped runtime lifecycle frames

Co-authored-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>
Signed-off-by: npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf <95cae996907d7cab9f5dbf43c0f53edeac6ab0b032a6feae4abfd784e467b3f5@sprout-oss.stage.blox.sqprod.co>

Co-authored-by: Tyler Longwell <tlongwell@block.xyz>
Signed-off-by: Tyler Longwell <tlongwell@block.xyz>
This commit is contained in:
npub1jh9wn95s0472h86ahapupaf7m6kx4v9sx2n0atj2hltcfer8k06s5n3pyf
2026-07-19 11:03:36 -04:00
co-authored by Tyler Longwell
parent 0a1f5dd46a
commit d172aaf601
3 changed files with 54 additions and 1 deletions
+2 -1
View File
@@ -49,7 +49,7 @@ use huddle::{
use managed_agents::{
backfill_persona_snapshots, ensure_nest, list_managed_agent_runtimes,
reconcile_managed_agent_runtimes, restart_managed_agent_runtime, start_managed_agent_runtime,
stop_managed_agent_runtime, try_regenerate_nest,
stop_managed_agent_runtime, put_managed_agent_runtime_lifecycle, try_regenerate_nest,
};
#[cfg(not(feature = "mesh-llm"))]
use mesh_llm_stubs::*;
@@ -866,6 +866,7 @@ pub fn run() {
stop_managed_agent_runtime,
restart_managed_agent_runtime,
reconcile_managed_agent_runtimes,
put_managed_agent_runtime_lifecycle,
create_managed_agent,
start_managed_agent,
stop_managed_agent,
@@ -47,6 +47,49 @@ fn emit_status(app: &AppHandle, status: &ManagedAgentRuntimeStatus) {
let _ = app.emit(STATUS_EVENT, status);
}
#[tauri::command]
pub fn put_managed_agent_runtime_lifecycle(
outer_pubkey: String,
payload: super::ManagedAgentRuntimeLifecycleObserverPayload,
app: AppHandle,
) -> Result<ManagedAgentRuntimeStatus, String> {
if outer_pubkey.to_ascii_lowercase() != payload.pubkey.to_ascii_lowercase() {
return Err("observer signer does not match lifecycle payload pubkey".into());
}
if matches!(
payload.lifecycle,
ManagedAgentRuntimeLifecycle::Starting | ManagedAgentRuntimeLifecycle::Stopped
) {
return Err("observer cannot author starting or stopped lifecycle".into());
}
if payload.lifecycle == ManagedAgentRuntimeLifecycle::Failed && payload.error.is_none() {
return Err("failed lifecycle requires an error".into());
}
if payload.lifecycle != ManagedAgentRuntimeLifecycle::Failed && payload.error.is_some() {
return Err("lifecycle error is only valid for failed".into());
}
let state = app.state::<AppState>();
let key = ManagedAgentRuntimeKey::new(payload.pubkey, &payload.relay_url)?;
let records = load_managed_agents(&app)?;
let record = records
.iter()
.find(|record| record.pubkey.eq_ignore_ascii_case(&key.pubkey))
.ok_or_else(|| format!("agent {} not found", key.pubkey))?;
let mut runtimes = state.managed_agent_processes.lock().map_err(|e| e.to_string())?;
let runtime = runtimes
.get_mut(&key)
.ok_or_else(|| "lifecycle frame does not match a tracked runtime pair".to_string())?;
if runtime.child.try_wait().map_err(|e| e.to_string())?.is_some() {
return Err("lifecycle frame arrived after process exit".into());
}
runtime.lifecycle = payload.lifecycle;
runtime.error = payload.error;
let status = status_for(&app, record, &key, Some(runtime), None);
emit_status(&app, &status);
Ok(status)
}
#[tauri::command]
pub fn list_managed_agent_runtimes(app: AppHandle) -> Result<Vec<ManagedAgentRuntimeStatus>, String> {
let state = app.state::<AppState>();
@@ -85,6 +85,15 @@ pub struct ManagedAgentRuntimeStatus {
pub log_path: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ManagedAgentRuntimeLifecycleObserverPayload {
pub pubkey: String,
pub relay_url: String,
pub lifecycle: ManagedAgentRuntimeLifecycle,
pub error: Option<String>,
}
#[derive(Debug, Clone, Deserialize)]
#[serde(rename_all = "camelCase")]
pub struct ManagedAgentRuntimeTarget {