---
title: Every Message Gets a Receipt
description: The closed-loop contract for inbound messages — ingested, routed, receipted, processed — the scheduled dispatcher that runs it, and the three reliability lessons that cost real data.
track: delivery
url: https://www.chiefofstaff.io/guides/every-message-gets-a-receipt
published: 2026-09-01
author: chiefofstaff.io editorial system
outcome: run a scheduled poll faster than the platform's retention timer, route every inbound message to a terminal outcome, send a threaded receipt naming what happened, and monitor the poll job with a dead-man's switch.
prerequisites: telegram-as-the-channel
source: chiefofstaff.io
---

# Every Message Gets a Receipt

The closed-loop contract for inbound messages — ingested, routed, receipted, processed — the scheduled dispatcher that runs it, and the three reliability lessons that cost real data.

## The Complaint That Created the Rule

Paraphrased from the message that became a decision record: *"Everything I send you needs to result in something happening. I send you things over the day; I expect something to happen after. You might ask me about each one. I don't want us to just ignore the messages. Even if you did act on them, I want to know."*

The system had been pulling messages. It had been logging them. Several had sat in the log for days, fetched and unprocessed, because nothing *routed* the backlog — the pull step lived inside the brief command and only ran when the brief ran. Messages were "pulled, not processed", which to the person who sent them is indistinguishable from being ignored.

## The Contract

Every inbound message must reach a **terminal outcome** — actioned, filed as a task, noted in the vault, answered, or answered with a question — and get a **receipt** back, threaded to the message, naming the outcome, *before* it is flagged processed.

![Ingested (👀), routed (🤔), receipted (👍), processed — a scheduled poll every ten minutes and a headless routing session.](/img/diagrams/closed-loop-inbound.svg)

## The Dispatcher

A scheduled job every ten minutes:

1. **Pull** new messages via the coordinator's canonical pull script. New entries are appended to the history file *before* the platform offset is acknowledged, so a crash between the two loses nothing.
2. **If there is a backlog**, launch a headless routing session. It sweeps every unprocessed message, routes each to an outcome — creating tasks with provenance, updating notes, drafting answers — and sends one receipt per batch, threaded to the oldest message, with a line per item.
3. **Mark processed** only after the receipt is sent.
4. **Set reactions** at each transition — 👀 on ingest, 🤔 while the session runs, 👍 on processed — from the deterministic code paths that own those transitions, never from an agent's discretion.

A lock prevents two routing sessions from overlapping; the lock has a timeout, because a session that fans out to slow curators once held it for 57 minutes and the dispatcher double-processed a message when the lock expired mid-run. Locks need timeouts; timeouts need to exceed the longest legitimate run.

## The Three Lessons That Cost Data

**Respect retention timers.** Bot-API polling endpoints often keep unfetched updates for only about 24 hours. If your poll cadence is "whenever the human runs a brief", a quiet ten days silently discards everything. Rule: any surface with a retention timer gets a scheduled poll at least twice as fast as the timer, independent of anything the human does. "Embedded in another routine" is not "scheduled."

**The monitor needs a monitor.** The scheduled poll later died silently — the job stopped, nobody noticed, a message was lost anyway. Every loss-sensitive scheduled job now needs a dead-man's switch: an independent check that alarms when the job stops running. Silence from a monitor is not good news.

**Pulled is not processed; routed is not done.** A message can be fetched and logged and still never acted on. And — the later, sharper version — a request can be routed into a task, receipted, and *still* never executed, because nothing works the system's own tasks. That produced the [execution lane](/guides/the-execution-lane).

## Report Loss Honestly

When messages age out, say so plainly — "messages between X and Y were lost to the retention window; please resend anything important" — and ask. Do not retry the API in the hope of finding what the contract says is gone.

## Receipts That Don't Mislead

A receipt is a claim, and claims need evidence. Lessons that shaped the wording:

- Name the exact artifact location. "Filed as task `abc` in venture X; note added to the venture hub" — not "handled."
- A receipt that says "will surface in the brief on the 23rd" is only true if the mechanism exists. One did not (deferred tasks never woke), and the receipt was a promise the system could not keep.
- A blocked receipt — the session could not send — must not be silently retried later as if fresh, and must not starve the queue behind it. Surface the block where the human will see it, at the first tick, not the third.

## Resuming Context

The newest refinement: a reply to a message the system sent **resumes the session that sent it**, instead of starting a fresh session that must re-read everything. The history log records which session produced each outbound message; the dispatcher routes a threaded reply to `resume <that session>`. Your reply *is* the instruction to continue, with the producing context intact.