fluid-core/docs/handover/fluid-telegram.md
tegwick 78454c59d8 Reframe the handover around composed posts, and plan the pr- pattern
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
2026-09-04 11:59:30 +02:00

9.3 KiB
Raw Blame History

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 revisionexperimentfitness 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:

  1. 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.
  2. 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.

  1. Register the bot through BotFather, and write the token straight to OpenBao.
  2. 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.
  3. Generate the pseudonymization salt and store it beside the token. It must never change.
  4. 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.