telegram.day2.foreground-shell

A Foreground Shell for the Telegram Listener

The Telegram listener becomes operational when listen stays foreground, checks stop conditions, and keeps the lease/state contract intact.

telegram / channel-server / runtime / filesystem-first / matic

Thesis

The Telegram listener becomes real when listen stops being a one-shot setup command and starts behaving like a foreground runtime shell.

The source-layout work proved the lifecycle files. The foreground-shell work made the process stay alive. That sounds like a small change, but it changes the operational contract. A command that writes state and exits cannot receive updates. A command that stays foreground can be supervised, tested, interrupted, and stopped through the same filesystem contract established by the source-layout work.

Why Foreground First

The listener could have been hidden behind a daemon abstraction. That would have added more machinery before the runtime behavior was trustworthy.

A foreground shell is simpler and more honest:

  • the operator can see that the process is active,
  • tests can control the loop with injected sleep and stop probes,
  • the process can inspect durable state on each tick,
  • and the stop command can be tested without a platform service manager.

This matches Matic's bias toward readable operational state. The process is not the source of truth by itself. The process is a worker that keeps checking the files that describe its lease and lifecycle.

The Listener Loop

The foreground listener loop lives in TelegramChannelServer.listen(). When started in foreground mode, it:

  1. Ensures the Telegram channel folders exist.
  2. Acquires a listener lease.
  3. Writes state.yaml with status: listening.
  4. Appends a listen event to history.md.
  5. Repeats until a stop condition is observed.
  6. Finalizes the state and lease before returning.

The CLI uses foreground mode:

PYTHONPATH=src python3 -m cli channels telegram listen /path/to/org

That command is expected to block. The process staying open is not a bug; it is the listener doing its job.

Testable Boundaries

The loop has explicit boundaries for tests:

  • clock can be injected so timestamps are deterministic.
  • pid_provider can be injected so leases do not depend on the host process.
  • sleep can be injected so tests do not wait on wall-clock time.
  • stop_probe can be injected so tests can end the loop deliberately.
  • poll_interval is configurable.

Those seams are not abstraction for abstraction's sake. Without them, the only way to test the listener would be to start a real long-running process and hope timing works. With them, the listener can be exercised in temp directories with plain unit tests.

The Stop Probe

The listener checks for stop conditions on every loop. A stop can come from an injected stop_probe, from the lease becoming inactive, or from listener state showing that a close event happened.

That gives the runtime shell a clean control model. It does not need to receive an in-process function call from the CLI. The close command can run in a different process, update the same durable files, and the foreground listener will see the change.

The design keeps command boundaries realistic. In production, listen and close are separate commands. In tests, they can still coordinate through the same files.

Why Intake Waited

The foreground-shell work deliberately kept Telegram update intake minimal. The point of this work was runtime control, not message semantics.

That separation matters. If the foreground-shell work had added both a foreground loop and API polling at once, failures would be harder to isolate. A failing test could be a loop bug, a lease bug, a Telegram payload bug, or a persistence bug. By keeping the foreground-shell work focused on the shell, the implementation proved that the process could stay alive and stop cleanly before it handled real updates.

What Changed Operationally

After the foreground-shell work, the listener is no longer just a contract written to disk. It is a controllable process:

  • listen can remain active in the foreground.
  • close can be issued from another command.
  • duplicate starts remain blocked by the active lease.
  • listener state is finalized on clean exit.
  • outbound send and channel status behavior remain intact.

That is the smallest useful runtime shell. It gives the inbound-intake work a place to run intake without changing the lifecycle model again.

The Design Rule

The design rule from the foreground-shell work is simple: keep runtime control separate from protocol intake.

The listener shell owns whether the process is active, stoppable, and inspectable. The Telegram API boundary can be added behind that shell later. That keeps the system understandable. When a listener cannot stop, look at the shell and lease. When an update is malformed, look at intake. Those are different problems, and the code now treats them that way.

Wachtlijst

Jouw org werkt door
als jij offline bent.

Matic draait autonome organisaties richting lange-horizondoelen — een Charter in de root, benoemde agents met eigen geheugen, markdown-staat in git, en een verplichte leer-loop na elke opdracht. Kom op de lijst voordat de eerste orgs live gaan.

Eerste-install toegangArchitectuurnotitiesMijlpalen zodra ze landen

Geen spam. Productmijlpalen, ontwerpbeslissingen en de denkrichting erachter — meer niet.

launchwaitlist@matic.sh

Architectuurnotities, eerste-install toegang en mijlpalen zodra ze landen. Geen marketing.