telegram.day1.source-layout

Telegram Source Layout and Contract Boundaries

The first Telegram listener decision is the boundary between source code and org runtime state.

telegram / channel-server / source-layout / filesystem-first / matic

Thesis

The first production decision in the Telegram listener was not how to poll Telegram. It was where the code belongs, where runtime data belongs, and how to keep those two surfaces from pretending to be the same thing.

Telegram creates a naming trap. There is Telegram implementation code, and there is an org's Telegram runtime state. Both naturally want to be called channels/telegram. If those are allowed to blur, the codebase becomes harder to package, harder to test, and harder to explain to an operator inspecting an org folder. The source-layout work solved that boundary before adding any network loop.

The Two Telegram Roots

The Python implementation root is:

src/pkgs/core/channels/telegram/

The org runtime root is:

<org-root>/.matic/channels/telegram/

Those paths look similar because they describe the same product concept, but they have different jobs. The src/pkgs/... path is importable source code. It contains modules such as client.py, server.py, state.py, lease.py, paths.py, and cli.py. The .matic/... path is durable state. It contains operator-facing files and folders such as spec.md, outbox/, inbox/, sent/, failed/, and listener/.

This split follows the current Python source convention from ADR 0022: implementation packages live under src/pkgs/; org runtime data stays in the org filesystem. The older repo-root source-layout exception is no longer the active strategy.

Why Layout Is Behavior

Source layout can look like housekeeping, but in a filesystem-first system it is part of the behavior contract. Matic is built around readable files. That means a user should be able to open an org folder and understand what happened without also needing to know Python import rules.

The Telegram listener needs both kinds of files:

  • Python modules that implement sending, listening, leases, state, and intake.
  • Runtime records that show what the listener did inside a specific org.

If implementation code were mixed into the runtime data path, an operator could not tell whether a file is part of the product or part of one org's history. If runtime files were treated as Python source, tests and packaging would inherit accidental behavior from a local org checkout. The source-layout work made that impossible by keeping the roots explicit.

The Contract Before The Loop

The source-layout work also defined the filesystem contract that later runtime features build on:

<org-root>/
  .matic/
    channels/
      telegram/
        spec.md
        outbox/
        inbox/
        sent/
        failed/
        listener/
          state.yaml
          history.md
          lease.yaml

That contract matters before a listener polls a single update. state.yaml describes the listener lifecycle. lease.yaml prevents duplicate starts. history.md records lifecycle events. inbox/ is reserved for inbound records. The outbound folders keep the existing send behavior intact.

The implementation centralizes these paths in constants and exposes them through TelegramFilesystemPaths. Tests assert that source paths and runtime paths resolve to the expected places. That gives later work a stable surface: the foreground-shell work can add a foreground shell without redefining the folder layout, and the intake work can add inbox records without changing the listener contract.

Compatibility Without Drift

The Telegram sender already existed before the listener sprint. The source-layout work preserved that behavior while moving the Telegram-specific implementation into the active source package. Existing channel commands still send outbound messages through the Telegram sender, and the generic channel service still reports the channel folders.

The important point is that the listener did not become a special case outside the channel model. Telegram got its own package because Telegram has Telegram-specific protocol code. It did not get a separate runtime data model. The org still sees a channel folder with inbox, outbox, sent, failed, and listener state.

Why This Was The Right First Slice

It is tempting to start a listener by writing the loop. That would have been the wrong first move. A loop without a durable contract only proves that a process can run. It does not prove that the work can be inspected, stopped, restarted, packaged, or reviewed.

The source-layout work established the pieces that make the later loop meaningful:

  • a source package under src/pkgs/core/channels/telegram/,
  • a runtime data surface under .matic/channels/telegram/,
  • explicit path constants,
  • tests for the filesystem contract,
  • and a CLI command shape for channels telegram listen and close.

That is not cosmetic structure. It is the foundation that lets the listener become operational software instead of a script with side effects.

What Comes Next

The lifecycle work builds on this boundary by turning the planned listener state into an actual lifecycle contract. The next question is not "can we call Telegram?" It is "can an operator start and stop the listener, inspect the files, and trust that duplicate starts are blocked?"

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.