Skip to content

[factory] Deliver briefs and messages over MCP instead of simulated keystrokes #1531

Description

@khaliqgant

Summary

Agent briefs and agent-to-agent messages are delivered by simulating keystrokes into a PTY. That forces the broker to decide when a third-party TUI is "ready for typed input", which it can only do by scraping the vendor's screen. On 2026-08-15 that heuristic stopped matching and the entire fleet silently stopped accepting work for hours.

Deliver over the agent-relay MCP channel the agent already holds, and readiness detection stops being load-bearing.

What happened, so the motivation is concrete

claude_grid_ready (crates/relay-pty/src/readiness.rs) requires the literal string "Welcome back" or "Welcome to " on screen:

let has_welcome = grid.screen.contains("Welcome back") || grid.screen.contains("Welcome to ");
has_welcome && claude_prompt_row(grid)

Claude Code does not render that greeting on every launch. Three consecutive live launches on finn-mini showed a composer and no banner, so the gate returned false every time → worker_ready never fired → initial_tasks was never released.

The symptom is an agent that looks completely healthy and does nothing: it boots, registers, holds a brief it is never handed, sits at 0% CPU, and writes no session transcript. The objective was present and correct on the agent record the whole time — 7371 characters of it. Injected relay messages ride the same path, so agent-to-agent DMs died with it.

relay#1529 restores a bounded timeout so this degrades instead of hanging. That is a safety net, not the fix.

Why the current design cannot be made reliable

  1. It depends on vendor cosmetics. Any upstream change to a greeting, a what's-new panel, or a composer glyph can strand every agent. We do not control, and are not notified of, those changes.
  2. Failure is silent. The one diagnostic is tracing::warn!(target: "agent_relay::worker::pty", ...), and no broker log file exists with RUST_LOG unset on these nodes — so the outage produced no observable output anywhere.
  3. The tests structurally cannot catch it. ->pty:ready short-circuits readiness before any heuristic runs. It appears three times in the repo: the check at readiness.rs:28, a unit test, and tests/e2e/fleet/nodes/stub-agent.cjs:70 which writes it. Nothing in production emits it — only the E2E test double. So every fleet E2E run takes the reliable path while production takes the fragile one, and claude_grid_ready is never exercised under test. "Fleet E2E is green" currently carries no information about readiness detection.
  4. It is unevenly applied. claude and gemini early-return on a pure screen heuristic with no fallback; codex and the generic set fall through to total_bytes > 500, which is why they kept working. Gemini has the identical latent hazard.

Proposed direction

The broker already spawns each harness with --mcp-config pointing at its own agent-relay mcp server, and that server already exposes check_inbox. So a relay-owned, vendor-independent delivery channel exists and is connected on every spawn — it is simply not used for delivery.

Sketch, for the implementer to challenge rather than follow:

  • On MCP initialize, have the server report to the broker that the harness is up. That is a real handshake rather than a string match. Note the honest limit: it proves the process booted and is loading config — roughly what harness_started already proves — not that a TUI accepts keystrokes. It is a better trigger, not a complete answer.
  • Deliver the initial task and subsequent messages as MCP-visible inbox items rather than typed keystrokes, so delivery never depends on terminal state at all. This is the part that actually removes the failure class.
  • Keep PTY injection as a fallback for harnesses without an MCP channel.
  • Decide explicitly what happens for a harness that never connects MCP, and make that path loud.

Definition of done

  • A brief reaches a spawned agent without any dependency on screen content. Demonstrate by making detect_cli_ready return false unconditionally and showing the agent still receives and acts on its task.
  • Agent-to-agent DM delivery demonstrated the same way, measured at the recipient with a unique marker grepped from the recipient's own transcript — never from the sender's receipt. Sender-side receipts are exactly what hid relay#1523 for a day.
  • A test that exercises the real path. If the fleet E2E stub keeps emitting ->pty:ready, add coverage that does not, or the suite keeps passing while production is broken.
  • State plainly which harnesses are covered and which still fall back to PTY injection.

Notes for whoever picks this up

  • Do not treat relay#1529 as having fixed this. It bounds the damage; the coupling remains.
  • The ->pty:ready marker is a designed escape hatch that only a test stub uses. Either make it real or stop pretending it is a production path.
  • Read relay#1529 and its review thread first — two reviewers caught a genuine hole in the first cut, where a timeout would have released a brief into an unanswered Codex trust prompt. Distinguishing "unrecognised prompt" from "deliberate veto" matters in any redesign.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions