Skip to main content

Module duplex

Module duplex 

Source
Expand description

Daemon-side full-duplex conversation-floor state machine (ADR-068 D1).

DuplexChannel is the actuator of ECC floor verdicts, never a decider of the floor. ECC (the TalkModeLoop) decides; this machine executes. Every ad-hoc hold that lived inline in the old half-duplex TalkModeController (mic-drain, prebuffer, barge grace, ack-cache) becomes one transition rule here, in the daemon (ADR-068 §Context thesis 1).

§The actuator/decider boundary, enforced by construction (ADR-068 D1, plan §2)

The two mutators are split by the type they consume:

  • observe takes a StreamObservation — something the daemon derived from the media stream (VAD onset, endpoint, ERL, drain, heartbeat) — and returns (Option<DuplexImpulse>, Vec<EdgeCommand>). It may perform daemon-local transitions and emit floor-pressure impulses, but its input and output types contain no FloorVerdict, and a DuplexImpulse can only carry an ImpulseType (TurnClaim/EndOfUtterance) — there is no Grant variant. So observe is structurally incapable of granting the floor.
  • apply is the only method that takes a FloorVerdict (the type that carries GrantUser/GrantAgent), so it is the only path that can perform an ECC-authoritative floor transition.

A reviewer verifies “the daemon never decides the floor from the stream” by reading the two signatures — not by auditing the bodies.

§Resolved D1-table ambiguity: Overlap entry vs. the ERL gate

The D1 row Speaking | onset above gate, ERL confirms → Overlap reads, in isolation, as if a high ERL were required to enter Overlap. That is not how this machine implements it, because the required self-cancel regression test (plan §3) drives a low-ERL onset during Speaking and asserts Overlap is entered and then reverted by an ECC DenyBarge. The two are reconciled per the ADR’s own stated mechanism (ADR-068 D1 “The self-cancel fix”): barge-in “becomes a runtime floor decision”, i.e. the ERL discriminates genuine barge from AEC self-echo at the floor read, not at Overlap entry. So:

  • Any VAD onset during Speaking (the energy gate is upstream, in the edge/daemon VAD) enters the provisional Overlap and emits a TurnClaim carrying the last measured ERL confidence.
  • ECC weights a low-ERL TurnClaim down (compute_urgency, ADR-062 D4, Phase 1) and returns FloorVerdict::DenyBarge → back to Speaking; a genuine high-ERL onset yields FloorVerdict::GrantUser → Listening + cancel.

This keeps the decider in ECC and makes the self-cancel fix expressible without the machine judging the barge itself. Overlap is explicitly provisional (ADR-068 D1 states table).

Structs§

DuplexChannel
The daemon-side floor state machine (ADR-068 D1). One per conversation. Pure logic — no I/O, no async: it turns stream observations and ECC verdicts into a state trajectory plus concrete EdgeCommands.
DuplexImpulse
A floor-pressure impulse the daemon derives from the media stream and lifts onto the ECC ImpulseQueue that feeds the TalkModeLoop. Named to avoid colliding with crate::impulse::Impulse; the plan (§Phase 0) calls this the derived “Impulse”. It can only carry an ImpulseType (no grant variant exists), which is what makes observe unable to decide the floor.

Enums§

DuplexState
The seven floor states (ADR-068 D1 states table). The edge holds only the reflex shadow of these (see clawft-channels edge_reflex); the authoritative machine is daemon-side.
EdgeCommand
A concrete command down the control lane to the edge (ADR-068 D3). Floor verdicts reach the edge only as these — the edge never sees a FloorVerdict or an impulse code.
FloorVerdict
The ECC-authoritative floor verdict from the TalkModeLoop. This is the only type carrying a floor grant, and apply is the only method that consumes it — the structural core of the actuator/decider boundary (ADR-068 D1, plan §2).
MediaPayload
A media-lane payload carried by EdgeCommand::Play (ADR-068 D3): PCM when the edge offers no local codec, rendered text when it advertises local TTS.
PayloadKind
Which payload rides the media lane — the axis that decides which transitions are reachable (ADR-068 D2/D6). The machine is identical; text simply cannot reach the audio-only states.
StreamObservation
Something the daemon derived from the media stream (ADR-068 D3: “the daemon derives impulses from the media stream itself”). None of these can grant the floor — they are observations, not verdicts.