Summary
When agent lifecycle state is reported through a custom hook source (a source
not on herdr's full_lifecycle_hook_authority allow-list), herdr's own screen/process
detection can clear that pushed authority as soon as detection stops recognizing
the agent on screen. Because detection is never paused for a non-full-lifecycle source,
the pane then has neither a hook authority nor a detected agent, so
effective_agent_label() returns None, is_agent_terminal() is false, and the pane
drops out of the agent sidebar entirely (blank, no state) even while the agent is
actively working.
This is especially visible when the agent runs somewhere herdr cannot introspect
locally (e.g. inside a container/sandbox), so herdr's own detection cannot corroborate
the pushed state, yet the push is the only authoritative signal.
Environment
- herdr 0.8.2 (line references below are from a recent
main checkout, commit
9ad65d9).
- macOS host; agent runs inside a Linux container and reports state over the control
socket via pane.report_agent using a custom source (e.g. custom:sbh-pi,
agent: "pi"). Reports are coarse transitions (working/idle/blocked/release) at turn
boundaries.
Root cause
full_lifecycle_hook_authority (src/detect/mod.rs:316) only matches a fixed set of
sources (herdr:pi, herdr:omp, herdr:opencode, herdr:kilo, herdr:kimi,
herdr:mastracode). A custom source is not on it.
- Because the source is not full-lifecycle, herdr never pauses its own detection for
the pane (src/app/api.rs:438 sync_full_lifecycle_authority_detection_pauses).
- On a detection update where the previously-detected agent is lost, herdr clears the
pushed hook authority (src/terminal/state.rs:553-576 -> self.hook_authority = None
plus FullLifecycleHookSuppressionReason::HookClear).
- With the authority cleared and no agent detected,
effective_agent_label()
(src/terminal/state.rs:1812) is None, so is_agent_terminal()
(src/terminal/state.rs:2094) is false and the pane is no longer listed as an agent.
- The reporter only pushes at transitions, so nothing re-establishes the state until
the next transition; herdr also does not recover the agent when its screen marker
returns (HookClear suppression), so the pane stays blank.
Reproduction
Two identical agents (same terminal title, same cwd), both actively working, both
showing the agent's live status text on screen, reporting state via the same custom
source over the control socket:
- Pane A:
herdr pane list -> {"agent":"pi","agent_status":"working"} (correct).
- Pane B:
herdr pane list -> no agent key, "agent_status":"unknown" -> blank
in the sidebar, even though the pane is mid-work (live "working" status text visible
on screen).
Delivery is not the problem: a test pane.report_agent for a nonexistent pane returns
{"error":{"code":"pane_not_found"}}, i.e. herdr accepts and processes reports from the
custom source; the pane simply loses its agent afterward via the detection-clear path.
Expected vs actual
- Expected: a pane whose agent state is pushed by a hook keeps showing that state until
the hook says otherwise (or releases), regardless of whether herdr's own screen/process
detection currently recognizes the agent.
- Actual: herdr's own detection clears the pushed authority for a non-full-lifecycle
source, and the pane disappears from the agent list.
Suggested fix
Introduce a source category for externally/remotely reported agents that (a) pauses
herdr's own screen/process detection for the pane like full-lifecycle sources do, and
(b) stays effective without requiring local detected_agent to match -- because the
agent may run where herdr cannot introspect it (a container/sandbox).
Note a subtlety: simply adding such sources to the existing full_lifecycle_hook_authority
list is not sufficient, because hook_authority_is_effective
(src/terminal/state.rs:1805) then requires detected_agent == Some(agent) (its second
branch), which fails when herdr cannot detect the agent locally and would nullify the
pushed state. A "remote/proxy full-lifecycle" category needs to satisfy both (a) and (b).
Happy to test a patch against this setup.
Summary
When agent lifecycle state is reported through a custom hook source (a
sourcenot on herdr's
full_lifecycle_hook_authorityallow-list), herdr's own screen/processdetection can clear that pushed authority as soon as detection stops recognizing
the agent on screen. Because detection is never paused for a non-full-lifecycle source,
the pane then has neither a hook authority nor a detected agent, so
effective_agent_label()returnsNone,is_agent_terminal()is false, and the panedrops out of the agent sidebar entirely (blank, no state) even while the agent is
actively working.
This is especially visible when the agent runs somewhere herdr cannot introspect
locally (e.g. inside a container/sandbox), so herdr's own detection cannot corroborate
the pushed state, yet the push is the only authoritative signal.
Environment
maincheckout, commit9ad65d9).socket via
pane.report_agentusing a customsource(e.g.custom:sbh-pi,agent: "pi"). Reports are coarse transitions (working/idle/blocked/release) at turnboundaries.
Root cause
full_lifecycle_hook_authority(src/detect/mod.rs:316) only matches a fixed set ofsources (
herdr:pi,herdr:omp,herdr:opencode,herdr:kilo,herdr:kimi,herdr:mastracode). A custom source is not on it.the pane (
src/app/api.rs:438 sync_full_lifecycle_authority_detection_pauses).pushed hook authority (
src/terminal/state.rs:553-576->self.hook_authority = Noneplus
FullLifecycleHookSuppressionReason::HookClear).effective_agent_label()(
src/terminal/state.rs:1812) isNone, sois_agent_terminal()(
src/terminal/state.rs:2094) is false and the pane is no longer listed as an agent.the next transition; herdr also does not recover the agent when its screen marker
returns (HookClear suppression), so the pane stays blank.
Reproduction
Two identical agents (same terminal title, same cwd), both actively working, both
showing the agent's live status text on screen, reporting state via the same custom
source over the control socket:
herdr pane list->{"agent":"pi","agent_status":"working"}(correct).herdr pane list-> noagentkey,"agent_status":"unknown"-> blankin the sidebar, even though the pane is mid-work (live "working" status text visible
on screen).
Delivery is not the problem: a test
pane.report_agentfor a nonexistent pane returns{"error":{"code":"pane_not_found"}}, i.e. herdr accepts and processes reports from thecustom source; the pane simply loses its agent afterward via the detection-clear path.
Expected vs actual
the hook says otherwise (or releases), regardless of whether herdr's own screen/process
detection currently recognizes the agent.
source, and the pane disappears from the agent list.
Suggested fix
Introduce a source category for externally/remotely reported agents that (a) pauses
herdr's own screen/process detection for the pane like full-lifecycle sources do, and
(b) stays effective without requiring local
detected_agentto match -- because theagent may run where herdr cannot introspect it (a container/sandbox).
Note a subtlety: simply adding such sources to the existing
full_lifecycle_hook_authoritylist is not sufficient, because
hook_authority_is_effective(
src/terminal/state.rs:1805) then requiresdetected_agent == Some(agent)(its secondbranch), which fails when herdr cannot detect the agent locally and would nullify the
pushed state. A "remote/proxy full-lifecycle" category needs to satisfy both (a) and (b).
Happy to test a patch against this setup.