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:

  1. Text answers against options, a receiver-side mapping rule: a control.attention.respond whose answer carries only text against an option-bearing request SHOULD be honored when the text names an option, and MUST NOT be applied as the permissive choice of a kind: "permission" request when it does not.
  2. respond_via as the authoritative channel declaration, a consumer-side rule: when a request declares how its answer travels, consumers SHOULD offer control delivery only if control is listed; a present value without control marks 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.text first-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 word deny into the reply box was nacked invalid by 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_via existed 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 a control.attention.respond carries text and no option answering a request that offered options, the receiving emitter SHOULD treat text that, after trimming, case-insensitively equals an option's id, or exactly one option's label, as that option, recording the mapped choice as answer.option on attention.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 (under answer.text's capture rule) or reject the command (control.rejected{reason:"invalid"}).

The recorded answer always states what was actually applied. Consumers MUST NOT depend on text mapping: option remains 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 if control is listed, and a present value without control marks the request informational on the bus, with its answer arriving by the stated channel, e.g. oob for 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:

  1. Schema neutrality: vendor specifics ride generic shapes as instance data; no vendor-named field or enum value enters core schemas.
  2. 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.
  3. Answer round-trip fidelity: {option}/{text}/{values} carries the decision losslessly; the adapter owns what an option id means on its vendor surface.
  4. Cross-vendor recurrence: the capability class recurs across at least two surfaced agents before core semantics are minted; single-vendor concepts stay in x.*.
  5. No degradation by absence: absence is the default posture; presence is purely additive for everyone else.
  6. 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 options data with respond_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 values answer member and its gating rule: the pattern this AEP's in-place amendment follows).
On this page