0028. Agentic retest chat steering & Q&A (FR-17 Slice 4)
Date: 2026-07-16
Status: accepted
Superseded in part by 0042: the parallel read-only Q&A agent this ADR introduced is deleted — one agent answers every message, queued to the next turn boundary. The pure-queue steering model and the non-gated
respondtool are unchanged.
Context
FR-17 Slice 4 (epic #87,
issue #96) adds the
design spec's third steering channel: chat. The operator types free-text
to steer the agent ("focus on the login endpoint") or ask questions ("what
did that 500 mean?"), completing the trio with approve/edit (ADR-0025) and
type a command (ADR-0026, the ! path).
The Slice 0 gate constrains delivery. The agent is never idle — it is either running, suspended on a deferred tool call (the common resting state), or terminal — and Pydantic AI cannot accept a new user prompt while a tool call is deferred, nor interrupt a running turn. So an operator message can only be buffered and delivered at the next turn boundary, which while commands are gated is the next approve/reject.
Decision
- Pure-queue delivery, never an interrupt. A message is buffered on the live session and delivered on the next approve/reject; it never silently discards a pending proposal. Redirect = reject-with-message; augment = approve-with-message. (The autonomous drain — picking messages up mid-loop without a gate — arrives with free-launch, Slice 5.)
- Delivered as a first-class user turn. On the resume, the drained
message is passed as
user_promptalongsidedeferred_tool_results, so it lands as a realUserPromptPartafter the tool return — a model responds to a user turn far more reliably than to prose folded into a tool result. - Q&A via a non-gated
respondtool. The agent emits prose throughrespond(agent_messageevent); being a normal tool, the run continues to its next proposal/verdict. No new output type, no new session state, budget-exempt. - Observed-fact vs operator-voice split.
!-command results (Slice 2) stay folded into the tool result the agent reads; chat messages are the operator's voice (a user turn). Two channels, two framings. - No dequeue (audit). A sent message is committed to the append-only transcript immediately (evidence, NFR-02); the UI shows undelivered messages with a "queued" treatment but there is no edit/remove.
Alternatives considered
- Message implies reject (auto-reject the pending proposal so a steer takes effect in one action) — rejected: silently discards a proposal and breaks augment-and-approve.
- Fold the message into the tool-result string (like
!observations) — rejected: a model treats a buried tool-result note as data, not an instruction; a user turn is read reliably. - A new non-terminal prose
output_type— rejected: the run would end on prose, forcing the orchestrator to re-drive with no user prompt; arespondtool keeps prose a mid-run side-effect with no loop change.
Consequences
- Good: full three-channel steering; Q&A reuses the already-reserved
agent_messageplumbing; zero new session states; no change to the gate, budget, or egress lock. Fully testable with a scriptedFunctionModel(message queued → delivered asuser_prompt→respondobserved). - Accepted limitations: a message sent while the agent finalizes a
concludemay go unread (still recorded); a question asked while a command is pending is answered on resolving it — both inherent to the pure-queue gate, stated plainly. - Invariants preserved: command gating, egress lock (NFR-03), and the
budget backstop are unchanged (
respondand queued messages are budget-exempt by design); the old batch path still coexists until the last slice.
References
- Design spec:
docs/superpowers/specs/2026-07-16-agentic-retest-console-slice-4-design.md(chat steering +respondQ&A tool) - Plan:
docs/superpowers/plans/2026-07-16-agentic-retest-console-slice-4.md(Slice 4) - Builds on ADR-0025 (agentic console), ADR-0026 (operator
!commands) and ADR-0027 (guiding plan); epic #87, issue #96