systemprompt_cli/commands/admin/agents/
process_stop.rs1use std::time::Duration;
9
10use systemprompt_agent::services::agent_orchestration::{AgentOrchestrator, AgentStatus};
11use systemprompt_identifiers::{AgentName, ServiceName};
12use systemprompt_loader::subprocess::{self, ChildKind, StopOutcome};
13use systemprompt_logging::CliService;
14use systemprompt_scheduler::port_holders;
15
16const AGENT_STOP_GRACE: Duration = Duration::from_secs(2);
17
18pub async fn stop_agent_process(
19 agent_name: &AgentName,
20 agent_port: Option<u16>,
21 orchestrator: &AgentOrchestrator,
22) -> bool {
23 let recorded_pid = match orchestrator.get_status(agent_name).await {
24 Ok(AgentStatus::Running { pid, .. }) => Some(pid),
25 Ok(AgentStatus::Failed { .. }) => None,
26 Err(e) => {
27 tracing::warn!(
28 agent = %agent_name,
29 error = %e,
30 "Could not read the agent's recorded process"
31 );
32 return false;
33 },
34 };
35
36 match orchestrator.delete_agent(agent_name).await {
37 Ok(()) => {
38 tracing::debug!(agent = %agent_name, "Agent stopped via orchestrator");
39 true
40 },
41 Err(e) => {
42 tracing::warn!(
43 agent = %agent_name,
44 error = %e,
45 "Orchestrator termination failed; stopping only a verified agent process"
46 );
47 stop_verified_port_holder(agent_name, agent_port, recorded_pid).await
48 },
49 }
50}
51
52pub async fn stop_verified_port_holder(
53 agent_name: &AgentName,
54 agent_port: Option<u16>,
55 recorded_pid: Option<u32>,
56) -> bool {
57 let Some(port) = agent_port else {
58 return recorded_pid.is_none();
59 };
60
61 let holders = match port_holders(port).await {
62 Ok(holders) => holders,
63 Err(e) => {
64 tracing::warn!(
65 agent = %agent_name,
66 port,
67 error = %e,
68 "Could not read the port's holders"
69 );
70 return false;
71 },
72 };
73 let Some(&holder) = holders.first() else {
74 tracing::debug!(agent = %agent_name, port, "No process on port; agent is stopped");
75 return true;
76 };
77
78 if let Some(&stranger) = holders.iter().find(|pid| Some(**pid) != recorded_pid) {
79 CliService::warning(&format!(
80 "Process {stranger} holds port {port} but is not the recorded process of agent \
81 '{agent_name}'; refusing to kill it"
82 ));
83 return false;
84 }
85
86 CliService::info(&format!(
87 "Stopping agent '{}' (pid {}) on port {}...",
88 agent_name, holder, port
89 ));
90 let service = ServiceName::of_agent(agent_name);
91 match subprocess::stop_owned(holder, ChildKind::Agent, &service, AGENT_STOP_GRACE).await {
92 Ok(StopOutcome::Stopped(_) | StopOutcome::NotRunning) => true,
93 Ok(StopOutcome::NotOurs) => {
94 CliService::warning(&format!(
95 "Process {holder} is agent '{agent_name}''s recorded pid but carries no matching \
96 spawn marker; refusing to kill it"
97 ));
98 false
99 },
100 Err(e) => {
101 tracing::warn!(
102 agent = %agent_name,
103 port,
104 pid = holder,
105 error = %e,
106 "Failed to stop agent process"
107 );
108 false
109 },
110 }
111}