Expand description
Process Broker — Phase A of the process-tracking consolidation.
See docs/specs/REPORT_PROCESS_ARCHITECTURE_STATE_AND_RETHINK_2026_07_22.md
for the full rationale. AgentMux answers “is this agent/process alive and
what’s it doing” six different, only-partially-overlapping ways today
(blockcontroller::CONTROLLER_REGISTRY, process_tracker, the reactive
handler’s own registration list, pidregistry, TurnActivityTracker’s
turn-active bookkeeping, and watchdog.rs’s PTY-idle timers). This
module is the consolidation point for the two callers that most directly
motivated it (the Agent pane’s process badge and the Swarm pane’s block
discovery/overview), modeled on broker::scheduler’s Credential Broker
(same crate, sibling module — same “one broker, register your own
backing store” pattern applied to a second domain) and on
agentmux-srv::reducer’s existing discipline of computing state through
one arbiter with explicit transition logic rather than letting every
caller mutate a shared map directly (see lifecycle_from below, which
mirrors reducer/lifecycle.rs::handle_register’s shape: pure function,
explicit rule per legal state, no bare field copy).
Phase A scope: read-side only. status()/list() compute a
normalized ProcessStatus by querying today’s existing sources
(blockcontroller for lifecycle — authoritative, has an entry for every
controller type; process_tracker for OS-process detail — only
populated for subprocess/persistent controllers today) and cache the
result, single-flight-guarded per call. This closes the concrete,
reported bug directly: agent.tracked-blocks’s .chain() of
process_tracker + the reactive handler’s registration list (two
structurally different registries unioned with no reconciliation) is
replaced by blockcontroller::get_all_controllers(), which is already
authoritative for “which blocks exist” across every controller type.
Deferred to later phases (see the report’s §6 open questions):
migrating each Controller impl to register with this broker directly
at spawn (closing the coverage gap at the write side, not just papering
over it at the read side); consolidating the two independent
activity-summary pipelines; moving the Swarm pane’s client-side-only
fine-grained activity chip server-side. None of that is touched here.
Structs§
- Process
Broker - Process
Status - The broker’s public read shape — one struct every consumer (Agent pane, Swarm pane, future consumers) reads instead of composing raw registry data themselves.
Enums§
- Lifecycle
- Coarse “is it alive” question, normalized from
BlockControllerRuntimeStatus’sturn_active/shellprocstatus/shellprocexitcodefields into one enum — the Pod-phase-equivalent signal in the Kubernetes phase/status/probe analogy the report draws in §4.
Constants§
- EVENT_
STATUS_ CHANGED - MPS event name for a
ProcessStatuschange. Scopedblock:<id>, same convention asagent:process-added/controllerstatus. Consumers that want the unified signal subscribe to this instead of the two separate events the old.chain()-based discovery relied on. - EVENT_
TRACKED_ BLOCKS_ CHANGED - MPS event name for “
list()/list_agent_panes()membership may have changed” — published wheneverblockcontroller::register_controllerordelete_controllerruns. Unscoped (noblock:<id>— seeemit_tracked_blocks_changed’s doc comment for why).
Statics§
- GLOBAL 🔒
Functions§
- compute_
status 🔒 - Compute a block’s current
ProcessStatusfrom today’s underlying sources. Pure with respect to its two inputs — the only I/O is the two read calls themselves (both cheap in-memory lock acquisitions, not blocking I/O — process_tracker’s registry and blockcontroller’s CONTROLLER_REGISTRY are bothparking_lot/stdsync locks guarding plain maps). - global
- lifecycle_
from 🔒 - Pure reducer over one block’s raw controller status: decides
Lifecyclevia explicit per-case rules, not a bare field copy. Isolated from I/O so the transition rules are unit-testable without a real controller instance — mirrorsreducer/lifecycle.rs::handle_register’s shape (compute the legal next state from the current observation, rather than trusting the caller’s intent unconditionally). - set_
global