Skip to main content

Module muxspect_handlers

Module muxspect_handlers 

Source
Expand description

HTTP handlers backing the muxspect CLI — Phase 1 of docs/specs/SPEC_MUXSPECT_LIVE_INTROSPECTION_TOOL_2026_08_01.md, plus the dock/dock clear extension from docs/specs/SPEC_MUXSPECT_DOCK_DIAGNOSIS_AND_REMEDIATION_2026_08_06.md.

Diagnostic-only surface: a thin read composition over ProcessBroker (Phase A of the process-tracking consolidation, crates/srv/src/broker/process.rs) and its sibling registries — never a new independent snapshot of process/turn state (spec §5.1/§3 point 8). Reached the same way agentmux-mcp already reaches every other /api/v1/* route: plain HTTP, X-AuthKey header, $AGENTMUX_LOCAL_URL/ $AGENTMUX_AUTH_KEY inherited from the caller’s own environment (spec §5.2) — no new IPC mechanism, no new auth scheme. dock clear is this module’s first MUTATING route — see the 2026-08-06 spec §2 for why that scope call is narrow and deliberate, not a reversal of the read-only design generally.

Structs§

BackgroundTaskView
DockNodeView
LastErrorFrame
The most recent persisted error_during_execution frame at the tail of a block’s own output file — populated ONLY when the very last non-blank line IS one (a block that errored once and then kept producing normal output afterward must NOT be flagged; only “the last thing that happened to this block was an unrecovered error” counts).
MuxspectBackgroundTasksQuery
MuxspectDescribeQuery
MuxspectDockClearRequest
MuxspectDockQuery
MuxspectFindQuery
MuxspectLayoutQuery
MuxspectVerifySenderQuery
SenderCandidate 🔒
One already-fetched candidate, tagged with the tier it was found on. Keeps classify_sender pure and independent of AgentRegistration/ AgentEntry/LanInstance’s actual (differently-shaped) types — the handler below does the field mapping once, at the IO boundary, same discipline as dock_node_views/last_error_frame above.
VerifySenderVerdict
Not a security/trust primitive. This reports registry LIVENESS only (does an agent named X currently exist in the discovery data at all) — it performs NO cryptographic check and must not be confused with the actual JEKT sender-authentication mechanism, which is per-message, automatic, and already computes a real TRUST=/SIG= value on every delivered jekt via HMAC-SHA256 (host tier) / Ed25519 (LAN tier) / a pinned Ed25519 key (reagent WAN) — see this repo’s root CLAUDE.md, “Is a jekt’s sender identity actually verified? — the real answer”. Earlier drafts of this route reused that exact vocabulary (host-verified/network-claimed) for a mere presence check, which collided with — and could be mistaken for — that real, stronger guarantee (reagentx-workflow review on PR #2702, P1). Deliberately no trust/verified field here at all: status + tier fully describe what was actually checked (presence, not authenticity), with nothing that could be misread as more than that.

Constants§

CROSS_CHANNEL_PREVIEW_TIMEOUT_MS 🔒
Short per-forward timeout for cross-channel preview fetches in handle_muxspect_conversations — a single dead/unresponsive channel must not stall the whole listing; best-effort, matches this file’s diagnostic (never authoritative) posture elsewhere.
DOCK_STUCK_THRESHOLD_MS 🔒
A ToolNode’s status stays "running" this long (matches the frontend’s own TOOL_PROMOTION_MS, frontend/app/view/agent/activity/ tool-adapter.ts) before it’s eligible to be flagged stuck here — a tool call that’s merely taking a while must not be flagged the instant it starts. No shared constant exists between the two languages; keep this value in sync with the frontend’s if either changes.
LAST_ERROR_TAIL_BYTES 🔒
Bounded tail window read from a block’s own persisted output file when looking for a trailing error_during_execution frame — see last_error_frame. Large enough to comfortably hold the frame itself (a single JSON line, typically well under 1KB) plus whatever partial line/chunking noise might precede it; small enough to stay O(1) and not turn muxspect into a log-walker (that’s muxlog’s job, not this tool’s — see the tool’s own design spec §1).
PREVIEW_MAX_CHARS 🔒
Truncation length for a last_message_preview — keeps conversations’ response small even when the tail line is a long tool-call/result frame; this is a liveness-glance preview, not a transcript reader (that’s GetAgentTranscript/muxspect conversation <agent>’s job). Applied to BOTH the host-tier read (last_line_preview_and_activity) and the cross-channel forwarded read — codex P2 on PR #2715 caught the cross-channel path skipping this entirely, which let one huge remote tool-call/result line inflate an otherwise-bounded response.
PREVIEW_TAIL_BYTES 🔒
Bounded tail window read directly off the live FileStore file in last_line_preview_and_activity’s fast path — deliberately small relative to a whole session (which can be many MB): only needs to comfortably contain the single last non-blank line, not search for a specific frame shape the way LAST_ERROR_TAIL_BYTES’s window does.

Functions§

classify_last_error_source 🔒
Best-effort classification of which pre-spawn failure path wrote a persisted error_during_execution frame, inferred from the message text itself (each construction site’s message is distinct and stable — see identity/resolver/errors.rs’s SpawnGateError::Display, subprocess/container_spawn.rs, subprocess/host_spawn.rs, and agent_handlers/input.rs’s container ensure_running path, the only four call sites that build this frame today). message should have the [AgentMux] prefix already stripped.
classify_sender 🔒
Pure verdict computation over already-fetched discovery data — see handle_muxspect_verify_sender for the IO/fetch boundary this composes with. candidates must already be in tier-priority order (host, then cross-channel, then lan, then wan — most-trusted first).
describe_lifecycle_is_unknown 🔒
GET /api/v1/muxspect/find?block_id=X or ?agent=X — Ext 4 of docs/reports/REPORT_MUXSPECT_MUXLOG_CROSS_CHANNEL_INSPECTION_2026_08_22.md: “which running instance(s), if any, have a controller or subagent dispatch matching block_id/agent X” — the query that debugging session needed and had no tool for, ending in manual filesystem/env-var archaeology across every channel by hand.
dock_node_views 🔒
Pure computation behind handle_muxspect_dock — separated out so the stuck-flagging logic (the actual diagnostic value of this endpoint) is unit-testable without constructing a full AppState/axum request, same discipline as last_error_frame/classify_last_error_source above.
flatten_layout_nodes 🔒
Flatten one layout tree into a compact, renderable node list.
handle_muxspect_background_tasks
GET /api/v1/muxspect/background-tasks[?block_id=X] — the durable declared-background task registry (db_background_tasks), the source of truth handle_muxspect_dock’s ephemeral, 1-hour-evicted DockSnapshotCache deliberately isn’t (see docs/status/STATUS_ATTACHED_TASK_AXIS_AND_DEV_LOOP_2026_08_15.md). A task still shows up here after the dock cache has evicted it, and survives a pane reload — it’s backed by SQLite, not an in-memory cache.
handle_muxspect_conversations
GET /api/v1/muxspect/conversations — a single-call, all-tier glance at every agent’s most recent activity, so an agent (or a human via muxspect conversations) doesn’t have to compose DiscoverAgents + N×GetAgentTranscript calls by hand. See docs/specs/SPEC_MUXSPECT_CROSS_TIER_CONVERSATION_VISIBILITY_2026_08_21.md Phase A.
handle_muxspect_describe
GET /api/v1/muxspect/describe?block_id=X — composes ProcessBroker status, the coarse BlockControllerRuntimeStatus, and the OS-process tree for one block into a single response. This is the “describe everything about block X” query REPORT_PROCESS_ARCHITECTURE_STATE_AND_RETHINK_2026_07_22.md §5.4 named as missing — getting this picture today takes 2-3 separate, uncomposed RPC round-trips.
handle_muxspect_dock
GET /api/v1/muxspect/dock?block_id=X — the cached ToolNode status snapshot for one block, cross-referenced against ProcessBroker to flag entries that look stuck. See docs/specs/SPEC_MUXSPECT_DOCK_DIAGNOSIS_AND_REMEDIATION_2026_08_06.md §3.1.
handle_muxspect_dock_clear
POST /api/v1/muxspect/dock/clear — validates node_id is a currently tracked node for block_id (per the cache — see docs/specs/SPEC_MUXSPECT_DOCK_DIAGNOSIS_AND_REMEDIATION_2026_08_06.md §3.2), then publishes EVENT_DOCK_CLEAR scoped to block:<id>. Only a renderer currently displaying that block and still holding that node ever receives or acts on it (real server-side scope routing, not client-side self-filtering — same mechanism EVENT_SHELL_NODE_CREATE already uses). Returns 404 if the node isn’t in the cache — either it was never pushed, or it already resolved/was already cleared; either way there’s nothing to do.
handle_muxspect_find
handle_muxspect_layout
GET /api/v1/muxspect/layout[?tab_id=X] — the persisted layout tree for every tab (or one), plus the layout doctor’s verdict on each.
handle_muxspect_list
GET /api/v1/muxspect/list — every controller-backed block’s current ProcessStatus, full detail (unlike agent.tracked-blocks, which intentionally returns only block_ids for its Swarm-pane contract). Each row also carries is_agent — ProcessStatus::is_agent()’s complete classification rule (subprocess/persistent/acp are ALWAYS agents regardless of is_agent_pane, which only applies to shell/cmd) — so consumers don’t reimplement that rule themselves (codex P2 on PR #2380: the CLI’s own naive is_agent_pane-only rendering mislabeled exactly those three controller types).
handle_muxspect_migrations
GET /api/v1/muxspect/migrations — the migration doctor for THIS instance’s data dir: every registry migration with its applied/pending state and, for applied ones, the same post-condition verdict agentmux-srv migrate --verify prints (SPEC_MIGRATION_SYSTEM_HARDENING_2026_08_03.md Phase 1c). Built by the one producer both share (migrations::doctor_report_for_instance), so an operator inside a running instance gets exactly the answer the CLI would give against a stopped one — without hunting for the srv binary or the data dir. The incident the hardening spec was written about (§1.6: a migration “recorded as applied that wrote nothing”) is exactly a “why did this agent fail to start” question, which is muxspect’s job.
handle_muxspect_verify_sender
GET /api/v1/muxspect/verify-sender?name=X — answers “is an agent named X currently registered, and via what tier” in one call, so an agent that just received a [JEKT:FROM=X ...] marker doesn’t have to fall back to manual filesystem/process archaeology to sanity-check the claimed sender exists at all. See VerifySenderVerdict’s doc comment for the important caveat this is NOT the JEKT protocol’s own cryptographic sender verification. See docs/retro/RETRO_JEKT_CROSS_ CHANNEL_TRUST_SELF_DECLARED_2026_08_21.md for the incident this closes the liveness-lookup gap on, and docs/specs/SPEC_MUXSPECT_VERIFY_ SENDER_2026_08_21.md for the full design.
last_error_frame 🔒
Read the bounded tail of block_id’s output file and return the parsed LastErrorFrame if its last non-blank line is one. None covers every other case (no such block, empty file, last line is normal output, last line doesn’t parse as this exact frame shape) — deliberately not distinguished further, since all of them mean the same thing to a caller: nothing actionable to surface here.
last_line_preview_and_activity 🔒
Last non-blank line of a block’s own transcript, truncated for preview display, plus a real “when was this last written” timestamp — None for either if the block has no session output yet (e.g. an agent registered but no turn has produced output) — missing is not an error, same “encode absence in the body, not the status” posture as the rest of this file.
merge_background_tasks 🔒
Append a synthetic DockNodeView for every Running db_background_tasks row not already represented in nodes (by id — db_background_tasks.id mirrors the dock’s own node_id, see background_tasks.rs’s module doc comment). Pure, unit-testable like dock_node_views above. A task still present in nodes is left as-is — the live cache’s own status is more specific (carries tool_name, real stuck computation) than anything this synthesizes from the registry alone.
truncate_preview 🔒
Truncate a preview line to PREVIEW_MAX_CHARS, appending an ellipsis when it was actually cut. Shared by the host-tier and cross-channel preview paths so they can’t drift out of sync again (codex P2 on PR #2715 — the cross-channel path originally didn’t call this at all).