feat(orchestrator): backstop no-reply du rendez-vous inter-agents
Remédiation du wedge persistant après échec live du fix Finding A (77e62e5).
Détecte la fin de tour d'un agent sollicité qui n'a pas appelé idea_reply et
débloque l'agent demandeur au lieu de le laisser en attente indéfinie.
Ajoute le suivi de tour côté inspector Claude (claude_turn_watcher) et la
résolution des chemins de session (claude_paths), câblés dans le rendez-vous
idea_ask_agent ⇄ idea_reply.
Build workspace vert, suite complète verte, zéro warning.
Backstop NON prouvé levé en live : merge develop interdit tant que la levée
du wedge n'est pas validée en conditions réelles.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@ -173,32 +173,22 @@ pub trait InputMediator: Send + Sync {
|
||||
/// own the delivery write).
|
||||
fn bind_handle(&self, _agent: AgentId, _handle: PtyHandle) {}
|
||||
|
||||
/// Like [`InputMediator::bind_handle`], but also arms **prompt-ready detection**
|
||||
/// (cadrage §6, lot C5): `prompt_ready_pattern` is the agent profile's optional
|
||||
/// literal marker (`AgentProfile::prompt_ready_pattern`). When `Some`, the mediator
|
||||
/// watches the bound handle's output stream and calls [`InputMediator::mark_idle`]
|
||||
/// the first time the marker appears (one of the two OR signals; the other is an
|
||||
/// explicit `idea_reply`). When `None`, no pattern detection is armed — the agent
|
||||
/// only returns `Idle` on the explicit signal or the per-turn timeout (fallback
|
||||
/// «en cas de doute → reste Busy mais la file accepte»; never a false `Idle`).
|
||||
///
|
||||
/// It also records the target's [`SubmitConfig`] (ARCHITECTURE §20.3) so the
|
||||
/// adapter can echo `submit_sequence`/`submit_delay_ms` on the
|
||||
/// Like [`InputMediator::bind_handle`], but also records the target's
|
||||
/// [`SubmitConfig`] (ARCHITECTURE §20.3) so the adapter can echo
|
||||
/// `submit_sequence`/`submit_delay_ms` on the
|
||||
/// [`crate::events::DomainEvent::DelegationReady`] published when a turn starts.
|
||||
/// The bind is the natural carrier: both the prompt pattern and the submit config
|
||||
/// are per-agent profile data the orchestrator resolves at the same time, so they
|
||||
/// travel together (no extra ticket field, no second resolve).
|
||||
/// The bind is the natural carrier: the submit config is per-agent profile data the
|
||||
/// orchestrator resolves at bind time, so it travels with the handle (no extra
|
||||
/// ticket field, no second resolve).
|
||||
///
|
||||
/// Default: delegates to [`InputMediator::bind_handle`], ignoring the pattern and
|
||||
/// submit config (a mediator that does not observe the output stream). The infra
|
||||
/// adapter overrides it to arm the watcher and stash the submit config.
|
||||
fn bind_handle_with_prompt(
|
||||
&self,
|
||||
agent: AgentId,
|
||||
handle: PtyHandle,
|
||||
_prompt_ready_pattern: Option<String>,
|
||||
_submit: SubmitConfig,
|
||||
) {
|
||||
/// Turn-end detection is **no longer** armed here: the dead PTY prompt-ready sniff
|
||||
/// was replaced by the transcript [`crate::ports::TurnWatcher`] (armed at the
|
||||
/// composition root, once per live session) which calls
|
||||
/// [`InputMediator::turn_ended`].
|
||||
///
|
||||
/// Default: delegates to [`InputMediator::bind_handle`], ignoring the submit config.
|
||||
/// The infra adapter overrides it to stash the submit config.
|
||||
fn bind_handle_with_submit(&self, agent: AgentId, handle: PtyHandle, _submit: SubmitConfig) {
|
||||
self.bind_handle(agent, handle);
|
||||
}
|
||||
|
||||
@ -212,12 +202,12 @@ pub trait InputMediator: Send + Sync {
|
||||
}
|
||||
|
||||
/// Déclare qu'`agent` vient d'être **lancé à froid** : la livraison de son tout
|
||||
/// premier tour doit être *gatée* sur le prompt-ready (la `DelegationReady` est
|
||||
/// différée jusqu'à l'apparition du prompt du CLI), pour éviter d'écrire la tâche
|
||||
/// premier tour doit être *gatée* sur la readiness MCP (la `DelegationReady` est
|
||||
/// différée jusqu'à la connexion du pont MCP du CLI), pour éviter d'écrire la tâche
|
||||
/// avant que le CLI ait fini de booter (premier tour perdu sinon).
|
||||
///
|
||||
/// À n'appeler **que** lorsqu'un prompt-ready watcher sera effectivement armé (le
|
||||
/// profil porte un `prompt_ready_pattern` non vide). Sans watcher pour le libérer,
|
||||
/// À n'appeler **que** lorsqu'un signal de readiness le libérera (le profil porte un
|
||||
/// pont MCP ⇒ [`InputMediator::release_cold_start`]). Sans signal pour le libérer,
|
||||
/// gater le premier tour le bloquerait indéfiniment ; dans ce cas l'orchestrateur ne
|
||||
/// doit **pas** appeler `mark_starting` et l'`enqueue` livre la tâche immédiatement
|
||||
/// (chemin chaud, fallback sûr, zéro régression).
|
||||
@ -229,10 +219,10 @@ pub trait InputMediator: Send + Sync {
|
||||
/// connecter (son CLI est up et a chargé les outils `idea_*`). Si un premier tour
|
||||
/// a été différé par [`InputMediator::mark_starting`] (démarrage à froid), c'est le
|
||||
/// moment de le livrer ⇒ draine le `DelegationReady` retenu. **Contrairement à
|
||||
/// `prompt_ready`, aucun repli `mark_idle`** : c'est un signal de DÉMARRAGE, pas de
|
||||
/// fin de tour. Idempotent : un second appel (ou après que `prompt_ready` ait déjà
|
||||
/// drainé) ne trouve rien et est un no-op. En OR avec le prompt-ready : le premier
|
||||
/// arrivé draine, l'autre est no-op.
|
||||
/// `turn_ended`, aucun repli `mark_idle`** : c'est un signal de DÉMARRAGE, pas de
|
||||
/// fin de tour. Idempotent : un second appel ne trouve rien et est un no-op. C'est
|
||||
/// désormais le **seul** drain du tour différé (le signal MCP-initialize), le
|
||||
/// watcher prompt-ready PTY ayant été supprimé.
|
||||
///
|
||||
/// Default: no-op (médiateur qui ne gate pas les démarrages à froid).
|
||||
fn release_cold_start(&self, _agent: AgentId) {}
|
||||
@ -254,9 +244,24 @@ pub trait InputMediator: Send + Sync {
|
||||
/// is **not** an enqueue and correlates **no** ticket.
|
||||
fn preempt(&self, agent: AgentId);
|
||||
|
||||
/// Marks `agent` free (prompt-ready or explicit signal) so its FIFO advances.
|
||||
/// Marks `agent` free (explicit signal) so its FIFO advances.
|
||||
fn mark_idle(&self, agent: AgentId);
|
||||
|
||||
/// **End-of-turn** signal: the agent's transcript just recorded a completed turn
|
||||
/// (the [`crate::ports::TurnWatcher`] fired), and **no** `idea_reply` carried a
|
||||
/// result. This is the no-reply backstop trigger: the adapter captures the active
|
||||
/// ticket, marks the agent `Idle` (advancing the FIFO), then arms a short **grace**
|
||||
/// window — if no `idea_reply` correlates the ticket by then, it completes the turn
|
||||
/// "without reply" (waking a parked caller with a typed error instead of leaving it
|
||||
/// blocked until the long timeout). A late `idea_reply` within the grace wins.
|
||||
///
|
||||
/// Replaces the former prompt-ready watcher branch verbatim; only the **trigger**
|
||||
/// changed (transcript `turn_duration` instead of a PTY prompt sigil). Default:
|
||||
/// [`InputMediator::mark_idle`] (a mediator with no mailbox/grace just advances).
|
||||
fn turn_ended(&self, agent: AgentId) {
|
||||
self.mark_idle(agent);
|
||||
}
|
||||
|
||||
/// Records a **proof of liveness** (« battement ») for `agent` — called on every
|
||||
/// non-terminal turn event (text delta, tool activity, [`crate::ports::ReplyEvent::Heartbeat`])
|
||||
/// by the drain loop. Refreshes the per-agent `last_seen` timestamp so the stall
|
||||
|
||||
Reference in New Issue
Block a user