0035 — Agent-asked event: a pending human-answerable question on the wire
0035 — Agent-asked event: a pending human-answerable question on the wire
TL;DR. An agent running in a pane that has blocked for a human answer
emits an additive AgentEvent::Asked on the existing agent-event stream
(EVENT, 0xB3) carrying { id, question, suggestions, elapsed_seconds }.
A projection consumer (ADR-0030)
— the native mobile client, phux-web — renders the waiting prompt and raises a
notification without re-deriving “is this pane waiting on me?” from the grid.
It is a sibling of the ADR-0033
TerminalControl broadcast: a structured agent-surface signal, not a new wire
tier. The body is field-tagged TLV so suggestions and elapsed_seconds are
additive; an older decoder skips it by length to AgentEvent::Unknown.
Status: Accepted Date: 2026-06-17
Context
“An agent is waiting on you” is the headline of a phux projection consumer: the mobile client wants to badge the pane, surface the question, and fire a native notification the moment an agent (Claude Code, Codex, …) blocks for input. But the wire carries opaque VT bytes (ADR-0030); every consumer would otherwise have to scrape its local grid projection and re-derive the waiting state with per-agent heuristics — duplicated in every consumer, and invisible to a consumer that has not yet drawn the pane.
The agent-event stream already carries structured, non-normative convenience
signals (Bell, TitleChanged, Idle, and TerminalControl). A pending
question is the same shape of signal: out-of-band, server-observed, useful to
every consumer, and not part of the grid.
Decision
Add an additive variant to the AgentEvent tagged union on the EVENT
(0xB3) stream:
Asked { id: str, question: str, suggestions: [str], elapsed_seconds: opt<u64> }
at event tag 0x09 (appended after TerminalControl’s 0x08). Unlike the
other (positional) AgentEvent bodies, the Asked body is itself
field-tagged TLV (id=1, question=2, suggestion=3 repeated,
elapsed_seconds=4), so the suggestion list and the elapsed counter are
additive and a future field is too. It mirrors the consumer-side question
model one-for-one (AgentQuestion).
This is a draft-level, additive change: no tag is renumbered, no existing
bytes change, and PROTOCOL_VERSION stays 0.5.0 (CHANGELOG 0.5.0-draft.8).
v1 trigger — the phux-ask title sentinel. The server decides a pane is
asking by observing its terminal title (OSC 0 / OSC 2): an agent sets the
title to phux-ask[<id>]:<question>?s=opt1|opt2. libghostty-vt does not
surface OSC 9 / OSC 777 desktop-notification escapes through its Rust API
(title, cwd, and bell are the only user-notification signals it exposes), so
the title is the honest closest signal an agent can drive and the server can
observe without disturbing the per-consumer snapshot synthesizer. The marker
is parsed and coalesced (a re-asserted identical marker does not re-fire), and
retitling away clears the ask.
Alternatives considered
- Reuse the spec-only
TERMINAL_EVENT(0xB1) /USER_NOTIFICATION. That frame and OSC 9/777 plumbing are unimplemented, and OSC 9 is a generic desktop notification, not a structured question with suggestions. More to build, weaker payload. RUN_HOOK/ a hook-dispatch command. Solves a different problem (running a consumer-side action), and is also spec-only.- No wire signal; each consumer scrapes its grid. Duplicates per-agent heuristics in every consumer and cannot signal a pane a consumer has not drawn. Rejected — this is exactly what the agent-event stream exists to avoid.
Consequences
- Forward-compatible: an older decoder skips tag
0x09toAgentEvent::Unknown { tag, body }by the outer length prefix; consumers re-pin (PHUX_REV) rev-for-rev when they adopt it. - The
phux-asktitle sentinel is a v1 trigger. Full agent-state detection — per-agent manifests and/or opt-in agent hooks, and surfacing OSC 9 if libghostty grows the accessor — is follow-up work (phux-2sl6). Askedis a server-observed convenience projection, not a normative type system: it stays out of the L1 substrate’s closed job list (ADR-0030).