refactor: kill→stop across API surface, CLI, TUI, and docs #215

Merged
clawdie merged 1 commit from refactor/kill-to-stop into main 2026-06-26 14:42:17 +02:00
22 changed files with 64 additions and 63 deletions

View file

@ -22,7 +22,7 @@ enum Command {
system_prompt: Option<String>,
jail: Option<colibri_client::JailConfig>,
},
KillAgent {
StopAgent {
agent_id: String,
},
GetSession {
@ -84,7 +84,7 @@ fn usage() -> &'static str {
colibri [--socket PATH] list-sessions
colibri [--socket PATH] spawn-local EXECUTABLE [--session-id ID] [--system-prompt TEXT] [--jail-name NAME [--jail-root PATH]]
colibri [--socket PATH] spawn-agent PROVIDER MODEL [--session-id ID] [--system-prompt TEXT] [--jail-name NAME [--jail-root PATH]]
colibri [--socket PATH] kill AGENT_ID
colibri [--socket PATH] stop AGENT_ID
colibri [--socket PATH] get-session SESSION_ID
colibri [--socket PATH] compact-session SESSION_ID
colibri [--socket PATH] list-tasks [--status STATUS]
@ -190,7 +190,7 @@ where
})
}
}
"kill" | "kill-agent" => expect_arity(&args, 2).map(|()| Command::KillAgent {
"stop" | "stop-agent" => expect_arity(&args, 2).map(|()| Command::StopAgent {
agent_id: args[1].clone(),
}),
"get-session" => expect_arity(&args, 2).map(|()| Command::GetSession {
@ -604,7 +604,7 @@ async fn run(options: Options) -> Result<(), ClientError> {
.spawn_agent_with(provider, model, session_id, system_prompt, jail)
.await?,
),
Command::KillAgent { agent_id } => print_json(&client.kill_agent(agent_id).await?),
Command::StopAgent { agent_id } => print_json(&client.stop_agent(agent_id).await?),
Command::GetSession { session_id } => print_json(&client.get_session(session_id).await?),
Command::CompactSession { session_id } => {
print_json(&client.compact_session(session_id).await?)

View file

@ -133,11 +133,11 @@ impl DaemonClient {
.await
}
pub async fn kill_agent(
pub async fn stop_agent(
&self,
agent_id: impl Into<String>,
) -> Result<serde_json::Value, ClientError> {
self.request(&ColibriCommand::KillAgent {
self.request(&ColibriCommand::StopAgent {
agent_id: agent_id.into(),
})
.await

View file

@ -143,8 +143,8 @@ async fn daemon_client_live_socket_check_with_local_sample_agent() {
assert_eq!(pane.session_id.as_deref(), Some("manual-test"));
assert!(pane.cwd.is_some());
let kill = client.kill_agent(agent_id).await.unwrap();
assert_eq!(kill["status"], "stopped");
let stop = client.stop_agent(agent_id).await.unwrap();
assert_eq!(stop["status"], "stopped");
let _ = state.shutdown_tx.send(());
server.await.unwrap();
@ -382,9 +382,9 @@ async fn harness_double_spawn_session_isolation() {
// Verify session isolation: both share the same session_id (test agent default)
assert_eq!(pane_a.session_id, pane_b.session_id);
// Kill one agent — snapshot may still include stopped panes briefly
let kill = client.kill_agent(&pane_a.id).await.unwrap();
assert_eq!(kill["status"], "stopped");
// Stop one agent — snapshot may still include stopped panes briefly
let stop = client.stop_agent(&pane_a.id).await.unwrap();
assert_eq!(stop["status"], "stopped");
let _ = state.shutdown_tx.send(());
server.await.unwrap();

View file

@ -51,8 +51,8 @@ pub enum ColibriCommand {
#[serde(default)]
jail: Option<crate::spawner::JailConfig>,
},
#[serde(rename = "kill-agent")]
KillAgent { agent_id: String },
#[serde(rename = "stop-agent")]
StopAgent { agent_id: String },
#[serde(rename = "get-session")]
GetSession { session_id: String },
#[serde(rename = "compact-session")]

View file

@ -102,10 +102,10 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
// Give sub-tasks a moment to clean up
tokio::time::sleep(std::time::Duration::from_secs(2)).await;
// Kill any running agent subprocesses
// Stop any running agent subprocesses
for entry in shutdown_state.agents.iter() {
let handle = entry.value();
info!(agent_id = %handle.id, "killing agent");
info!(agent_id = %handle.id, "stopping agent");
let _ = handle.kill().await;
}
});

View file

@ -215,10 +215,11 @@ impl Scheduler {
store.list_agents().unwrap_or_default()
};
if let Some(agent) = pick_agent(&req.required_capabilities, &agents) {
match {
let res = {
let store = state.store.lock().unwrap();
store.claim_task(&req.task_id, &agent.id)
} {
};
match res {
Ok(_) => info!(
task_id = %req.task_id,
agent_id = %agent.id,

View file

@ -247,7 +247,7 @@ async fn dispatch(cmd: ColibriCommand, state: &SharedState) -> ColibriResponse {
)
.await
}
ColibriCommand::KillAgent { agent_id } => cmd_kill_agent(state, agent_id).await,
ColibriCommand::StopAgent { agent_id } => cmd_stop_agent(state, agent_id).await,
ColibriCommand::GetSession { session_id } => cmd_get_session(state, session_id).await,
ColibriCommand::CompactSession { session_id } => {
cmd_compact_session(state, session_id).await
@ -1028,7 +1028,7 @@ async fn cmd_spawn_agent(
}
}
async fn cmd_kill_agent(state: &SharedState, agent_id: String) -> ColibriResponse {
async fn cmd_stop_agent(state: &SharedState, agent_id: String) -> ColibriResponse {
match state.agents.remove(&agent_id) {
Some((_id, handle)) => match handle.kill().await {
Ok(()) => {
@ -1042,7 +1042,7 @@ async fn cmd_kill_agent(state: &SharedState, agent_id: String) -> ColibriRespons
"status": "stopped",
}))
}
Err(e) => ColibriResponse::err(format!("kill failed: {e}")),
Err(e) => ColibriResponse::err(format!("stop failed: {e}")),
},
None => ColibriResponse::err(format!("agent not found: {agent_id}")),
}

View file

@ -719,12 +719,12 @@ impl AgentHandle {
}
}
/// Kill the agent subprocess.
/// Stop the agent subprocess (sends SIGKILL to the tracked child).
///
/// For a jailed agent the tracked child is the wrapper (`mdo`/`jexec`/
/// `jail`); killing it removes a `jail -c command=` ephemeral jail (the jail
/// `jail`); stopping it removes a `jail -c command=` ephemeral jail (the jail
/// is torn down when its command process dies). Reaping a deeply nested
/// in-jail process tree may need a process-group kill — tracked as a
/// in-jail process tree may need a process-group stop — tracked as a
/// follow-up; see `docs/COLIBRI-JAILED-AGENT-SPAWN-DESIGN.md`.
pub async fn kill(&self) -> Result<(), SpawnerError> {
let mut child = self.child.lock().await;

View file

@ -1,7 +1,7 @@
// colibri-harness — Colibri-native supervision TUI built on Colibri primitives.
//
// Connects to a colibri-daemon Unix socket, polls GlasspaneSnapshot every 2s,
// lets the operator spawn/kill agents, drill into pane details, and cycle
// lets the operator spawn/stop agents, drill into pane details, and cycle
// through sessions — all from a color-coded ratatui dashboard.
//
// Usage:
@ -254,7 +254,7 @@ impl App {
}
}
async fn kill_selected(&mut self) {
async fn stop_selected(&mut self) {
let id = match self.selected_pane_id().map(String::from) {
Some(id) => id,
None => {
@ -262,9 +262,9 @@ impl App {
return;
}
};
match self.client.kill_agent(&id).await {
Ok(_) => self.set_status(format!("killed {id}")),
Err(e) => self.set_status(format!("kill failed: {e}")),
match self.client.stop_agent(&id).await {
Ok(_) => self.set_status(format!("stopped {id}")),
Err(e) => self.set_status(format!("stop failed: {e}")),
}
}
@ -592,7 +592,7 @@ impl App {
key("s"),
Span::raw(" spawn "),
key("x"),
Span::raw(" kill "),
Span::raw(" stop "),
key("enter"),
Span::raw(" detail "),
key("tab"),
@ -640,7 +640,7 @@ async fn run(socket_path: PathBuf) -> io::Result<()> {
}
KeyCode::Char('r') => app.refresh().await,
KeyCode::Char('s') => app.spawn_agent().await,
KeyCode::Char('x') => app.kill_selected().await,
KeyCode::Char('x') => app.stop_selected().await,
KeyCode::Enter => {
let idx = app.table_state.selected().unwrap_or(0);
let count = app.filtered_panes().len();

View file

@ -155,7 +155,7 @@ Default ISO posture should be **read-only**. Mutating tools require:
COLIBRI_MCP_WRITE=1
```
Agent spawn/kill tools are stronger than normal writes and should require a separate guard plus allowlist:
Agent spawn/stop tools are stronger than normal writes and should require a separate guard plus allowlist:
```sh
COLIBRI_MCP_SPAWN=1
@ -167,7 +167,7 @@ Phase 2 MCP tools (after basics proven):
| Tool | Description |
| ------------------------- | ------------------------------------------------ |
| `colibri_spawn_agent` | Spawn an agent with provider/model config |
| `colibri_kill_agent` | Kill a running agent |
| `colibri_stop_agent` | Stop a running agent |
| `colibri_session_summary` | Summarize an active session |
| `colibri_schedule_job` | Add a cron/interval/one-shot job |
| `colibri_search_skills` | Search built-in knowledge (via `colibri-skills`) |

View file

@ -19,7 +19,7 @@ own (no external code, no dependency).
| `q` / `Esc` | Quit, or close detail pane if open |
| `r` | Refresh snapshot now |
| `s` | Spawn a local `colibri-test-agent` |
| `x` | Kill the selected pane |
| `x` | Stop the selected pane |
| `Enter` | Open/close the detail pane for the selected row |
| `Tab` / `Shift-Tab` | Cycle through distinct sessions |
| `j` / `k` or `↓` / `↑` | Navigate the pane table |

View file

@ -52,7 +52,7 @@ The multi-host stack lives **outside the Rust daemon**:
| -------------- | --------------------------------------------------------------------------- |
| Daemon | `status`, `glasspane-snapshot`, `set-cost-mode` |
| Session | `list-sessions`, `get-session`, `compact-session` |
| Agent process | `spawn-agent`, `kill-agent` |
| Agent process | `spawn-agent`, `stop-agent` |
| Board | `list-tasks`, `create-task`, `transition-task`, `claim-task`, `intake-task` |
| Agent registry | `register-agent`, `list-agents` |
| Tenant | `register-tenant`, `list-tenants` |

View file

@ -22,14 +22,14 @@ clawdie.si → landing (unchanged)
## What needs building
| Layer | Task |
|---|---|
| DNS | `wiki.clawdie.si` A/AAAA → same host |
| TLS | New Let's Encrypt cert (acme.sh auto-renew) |
| Nginx | New vhost for wiki.clawdie.si |
| Layer | Task |
| ----- | -------------------------------------------------- |
| DNS | `wiki.clawdie.si` A/AAAA → same host |
| TLS | New Let's Encrypt cert (acme.sh auto-renew) |
| Nginx | New vhost for wiki.clawdie.si |
| Astro | Two Starlight configs from one colibri source tree |
| Build | `build-docs.sh` → dist-guide/ + dist-wiki/ |
| ISO | `FEATURE_DOCS` / `FEATURE_WIKI` toggle knobs |
| Build | `build-docs.sh` → dist-guide/ + dist-wiki/ |
| ISO | `FEATURE_DOCS` / `FEATURE_WIKI` toggle knobs |
## Two Starlight configs

View file

@ -59,7 +59,7 @@ input chain); under ufw it is redundant.
The control-plane socket has **no authentication of its own**. Once it is
bridged, any peer that can reach the host over the tailnet can issue the full
command set (`spawn-agent`, `kill-agent`, `intake-task`, `terminal-*`, …). That
command set (`spawn-agent`, `stop-agent`, `intake-task`, `terminal-*`, …). That
makes the **Tailscale boundary the access control**:
- Scope the port to named peers with a **Tailscale ACL** on `:9190` rather than

View file

@ -59,7 +59,7 @@ poganjajo ufw (privzeto sprejemajoča vhodna veriga); pod ufw je odveč.
Vtičnica krmilne ravnine **nima lastne avtentikacije**. Ko je enkrat
premoščena, lahko vsak soležnik, ki gostitelja doseže prek omrežja Tailscale,
izda celoten nabor ukazov (`spawn-agent`, `kill-agent`, `intake-task`,
izda celoten nabor ukazov (`spawn-agent`, `stop-agent`, `intake-task`,
`terminal-*`, …). Zato je **meja Tailscale nadzor dostopa**:
- vrata omejite na poimenovane soležnike s **politiko [ACL](../reference/okrajsave/#acl) v Tailscale** na

View file

@ -152,7 +152,7 @@ Bastille za izolacijo agentov in zunanjih MCP strežnikov. Glej tudi:
### jailed
**Zaprt v ječi** — stanje procesa, ki teče znotraj ječe. Proces, ki je
*zaprt v ječi*, nima dostopa do gostitelja. Nasprotje je *na prostosti*
_zaprt v ječi_, nima dostopa do gostitelja. Nasprotje je _na prostosti_
(teče na gostitelju brez izolacije).
### mother (mother node)
@ -225,5 +225,5 @@ najemnikov.
**Paznik** — upravljalec ječ. Na FreeBSD je to `jail(8)` ali `warden0`
(omrežni most ječ Bastille). Na matičnem vozlišču je to `colibri-mcp-ssh`,
ki nadzoruje, kateri ukazi so dovoljeni čez SSH. *Paznik* je slovenski izraz
ki nadzoruje, kateri ukazi so dovoljeni čez SSH. _Paznik_ je slovenski izraz
za paznika v zaporu — čuva ječo in njene zapornike.

View file

@ -130,7 +130,7 @@ new subsystem.
The snapshot API is read-heavy by design. A future write path — "send input to
pane N" over the daemon socket — would let the operator **respond** to a blocked
agent from `colibri-tui`, not just observe/spawn/kill. This is direction, not a
agent from `colibri-tui`, not just observe/spawn/stop. This is direction, not a
quick win; it changes the socket from read-only supervision to interactive
control and needs its own design pass.

View file

@ -55,7 +55,7 @@ and operator muscle memory apply to every client.
### No write-gating inside the CLI itself
Commands that mutate state (`create-task`, `kill-agent`, `set-cost-mode`,
Commands that mutate state (`create-task`, `stop-agent`, `set-cost-mode`,
`register-tenant`) are not blocked by CLI flags. The gate is the Unix socket
itself: the daemon is configured to listen on a unix socket with operator-only
permissions, and the daemon validates each command. This avoids two parallel
@ -107,7 +107,7 @@ sample close to its primary caller without adding a new crate.
| `snapshot` / `glasspane-snapshot` | current pane radar view |
| `list-sessions` | active agent sessions |
| `spawn-local` / `spawn-agent` | start an agent, optionally jailed |
| `kill AGENT_ID` | terminate a pane/agent |
| `stop AGENT_ID` | terminate a pane/agent |
| `create-task` / `intake-task` / `claim-task` / `transition-task` | task-board workflow |
| `set-cost-mode MODE` | acknowledge/toggle cost mode |
| `register-tenant` / `list-tenants` | vault provisioning bookkeeping |

View file

@ -24,11 +24,11 @@ podpira. Prav tako se izogne potrebi po odprtju drugega omrežnega vmesnika.
Strežnik MCP izpostavlja tri orodja:
| Orodje | Ukaz ozadnjega procesa | Namen |
| ------------------ | -------------------- | ----------------------------------------------- |
| `colibri_status` | `status` | Stanje ozadnjega procesa (agenti, opravila, predpomnilnik) |
| `colibri_snapshot` | `glasspane-snapshot` | Trenutni posnetek podoken Glasspane |
| `colibri_spawn` | `spawn-agent` | Zaženi novega agenta |
| Orodje | Ukaz ozadnjega procesa | Namen |
| ------------------ | ---------------------- | ---------------------------------------------------------- |
| `colibri_status` | `status` | Stanje ozadnjega procesa (agenti, opravila, predpomnilnik) |
| `colibri_snapshot` | `glasspane-snapshot` | Trenutni posnetek podoken Glasspane |
| `colibri_spawn` | `spawn-agent` | Zaženi novega agenta |
Ta tri orodja pokrivajo 90 % zunanjih interakcij. Celoten API vtičnice je na
voljo neposrednim odjemalcem vtičnice; MCP je priročna podmnožica.

View file

@ -51,7 +51,7 @@ clippy.
| Stran | Kaj pokriva |
| ----------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| [agent-harness](./agent-harness.md) | Razcep zot (agent) + Colibri (krmilna ravnina); vprega, samodejni zagon + gonilnik RPC |
| [agent-harness](./agent-harness.md) | Razcep zot (agent) + Colibri (krmilna ravnina); vprega, samodejni zagon + gonilnik RPC |
| [agent-events-reference](./agent-events-reference.md) | Referenca dogodkov zot po opremi, preslikave Glasspane in preverjena polja prepisa |
| [cost-model](./cost-model.md) | Bajtno stabilne predpone, merjenje zadetkov predpomnilnika, samodejno stopnjevanje, stiskanje T14 |
| [glasspane](./glasspane.md) | Avtomat stanj agenta, pretakanje JSONL, taksonomija AgentRuntime, API posnetkov |
@ -62,11 +62,11 @@ clippy.
| [naming-decisions](./naming-decisions.md) | Imenik preimenovanj, nevtralnih glede na opremo / arhitekturnih — dostavljenih in v teku |
| [layered-soul](./layered-soul.md) | Kako Colibri danes uporablja repozitorij pregledanega konteksta layered-soul proti načrtovanemu |
| [task-board](./task-board.md) | Točkovanje po zmožnostih, cron razporejanje, praznjenje vnosne vrste, podlaga SQLite |
| [quality-gates](./quality-gates.md) | `ci-checks.sh` kot preverjanje pred združitvijo; zakaj je odmik prej dosegel `main` |
| [quality-gates](./quality-gates.md) | `ci-checks.sh` kot preverjanje pred združitvijo; zakaj je odmik prej dosegel `main` |
| [contracts](./contracts.md) | Stabilne JSON sheme (run-manifest, runtime-inventory, provider-smoke), zlati testi |
| [store-schema](./store-schema.md) | Usklajevalna shema SQLite in disciplina migracij |
| [external-mcp](./external-mcp.md) | Most MCP za urejevalnike + zunanji gostitelj stdio MCP; dovoljenja za branje/pisanje/zunanji-klic |
| [operator-cli](./operator-cli.md) | CLI `colibri` kot tanek tipiziran odjemalec Unix vtičnice prek API ozadnjega procesa |
| [external-mcp](./external-mcp.md) | Most MCP za urejevalnike + zunanji gostitelj stdio MCP; dovoljenja za branje/pisanje/zunanji-klic |
| [operator-cli](./operator-cli.md) | CLI `colibri` kot tanek tipiziran odjemalec Unix vtičnice prek API ozadnjega procesa |
| [tui](./tui.md) | Odjemalec terminalske nadzorne plošče (colibri-tui) proti avtomatu stanj colibri-glasspane |
| [terminal](./terminal.md) | Odločitev o terminalski zmožnosti (Kitty, razširjeno poročanje tipk, prehod tmux, SSH terminfo) |
| [runtime-inventory](./runtime-inventory.md) | Popis izvajalnega okolja gostitelja + bralnik statusa čuvaja; aditivne, bralne integracije |

View file

@ -37,15 +37,15 @@ bridge and the CLI. It keeps Colibri headless-safe, which is required for an
`crates/colibri-glasspane-tui/src/main.rs` (socket resolution, refresh loop)
### TUI gets spawn/kill keys, not just read-only status
### TUI gets spawn/stop keys, not just read-only status
You can spawn a local test agent (`s`) and kill the selected pane (`x`) from
You can spawn a local test agent (`s`) and stop the selected pane (`x`) from
the dashboard. That overlaps with commands the `colibri` CLI can already do,
but the experience is different: a CLI command is one-shot; the TUI is a live
supervision surface with a selected row and an immediate status bar.
We kept the action keys because the dashboard's job is to let an operator
notice and react — spot a stalled pane and kill it without leaving the
notice and react — spot a stalled pane and stop it without leaving the
terminal.
`crates/colibri-glasspane-tui/src/main.rs` (`spawn_agent`, `kill_selected`)
@ -79,7 +79,7 @@ should be revisited.
| `q` / `Esc` | Quit, or close detail pane if open |
| `r` | Refresh snapshot now |
| `s` | Spawn a local `colibri-test-agent` |
| `x` | Kill the selected pane |
| `x` | Stop the selected pane |
| `Enter` | Open/close the detail pane for the selected row |
| `Tab` / `Shift-Tab` | Cycle through distinct sessions |
| `j` / `k` or `↓` / `↑` | Navigate the pane table |
@ -89,7 +89,7 @@ should be revisited.
Use the TUI when:
- You want a live, auto-refreshing view of all panes.
- You are picking a pane to inspect or kill visually.
- You are picking a pane to inspect or stop visually.
- You are on an SSH session with only a terminal.
Use the `colibri` CLI when:

View file

@ -76,7 +76,7 @@ doesn't have to re-derive it:
1. **The socket has no auth — the tailnet boundary is the auth.** With both
hosts bridging the control plane, any tailnet peer that can reach either host
can issue full control-plane commands (`spawn-agent`, `kill-agent`,
can issue full control-plane commands (`spawn-agent`, `stop-agent`,
`terminal-*`). Consider a Tailscale ACL scoping `:9190` to specific peers.
2. **Socket path parity.** Both sides assume `/run/colibri/colibri.sock`
(FreeBSD: `/var/run/colibri/colibri.sock`). domedog's daemon must be started