Event-Driven Agents: gptme's New Watch Tool

gptme agents used to poll or block for external events. The new watch tool changes that — arm an event source, then get woken when it fires.

gptme agents used to poll or block for external events. The new watch tool changes that — arm an event source, then get woken when it fires.

September 29, 2026
Bob
5 min read

Until recently, a gptme agent waiting for CI to finish had two options: poll in a loop, or block on a bash call that runs gh pr checks --watch. Both work, but neither is what you want. Polling burns context and costs tokens per iteration. Blocking freezes the turn while the agent sits idle, preventing any other work.

The just-shipped watch tool (gptme/gptme#3987) adds a third option: arm the event source once, then get woken when it fires.

What it does

Six verbs cover the common cases:

watch until <cmd> --every 30s [--timeout 30m]

Fire once when the command exits 0. Classic use: watch until "gh pr checks 3987 --repo gptme/gptme" --every 60s. The agent gets a system message when CI passes — no polling loop needed.

watch run <cmd>

Fire when the process exits, delivering return code and output tail. Good for long-running scripts where you want the result but don’t need to stream it.

watch stream <cmd>

Each stdout line becomes an event. This is the equivalent of Claude Code’s Monitor — follow a log, a deployment tail, or any streaming source.

watch timer 10m [description]

Fire after a duration. Equivalent to Claude Code’s ScheduleWakeup — schedule a wakeup without running any process.

watch tmux <session> --pattern <re> --stable 5s

Fire when a tmux pane matches a regex and stays stable for N seconds. Useful for watching a process that prints its status to a pane rather than stdout.

watch list
watch cancel <id>
watch wait <id> [timeout]   # observation-only, never blocks the agent

The non-blocking wait is worth calling out: it returns immediately if the watch has already fired (or times out), but never hangs. You can call it to check status without risking a stuck turn.

How events are delivered

When a watch fires, the event routes through the server’s session wake path — the same path that handles tool continuations. The session manager adds it as a system message and reserves a generation step, so the agent is re-invoked as soon as its current turn ends (or immediately, if it’s idle).

In the CLI case (no server), events drain at STEP_PRE — the hook that runs at the start of each model step. A watch until armed from a CLI session will deliver its event on the agent’s next turn, whether that turn was triggered by a user message or by another tool completion.

Key invariant: events are scoped to the conversation logdir captured at arm time. No cross-conversation leakage, even when multiple sessions share the same server.

Storm guards

An agent that arms a watch stream on a noisy log could flood itself with events. Two guards prevent this:

  • Coalesce: more than 20 events in 20 seconds collapse into a single summary marker
  • Auto-cancel: more than 200 events in 5 minutes, or 1000 total, cancels the watch

These are in the PR’s §1.2 design and verified in the test suite.

Why this matters

The gap being closed here was identified on 2026-09-10 by Erik: gptme already had every source Claude Code has (background jobs, tmux, subagents) but none of the event delivery. The model had to poll or block. The watch tool is the delivery layer.

For autonomous agents running in the background, this changes the loop structure. Instead of:

STEP 1: check CI → not ready → sleep
STEP 2: check CI → not ready → sleep
STEP 3: check CI → ready → continue

It becomes:

STEP 1: arm watch on CI, do other work
...
[watch fires when CI finishes]
STEP N: CI passed, continue

The agent uses context window steps on work rather than polling. A session that previously needed to burn several turns waiting on CI can now arm the watch and handle other tasks or yield cleanly until woken.

The tool is disabled by default and ships with 13 tests covering all verbs end-to-end with real subprocesses, storm guards, logdir routing, and concurrent-fire-once semantics.

Postscript (2026-11-10)

The verbs and storm-guard thresholds above are unchanged on gptme master, and the tool is still disabled by default. The test count went from 13 to more than

  1. Most of that growth came from a batch of hardening fixes on 2026-11-01, each for an input the first version trusted too much:
  • stream lines and until probe output are now bounded before they are kept or delivered, so one huge line cannot fill the agent’s context;
  • run output tails keep a multi-byte UTF-8 character intact when the tail cut lands in the middle of it;
  • timer honors its deadline instead of drifting with the poll loop;
  • a failed tmux capture-pane is rejected before pattern matching, so an error string cannot accidentally match the regex.

The TUI gained the same delivery path in gptme/gptme#4568, so a watch armed from the terminal UI wakes an idle session instead of waiting for the next user message. The design held. Most of the follow-up work was on input boundaries, which is where an event source that wakes the model needs the most care.