subagent daemon: report a killed session as killed, not idle
A subagent whose claude process died on a signal — the kernel's OOM
killer, a stopped unit, an `interrupt` — was indistinguishable from one
that finished its turn: its entry left the `running` map, `status` fell
through to "a session exists on disk" and answered `idle`, and the
end-of-turn todo said the subagent had "finished". The usual next move
on that reading is `continue`, which resumes work that was cut mid-turn
with nothing having recorded that it was cut.
The driver already preserves how the child ended — `RunningClaude::wait`
returns `Error::Exit` carrying the `ExitStatus`, whose `signal()` is the
whole answer — so this reads it rather than having to recover it:
`classify_end` turns the outcome into `Complete` / `Killed { signal }` /
`Failed`, and `State::finish_turn` remembers a kill against the name
(cleared by the next confirmed spawn under it).
What an agent sees as a result:
- `status` reports the session killed, naming the signal, instead of idle;
- the todo the daemon pushes without being asked says the subagent was
KILLED mid-turn rather than that it finished;
- `continue` still resumes such a session, but its reply says the
previous turn was killed, so no caller carries on from cut-off work
believing it was complete.
Refs #4326
This commit is contained in:
parent
fde4a36b93
commit
f817e27d4c
5 changed files with 383 additions and 29 deletions
|
|
@ -131,7 +131,9 @@ impl SubagentMcp {
|
|||
because its previous turn finished and you have a follow-up instruction, or you're \
|
||||
reattaching after this daemon restarted (the session itself survives independently \
|
||||
of the daemon that spawned it). Returns as soon as confirmed running, same as \
|
||||
`start`. Refuses a name with no session on disk at all, or one already running."
|
||||
`start`. Refuses a name with no session on disk at all, or one already running. \
|
||||
Resuming a session whose last turn was killed is allowed — the reply says so, since \
|
||||
that turn's work stopped wherever it had got to."
|
||||
)]
|
||||
fn r#continue(&self, Parameters(args): Parameters<ContinueArgs>) -> String {
|
||||
match session::continue_(
|
||||
|
|
@ -164,8 +166,10 @@ impl SubagentMcp {
|
|||
description = "Report whether a subagent is currently running — a zero-cost check that \
|
||||
never launches a process, unlike `continue`. Distinguishes running, starting (a \
|
||||
`start`/`continue` is in flight but not yet a confirmed spawn — this is normally \
|
||||
over in well under a second), idle (a session exists but nothing is in flight — \
|
||||
`continue` to give it another turn), and no such session at all."
|
||||
over in well under a second), killed (its last turn died on a signal — the kernel's \
|
||||
OOM killer, a stopped unit, an `interrupt` — so its work was cut off mid-turn rather \
|
||||
than finished), idle (a session exists, its last turn finished, nothing is in flight \
|
||||
— `continue` to give it another turn), and no such session at all."
|
||||
)]
|
||||
fn status(&self, Parameters(args): Parameters<StatusArgs>) -> String {
|
||||
match session::status(&self.state, &args.name, args.dir.as_deref()) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue