subagent: report turn liveness, and stop pre-checking continue
`status` could only answer running / starting / idle / killed / none, because every turn ran against `&NoopSink` and the whole stream-json stream was discarded. "Running" describes a wedged subagent exactly as well as a busy one, leaving a caller to tell them apart from `ps` output and CPU-time deltas. So the daemon now keeps a `name -> last_event_at` clock, bumped by `LivenessSink` on every line of every stream — stream-json events, plain stdout chatter and stderr alike — and `status` reports its age on a running answer: a few seconds means working, an age climbing into the minutes with no end-of-turn todo means wedged. Nothing is read out of the content; classifying *what* a subagent is doing is a separate question and waits on its own driver work. In memory with the rest of this daemon's state, dropped when the turn ends, no persistence. The clock is seeded at the spawn rather than at the first line, so a subagent that wedged before emitting anything still reports a climbing age rather than no age at all — the case an age is worth most in. Separately, `continue`'s existence pre-check is gone. It could only repeat the lookup `Claude::spawn` was about to do, and its message — "no session named `x` exists" — was false in the common failure: the session existed, just not under the claude home + cwd `build_store` resolved from. claude's own `--resume` is the authority and exits non-zero (`does not match any session title`) rather than quietly starting a fresh session, so the turn fails on its own. `classify_end` appends the one fact the CLI's message lacks — the directory searched: claude error: no session matched the requested id or title (searched <claude_home> for cwd <cwd>; if the session was started elsewhere, pass `dir`) The `dirs` map's durability is untouched; whether to persist it stays an open operator decision. Module doc, `docs/tools/subagent.md`, the `continue`/`status` tool descriptions and the `base:claude-subagents` skill all updated — including `continue`'s `dir` doc, which said "the daemon remembers it" without saying that a restart is both when it forgets and when you most want it. Refs #4330 Refs #4405
This commit is contained in:
parent
30fa54cbc6
commit
307df77948
4 changed files with 490 additions and 56 deletions
|
|
@ -26,12 +26,57 @@ Served under the `subagent` MCP server (`mcp__subagent__<tool>`): `start`,
|
|||
|
||||
## State
|
||||
|
||||
In-memory only: a map of currently running processes, live only as long
|
||||
as the daemon process is. A daemon restart stops whatever was running
|
||||
rather than adopting it. The durable record of a subagent's existence is
|
||||
claude's own on-disk session (`hive_claude::SessionStore`), which
|
||||
`continue` reattaches to independent of the daemon's own lifetime — a
|
||||
restart loses the _in-flight turn_, not the subagent's history.
|
||||
In-memory only: what's running now, where each name's session lives, how
|
||||
each name's last turn ended, and when each running turn last produced
|
||||
output. All of it lives only as long as the daemon process does. A daemon
|
||||
restart stops whatever was running rather than adopting it. The durable
|
||||
record of a subagent's existence is claude's own on-disk session
|
||||
(`hive_claude::SessionStore`), which `continue` reattaches to independent
|
||||
of the daemon's own lifetime — a restart loses the _in-flight turn_, not
|
||||
the subagent's history.
|
||||
|
||||
Because the remembered directory goes with the rest of it, a `continue`
|
||||
after a restart has to re-supply `dir` when the session lives anywhere
|
||||
other than the daemon's own working directory — and a restart is the
|
||||
situation you reach for `continue` in most often.
|
||||
|
||||
## Is it working, or is it wedged?
|
||||
|
||||
`status` reporting **running** says a process is tracked, which a wedged
|
||||
subagent satisfies as fully as a busy one. A running answer therefore
|
||||
carries the age of that turn's last event too: seconds means it's working,
|
||||
an age climbing into the minutes with no end-of-turn todo means it's
|
||||
stuck. That one number replaces inferring the same thing from `ps` output
|
||||
and CPU-time deltas.
|
||||
|
||||
Every line the subagent's `claude` process writes bumps the timestamp —
|
||||
stream-json events, plain stdout chatter and stderr alike — and nothing
|
||||
about the content is inspected. The record says the child is alive, not
|
||||
what it's doing. The clock starts at the spawn, so a subagent that wedged
|
||||
before it ever emitted anything still reports a climbing age rather than
|
||||
no age at all. It's dropped when the turn ends, since a finished turn has
|
||||
no progress left to describe.
|
||||
|
||||
## A `continue` that finds no session
|
||||
|
||||
`continue` doesn't check for the session before spawning. claude's own
|
||||
`--resume` is the authority, and it exits non-zero rather than quietly
|
||||
starting a fresh session, so the check could only duplicate the lookup
|
||||
the driver was about to do — while answering as though the session were
|
||||
gone. The usual truth is that the session exists somewhere else.
|
||||
|
||||
`continue` returns as soon as the process is confirmed running, same as
|
||||
always, so the failure lands where every other failed turn lands: that
|
||||
turn's end-of-turn todo, carrying claude's own message plus the location
|
||||
this daemon searched.
|
||||
|
||||
```
|
||||
claude error: no session matched the requested id or title (searched /home/agent/.claude for cwd /home/agent/work; if the session was started elsewhere, pass `dir`)
|
||||
```
|
||||
|
||||
The directory is the part claude's own message never names, and the part
|
||||
that resolves the confusion: pass `dir` to point `continue` at the
|
||||
directory the session was started in.
|
||||
|
||||
## A killed turn
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue