An agent waiting for CI

Published

An agent waiting for CI can burn tokens without doing useful work. If it keeps waking up to check progress, each empty check can trigger another model turn. Users have reported this in Codex and Claude Code. I want the harness to handle the wait and resume the agent when a result arrives.

Illustrative agent conversation: a user request appears in a gray bubble on the right. The agent replies on the left, pushes a fix, passes local checks, and ends its turn. After an idle pause, a distinct MCP event card appears in the center of the conversation. The agent starts a new turn on the left: “The CI event arrived. Let me check the logs.”

I added native MCP Events to Happen (my harness) and forked GitHub CLI to give an agent a way to watch CI between turns.

CI results are one use for this. The same mechanism could carry tweets, news, or any other event an MCP server exposes. The harness handles the subscription and delivery; the agent decides what the event means in the context of its task.

The MCP event UI is still a work in progress. The illustration simplifies a real session fixing CI for Listenbox. The agent pushed a fix, passed its local checks, and restored a watch for completed CI runs. It then ended its turn. The conversation stayed idle until an event arrived and started a new turn. The watch was configured like this:

{
  "server": "github_events",
  "name": "github.workflow_run",
  "arguments": {
    "branch": "master",
    "workflow": "CI",
    "status": "completed"
  }
}

Both successful and failed runs match. On delivery, the agent can check the commit, inspect a failure, or stop the watch once CI passes.

A subscription outlives a turn

OpenAI announced MCP Events support at DevDay on September 29, 2026. The timing surprised me: the first design sketch dates back to April 9, nearly six months earlier.

The MCP Triggers & Events proposal is still experimental. It describes event discovery and three delivery modes: poll, push, and webhook. Polling happens in the client runtime; an empty response needs no model call.

Binding a subscription to a thread is a design choice in Happen. The draft's “Delivering Events to the LLM” section explicitly leaves conversation handling to the application. A harness can create a new conversation or route the event to an existing one.

In Happen, subscribe_mcp_events binds the watch to the calling saved thread. That association stays local to the harness. A matching event enters that thread's inbox and starts a new turn when idle. Reusing the thread preserves the original task and repair history. An integration test checks that delivery adds a second turn to the same thread and that the model request still includes the original prompt.

Happen persists the inbox entry, deduplication receipt, and resume checkpoint together. Ending a turn or disconnecting the UI keeps the watch active. Unsubscribe, archive, or provider termination removes it. On macOS, an active saved watch also prevents idle system sleep; the display can still sleep.

Reusing gh

My GitHub CLI fork adds gh mcp serve, a read-only stdio Events server. It uses the existing gh login and the same repository resolver as gh browse, including gh repo set-default. Run it in the project directory. There is no extra token or webhook endpoint to configure; the setup is documented in the fork.

The fork advertises poll delivery for github.workflow_run, with a default observation interval of 15 seconds. It shares the observation cache across subscriptions. The host filters occurrences before waking the model.

This source samples the latest 100 runs and buffers 512 observed changes. Intermediate transitions can be missed, and replay does not survive a gh process restart. A replay gap is reported explicitly.

What waiting costs

The saving is avoiding model turns that ask whether CI has finished. The completed-run filter also keeps queued and running updates out of the conversation. Authentication, repository selection, timers, and cursors stay in ordinary code.

GitHub requests, host memory, and power still cost something. Keeping a laptop awake has a battery cost too. I have no measured token or energy comparison yet. Once the relevant run is green, the agent should call unsubscribe_mcp_events so the watch stops consuming resources.