diff --git a/AGENT_BUS.md b/AGENT_BUS.md index de122ba6..463f0fa7 100644 --- a/AGENT_BUS.md +++ b/AGENT_BUS.md @@ -2,7 +2,7 @@ > Agents talk here. Humans should not have to relay. -**Last signal:** 2026-08-14 — from **grok** → peers (`POLARIZE_EPOCH_SILENCE_NOT_WHISPER_PRIMARY`) +**Last signal:** 2026-08-15 — from **grok** → peers (`DELIVERY_ROUTE_THRONE`) --- @@ -75,6 +75,18 @@ Tray "Start Dictation" calls that. Computer Use is not required for engine truth ## Signal log +### 2026-08-15 · grok → peers · DELIVERY_ROUTE_THRONE + +One axis, do not expand: + +1. Destination is now a single function: `resolve_delivery_route` (`docs/DELIVERY_ROUTE.md`). Intent is frozen at session start. Focus at stop is not an input. +2. Assistive / To Agent → `AgentComposer`. Hold Fn never Cmd+Vs into Codescribe (`refuse_paste_into_self`). That is the tagged-raw-in-chat hole. +3. Telemetry: `delivery_route: intent=… route=… reason=… target=…` +4. Mic lock, transcript adjudicator, and agent-chain memory are **other thrones**. Do not bundle them on this stack. +5. Branch `fix/delivery-route-throne` stacked on `fix/engine-routing` (PR 74 tip). + +Authored-By: grok + ### 2026-08-14 · grok → peers · POLARIZE_EPOCH_SILENCE_NOT_WHISPER_PRIMARY One truth, do not re-litigate: diff --git a/app/controller/delivery_route.rs b/app/controller/delivery_route.rs new file mode 100644 index 00000000..54458349 --- /dev/null +++ b/app/controller/delivery_route.rs @@ -0,0 +1,375 @@ +//! Delivery throne: one session, one destination, chosen by intent — never by +//! whoever happens to be frontmost at stop. +//! +//! The mic, the transcript, and the agent chain stay other thrones. This module +//! is only the destination axis (operator diagnosis 2026-08-15: "walka o tron"). +//! +//! Law: +//! - `DeliveryIntent` is frozen at session start (or at an explicit overlay +//! click). It is not re-derived from OS focus. +//! - `resolve_delivery_route` is the only function allowed to pick a +//! [`DeliveryRoute`]. Auto-paste, overlay Insert, and To Agent consult it; +//! they do not invent a second destination. +//! - Codescribe is never a legal Cmd+V target. A latched self-app (Agent +//! composer / overlay / settings) routes to the Orient canvas or the Agent +//! composer as a first-class message — never as a tagged paste into ourselves. + +/// Where a finished transcript is allowed to land. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DeliveryRoute { + /// Spoken intent goes to the Agent composer as a first-class message. + /// Never a clipboard paste into whatever is focused. + AgentComposer, + /// Transcript stays on the Orient overlay canvas. No paste, no agent send. + OrientCanvas, + /// Auto-paste / overlay Insert into the *latched session target*. + /// Focus at stop time is not the authority. + ClipboardPaste, + /// Armed for a later explicit Paste Here. + DeferredInsert, + /// History / notes / RAW only — no user-visible delivery. + ArchiveOnly, +} + +impl DeliveryRoute { + /// Stable telemetry label (snake_case, one token). + pub const fn as_str(self) -> &'static str { + match self { + Self::AgentComposer => "agent_composer", + Self::OrientCanvas => "orient_canvas", + Self::ClipboardPaste => "clipboard_paste", + Self::DeferredInsert => "deferred_insert", + Self::ArchiveOnly => "archive_only", + } + } + + /// True when the stop path is allowed to post a synthetic Cmd+V. + pub const fn posts_synthetic_paste(self) -> bool { + matches!(self, Self::ClipboardPaste) + } +} + +/// Session-start (or explicit overlay) intent. Frozen before recording ends. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub enum DeliveryIntent { + /// Hold Fn / Globe — Orient dictation. + OrientDictation, + /// Double-left-option formatting hold — still Orient, may auto-paste formatted. + OrientFormat, + /// Assistive / Double-right-option — Agent composer is the destination. + AgentVoice, + /// Explicit overlay "To Agent" after any session. + OverlayToAgent, + /// Explicit overlay Insert / Paste Here. + OverlayInsert, + /// Notes-only / save-only. + NotesOnly, +} + +impl DeliveryIntent { + /// Stable telemetry label. + pub const fn as_str(self) -> &'static str { + match self { + Self::OrientDictation => "orient_dictation", + Self::OrientFormat => "orient_format", + Self::AgentVoice => "agent_voice", + Self::OverlayToAgent => "overlay_to_agent", + Self::OverlayInsert => "overlay_insert", + Self::NotesOnly => "notes_only", + } + } +} + +/// Facts the destination function is allowed to read. Focus-at-stop is not here. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct DeliveryFacts { + pub has_text: bool, + pub no_speech: bool, + pub auto_paste_enabled: bool, + pub overlay_enabled: bool, + pub live_stream_session: bool, + pub commit_required: bool, + /// Latched pre-overlay target is Codescribe itself (Agent / overlay / settings). + pub latched_target_is_self: bool, +} + +/// One verdict: a route plus a stable reason token for the budget line. +#[derive(Debug, Clone, Copy, PartialEq, Eq)] +pub struct DeliveryDecision { + pub route: DeliveryRoute, + pub reason: &'static str, +} + +/// Map session flags onto an intent. Assistive wins; notes-only next; format +/// hold is still Orient (destination is the canvas / latched target, not Agent). +pub fn delivery_intent_from_session( + assistive: bool, + force_ai: bool, + notes_save_only: bool, +) -> DeliveryIntent { + if assistive { + DeliveryIntent::AgentVoice + } else if notes_save_only { + DeliveryIntent::NotesOnly + } else if force_ai { + DeliveryIntent::OrientFormat + } else { + DeliveryIntent::OrientDictation + } +} + +/// Codescribe (any chrome) is never a legal synthetic-paste target. +pub fn target_is_self_app(name: &str) -> bool { + name.trim().eq_ignore_ascii_case("codescribe") +} + +/// Single destination function. Advisors (quality gate, overlay flag, auto-paste +/// toggle) may veto a paste; they may not pick a different throne. +pub fn resolve_delivery_route(intent: DeliveryIntent, facts: DeliveryFacts) -> DeliveryDecision { + if !facts.has_text || facts.no_speech { + return DeliveryDecision { + route: DeliveryRoute::ArchiveOnly, + reason: "empty_or_no_speech", + }; + } + + match intent { + DeliveryIntent::AgentVoice => DeliveryDecision { + route: DeliveryRoute::AgentComposer, + reason: "assistive_intent", + }, + DeliveryIntent::OverlayToAgent => DeliveryDecision { + route: DeliveryRoute::AgentComposer, + reason: "explicit_to_agent", + }, + DeliveryIntent::NotesOnly => DeliveryDecision { + route: DeliveryRoute::ArchiveOnly, + reason: "notes_save_only", + }, + DeliveryIntent::OverlayInsert => DeliveryDecision { + route: DeliveryRoute::ClipboardPaste, + reason: "explicit_insert", + }, + DeliveryIntent::OrientDictation | DeliveryIntent::OrientFormat => orient_route(facts), + } +} + +fn orient_route(facts: DeliveryFacts) -> DeliveryDecision { + if facts.live_stream_session { + return DeliveryDecision { + route: DeliveryRoute::OrientCanvas, + reason: "live_stream_owns_canvas", + }; + } + if facts.commit_required { + return DeliveryDecision { + route: DeliveryRoute::OrientCanvas, + reason: "quality_commit_pending", + }; + } + if facts.latched_target_is_self { + return DeliveryDecision { + route: DeliveryRoute::OrientCanvas, + reason: "refuse_paste_into_self", + }; + } + if facts.auto_paste_enabled { + return DeliveryDecision { + route: DeliveryRoute::ClipboardPaste, + reason: "auto_paste_to_latched_target", + }; + } + if facts.overlay_enabled { + return DeliveryDecision { + route: DeliveryRoute::OrientCanvas, + reason: "overlay_is_destination", + }; + } + DeliveryDecision { + route: DeliveryRoute::ArchiveOnly, + reason: "no_visible_surface", + } +} + +/// One INFO line: route, reason, intent, latched target. The stop-path budget +/// already has a `delivery_secs` phase; this names *where* those seconds went. +pub fn format_delivery_route_line( + intent: DeliveryIntent, + decision: DeliveryDecision, + latched_target: Option<&str>, +) -> String { + format!( + "delivery_route: intent={intent} route={route} reason={reason} target={target}", + intent = intent.as_str(), + route = decision.route.as_str(), + reason = decision.reason, + target = latched_target.unwrap_or("-"), + ) +} + +#[cfg(test)] +mod tests { + use super::*; + + fn facts(overrides: impl FnOnce(&mut DeliveryFacts)) -> DeliveryFacts { + let mut f = DeliveryFacts { + has_text: true, + no_speech: false, + auto_paste_enabled: true, + overlay_enabled: true, + live_stream_session: false, + commit_required: false, + latched_target_is_self: false, + }; + overrides(&mut f); + f + } + + #[test] + fn empty_or_no_speech_archives_regardless_of_intent() { + for intent in [ + DeliveryIntent::OrientDictation, + DeliveryIntent::AgentVoice, + DeliveryIntent::OverlayToAgent, + DeliveryIntent::OverlayInsert, + ] { + let empty = resolve_delivery_route( + intent, + facts(|f| { + f.has_text = false; + }), + ); + assert_eq!(empty.route, DeliveryRoute::ArchiveOnly, "{intent:?}"); + assert_eq!(empty.reason, "empty_or_no_speech"); + + let silent = resolve_delivery_route( + intent, + facts(|f| { + f.no_speech = true; + }), + ); + assert_eq!(silent.route, DeliveryRoute::ArchiveOnly, "{intent:?}"); + } + } + + #[test] + fn assistive_never_pastes() { + let decision = resolve_delivery_route(DeliveryIntent::AgentVoice, facts(|_| {})); + assert_eq!(decision.route, DeliveryRoute::AgentComposer); + assert_eq!(decision.reason, "assistive_intent"); + assert!(!decision.route.posts_synthetic_paste()); + } + + #[test] + fn overlay_to_agent_is_first_class_not_focus_paste() { + let decision = resolve_delivery_route(DeliveryIntent::OverlayToAgent, facts(|_| {})); + assert_eq!(decision.route, DeliveryRoute::AgentComposer); + assert_eq!(decision.reason, "explicit_to_agent"); + } + + #[test] + fn hold_fn_with_agent_focused_stays_on_canvas() { + let decision = resolve_delivery_route( + DeliveryIntent::OrientDictation, + facts(|f| { + f.latched_target_is_self = true; + f.auto_paste_enabled = true; + }), + ); + assert_eq!(decision.route, DeliveryRoute::OrientCanvas); + assert_eq!(decision.reason, "refuse_paste_into_self"); + assert!(!decision.route.posts_synthetic_paste()); + } + + #[test] + fn hold_fn_auto_paste_targets_latched_app() { + let decision = resolve_delivery_route(DeliveryIntent::OrientDictation, facts(|_| {})); + assert_eq!(decision.route, DeliveryRoute::ClipboardPaste); + assert_eq!(decision.reason, "auto_paste_to_latched_target"); + assert!(decision.route.posts_synthetic_paste()); + } + + #[test] + fn overlay_without_auto_paste_is_the_canvas() { + let decision = resolve_delivery_route( + DeliveryIntent::OrientDictation, + facts(|f| { + f.auto_paste_enabled = false; + }), + ); + assert_eq!(decision.route, DeliveryRoute::OrientCanvas); + assert_eq!(decision.reason, "overlay_is_destination"); + } + + #[test] + fn quality_commit_and_live_stream_veto_paste() { + let commit = resolve_delivery_route( + DeliveryIntent::OrientFormat, + facts(|f| { + f.commit_required = true; + }), + ); + assert_eq!(commit.route, DeliveryRoute::OrientCanvas); + assert_eq!(commit.reason, "quality_commit_pending"); + + let live = resolve_delivery_route( + DeliveryIntent::OrientDictation, + facts(|f| { + f.live_stream_session = true; + }), + ); + assert_eq!(live.route, DeliveryRoute::OrientCanvas); + assert_eq!(live.reason, "live_stream_owns_canvas"); + } + + #[test] + fn notes_only_never_pastes() { + let decision = resolve_delivery_route(DeliveryIntent::NotesOnly, facts(|_| {})); + assert_eq!(decision.route, DeliveryRoute::ArchiveOnly); + assert_eq!(decision.reason, "notes_save_only"); + } + + #[test] + fn session_flags_map_to_intent() { + assert_eq!( + delivery_intent_from_session(true, true, true), + DeliveryIntent::AgentVoice + ); + assert_eq!( + delivery_intent_from_session(false, false, true), + DeliveryIntent::NotesOnly + ); + assert_eq!( + delivery_intent_from_session(false, true, false), + DeliveryIntent::OrientFormat + ); + assert_eq!( + delivery_intent_from_session(false, false, false), + DeliveryIntent::OrientDictation + ); + } + + #[test] + fn codescribe_is_self_case_insensitive() { + assert!(target_is_self_app("Codescribe")); + assert!(target_is_self_app(" codescribe ")); + assert!(!target_is_self_app("Ghostty")); + assert!(!target_is_self_app("")); + } + + #[test] + fn budget_line_names_the_throne() { + let line = format_delivery_route_line( + DeliveryIntent::OrientDictation, + DeliveryDecision { + route: DeliveryRoute::ClipboardPaste, + reason: "auto_paste_to_latched_target", + }, + Some("Ghostty"), + ); + assert_eq!( + line, + "delivery_route: intent=orient_dictation route=clipboard_paste reason=auto_paste_to_latched_target target=Ghostty" + ); + } +} diff --git a/app/controller/mod.rs b/app/controller/mod.rs index 34e75850..8d5730a5 100644 --- a/app/controller/mod.rs +++ b/app/controller/mod.rs @@ -25,6 +25,8 @@ mod assistive_delivery; /// Per-session assistive context bag (selection, app, images). mod context_bucket; +/// One destination throne: intent → Agent / Orient / paste. Focus is not king. +mod delivery_route; /// Stop-path final-pass routing, completeness, and budget reporting. mod final_pass; /// Session telemetry, image attach helpers, assistive send wiring. @@ -97,6 +99,10 @@ pub(crate) use assistive_delivery::{AssistiveDelivery, AssistiveLane}; use assistive_delivery::{ assemble_assistive_delivery_lane, assemble_raw_paste_wire, capture_combo_context_with_image, }; +use delivery_route::{ + DeliveryFacts, DeliveryIntent, DeliveryRoute, delivery_intent_from_session, + format_delivery_route_line, resolve_delivery_route, target_is_self_app, +}; pub(crate) use final_pass::{ FinalPassAction, FinalPassRoutingMode, FinalPassStages, SmartTailGapSource, StopPathBudget, StreamingCompletenessEvidence, append_tail_gap, apply_committed_density_floor, @@ -138,11 +144,14 @@ use overlay_paste::{ #[cfg(test)] use quality_delivery::AutomaticDeliverySink; use quality_delivery::{ - ActionQualityProbe, AutoPastePolicyContext, AutoPasteTrigger, AutomaticDeliveryOwner, - ClipboardDeliverySink, compose_final_status, evaluate_quality_commit_trigger, - maybe_wrap_transcript_for_delivery, maybe_wrap_transcript_for_delivery_with_quality, - recording_mode_label, resolve_auto_paste_policy, session_auto_format_enabled, - session_prewarms_semantic_guard, truth_recording_mode_label, + ActionQualityProbe, AutomaticDeliveryOwner, ClipboardDeliverySink, compose_final_status, + evaluate_quality_commit_trigger, maybe_wrap_transcript_for_delivery, + maybe_wrap_transcript_for_delivery_with_quality, recording_mode_label, + session_auto_format_enabled, session_prewarms_semantic_guard, truth_recording_mode_label, +}; +#[cfg(test)] +pub(crate) use quality_delivery::{ + AutoPastePolicyContext, AutoPasteTrigger, resolve_auto_paste_policy, }; pub(crate) use truth::{ adjudicate_recording_truth, apply_ai_noop_signal, postprocess_transcript_for_delivery, @@ -770,6 +779,22 @@ impl RecordingController { ); return Ok(false); } + let to_agent = resolve_delivery_route( + DeliveryIntent::OverlayToAgent, + DeliveryFacts { + has_text: true, + no_speech: false, + auto_paste_enabled: false, + overlay_enabled: true, + live_stream_session: false, + commit_required: false, + latched_target_is_self: false, + }, + ); + info!( + "{}", + format_delivery_route_line(DeliveryIntent::OverlayToAgent, to_agent, None,) + ); // Dictation/formatting sessions never run the assistive pipeline branch // that arms `pending_assistive_context`, so the overlay's explicit // "To Agent" used to fail closed behind a live button (review P0-03). @@ -4249,24 +4274,28 @@ impl RecordingController { let has_final_text = !final_formatted_text.trim().is_empty(); let notes_save_only = config.quick_notes_enabled && config.quick_notes_save_only; - let should_auto_paste = resolve_auto_paste_policy(AutoPastePolicyContext { - trigger: if force_ai { - AutoPasteTrigger::DoubleLeftOption - } else { - AutoPasteTrigger::Hold + let latched_target = self.pre_overlay_frontmost_app.read().await.clone(); + let intent = delivery_intent_from_session(assistive, force_ai, notes_save_only); + let decision = resolve_delivery_route( + intent, + DeliveryFacts { + has_text: has_final_text, + no_speech: truth_no_speech_reason.is_some(), + auto_paste_enabled: config.auto_paste_enabled, + overlay_enabled: config.transcription_overlay_enabled, + live_stream_session, + commit_required: commit_trigger.is_some(), + latched_target_is_self: latched_target.as_deref().is_some_and(target_is_self_app), }, - persisted_enabled: config.auto_paste_enabled, - overlay_enabled: config.transcription_overlay_enabled, - assistive, - no_speech: truth_no_speech_reason.is_some(), - empty_output: !has_final_text, - notes_save_only, - // Live-stream preview and explicit quality/safety commit branches - // remain separate named vetoes. Toggle-adjudicated final delivery is - // no longer a veto. - live_stream_session, - commit_required: commit_trigger.is_some(), - }); + ); + info!( + "{}", + format_delivery_route_line(intent, decision, latched_target.as_deref()) + ); + // Destination is the route, not a second policy boolean. The legacy + // auto-paste matrix still exists for its own tests; the stop path no + // longer consults it as a competing king. + let should_auto_paste = decision.route.posts_synthetic_paste(); // Delivery span: history persistence + paste/deliver_once handoff. // This is the user-visible delivery cone — not phase-4 cleanup. @@ -4298,9 +4327,9 @@ impl RecordingController { // Paste lane consumes the ContextBucket exactly like assistive delivery // does (assemble + archive under one lock — parity with - // deliver_pending_assistive_transcript). Assistive sessions never take - // this branch (`resolve_auto_paste_policy` vetoes them), so their bucket - // stays intact for the overlay delivery lane. + // deliver_pending_assistive_transcript). Agent-composer sessions never + // take this branch (`DeliveryRoute::ClipboardPaste` is the only paste + // king), so their bucket stays intact for the overlay To Agent lane. let paste_wire = if should_auto_paste { let mut bucket = self.context_bucket.lock().await; let wire = assemble_raw_paste_wire(&final_formatted_text, &bucket); @@ -4323,17 +4352,51 @@ impl RecordingController { &mode_label, Some(&truth_metadata), ); - if self - .automatic_delivery - .deliver_once(recording_timestamp, &paste_text) - .await? - { - info!("Text pasted successfully"); + // Restore the *latched* target before Cmd+V. Frontmost-at-stop is + // not the destination — that is how tagged raw landed in the Agent + // composer (operator: walka o tron, delivery axis). + if let Some(app_name) = latched_target.as_deref() { + let activated = activate_app_by_name(app_name); + let focus_confirmed = + activated && wait_for_frontmost_app(app_name, OVERLAY_PASTE_FOCUS_BUDGET); + debug!( + app_name, + activated, focus_confirmed, "Stop-path paste target activation" + ); + } + let frontmost = crate::os::selection::current_frontmost_app_name(); + let preflight = clipboard::synthetic_paste_preflight(); + let disposition = overlay_paste_disposition( + latched_target.as_deref(), + frontmost.as_deref(), + preflight.can_post_events(), + ); + if disposition == OverlayPasteDisposition::Paste { + if self + .automatic_delivery + .deliver_once(recording_timestamp, &paste_text) + .await? + { + info!("Text pasted successfully"); + } else { + info!("Automatic delivery skipped: recording timestamp already delivered"); + } } else { - info!("Automatic delivery skipped: recording timestamp already delivered"); + clipboard::set_clipboard(&paste_text) + .context("Failed to copy transcript after paste target was not confirmed")?; + info!( + ?disposition, + target = ?latched_target, + frontmost = ?frontmost, + "delivery_route: synthetic paste refused; clipboard copy only" + ); } } else { - info!("Auto-paste skipped (mode={})", mode_label); + info!( + "Auto-paste skipped (mode={mode_label} route={route} reason={reason})", + route = decision.route.as_str(), + reason = decision.reason, + ); } let delivery_secs = delivery_started.elapsed().as_secs_f64(); diff --git a/docs/DELIVERY_ROUTE.md b/docs/DELIVERY_ROUTE.md new file mode 100644 index 00000000..cbe7bf4a --- /dev/null +++ b/docs/DELIVERY_ROUTE.md @@ -0,0 +1,52 @@ +# Delivery route — one throne for destination + +> Operator 2026-08-15: the stop path was a fight for the throne. This file is +> the destination axis only. Mic lock, transcript truth, and agent-chain +> memory stay other thrones. + +## Law + +1. **Intent is frozen at session start** (or at an explicit overlay click). + OS focus at stop time is not an input. +2. **`resolve_delivery_route` is the only function that picks a destination.** + Auto-paste, overlay Insert, and To Agent consult it. They do not invent a + second king. +3. **Codescribe is never a legal Cmd+V target.** A latched self-app (Agent + composer, overlay, settings) stays on the Orient canvas or goes to the + Agent composer as a first-class message — never as a tagged paste into + ourselves. That is how `` stopped landing in chat. + +## Intent → route + +| Intent | Typical gesture | Route | +|---|---|---| +| `AgentVoice` | Double Right Option / assistive hold | `AgentComposer` | +| `OverlayToAgent` | overlay **To Agent** | `AgentComposer` | +| `OrientDictation` | Hold Fn / Globe | `ClipboardPaste` if auto-paste + latched foreign app; else `OrientCanvas` | +| `OrientFormat` | Double Left Option | same as dictation | +| `OverlayInsert` | overlay Insert | `ClipboardPaste` (fail-closed to copy / Paste Here) | +| `NotesOnly` | save-only notes | `ArchiveOnly` | + +Vetoes that keep Orient off the paste gun: empty / no-speech, live-stream +session, quality-commit pending, latched target is Codescribe. + +## Telemetry + +One INFO line per stop / To Agent: + +```text +delivery_route: intent=orient_dictation route=clipboard_paste reason=auto_paste_to_latched_target target=Ghostty +``` + +`reason=refuse_paste_into_self` is the smoking gun for "I was looking at the +Agent and Hold Fn dumped raw into the composer". + +## What this cut does not do + +- It does not pick the transcript (Apple / Whisper / final-pass). That is + `adjudicate_recording_truth`. +- It does not lock the microphone. That is still a missing `RecordingSessionOwner`. +- It does not make the agent chain mandatory. `previous_response_id` stays + best-effort until that throne is cut. + +Stacked on `fix/engine-routing`. diff --git a/docs/HOTKEYS_CONTRACT.md b/docs/HOTKEYS_CONTRACT.md index ce5c341b..6a178f52 100644 --- a/docs/HOTKEYS_CONTRACT.md +++ b/docs/HOTKEYS_CONTRACT.md @@ -47,6 +47,18 @@ new thread is only ever minted by an explicit "+ New thread" (published as a utterance). If the Agent UI never published a selection (window never opened), the lane continues its bound conversation as before. +**Delivery destination (operator contract 2026-08-15).** The hotkey picks the +*intent*; it does not paste into the frontmost app. `resolve_delivery_route` +(`docs/DELIVERY_ROUTE.md`) is the only destination function: + +- Assistive hold → Agent composer (first-class message, never Cmd+V) +- Hold Fn / Globe → Orient canvas, plus auto-paste **only** into the app + latched at key-down — never into Codescribe itself +- Overlay **To Agent** → Agent composer, even after the session reset + +Focus at stop time is not an input. A tagged `` paste +into the Agent composer is a doctrine violation. + ```mermaid flowchart TB subgraph Input["🎹 Input Layer"]