The Telegram channel publishes posts written from hall entries, not the entries themselves, so the handover's pressure, competition and guardrails are rebuilt around that. Composition sits outside the interface because Blueprint 48.1 forbids an LLM in the mandatory request path, and because a post about a named person's work deserves a human read that a request-path step would never get. Adds FLUID-WP-0009 for the pr- campaign repository convention and for reducing the Hall of Helix case from the thing this framework is for to an example of using it. The prefix deliberately includes "propaganda": a campaign repository that cannot name what it is doing will not govern it well. The workplan starts only after the loop has closed once on real traffic. Extracting a seam before using it is how you learn you cut it in the wrong place. 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
9.3 KiB
Handover — fluid-telegram
From: fluid-core (FLUID-WP-0008) To: fluid-telegram (FT-WP-0001) Date: 2026-09-04
The first real FLUID interface: a Telegram identity for HelixForge, and a channel publishing composed posts about hall-of-helix work.
The channel does not republish entries. A hall entry is a considered first-person account written for a colleague reading a repository. A channel post is short, personal, entertaining and written to travel. The post is composed from the entry; the entry is its source and its check, never its payload.
What this is
fluid-core is finished through Blueprint Phase C. It can run the complete
revision–experiment–fitness loop, and TestFirstVerticalSlice proves it with no
human steps. What it has never done is carry a real workload.
helix-forge-telegram-publishing is that workload. It was chosen because the
pressure it faces is real, measurable and unavoidable rather than contrived.
The pressure, measured
A hall entry and a channel post are written for different readers. Measured
against hall-of-helix on 2026-09-04:
| Entries | 94 |
| Bodies over 4096 characters | 80 (85%) |
| Median body | 5767 characters |
| Largest body | 10220 characters |
| Median parts if split at the limit | 2 |
| Entries with a portrait | 72 (77%) |
The length figures are the visible symptom. The mismatch is one of register and audience, and it would remain if the limit were ten times larger: an entry written under fixed headings for someone already invested does not become a post that stops a stranger scrolling by being shorter.
This is a better first pressure than anything a fixture could stage. It is
structural rather than behavioural, so it is real before the interface serves a
single request — and InterfaceEvolutionIntent.md §13 permits a structural
constraint to found a hypothesis without waiting for repetition.
Where composition sits, and why
Composing a condensed, entertaining post is a probabilistic editorial task. It happens before this interface is called, for two reasons:
ArchitectureBlueprint.md§48.1 names an LLM in the mandatory request path as an anti-pattern. An interface that composed on demand would be non-deterministic at exactly the point where determinism is its whole claim.- A post speaks about a named person's work in HelixForge's voice. That deserves a human read, and a review step inside the request path is a review step nobody performs.
So the contract takes a composed post: body text already within the limit, a cited source entry, a named reviewer, and a consent basis. A post carrying none of those is refused with 403.
This also draws the line that pr-hall-of-helix will later be extracted along.
The campaign owns what to say and when; this interface owns delivery and
measurement.
What is delivered
Everything that could be prepared without credentials.
| Artifact | Location |
|---|---|
| Governing intent | fluid-telegram/InterfaceEvolutionIntent.md |
| R-1 contract | fluid-telegram/contracts/r1.openapi.yaml |
| R-1 descriptor | fluid-telegram/revisions/R-1.yaml |
| Seed pressure record | fluid-telegram/pressure/P-register-mismatch.yaml |
| Competing hypotheses | fluid-telegram/hypotheses/H-tg-{visual,textonly}.yaml |
| First experiment | fluid-telegram/experiments/E-tg-delivery.yaml |
| Adapter connector contract | fluid-telegram/docs/adapter-contract.md |
| Cohorts, telemetry, redaction | fluid-telegram/docs/observation.md |
| Runbook | fluid-telegram/docs/runbook.md |
| Work | fluid-telegram/workplans/FT-WP-0001-*.md |
All four seed records validate against fluid-core/schemas/, and the R-1
contract compiles with fluid-core's own validator —
conformance/handover_test.go asserts both, so a change on either side that
broke the handover would fail CI rather than fail at the first publication.
The competition
This interface's first experiment is about delivery, which is what it owns.
H-tg-visual — attach the portrait; publish as a photo with the post as its caption. Reach in a scanned feed is won by stopping the scroll, and 72 of the portraits already exist.
H-tg-textonly — publish as text with a source link, no visual. Telegram caps a caption at 1024 characters against 4096 for text. The scarce resource in a condensed post is room to say something specific, and a generated portrait is interchangeable in a way that a concrete detail is not.
They cannot both be right, which is what makes the pair worth running. Both are presentation adaptations — Blueprint §37's safest and highest-value place to start.
Composition competitions are the campaign's, not this interface's. Every
post carries an opaque variant label, and the interface reports engagement
grouped by it. That is the whole of its involvement: it does not know what
variant: warm-specific-v2 means and must not acquire an opinion. The campaign
runs its own FLUID loop over its own artifacts using that data.
Where the framework will be tested honestly
Three places where this workload will strain fluid-core, named now so the strain is recognised as information rather than as a surprise.
The experiment will be underpowered, and should say so. At roughly one entry per week, a 90-day experiment yields about twelve entries. The evaluator will report INCONCLUSIVE, and that is the correct answer. The framework was built to refuse a verdict it has not earned; the first live experiment is where that refusal will be inconvenient rather than admirable. Do not shorten the window to force a result.
engagement_rate is a proxy, and a noisy one. Telegram reports views,
reactions and forwards per message, not per reader, so the denominator is
subscribers rather than people who saw the post. At small subscriber counts
that is weak. It belongs in the experiment's amendment record rather than
hidden behind a confident-looking number.
Task grouping will overstate repeat counts. The measurer buckets requests by consumer and elapsed time when no chain id is supplied, so a burst of publications will read as one task. The direction of any measurement will be right and the magnitude will not. Supplying chain ids from the publishing job is the fix, and it is cheap.
Human steps
Four things need a person; nothing in the framework can or should do them.
- Register the bot through BotFather, and write the token straight to OpenBao.
- Create the private test channel first, then the public one. Grant the bot Post Messages and nothing else — the intent forbids it from using a wider right, so granting one would create a permission the system may not exercise.
- Generate the pseudonymization salt and store it beside the token. It must never change.
- Look at the first publication in the private channel before anything reaches a subscriber. No automated check substitutes for someone reading the post and deciding it is faithful.
The boundary that matters most
InterfaceEvolutionIntent.md §7 forbids the interface from composing,
rewriting, shortening or embellishing post text, and §14 sets every generate,
experiment and promote authority to no.
A post is a condensation written in HelixForge's voice about a named person's work. It can flatter, it can misstate, and it can put a claim in someone's mouth that their own entry never made. In the audit trail an unfaithful post looks exactly like a faithful one.
The defence is not that the composer is careful. It is structural: a human read
the post, the post names the entry it came from, the subject's consent has a
recorded basis, and all three are stored before anything reaches a subscriber.
unreviewed_publication, missing_source_attribution and
missing_subject_consent are hard guardrails, and a post carrying none of them
is refused rather than logged.
Ninety-four entries already exist, written by people who took a seat in the hall
without anyone mentioning a Telegram channel. FT-WP-0001 T07 exists to settle
that before T10 opens the public channel. Absence of objection is not consent.
Verification, in order
export FLUID_INTERFACE=helix-forge-telegram-publishing
fluid intent put --version IEI-1 --file InterfaceEvolutionIntent.md --activate
fluid revision publish --file revisions/R-1.yaml --key-file "$FLUID_SIGNING_KEY" \
--adaptation-classes presentation --approved-by "$USER" --traffic-share 1.0
# then, against the private channel only:
# publish one entry
# publish it again; confirm one post, edited
# confirm no Telegram identity and no token in the evidence store
fluid audit trace R-1
The last command is the deliverable. A trace running from the register mismatch through a hypothesis, an experiment and a promotion is what makes this the first FLUID interface rather than a bot that posts to a channel.
What comes after
FT-WP-0001 T13 extracts the campaign into pr-hall-of-helix, under a pr-
prefix for public-relations campaigns. After that split, publishing the Hall of
Helix is an example of using fluid-core and fluid-telegram rather than the
thing they are for — which is the state this framework needs to reach before
fluid-x and fluid-substack are worth starting.
Do it after the loop has closed at least once. Extracting a seam before you have used it is how you find out you cut it in the wrong place.