Channel pushes an ask_user question to a human on a specific
medium (Telegram, Slack, SMS, …) and bridges the human’s reply back into the
interactions store by forwarding it to the delivery’s public callback_url.
Channels are registered on the app handle (tai42_app.channels) by channel
plugins and looked up by name when ask_user is called with channel=....
Delivery either returns None (success) or raises
ChannelDeliveryError (any failure) — never a bool.
A channel also sends fire-and-forget notifications: notify pushes one
ChannelNotification to a human with no interaction, no ticket, and no
reply path, under the same loud-failure rule; it returns the per-message ids the
medium assigned the send (empty when the medium exposes none), never a bool.
Channel
tai42_contract.channels.Channel
tai42_app.channels.register); ask_user resolves it by name and calls
deliver after the interaction is persisted and its callback ticket is
minted. A channel never reaches the interactions store directly: the
human’s reply travels back through the delivery’s public callback_url.
A channel MAY advertise richer notify support with two OPTIONAL,
class-level capability flags — supports_media_notifications and
supports_template_notifications — set as plain class attributes.
They are a documented convention, NOT Protocol members: a channel that
supports the richer notify form sets the matching attribute to True; a
channel that omits it advertises no support (absent = False). Because
they are not part of the Protocol, a text-only channel that declares
neither is still a valid Channel (both structurally and under runtime
isinstance). The central notify_user helper reads them defensively
with getattr(channel, "<flag>", False) and refuses a media or template
notification to a channel that does not advertise the matching flag, so a
sibling channel that reads only notification.message can never silently
drop the extra content.
Members
deliver
tai42_contract.channels.Channel.deliver
delivery to the medium, or raise ChannelDeliveryError.
Send the question to the resolved recipient — delivery.recipient
when set (after checking it against the plugin’s operator allowlist),
else the plugin’s operator-configured default — and arrange for
the reply to reach delivery.callback_url — either a tappable link
carrying the URL, or an inbound-route correlation the plugin stores.
Any delivery failure — an unreachable or rejecting medium, a recipient
outside the operator allowlist, a required credential or recipient not
configured, a bad send response — raises
ChannelDeliveryError; a plain return is the only success
signal. One send attempt only:
retrying is the caller’s decision, never an implicit loop here (no
medium API offers an idempotency key, so a blind retry risks a
double-send).
Parameters
notify
tai42_contract.channels.Channel.notify
ChannelDeliveryError.
No interaction, no ticket, no callback, no reply. Any delivery failure raises
ChannelDeliveryError; a return means the medium ACCEPTED the message —
not that a human saw it — and yields the per-message ids it assigned this send
(several when the medium splits a long message, empty when it exposes no id),
which later correlate an out-of-band delivery receipt back to this send. One
send attempt only, no retry. A channel that cannot notify raises
NotImplementedError.
Parameters
ChannelDelivery
tai42_contract.channels.ChannelDelivery
callback_url is the public /api/interactions/callback/{ticket}
answer sink; the channel arranges for the human’s reply to reach it.
recipient is the OPTIONAL caller-requested address (chat id, phone
number, …): the channel plugin validates it against its operator-set
allowlist and refuses to send to an unlisted address; when omitted the
plugin sends to its operator-configured default recipient. It is an
address only, never a secret or credential.
Attributes
ChannelDeliveryError
tai42_contract.channels.ChannelDeliveryError
Channel when delivering a question fails.
Every failure mode — an unreachable medium API, a rejected send, a missing
credential, a misconfigured recipient — raises this single typed error.
deliver NEVER returns a bool and NEVER silently drops a message: an
undeliverable question is a loud failure, so the only success signal is a
plain return.
ChannelNotification
tai42_contract.channels.ChannelNotification
callback_url and
no deadline: the channel sends the message and nothing travels back.
recipient is the OPTIONAL caller-requested address (chat id, phone
number, …): the channel plugin validates it against its operator-set
allowlist and refuses to send to an unlisted address; when omitted the
plugin sends to its operator-configured default recipient. It is an
address only, never a secret or credential.
sender_identity is the OPTIONAL address to send FROM when the channel fronts
several operator identities: an internal routing control set by the sending side,
never caller-supplied, and an address only — never a secret.
media and template are OPTIONAL richer-send forms reusing the same
message as the human-readable equivalent. media is display media the
channel sends alongside the message (reusing MediaItem); a present
list is non-empty. template sends a pre-approved ChannelTemplate
for out-of-window delivery. The two are MUTUALLY EXCLUSIVE — a template is the
out-of-window send and a companion standalone media item would be rejected by
the medium there, so setting both is refused rather than partially delivered.
A channel that does not advertise the matching capability flag
(supports_media_notifications / supports_template_notifications, the
OPTIONAL class-attribute convention documented on Channel) never
receives these fields.
Attributes
ChannelTemplate
tai42_contract.channels.ChannelTemplate
name in an
approved language — both required and non-blank. parameters is the
POSITIONAL list of body-text values substituted into the template’s body
placeholders in order; it is empty when the template has no placeholders.
Body text only — templates with header media, button parameters, or typed
parameters (currency, date-time) are out of scope for this contract.
Attributes

