Telegram Listener Lifecycle Without a Daemon
A foreground lifecycle contract gives the Telegram listener durable start, stop, and lease files before any network intake exists.
Thesis
A Telegram listener does not need to begin life as a daemon. The safer first contract is a foreground command that writes explicit listener state, owns a lease, and can be closed through a second command.
The listener-lifecycle work introduced that lifecycle contract. It did not poll Telegram yet. It did not handle stale leases yet. It did not try to hide process management behind a service manager. It gave the listener a durable shape that could be tested before any network behavior existed.
The Control Surface
The command shape is intentionally small:
PYTHONPATH=src python3 -m cli channels telegram listen /path/to/org
PYTHONPATH=src python3 -m cli channels telegram close /path/to/org
The first command starts the listener contract. The second command closes it. That sounds ordinary, but the important detail is where the truth lives. The truth is not in a hidden Python object or a terminal window. The truth is in files under:
<org-root>/.matic/channels/telegram/listener/
The listener writes:
state.yamllease.yamlhistory.md
Those files are enough for a test, an operator, or a later runtime loop to know whether the listener is active, when it started, whether a lease exists, and what lifecycle events have happened.
State Is The Operational Contract
TelegramListenerState records the listener lifecycle in YAML. Through the
source-layout contract it carries fields such as:
handlestatusstarted_atstopped_atinbox_countlease_activelast_event
Later work extends this state with inbound offset fields, but the source-layout work already establishes the core rule: lifecycle status is durable and readable.
That matters because "the process is running" is not enough for Matic. Matic's runtime model needs work to be inspectable after the fact. A stopped listener should leave evidence. A failed duplicate start should be explainable. A later restart should have a file to read before it does any work.
Lease Before Background Magic
The lease file is the duplicate-start guard. When listen starts, it acquires
a TelegramListenerLease. If another active lease already exists, the second
start fails instead of silently creating a second listener.
That choice is deliberately strict. A Telegram listener can lose messages or duplicate work if two processes race over the same offset. The source-layout work does not yet process inbound updates, but it still sets the rule that only one active listener should own the channel lifecycle at a time.
The lease is also inspectable. It records active status, acquisition time, release time, owner process id when available, and a token. This is not a lock hidden in process memory. It is an operational record.
Close Should Mean Something
close is not a best-effort cleanup command. If there is no listener state and
no lease, it raises an error instead of pretending work happened. If state or a
lease exists, it finalizes the state as stopped, releases the lease, and records
the close event.
That behavior gives operators a clean mental model:
listencreates an active lifecycle record.- duplicate
listenis blocked by an active lease. closestops an existing lifecycle record.- closing nothing is an error worth seeing.
This makes the CLI contract testable. The tests do not need Telegram secrets or network access. They can create a temp org root, run the server lifecycle, and inspect the files.
Why No Daemon Yet
Skipping a daemon was not an omission. It was a design choice.
A daemon adds questions that the source-layout work did not need to answer yet: supervision, signals, stale process detection, service installation, platform behavior, and deployment policy. The core lifecycle contract is smaller and more important. If the filesystem state is wrong, a daemon only makes the bug harder to see.
By keeping the source-layout work focused on explicit start and close semantics,
the listener gets a stable base. The foreground-shell work can make listen
remain foreground. The inbound-intake work can add Telegram intake. The recovery
work can harden stop and recovery. Each layer depends on the same files instead
of replacing the previous work.
The Result
After the source-layout work, the Telegram server has a real lifecycle vocabulary:
- a package home for implementation code,
- an org folder for runtime data,
- a listener state file,
- a listener lease file,
- a listener history file,
- and CLI commands that exercise the contract.
That is enough to prove the listener can be operated before it is useful as an inbound channel. In a filesystem-first system, that order is the point.