ADR-0235accepted

Demand-Driven Gateway Events

Context

The gateway extension drains Redis events by injecting them as user messages into the pi session, which forces an LLM response for every batch. Over 7 days this produced:

  • 2,782 LLM responses, 1,335 from self-talk (responding to its own digest injections)
  • ~157K output tokens mostly spent on “No operator action needed” and unsolicited strategic analysis
  • 780 Telegram messages averaging 367 chars, many redundant triage summaries
  • 269 automated digest injections, each triggering a full LLM turn

The AUTO_MESSAGE_DIGEST_ONLY_THRESHOLD = 20 only throttles which events pass through — it still injects messages that demand LLM responses. The gateway generates long strategic advice, personnel commentary, and play-by-play status updates that nobody asked for.

Decision

Switch the gateway from broadcast mode (inject events → force LLM response → route output) to demand-driven mode (accumulate context silently → surface when operator asks).

Rules

  1. System events accumulate in a context buffer, not as user messages. The buffer is a structured summary stored in Redis and injected into the session via before_agent_start as context — NOT as a user message that demands a response.

  2. Only critical events break through as proactive alerts:

    • Sustained failures (pod down, worker crash, >3 consecutive errors)
    • Security concerns (auth failures, secret expiry)
    • Watchdog alarms (missed heartbeat — existing behavior, stays)
    • Explicit operator-facing notifications with critical: true flag
  3. When Joel sends a message, the accumulated context is prepended so the gateway knows what happened but only speaks about it if relevant to Joel’s question.

  4. The gateway stops generating unsolicited triage/strategy. No “Three things worth your attention” unless asked. No personnel advice. No “No operator action needed.”

  5. Subscription updates and feed digests go to the context buffer. They’re available if Joel asks “what happened” but don’t trigger responses.

  6. Counter and digest-only mode are removed. The automatedInjectedCount / AUTO_MESSAGE_DIGEST_ONLY_THRESHOLD mechanism is no longer needed — the default is silence.

Context Buffer Contract

interface GatewayContextBuffer {
  events: Array<{
    type: string;
    summary: string;  // one-line human-readable
    ts: number;
    critical: boolean;
  }>;
  lastFlushedAt: number;
  eventCount: number;  // total since last operator message
}
  • Stored in Redis key joelclaw:gateway:context-buffer
  • TTL: 24h (auto-expires stale context)
  • Max 50 events in buffer (oldest evicted)
  • Flushed (cleared) after operator message is processed

Consequences

  • Dramatically fewer LLM turns. Gateway only responds when Joel talks to it or when something is genuinely broken.
  • No more Telegram spam. Proactive messages limited to critical alerts.
  • Context is preserved, not lost. Events still accumulate — they’re just silent until relevant.
  • Watchdog alarms unchanged. Missed heartbeats still fire immediately.
  • Breaking change for gateway session. Requires daemon restart after deploy.