The No-Inference Rule

Maxx is the visible terminal-native runtime/control plane, not the workflow brain.

Maxx makes terminal-native work observable and controllable without pretending to understand workflow intent. It may display facts it directly owns or observes, and facts an agent explicitly declares to it. It must never manufacture workflow truth from incidental signals.

This document is the canonical statement of that rule. It exists because inference-based shortcuts look useful in the short term but create brittle behavior, surprising UX, and hidden product semantics. Keep the wording here consistent with the comments, tests, and fixtures that enforce it so the rule stays easy to search for (no-inference).

Three kinds of fact

Every value Maxx shows falls into exactly one of three buckets. The first two are allowed; the third is allowed only when it arrives as the second.

  1. Mechanical facts — things Maxx directly controls or observes as terminal runtime infrastructure. These are Maxx-owned and always safe to show as themselves:
    • process lifecycle (running / exited), reported by the kernel via ghostty_surface_process_exited
    • PTY / session / surface identifiers, the foreground pid, exit status
    • command start/stop timestamps
    • window / tab / worktree associations and the working directory
    • explicitly attached URLs and caller-supplied metadata
    • terminal escape sequences the running program emits — the bell (BEL) and OSC 9/4 progress — which are mechanical terminal facts, not agent prose
  2. Agent-declared facts — workflow meaning provided through an explicit structured channel: a control-API call, a protocol message, a structured hook event, a metadata field, or a deliberate user/agent action. Maxx stores and replays these verbatim; it never originates or reinterprets them. The declared workflow-state badge (set-state / set-summary) and the sidebar agent-activity indicator (the hook event pipeline) are both of this kind.

  3. Workflow truth — any semantic claim about the work itself: task complete, blocked, ready for review, PR created for this task, tests passed for this change, implementation done, next step known. Maxx may present workflow truth only when it is an agent-declared fact. Maxx never derives it.

The rule

If an agent has no explicit declaration path for a status you want to surface, document the gap — do not fill it with a heuristic.

Where the rule lives in the code

The rule is enforced by type boundaries, not just discipline: the surfaces that display status have no access to terminal output, so inference is impossible by construction rather than merely prohibited.

Surface Kind Source of truth File
lifecycle (running/exited/closed/archived) Mechanical Surface existence + kernel-reported process exit macos/Sources/Features/Control/ControlSession.swift, ControlSessionRegistry.swift
Declared workflow-state badge + summary Agent-declared sessions.set-state / sessions.set-summary only ControlSession.swift (WorkflowState, ControlDeclaredState), SurfaceView.swift (AgentStateBadge)
Declared session result Agent-declared sessions.set-result or structured Codex/Claude hook transcript final-answer records ControlSession.swift (result), TerminalAgentActivity.swift (AgentTranscriptResultExtractor)
Agent-reported metadata chip Agent-declared sessions.create (at spawn) / set-metadata / update / remove-metadata / clear-metadata ControlSession.swift (metadata), SurfaceView.swift (AgentMetadataBadge)
Audit log (wait / watch / events) Agent-declared + mechanical Explicit declarations + Maxx-recorded lifecycle actions ControlEvents.swift, ControlSessionRegistry.swift
Persistent session registry (restored records) Agent-declared + mechanical registry.json — only stored identity, relationships, declared facts, and timestamps; replayed verbatim on load, never re-derived ControlSessionPersistence.swift, ControlSessionRegistry.swift (rehydrate)
Sidebar agent-activity indicator Agent-declared + mechanical Hook events the agent CLI fires (maxx-agent) + terminal bell/progress macos/Sources/Features/Terminal/TerminalAgentActivity.swift, src/agent_hook/main.zig

The control API (declared workflow state)

sessions.set-state accepts only a fixed, validated vocabulary (running, needsInput, blocked, complete, failed); sessions.set-summary carries a free-form line. Both are agent-declared, recorded with a source and timestamp, audited, and shown verbatim. They are intentionally separate from the Maxx-owned lifecycle and from the free-form status, so the UI presents them as agent-provided rather than Maxx-derived (the badge popover literally says “Reported by the agent — not derived by Maxx”). See Control API.

sessions.set-result is the separate child-answer retrieval channel. It stores bounded result text with result_at and result_source, records a kind: result event, and is cleared on restart so a previous run’s answer does not look current. Maxx may populate it from structured Codex/Claude transcript JSON when an explicit hook supplies a .jsonl transcript path under the known agent transcript roots; this reads final-answer records from the CLI transcript, not PTY scrollback, and does not infer workflow truth from the answer prose.

The hook event pipeline (sidebar agent-activity)

The “automatic” Claude Code / Codex status is not Maxx watching the terminal. The agent CLI fires explicit hooks (e.g. Claude Code’s UserPromptSubmit, Stop, Notification; Codex’s configured hooks). The bundled maxx-agent helper translates the explicit hook event name to a normalized state and writes a structured JSON line to a per-surface event file; Maxx reads that file and renders the declared state. The helper and the reducer recognize only a closed vocabulary of declared event names — an unrecognized event yields no state rather than a guess. No terminal output is ever read.

The idle-time boundary (a mechanical display bound, not inference)

TerminalAgentActivityReducer keeps a running indicator lit only while a turn is in progress, with one safety bound: a running indicator that is never closed out by a further hook event expires after runningTTL (6 hours). This is a display-hygiene bound, not idle-time inference of completion: it clears a stale running spinner back to the neutral idle baseline (the absence of an active turn) and can never produce a complete, failed, or any other workflow claim. Idle time is never read as “the work finished.”

Tests that lock this down

Negative fixtures feed Maxx the tempting-but-prohibited signals and assert it infers nothing; positive fixtures prove explicit declarations still render.