AEP-0008: Text Answers and Response-Channel Declarations
Two small receiver/consumer rules that close one live-verified usability hole in the attention control loop, without changing any schema:
Draft: the AEP suite is pre-v0.1; each document's status lives in its header table. This page is rendered from the canonical spec/AEP-0008-text-answers-and-response-channels.md, which is the normative text.
| Field | Value |
|---|---|
| AEP | 0008 |
| Title | Text Answers and Response-Channel Declarations |
| Type | Standards Track — Core |
| Status | Accepted (2026-07-16: normative text, fixtures, and prototype landed in one change per GOVERNANCE.md §2) |
| Sponsor | AEP maintainers (per GOVERNANCE.md §2) |
| Created | 2026-07-16 |
| Requires | AEP-0002 (attention payloads, lifecycle), AEP-0004 (control commands, ack semantics) |
| Supersedes / Superseded-by | n/a (additive to AEP-0002 §5.3) |
Abstract
Two small receiver/consumer rules that close one live-verified usability hole in the attention control loop, without changing any schema:
- Text answers against
options, a receiver-side mapping rule: acontrol.attention.respondwhoseanswercarries onlytextagainst an option-bearing request SHOULD be honored when the text names an option, and MUST NOT be applied as the permissive choice of akind: "permission"request when it does not. respond_viaas the authoritative channel declaration, a consumer-side rule: when a request declares how its answer travels, consumers SHOULD offer control delivery only ifcontrolis listed; a present value withoutcontrolmarks the request informational on the bus (its answer arrives by the stated channel, e.g.oob).
Both rules are stated once, in AEP-0002 §5.3 (amended in place), so every adapter and every consumer inherit the same behavior. No field, kind, enum value, or lifecycle state is added or changed; the change is entirely in what receivers and consumers do with members that already exist.
Motivation
Live operation surfaced a hole neither side of the control loop owned:
- Consumers (correctly) render a free-text reply affordance beside option
buttons on attention cards: free text is load-bearing for deny reasons
and steer-style guidance, and AEP-0002 §5.3 already gives
answer.textfirst-class carriage. - Reference emitters with held permission gates (two of them, on different
vendor surfaces) validated answers strictly against
answer.option, so an operator who typed the worddenyinto the reply box was nackedinvalidby the very gate whose Deny button sat beside the box. Every posture of the loop refused honest human input that named the decision. - The same live sessions showed the inverse hole on observed (read-only)
approval cards: the emitter knew the request could never be answered from
the bus (the decision belongs to the agent's own UI), but the event did
not say so, and consumers rendered answer affordances that could only
ever earn a nack.
respond_viaexisted precisely to carry this fact and was not being declared or consulted.
One vendor's own terminal prompt already sets the precedent for the safe direction: it maps recognized option words to their choice and anything else to the refusing choice, never to permission.
Specification
The normative text lives in AEP-0002 §5.3, amended in place (the same mechanism AEP-0006 used). The two amendments, reproduced here:
1. Text answers against options (attention.answered)
Text answers against
options(AEP-0008). When acontrol.attention.respondcarriestextand nooptionanswering a request that offeredoptions, the receiving emitter SHOULD treat text that, after trimming, case-insensitively equals an option'sid, or exactly one option'slabel, as that option, recording the mapped choice asanswer.optiononattention.answered(the matched word is the decision itself, not accompanying text).For
kind: "permission"requests, unmatched text MUST NOT be applied as the permissive choice: the emitter SHOULD either apply its refusing option with the text preserved as the stated reason (underanswer.text's capture rule) or reject the command (control.rejected{reason:"invalid"}).The recorded
answeralways states what was actually applied. Consumers MUST NOT depend on text mapping:optionremains the lossless channel.
2. respond_via consumer guidance (attention.requested)
respond_via?: array(enum(control|oob)) [metadata]: how a response can be delivered; when present it is the emitter's authoritative declaration for THIS request: consumers SHOULD offer control delivery only ifcontrolis listed, and a present value withoutcontrolmarks the request informational on the bus, with its answer arriving by the stated channel, e.g.oobfor a decision made in the agent's own UI; see AEP-0008.
An absent respond_via means what it always has: nothing is declared, and
send-and-settle (the ack/nack loop of AEP-0004 §3) remains the honest way
to learn whether a target accepts an answer.
Rationale
Why receiver-side, not consumer-side. A consumer could rewrite
{text: "deny"} into {option: "deny"} before sending, but option-id
meaning belongs to the adapter that minted the options (only it knows that
deny blocks the call while session grants a scoped allowlist entry),
and N consumers would each re-implement the mapping, divergently. Stated
once at the receiver, every consumer, present and future, inherits it.
Why the refusing choice for unmatched text, specifically. Free text on a permission card is nearly always a reason or a redirection ("use the staging registry instead"): human intent that should land as a deny with the words preserved, not be bounced. Mapping it to the permissive choice is the one indefensible reading: ambiguous input must never grant.
This mirrors the surveyed vendors' own typed-input semantics (unrecognized input refuses) and one vendor family's documented "tell the agent what to do differently" path, where free text both denies the call and steers the retry.
Emitters that prefer to reject unmatched text may still do so (invalid
remains legal), but the permissive mapping is forbidden.
Why respond_via needed no new vocabulary. The member and both enum
values predate this AEP; the gap was behavioral (emitters not declaring,
consumers not consulting). Declaring ["oob"] on an observed read-only
approval card makes the card self-describing: a vendor-ignorant consumer
can render the decision menu as information (what the agent is asking its
own operator) without inviting an answer the emitter must nack. This is
deliberately request-scoped truth: it rides the event itself and cannot
go stale the way observed capability state can.
Vendor-neutrality criteria (how this change was screened). Control affordances originate on vendor surfaces, and the standing risk is per-vendor semantics leaking into core. Each addition here was screened against the criteria the project applies to any vendor-facing control feature, recorded so future adapter work inherits the test:
- Schema neutrality: vendor specifics ride generic shapes as instance data; no vendor-named field or enum value enters core schemas.
- Consumer-blind renderability: a consumer that never heard of the vendor renders the affordance correctly and completely from the event alone; correctness never requires vendor-conditional consumer logic.
- Answer round-trip fidelity:
{option}/{text}/{values}carries the decision losslessly; the adapter owns what an option id means on its vendor surface. - Cross-vendor recurrence: the capability class recurs across at
least two surfaced agents before core semantics are minted;
single-vendor concepts stay in
x.*. - No degradation by absence: absence is the default posture; presence is purely additive for everyone else.
- Sovereignty and honesty: nothing claims unobserved state or overrides the agent's own decision surface; failure modes stay honest.
Both rules pass all six: they use only preexisting generic members (1), bind receivers/consumers without vendor conditionals (2), record the applied decision on the stream (3), generalize behavior already shipped independently on two vendor gate surfaces, with a third vendor's own terminal semantics agreeing (4), leave rejecting receivers and undeclared requests exactly as they were (5), and fail toward refusal, never permission (6).
Alternatives rejected. Consumer-side text→option rewriting (breaks 2 and 3 above); suppressing the free-text affordance on option-bearing cards (free text is load-bearing for reasons/guidance; the fix is honoring it, not removing it); relay-side rewriting of text answers (AEP-0004 §4.4 byte-identical forwarding is non-negotiable); mapping unmatched text to the permissive choice (never); making the text-mapping rule a MUST (receivers legitimately differ on label matching and locale; the safety half, never permissive, is the only MUST-grade clause).
Backward Compatibility
Fully additive. Emitters that keep rejecting text-only answers remain
conformant (invalid stays legal); the only new obligation is
conditional and vacuous for them (MUST NOT apply unmatched text as the
permissive choice: a rejecting emitter never applies it at all). The
reference implementation's remaining held gates (the Codex-, VS Code-,
Qwen-, OpenCode-, and Kilo-surface loops) implement the SHOULD, and any
emitter that instead keeps rejecting stays conformant.
Consumers that ignore respond_via keep today's send-and-settle behavior
and learn from nacks; consumers that honor it render
strictly more truthfully. No schema changes, so no generated-type or
validator changes; existing captured streams remain valid byte-for-byte.
Prototype and fixtures (GOVERNANCE.md §2)
Prototype: two of the reference implementation's held permission gates (the Hermes-surface and Claude-Code-surface adapters) implement the text-mapping rule: option words map (any case, trimmed), unmatched text applies the refusing option with the text as the stated reason, and the answered event records the applied option. The Hermes-surface observed approval card declares the vendor's four-way decision menu as
optionsdata withrespond_via: ["oob"].Both are exercised end-to-end (real shim, real relay) in the reference CI smokes; the reference consumer renders declared non-control requests as informational.
Fixtures: the Hermes mapping fixture (
conformance/fixtures/mappings/hermes.json) pins the menu-as-data +respond_via: ["oob"]shape of the observed approval card; both conformance runners check it. Held-gate answer semantics are runtime state and are smoke-pinned in the reference repository (the AEP-0004 precedent for runtime control behavior).
Security considerations
The text-mapping rule tightens the loop's safety posture rather than
loosening it: the only MUST-grade clause forbids ambiguous input from
granting permission, and the mapped-word path is strictly narrower than
what a consumer could already send as {option}.
respond_via declarations are emitter-asserted like every other payload
fact (AEP-0003 §8.2); a false ["oob"] declaration can suppress a
consumer's answer affordance but never grants anything, and the send path
a consumer loses is one the emitter would nack anyway. Deployments
enforcing control ACLs (AEP-0004 §4.3) are unaffected: text answers ride
the same control.attention.respond command they already gate.
References
- AEP-0002 §5.3/§7 (attention payloads and lifecycle).
- AEP-0004 §3/§5 (acks,
control.attention.respond). - AEP-0006 (the
valuesanswer member and its gating rule: the pattern this AEP's in-place amendment follows).