Adds the governing intent, the R-1 contract and descriptor, the adapter's connector contract, the observation and redaction configuration, a runbook, and the seed evidence for the first experiment: a measured pressure record and two competing hypotheses about how a long hall entry should reach a reader. The interface is declared at FLUID-2 deliberately. It publishes under HelixForge's name to an audience that did not consent to being experimented on carelessly, and the cost of a bad post is reputational rather than recoverable. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_014KmVxhJ35tCo7rE7UnLwWu Assistant: claude-code Assistant-Model: opus Assistant-Process: 1116572@bnt-lap001 Assistant-Session: 8ba9bb93-a72a-4883-b189-2499cce5c400
4.2 KiB
Telegram adapter — connector capability contract
The adapter is an ordinary HTTP service. fluid-core sits in front of it and asks nothing of it beyond the endpoints below, so it may be written in whatever suits the platform — grammY on Node is the obvious choice for the Telegram Bot API.
What the adapter owns
- Rendering a hall entry into Telegram's message forms.
- Talking to the Telegram Bot API.
- Custody of the bot token for the duration of a call.
- Idempotency: recognising an
entry_idit has already published.
What the adapter must not do
- Create, rename or delete channels, groups or bots.
- Change membership, administrators or permissions.
- Post to any chat other than the one it is configured with.
- Edit entry content to make it fit. If an entry cannot be published faithfully, refuse it — that refusal is the pressure signal.
- Log, echo or forward the bot token, including in error messages.
Endpoints
Exactly the three operations in contracts/r1.openapi.yaml, at the same paths.
The gateway validates requests against that contract before the adapter sees
them, so the adapter may assume shape but must still assume hostile content.
Backend capability contract
| Property | Value |
|---|---|
| Backend identity | Telegram Bot API (api.telegram.org) |
| Authentication | Bot token, injected at start from OpenBao; never in the image |
| Timeout | 30s per Telegram call |
| Retry | On 429 only, honouring retry_after. Never retry a sendMessage that may have succeeded |
| Circuit breaker | 5 consecutive failures opens for 60s |
| Rate limits | ~20 messages/minute to a channel; ~30 requests/second overall |
| Error mapping | 429 → 502 with retry_after preserved; 400 → 400; network failure → 502 |
| Tenant propagation | None; single tenant |
The idempotency requirement
POST /v1/hall-entries is idempotent on entry_id. The caller is automation
that may be retried, and a duplicate hall entry in the channel is not something
you can quietly undo — subscribers have already seen it.
The adapter keeps its own mapping from entry_id to the Telegram message ids it
produced. On a repeat call it returns 200 with the existing publication rather
than posting again. If the entry content has changed, it edits the existing
messages rather than posting new ones.
This state is the adapter's, not fluid-core's. The evidence store records that a publication happened; the adapter records which Telegram messages implement it.
Rendering rules
Telegram formatting is not Markdown. Entry Markdown must be converted, not passed through.
| Entry construct | Telegram |
|---|---|
# Heading |
Bold line |
**bold**, _italic_ |
<b>, <i> in HTML parse mode |
| Fenced code | <pre> |
| Tables | Flattened to lines; Telegram has no table |
| Links | <a href> |
| Frontmatter | Stripped; display_name and recorded_at surfaced in the header |
Anything the converter cannot represent faithfully must fail the publication
rather than degrade it silently. entry_content_loss is a hard guardrail in
both competing hypotheses, and it can only be honest if the adapter refuses
rather than approximates.
Telemetry the adapter should emit
The gateway observes the request path already. The adapter adds what only it can
see, by POSTing to the control plane's /control/v1/telemetry:
messages_per_entry— how many Telegram messages one entry became.entry_content_loss— whether the conversion dropped anything.- Telegram rate-limit encounters, as error events with class
backend_failure.
It must not emit subscriber identity in any form. Engagement metrics
(read_through_rate, reaction_rate) come from Telegram's channel statistics
as aggregates, never per-reader.
Configuration
| Variable | Meaning |
|---|---|
TELEGRAM_BOT_TOKEN |
Injected from OpenBao at start |
TELEGRAM_CHANNEL_ID |
The channel this adapter publishes to |
FLUID_CONTROL_URL |
Where to POST telemetry |
HALL_BASE_URL |
Base URL for links back to hall-of-helix |
One adapter instance serves one channel. The private test channel and the public channel are separate instances with separate configuration, which is what keeps a test publication from reaching subscribers by accident.