telegram.day3.intake-boundary

The Inbound Intake Boundary for Telegram Updates

Telegram updates become operational only when the API boundary, normalization, and listener shell stay separate.

telegram / channel-server / api / intake / filesystem-first / matic

Thesis

The foreground listener becomes useful only when Telegram updates cross a clear intake boundary. The inbound-intake work adds that boundary without letting Telegram's API shape leak into the listener lifecycle.

The source-layout and foreground-shell work established the filesystem contract and foreground shell. The inbound-intake work answers the next question: what happens when the listener has to read real Telegram updates?

The answer is not "put an API call in the loop." The answer is a small intake boundary with explicit offsets, injectable HTTP behavior, representative payload tests, and a normalization step that preserves raw data.

The API Boundary

The Telegram API client lives in:

src/pkgs/core/channels/telegram/api.py

TelegramUpdatesClient calls the Telegram getUpdates endpoint. It accepts a bot token, an injectable HTTP getter, and a URL template. Its main method takes an explicit offset and timeout:

get_updates(offset=<next offset>, timeout=<seconds>)

That shape is deliberate. Offset and timeout are not hidden globals. They are the core operational controls for long polling. Tests can assert that they are sent to Telegram, and the listener can pass the current high-water mark from state.

The HTTP boundary is injectable so tests do not need real Telegram credentials. The suite also includes a loopback HTTP server test so the client is exercised against a real local request path, not only a fake function.

Why The Listener Does Not Own Telegram Payloads

The foreground server owns process lifecycle. It starts, stops, checks the lease, and decides when to tick intake. It should not also know the details of Telegram update payloads.

The inbound-intake work keeps that split:

  • TelegramChannelServer owns the foreground loop and stop contract.
  • TelegramUpdatesClient owns the Telegram API request.
  • TelegramUpdateIntake owns poll, normalize, write, and state advancement.
  • TelegramUpdateRecord owns payload normalization.
  • TelegramInboxWriter owns filesystem persistence.

That separation keeps debugging concrete. If the process does not stop, look at the listener shell. If Telegram returns an unexpected payload, look at update normalization. If a file is missing, look at the inbox writer.

Representative Payloads

Telegram updates are not all text messages. The inbound-intake work added representative fixture coverage for:

  • plain text messages,
  • captioned photo messages,
  • service-style updates such as new chat members,
  • and multi-update API responses.

The normalizer preserves the raw payload and emits stable metadata plus a markdown summary. That matters because incomplete normalization should not destroy evidence. If the summary is partial, the raw JSON still shows exactly what Telegram sent.

The Normalized Record

TelegramUpdateRecord turns a payload into a small operational record. It preserves:

  • update_id
  • raw payload data
  • normalized kind
  • summary text
  • chat id and type
  • received timestamp
  • next update offset

It also exposes a markdown summary for humans and a metadata mapping for YAML persistence.

The current normalizer handles text, photo captions, photo-only messages, and service updates. It does not claim to understand every Telegram update type. That is the right production posture for this stage: preserve everything, make the common cases readable, and keep room to add richer normalization later.

Intake As A Transaction Boundary

TelegramUpdateIntake coordinates the handoff from API response to filesystem record. It loads listener state, asks the client for updates using the next offset, converts payloads into records, writes them to the inbox, and advances state only after writes succeed.

This is where the listener becomes operational. The update is not acknowledged by Matic merely because Telegram returned it. It is acknowledged when the record is durable on disk and state has advanced.

That rule keeps the listener restart-safe. A process can crash after polling. If it did not write and advance state, the next run can ask for the same update again. If it did write and advance state, the next run starts after that update.

Tests As API Memory

The inbound-intake work's tests do more than assert code paths. They encode the API memory of the system:

  • the client must send explicit offset and timeout,
  • loopback HTTP must produce parseable updates,
  • payloads must normalize into stable records,
  • service updates must still produce useful summaries,
  • and the listener must keep using saved offsets across restarts.

That memory is important because Telegram is outside the repo. The fixtures and loopback test give the implementation a local contract that can fail fast when the boundary drifts.

What This Unlocks

After the intake-boundary work, the listener has an inbound boundary that is small enough to test and explicit enough to operate. It can request updates, preserve raw payloads, normalize common cases, and stay separate from the foreground shell.

The inbox work turns those records into durable artifacts and ties persistence to offset advancement.

Waitlist

An idle user shouldn't mean
an idle org.

Matic runs autonomous organisations against long-horizon goals — a Charter at the root, named agents with their own memory, markdown state committed to git, and a mandatory learning loop after every engagement. Get on the list before the first orgs come online.

First-install accessArchitecture notesMilestones when they land

No spam. Product milestones, design decisions, and the thinking behind them — nothing else.

launchwaitlist@matic.sh

Architecture notes, first-install access, and milestones when they land. Never marketing.