Reframe the interface around composed posts, not republished entries
The channel publishes a post written from a hall entry -- condensed, personal, reach-optimized -- not the entry itself. That changes what this interface is for and where its boundary sits. Composition is deliberately outside the interface. Composing is a probabilistic editorial task, and Blueprint 48.1 forbids an LLM in the mandatory request path, so an interface that composed on demand would be non-deterministic exactly where determinism is the whole claim. It is also editorial: a post speaks about a named person's work in HelixForge's voice, and a review step inside the request path is a review step nobody performs. The contract now takes a composed post with a required reviewer, a cited source entry and a consent basis. A post carrying none of those is refused. Absence of objection is not consent, and in an audit trail an unfaithful post looks exactly like a faithful one -- the defence is that a human read it, the source is named, and both are recorded. The first experiment is rebuilt accordingly. The old competition asked how to fit a whole entry into Telegram; that question no longer exists. The new one asks whether the portrait earns the 3072 characters of caption limit it costs, which is a delivery question this interface actually owns. Competition over editorial voice belongs to the campaign and runs on the per-variant engagement this interface reports. Adds T05b (composition and review), T05c (consent basis for the 94 existing participants, blocking public publication) and T11 (extracting the campaign into pr-hall-of-helix). 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
This commit is contained in:
parent
aa95a09ab6
commit
3aa8cb7eaf
14 changed files with 793 additions and 521 deletions
|
|
@ -59,21 +59,37 @@ Raising the mode is a governance change under §20, not an operational decision.
|
|||
|
||||
### Mission statement
|
||||
|
||||
> Make hall-of-helix entries reliably and faithfully available to a Telegram
|
||||
> audience, without the people writing them having to think about Telegram.
|
||||
> Deliver composed channel posts to a Telegram audience reliably, verifiably,
|
||||
> and without an unreviewed word ever reaching a subscriber.
|
||||
|
||||
An entry is written once, as Markdown, in `hall-of-helix/entries/`. Everything
|
||||
about how it becomes a Telegram post — length, formatting, media, threading —
|
||||
is this interface's problem and nobody else's.
|
||||
The unit of publication is a **post**, not a hall entry. A post is short,
|
||||
personal and written to travel; a hall entry is a considered first-person
|
||||
account written for someone reading a repository. They are different pieces of
|
||||
writing for different readers, and the second is the source of the first rather
|
||||
than its content.
|
||||
|
||||
**Composition is not this interface's job.** Turning an entry into a post that
|
||||
is condensed, entertaining and reach-optimized is a probabilistic, editorial
|
||||
task. It happens upstream, before this interface is called. Two reasons, and
|
||||
the first is architectural rather than stylistic:
|
||||
|
||||
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 the whole claim.
|
||||
2. A composed post speaks about a named person's work in HelixForge's voice.
|
||||
That deserves a human read before it goes out, and a review step in the
|
||||
request path is a review step nobody performs.
|
||||
|
||||
### Success looks like
|
||||
|
||||
- A finished hall entry reaches the channel without a human reformatting it.
|
||||
- The published form is faithful: no entry is silently truncated, and no
|
||||
attribution is lost.
|
||||
- A composed post reaches the channel without a human formatting it for
|
||||
Telegram.
|
||||
- No post reaches a subscriber unreviewed.
|
||||
- Every post is traceable to the entry it was written from, so a claim can be
|
||||
checked against what the person actually said.
|
||||
- Publishing twice does not post twice.
|
||||
- When Telegram's constraints and the entry's shape conflict, the conflict
|
||||
surfaces as recorded pressure rather than as a mangled post.
|
||||
- Engagement is attributable to the composition strategy that produced it, so
|
||||
the campaign can learn which writing travels.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -105,10 +121,13 @@ is this interface's problem and nobody else's.
|
|||
|
||||
### In scope
|
||||
|
||||
- Publishing hall-of-helix entries to a Telegram channel (Canon PUB-01).
|
||||
- Rendering an entry into Telegram's message forms (Canon PUB-02, MED-02).
|
||||
- Publishing composed posts to a Telegram channel (Canon PUB-01).
|
||||
- Rendering a post into Telegram's message forms (Canon PUB-02, MED-02).
|
||||
- Attaching a visual to a post (Canon MED-01).
|
||||
- Publication state: what was published, when, as which message.
|
||||
- Idempotency and correction of previously published entries.
|
||||
- Idempotency and correction of previously published posts.
|
||||
- Recording the composition variant a post came from, and reporting engagement
|
||||
against it.
|
||||
- Bot identity presentation (Canon BOT-01).
|
||||
|
||||
### Conditionally in scope
|
||||
|
|
@ -116,12 +135,14 @@ is this interface's problem and nobody else's.
|
|||
Capabilities that may be introduced if evidence justifies them:
|
||||
|
||||
- Scheduling a publication for a later time.
|
||||
- Publishing other HelixForge artifacts, such as release notes.
|
||||
- Attaching entry visuals as media (Canon MED-01).
|
||||
- Publishing other HelixForge material, such as release notes.
|
||||
- A linked discussion group for comments.
|
||||
- Publishing to more than one channel from one call.
|
||||
|
||||
### Out of scope
|
||||
|
||||
- **Composing post text from a source entry.** This belongs to the campaign
|
||||
that owns the editorial voice, not to the interface that delivers it. See §7.
|
||||
- Inbound bot commands and conversation (Canon BOT-02, AGT-01). A second
|
||||
revision family may add this later; it is not this interface today.
|
||||
- Payments and paid content (Canon PAY-*).
|
||||
|
|
@ -135,20 +156,28 @@ Capabilities that may be introduced if evidence justifies them:
|
|||
|
||||
### Responsibilities owned by this interface
|
||||
|
||||
- Representation: how an entry becomes one or more Telegram messages.
|
||||
- Composition: assembling an entry, its metadata and its visual.
|
||||
- Validation: refusing an entry that cannot be published faithfully.
|
||||
- Representation: how a composed post becomes a Telegram message.
|
||||
- Delivery: attaching the visual, the source link, the formatting.
|
||||
- Validation: refusing a post that arrives unreviewed, unattributed, or in a
|
||||
form Telegram cannot carry.
|
||||
- Routing: which revision serves a publication request.
|
||||
- Idempotency: recognising an entry it has already published.
|
||||
- Idempotency: recognising a post it has already published.
|
||||
- Attribution of engagement to composition variants.
|
||||
|
||||
### Responsibilities owned by backend systems
|
||||
|
||||
| Backend | Responsibility | Boundary |
|
||||
|---|---|---|
|
||||
| Telegram Bot API | Message delivery, channel state, subscriber identity | FLUID may request delivery. It may not assume a limit will move, and must treat platform constraints as fixed. |
|
||||
| `hall-of-helix` | Entry content, its correctness, and whether it is finished | FLUID may read entries. It must never edit one to make it publishable. |
|
||||
| `pr-hall-of-helix` (planned) | Composing posts, the editorial voice, choosing what is worth publishing, obtaining subject consent | FLUID publishes what it is given and reports how it performed. It does not write, edit or select. |
|
||||
| `hall-of-helix` | Entry content, its correctness, and whether it is finished | FLUID never reads or edits an entry. Its only knowledge of one is the id and URL a post cites. |
|
||||
| OpenBao | Custody of the bot token | FLUID receives a credential. It never stores, logs or forwards one. |
|
||||
|
||||
Until `pr-hall-of-helix` exists, composition is done by hand as part of
|
||||
`FT-WP-0001`. That is a temporary arrangement, and the boundary above is
|
||||
written for the state it is heading toward rather than the state it is in — so
|
||||
that the campaign, when it is extracted, finds the seam already cut.
|
||||
|
||||
### Explicit non-authority
|
||||
|
||||
The Daimon must not autonomously:
|
||||
|
|
@ -156,14 +185,20 @@ The Daimon must not autonomously:
|
|||
- create, rename or delete Telegram channels, groups or bots;
|
||||
- change channel membership, administrators or permissions;
|
||||
- read, rotate or re-scope the bot token;
|
||||
- edit or truncate a hall-of-helix entry to fit a platform constraint;
|
||||
- compose, rewrite, shorten or embellish post text;
|
||||
- publish a post that carries no reviewer;
|
||||
- publish to any chat other than those declared in the routing configuration;
|
||||
- post on behalf of a person who has not asked for it.
|
||||
- publish a post about a person who has not consented, on either basis in §15.
|
||||
|
||||
The last of these matters most. A hall entry is a first-person account someone
|
||||
wrote about their own work. Publishing it is an act taken on their behalf, and
|
||||
that authority comes from their having written it — not from the interface
|
||||
finding it convenient.
|
||||
The last two matter most, and they are the reason composition sits outside this
|
||||
interface. 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 — and in the audit trail an
|
||||
unfaithful post looks exactly like a faithful one.
|
||||
|
||||
The defence is not that the composer is careful. It is that a human read the
|
||||
post, the post names the entry it came from, and both are recorded before
|
||||
anything reaches a subscriber.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -171,23 +206,29 @@ finding it convenient.
|
|||
|
||||
### Concepts consumers should naturally understand
|
||||
|
||||
- An **entry** is the unit of publication, identified by its hall entry id.
|
||||
- Publishing is **idempotent** on that id.
|
||||
- A **post** is the unit of publication, identified by its own post id.
|
||||
- A post **cites** the hall entry it was written from; it is not that entry.
|
||||
- Publishing is **idempotent** on the post id. A recomposition is a new post,
|
||||
because it is a different piece of writing about the same work.
|
||||
- A **publication** has state: pending, published, failed, superseded.
|
||||
|
||||
### Interaction patterns considered natural
|
||||
|
||||
- Publish one finished entry, then check its state.
|
||||
- Re-publish the same entry after a correction and get the same message updated.
|
||||
- List what has been published to reconcile against the hall.
|
||||
- Compose a post, have it reviewed, publish it, check its state.
|
||||
- Re-publish the same post id after a correction and get the same message
|
||||
updated.
|
||||
- List publications by composition variant to compare how they performed.
|
||||
|
||||
### Expectations that should be discouraged
|
||||
|
||||
- That Telegram formatting is Markdown. It is not, and entries that assume so
|
||||
- That Telegram formatting is Markdown. It is not, and posts that assume so
|
||||
will render wrongly.
|
||||
- That an entry appears instantly. Rate limits are real.
|
||||
- That a long entry arrives as one message. It cannot, and the interface's job
|
||||
is to make that invisible rather than to pretend otherwise.
|
||||
- That a post appears instantly. Rate limits are real.
|
||||
- That this interface will shorten an over-length post. It will refuse it. A
|
||||
composition that does not fit was not condensed, and deciding what to cut is
|
||||
the composer's judgement, not the deliverer's.
|
||||
- That publishing a hall entry is possible. It is not: entries are sources, and
|
||||
posts are what this interface carries.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -203,10 +244,10 @@ volume.
|
|||
|
||||
| Dimension | Priority | Target / Constraint | Notes |
|
||||
|---|---:|---|---|
|
||||
| Correctness | High | No entry published in a form that loses content | Hard guardrail |
|
||||
| Client utility | High | One call publishes one entry | The reason the interface exists |
|
||||
| Correctness | High | No post published unreviewed or unattributed | Hard guardrail |
|
||||
| Client utility | High | One call publishes one post | The reason the interface exists |
|
||||
| Reliability | High | Publication succeeds or reports why | |
|
||||
| Discoverability | High | A reader can reach the full entry | Drives the first experiment |
|
||||
| Discoverability | High | A reader can reach the source entry | Drives the first experiment |
|
||||
| Security | High | Token never leaves its custody | Hard guardrail |
|
||||
| Compatibility | Medium | Versioned evolution | Few consumers, all internal |
|
||||
| Simplicity | Medium | Prefer one publication concept | |
|
||||
|
|
@ -226,7 +267,7 @@ volume.
|
|||
| Dimension | Current | Preferred | Maximum | Scoring method |
|
||||
|---|---:|---:|---:|---|
|
||||
| Surface area (operations) | 3 | 4 | 8 | Count of declared operations |
|
||||
| Concept count | 2 | 3 | 5 | entry, publication |
|
||||
| Concept count | 2 | 3 | 5 | post, publication |
|
||||
| Parameter dimensionality | 4 | 6 | 12 | Declared parameters across operations |
|
||||
| Semantic overlap | 0 | 0 | 1 | Operations meaning the same thing |
|
||||
| Alternative-path count | 1 | 1 | 3 | Ways to achieve one outcome |
|
||||
|
|
@ -242,7 +283,9 @@ volume.
|
|||
another endpoint.
|
||||
- A candidate exceeding a hard maximum must not be promoted.
|
||||
- Presentation adaptations do not consume surface-area budget: they change how
|
||||
an entry is rendered, not what the interface offers.
|
||||
a post is delivered, not what the interface offers.
|
||||
- Any candidate that would move composition inside this interface is rejected
|
||||
regardless of its complexity score. That boundary is not a budget question.
|
||||
|
||||
---
|
||||
|
||||
|
|
@ -258,8 +301,9 @@ volume.
|
|||
- Breaking changes require a successor revision.
|
||||
- Every consumer is internal and known, so a breaking change is a coordination
|
||||
problem rather than a public one — but it is still a successor revision.
|
||||
- A change to how an entry is *rendered* is a presentation adaptation and does
|
||||
not break the contract, even though readers will notice it.
|
||||
- A change to how a post is *delivered* — visual placement, link style,
|
||||
formatting — is a presentation adaptation and does not break the contract,
|
||||
even though readers will notice it.
|
||||
|
||||
### Coexistence expectations
|
||||
|
||||
|
|
@ -325,6 +369,12 @@ success criteria, guardrails, complexity impact, risk, expected learning value.
|
|||
defensible answers, and choosing between them by argument rather than evidence
|
||||
is exactly the habit FLUID exists to break.
|
||||
|
||||
Note the division of labour. Competitions about **delivery** — does a visual
|
||||
help, does a link preview help — belong to this interface and are run here.
|
||||
Competitions about **composition** — which editorial voice travels — belong to
|
||||
the campaign, and are run on the per-variant engagement this interface reports.
|
||||
Both are legitimate FLUID experiments; only the first is this interface's.
|
||||
|
||||
### Reach preference
|
||||
|
||||
Prefer the smallest sufficient adaptation. This interface is small enough that a
|
||||
|
|
@ -378,6 +428,23 @@ population, not a caller.
|
|||
|
||||
Single tenant. The interface serves HelixForge and no one else.
|
||||
|
||||
### Subject consent
|
||||
|
||||
A post speaks publicly about a named person's work. Two bases are recognised:
|
||||
|
||||
- **standing** — the contributor agreed, when they took a seat in the hall,
|
||||
that HelixForge may write about that work publicly. This is the ordinary case
|
||||
and requires the agreement to actually exist, in `hall-of-helix/ENTRY.md`, and
|
||||
to be revocable.
|
||||
- **explicit** — per-post approval from the subject. Required where the post
|
||||
makes a claim the entry does not, quotes the subject, or concerns work they
|
||||
have since asked not to be publicised.
|
||||
|
||||
`not-required` covers posts about the project itself with no personal subject.
|
||||
|
||||
A post carrying no basis is refused with 403. Absence of objection is not
|
||||
consent, and the interface must not treat it as such.
|
||||
|
||||
### Data handling
|
||||
|
||||
Telegram user identity (Canon ID-01) must not enter the evidence store in any
|
||||
|
|
@ -388,9 +455,13 @@ consumers must not quietly learn about audiences.
|
|||
|
||||
### Logging and provenance
|
||||
|
||||
Every publication records which entry, which revision, which message, and which
|
||||
consumer asked. The bot token appears in no log, error, telemetry event or
|
||||
evidence record.
|
||||
Every publication records which post, which source entry, which reviewer, which
|
||||
revision, which message, and which consumer asked. The bot token appears in no
|
||||
log, error, telemetry event or evidence record.
|
||||
|
||||
Post *text* is retained in the publication record. It is what was said in
|
||||
HelixForge's name, and an audit that could not reproduce it would be unable to
|
||||
answer the only question that matters when a post is disputed.
|
||||
|
||||
### AI boundary
|
||||
|
||||
|
|
@ -404,14 +475,15 @@ reaches this interface at all.
|
|||
|
||||
| Quality attribute | Requirement | Hard guardrail? |
|
||||
|---|---|---:|
|
||||
| Correctness | No published entry loses content or attribution | yes |
|
||||
| Idempotency | Publishing an entry twice yields one publication | yes |
|
||||
| Correctness | No post published without a reviewer and a cited source entry | yes |
|
||||
| Consent | No post about a subject without a recorded basis | yes |
|
||||
| Idempotency | Publishing a post twice yields one publication | yes |
|
||||
| Availability | Best effort; publication may be retried | no |
|
||||
| Reliability | A failed publication reports why and may be retried safely | yes |
|
||||
| p95 latency | Under 10s excluding Telegram rate limiting | no |
|
||||
| Error rate | Under 1% of publication attempts | yes |
|
||||
| Security | Token never leaves custody | yes |
|
||||
| Auditability | Every publication traceable to entry, revision and caller | yes |
|
||||
| Auditability | Every publication traceable to post text, source entry, reviewer, revision and caller | yes |
|
||||
| Rollback | Any revision can be replaced by its predecessor within one policy generation | yes |
|
||||
|
||||
---
|
||||
|
|
@ -503,5 +575,9 @@ of scope, the first 100 subscribers, or annually.
|
|||
The Daimon should optimize aggressively inside this document and remain
|
||||
conservative at its boundaries.
|
||||
|
||||
This interface may evolve in how it presents an entry. It may not evolve in
|
||||
whose voice it speaks.
|
||||
This interface may evolve in how it delivers a post. It may not evolve into
|
||||
writing one.
|
||||
|
||||
The voice belongs to the campaign, the work belongs to the person who did it,
|
||||
and this interface's job is to carry the first without misrepresenting the
|
||||
second.
|
||||
|
|
|
|||
|
|
@ -9,5 +9,8 @@ live. Governance, contract, seed hypotheses and the adapter's connector contract
|
|||
are in place; the bot, the channel and the adapter itself are not.
|
||||
|
||||
- Governance: [`InterfaceEvolutionIntent.md`](InterfaceEvolutionIntent.md)
|
||||
- The channel publishes composed posts written *from* hall entries, not the
|
||||
entries themselves. Composition happens upstream and moves to
|
||||
`pr-hall-of-helix` once the loop has closed once (FT-WP-0001 T11).
|
||||
- Work: [`workplans/FT-WP-0001-telegram-identity-and-hall-channel.md`](workplans/FT-WP-0001-telegram-identity-and-hall-channel.md)
|
||||
- Handover from fluid-core: `fluid-core/docs/handover/fluid-telegram.md`
|
||||
|
|
|
|||
|
|
@ -3,96 +3,147 @@ info:
|
|||
title: HelixForge Telegram Publishing
|
||||
version: "R-1"
|
||||
description: |
|
||||
Publishes hall-of-helix entries to a Telegram channel.
|
||||
Publishes composed channel posts to a Telegram channel.
|
||||
|
||||
The interface takes an entry as it was written and is responsible for
|
||||
everything about how it becomes a Telegram post. A caller should never need
|
||||
to know Telegram's constraints.
|
||||
The unit of publication is a POST, not a hall entry. A post is a short,
|
||||
personal, reach-optimized piece written *from* a hall-of-helix entry — it
|
||||
is not the entry, and it is not a truncation of the entry.
|
||||
|
||||
Publication is idempotent on entry_id: publishing the same entry twice
|
||||
yields one publication. This matters because the caller is usually
|
||||
automation that may be retried.
|
||||
Composition happens upstream, before this interface is called. That is a
|
||||
deliberate architectural boundary and not an accident of layering:
|
||||
composing a condensed, entertaining post is a probabilistic task, and
|
||||
ArchitectureBlueprint.md section 48.1 names an LLM in the mandatory request
|
||||
path as an anti-pattern. This interface stays deterministic by taking the
|
||||
composed text as input.
|
||||
|
||||
Publication is idempotent on post_id, because the caller is automation that
|
||||
may be retried and a duplicate post cannot be quietly undone.
|
||||
|
||||
paths:
|
||||
/v1/hall-entries:
|
||||
/v1/channel-posts:
|
||||
post:
|
||||
operationId: publishEntry
|
||||
summary: Publish a hall-of-helix entry
|
||||
operationId: publishPost
|
||||
summary: Publish a composed channel post
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [entry_id, title, body]
|
||||
required: [post_id, body, source_entry_id, source_entry_url, reviewed_by]
|
||||
additionalProperties: false
|
||||
properties:
|
||||
entry_id:
|
||||
post_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 128
|
||||
description: |
|
||||
The hall entry's own id, from its frontmatter. Publication
|
||||
is idempotent on this value.
|
||||
title:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 256
|
||||
Stable id for this post. Publication is idempotent on it.
|
||||
A recomposition of the same entry is a NEW post_id, because
|
||||
it is a different piece of writing about the same work.
|
||||
|
||||
body:
|
||||
type: string
|
||||
minLength: 1
|
||||
maxLength: 4096
|
||||
description: |
|
||||
The entry body as Markdown, frontmatter removed.
|
||||
The composed post, already within Telegram's single-message
|
||||
limit. Bounded here on purpose: a composition that does not
|
||||
fit was not condensed, and this interface must not decide
|
||||
on the composer's behalf what to cut.
|
||||
|
||||
Deliberately unbounded. Telegram's 4096-character message
|
||||
limit is this interface's problem, not the caller's, and a
|
||||
maxLength here would push it back onto them.
|
||||
display_name:
|
||||
source_entry_id:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: |
|
||||
The hall entry this post was written from. Required: a post
|
||||
with no traceable source cannot be checked against what the
|
||||
person actually said about their own work.
|
||||
|
||||
source_entry_url:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: Canonical location of the source entry.
|
||||
|
||||
reviewed_by:
|
||||
type: string
|
||||
minLength: 1
|
||||
description: |
|
||||
Who approved this composition for publication. Required at
|
||||
FLUID-2 and above: the post speaks about a named person's
|
||||
work in HelixForge's voice, and no generated text reaches
|
||||
subscribers without someone having read it.
|
||||
|
||||
subject_display_name:
|
||||
type: string
|
||||
maxLength: 128
|
||||
worker_kind:
|
||||
description: The person or agent whose work the post is about.
|
||||
|
||||
subject_consent:
|
||||
type: string
|
||||
enum: [human, agent-session]
|
||||
recorded_at:
|
||||
enum: [explicit, standing, not-required]
|
||||
description: |
|
||||
On what basis this post may speak about the subject's work.
|
||||
"standing" covers contributors who agreed when they took a
|
||||
seat in the hall; "explicit" is per-post approval.
|
||||
|
||||
variant:
|
||||
type: string
|
||||
format: date
|
||||
source_url:
|
||||
maxLength: 64
|
||||
description: |
|
||||
Which composition strategy produced this post. Opaque to
|
||||
this interface: it records the label and reports engagement
|
||||
against it, so that the campaign can compare strategies it
|
||||
owns without this interface needing to understand them.
|
||||
|
||||
visual_url:
|
||||
type: string
|
||||
description: Canonical location of the entry in hall-of-helix.
|
||||
description: Portrait or illustration to attach.
|
||||
|
||||
tags:
|
||||
type: array
|
||||
maxItems: 8
|
||||
items: {type: string, maxLength: 32}
|
||||
responses:
|
||||
"201":
|
||||
description: Published
|
||||
"200":
|
||||
description: Already published; the existing publication is returned
|
||||
"400":
|
||||
description: The entry cannot be published faithfully
|
||||
description: The post cannot be published as given
|
||||
"403":
|
||||
description: Publication refused; no review or no basis for consent
|
||||
"502":
|
||||
description: Telegram was unreachable or refused the message
|
||||
|
||||
get:
|
||||
operationId: listPublications
|
||||
operationId: listPosts
|
||||
summary: List publications, newest first
|
||||
parameters:
|
||||
- name: limit
|
||||
in: query
|
||||
required: false
|
||||
schema: {type: integer, minimum: 1, maximum: 100}
|
||||
- name: state
|
||||
in: query
|
||||
required: false
|
||||
schema:
|
||||
type: string
|
||||
enum: [pending, published, failed, superseded]
|
||||
- name: variant
|
||||
in: query
|
||||
schema: {type: string, maxLength: 64}
|
||||
- name: source_entry_id
|
||||
in: query
|
||||
schema: {type: string}
|
||||
responses:
|
||||
"200":
|
||||
description: Publications
|
||||
|
||||
/v1/hall-entries/{entry_id}:
|
||||
/v1/channel-posts/{post_id}:
|
||||
get:
|
||||
operationId: getPublication
|
||||
summary: Publication state for one entry
|
||||
operationId: getPost
|
||||
summary: Publication state for one post
|
||||
parameters:
|
||||
- name: entry_id
|
||||
- name: post_id
|
||||
in: path
|
||||
required: true
|
||||
schema: {type: string, minLength: 1}
|
||||
|
|
@ -100,4 +151,4 @@ paths:
|
|||
"200":
|
||||
description: Publication state
|
||||
"404":
|
||||
description: This entry has not been published
|
||||
description: This post has not been published
|
||||
|
|
|
|||
|
|
@ -7,18 +7,22 @@ API.
|
|||
|
||||
## What the adapter owns
|
||||
|
||||
- Rendering a hall entry into Telegram's message forms.
|
||||
- Rendering a **composed post** into a Telegram message.
|
||||
- Attaching the post's visual, where the revision calls for one.
|
||||
- Talking to the Telegram Bot API.
|
||||
- Custody of the bot token for the duration of a call.
|
||||
- Idempotency: recognising an `entry_id` it has already published.
|
||||
- Idempotency: recognising a `post_id` it 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.
|
||||
- **Compose, rewrite, shorten or embellish post text.** The post arrives
|
||||
written. If it does not fit, refuse it: a composition that exceeds the limit
|
||||
was not condensed, and deciding what to cut is the composer's judgement.
|
||||
- Publish a post carrying no `reviewed_by`, or none of the recognised consent
|
||||
bases. Refuse with 403.
|
||||
- Log, echo or forward the bot token, including in error messages.
|
||||
|
||||
## Endpoints
|
||||
|
|
@ -27,6 +31,10 @@ 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.
|
||||
|
||||
Note what the contract does **not** accept: a hall entry. The adapter never
|
||||
reads `hall-of-helix`. Its only knowledge of an entry is the id and URL a post
|
||||
cites, which it renders as a link.
|
||||
|
||||
## Backend capability contract
|
||||
|
||||
| Property | Value |
|
||||
|
|
@ -42,49 +50,68 @@ them, so the adapter may assume shape but must still assume hostile content.
|
|||
|
||||
## 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.
|
||||
`POST /v1/channel-posts` is idempotent on `post_id`. The caller is automation
|
||||
that may be retried, and a duplicate post 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
|
||||
The adapter keeps its own mapping from `post_id` to the Telegram message id 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.
|
||||
than posting again. If the body has changed, it edits the existing message.
|
||||
|
||||
A **recomposition** of the same source entry is a new `post_id`, not an edit.
|
||||
It is a different piece of writing about the same work, and collapsing the two
|
||||
would lose the comparison between them.
|
||||
|
||||
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
|
||||
Telegram formatting is not Markdown. Post Markdown must be converted, not
|
||||
passed through.
|
||||
|
||||
| Entry construct | Telegram |
|
||||
| Post 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 |
|
||||
| Source link | Appended as an attributed link to `source_entry_url` |
|
||||
|
||||
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.
|
||||
rather than degrade it silently.
|
||||
|
||||
Two constants regardless of revision: every post carries a link to its source
|
||||
entry, and every post is attributable. `missing_source_attribution` is a hard
|
||||
guardrail, and a post that cannot be checked against what the person actually
|
||||
wrote is the failure this whole arrangement exists to prevent.
|
||||
|
||||
**Caption limit.** When the revision attaches a visual (R-2), Telegram caps the
|
||||
caption at 1024 characters rather than 4096. The adapter must refuse a post
|
||||
that exceeds the limit for the form it is publishing in, rather than truncating
|
||||
it — the difference between those two limits is the entire subject of the first
|
||||
experiment, and silently trimming would destroy the measurement.
|
||||
|
||||
## 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.
|
||||
- `variant` — the composition strategy label the post carried, so engagement
|
||||
can be attributed to it. This is what lets the campaign compare strategies it
|
||||
owns without this interface understanding them.
|
||||
- `unreviewed_publication` — always false in practice, since such a post is
|
||||
refused; emitted so the guardrail has a series rather than an absence.
|
||||
- `missing_source_attribution`, `missing_subject_consent` — likewise.
|
||||
- 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.
|
||||
It must not emit subscriber identity in any form, and it must not emit post
|
||||
body text as telemetry — the body belongs in the publication record, not in the
|
||||
observation stream.
|
||||
|
||||
Engagement metrics (`engagement_rate`, `source_link_rate`, `forward_rate`) come
|
||||
from Telegram's channel statistics as aggregates, never per-reader.
|
||||
|
||||
## Configuration
|
||||
|
||||
|
|
@ -94,6 +121,7 @@ as aggregates, never per-reader.
|
|||
| `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 |
|
||||
| `ATTACH_VISUAL` | Whether this revision attaches the portrait (R-2 true, R-3 false) |
|
||||
|
||||
One adapter instance serves one channel. The private test channel and the public
|
||||
channel are separate instances with separate configuration, which is what keeps
|
||||
|
|
|
|||
|
|
@ -7,8 +7,8 @@ enough to compare over time and no more granular than the analysis requires.
|
|||
|
||||
| Cohort | Assigned by | Why it is distinct |
|
||||
|---|---|---|
|
||||
| `hall-publishing-jobs` | `X-FLUID-Consumer` prefix `hall-` | Batch behaviour; publishes many entries at once |
|
||||
| `helix-forge-agents` | `X-FLUID-Consumer` prefix `agent-` | One entry at a time, at session end; the richest pressure signal |
|
||||
| `hall-publishing-jobs` | `X-FLUID-Consumer` prefix `hall-` | Batch behaviour; publishes many posts at once. Becomes the `pr-hall-of-helix` campaign job |
|
||||
| `helix-forge-agents` | `X-FLUID-Consumer` prefix `agent-` | One post at a time, at session end |
|
||||
| `operators` | `X-FLUID-Consumer` prefix `op-` | Rare, deliberate, often corrective |
|
||||
| `telegram-subscribers` | Not a caller | An observed population, never an API consumer |
|
||||
|
||||
|
|
@ -31,9 +31,10 @@ Beyond the fluid-core defaults, this interface adds:
|
|||
usernames, display names of subscribers — must not reach the evidence store
|
||||
in any form, redacted or otherwise. There is no legitimate analysis that
|
||||
needs them, so the safe rule is that they never arrive.
|
||||
- **Entry content is not telemetry.** An entry body may appear in a request; it
|
||||
must not appear in a telemetry event. What is recorded is its length, its
|
||||
section count, and whether conversion succeeded.
|
||||
- **Post text is not telemetry.** The body appears in the request and is kept
|
||||
in the publication record, because it is what was said in HelixForge's name.
|
||||
It must not appear in a telemetry event: observation is for measuring how
|
||||
posts performed, not for accumulating a second copy of what they said.
|
||||
- **The bot token appears nowhere.** Not in errors, not in traces, not in
|
||||
redacted form.
|
||||
|
||||
|
|
@ -55,16 +56,32 @@ loss and start a fresh evidence baseline, not to rotate quietly.
|
|||
|
||||
| Metric | Role | Source |
|
||||
|---|---|---|
|
||||
| `read_through_rate` | primary | Telegram channel statistics, aggregate |
|
||||
| `messages_per_entry` | guardrail | Adapter |
|
||||
| `entry_content_loss` | guardrail | Adapter |
|
||||
| `engagement_rate` | primary | Telegram channel statistics, aggregate |
|
||||
| `unreviewed_publication` | guardrail | Adapter |
|
||||
| `missing_source_attribution` | guardrail | Adapter |
|
||||
| `missing_subject_consent` | guardrail | Adapter |
|
||||
| `error_rate` | guardrail | Gateway |
|
||||
| `reaction_rate` | secondary | Telegram channel statistics, aggregate |
|
||||
| `time_to_first_read` | learning | Telegram channel statistics, aggregate |
|
||||
| `source_link_rate` | secondary | Telegram channel statistics, aggregate |
|
||||
| `forward_rate` | secondary | Telegram channel statistics, aggregate |
|
||||
| `time_to_first_view` | learning | Telegram channel statistics, aggregate |
|
||||
|
||||
`read_through_rate` is the one to be careful about. Telegram reports view counts
|
||||
per message, not per reader. For the teaser form it is link clicks over views;
|
||||
for the serialized form it is views of the last part over views of the first.
|
||||
Those are not the same measurement, and comparing them directly is the weakest
|
||||
part of the first experiment. Say so in the experiment's amendment record rather
|
||||
than letting the comparison look cleaner than it is.
|
||||
`engagement_rate` is the one to be careful about. Telegram reports views,
|
||||
reactions and forwards per message, not per reader, and a "rate" therefore has
|
||||
subscribers as its denominator rather than people who saw the post. It is a
|
||||
proxy, and a noisy one at small subscriber counts. Say so in the experiment's
|
||||
amendment record rather than letting the comparison look cleaner than it is.
|
||||
|
||||
## Variant attribution
|
||||
|
||||
Every post carries a `variant` label naming the composition strategy that
|
||||
produced it. This interface treats it as opaque: it records the label, and
|
||||
reports engagement grouped by it.
|
||||
|
||||
That is deliberately the whole of the interface's involvement in composition.
|
||||
The campaign compares its own strategies using this data, running its own FLUID
|
||||
experiments over its own artifacts. The interface does not know what
|
||||
`variant: warm-specific-v2` means and must not acquire an opinion about it.
|
||||
|
||||
Without this field the campaign would have to infer which post came from which
|
||||
strategy by timestamp, which is exactly the kind of reconstruction that quietly
|
||||
stops being accurate.
|
||||
|
|
|
|||
68
experiments/E-tg-delivery.yaml
Normal file
68
experiments/E-tg-delivery.yaml
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
# The interface's first experiment: does the portrait earn its place?
|
||||
#
|
||||
# This tests DELIVERY, which this interface owns. The competition about which
|
||||
# editorial voice travels belongs to the campaign and is run on the per-variant
|
||||
# engagement this interface reports — see docs/observation.md.
|
||||
#
|
||||
# fluid experiment design --file experiments/E-tg-delivery.yaml
|
||||
# fluid experiment start E-tg-delivery --generation 2 \
|
||||
# --default-revision R-2 --policy-out rp.json
|
||||
# fluid policy put --file rp.json
|
||||
fluid_experiment:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "E-tg-delivery"
|
||||
interface_id: "helix-forge-telegram-publishing"
|
||||
|
||||
hypothesis_refs:
|
||||
- "H-tg-visual"
|
||||
- "H-tg-textonly"
|
||||
|
||||
# R-2 attaches the portrait, R-3 publishes text only. Neither is an
|
||||
# incumbent: R-1 publishes without either treatment being settled, so this
|
||||
# is a comparison between two answers rather than against a baseline.
|
||||
control_revision: "R-2"
|
||||
candidate_revisions:
|
||||
- "R-3"
|
||||
|
||||
cohorts:
|
||||
- "telegram-subscribers"
|
||||
|
||||
allocation:
|
||||
control: 0.5
|
||||
candidate: 0.5
|
||||
|
||||
# An even split because there is no incumbent to protect. The usual 90/10
|
||||
# caution limits exposure to an unproven candidate; here both arms are
|
||||
# equally unproven and the scarce resource is posts, not safety.
|
||||
|
||||
metrics:
|
||||
primary:
|
||||
- "engagement_rate"
|
||||
guardrails:
|
||||
- "unreviewed_publication"
|
||||
- "missing_source_attribution"
|
||||
- "missing_subject_consent"
|
||||
- "error_rate"
|
||||
secondary:
|
||||
- "source_link_rate"
|
||||
- "forward_rate"
|
||||
learning:
|
||||
- "time_to_first_view"
|
||||
|
||||
start_conditions:
|
||||
- "both revisions verified against the private test channel"
|
||||
- "channel has at least 100 subscribers"
|
||||
|
||||
stop_conditions:
|
||||
- "hard_guardrail_violation"
|
||||
- "unreviewed_publication"
|
||||
- "manual_stop"
|
||||
- "max_duration_reached"
|
||||
|
||||
max_duration_hours: 2160 # 90 days
|
||||
|
||||
result:
|
||||
state: "PLANNED"
|
||||
preferred_revision: null
|
||||
evidence_refs: []
|
||||
|
|
@ -1,68 +0,0 @@
|
|||
# The first live experiment: which presentation serves readers better.
|
||||
#
|
||||
# Do not start this until the private test channel has confirmed both forms
|
||||
# render correctly, and the public channel has enough subscribers for the
|
||||
# result to mean anything. The intent (section 12) is explicit that an
|
||||
# underpowered window should report INCONCLUSIVE rather than be shortened.
|
||||
#
|
||||
# fluid experiment design --file experiments/E-tg-length.yaml
|
||||
# fluid experiment start E-tg-length --generation 2 \
|
||||
# --default-revision R-2 --policy-out rp.json
|
||||
# fluid policy put --file rp.json
|
||||
fluid_experiment:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "E-tg-length"
|
||||
interface_id: "helix-forge-telegram-publishing"
|
||||
|
||||
hypothesis_refs:
|
||||
- "H-tg-teaser"
|
||||
- "H-tg-serial"
|
||||
|
||||
# R-2 implements the teaser form, R-3 the serialized form. Neither is the
|
||||
# control in the usual sense: R-1 cannot publish a long entry at all, so
|
||||
# this is a comparison between two answers rather than against a baseline.
|
||||
control_revision: "R-2"
|
||||
candidate_revisions:
|
||||
- "R-3"
|
||||
|
||||
cohorts:
|
||||
- "telegram-subscribers"
|
||||
|
||||
allocation:
|
||||
control: 0.5
|
||||
candidate: 0.5
|
||||
|
||||
# An even split because there is no incumbent to protect. The usual 90/10
|
||||
# caution exists to limit exposure to an unproven candidate; here both arms
|
||||
# are equally unproven and the scarce resource is entries, not safety.
|
||||
|
||||
metrics:
|
||||
primary:
|
||||
- "read_through_rate"
|
||||
guardrails:
|
||||
- "entry_content_loss"
|
||||
- "error_rate"
|
||||
- "messages_per_entry"
|
||||
secondary:
|
||||
- "reaction_rate"
|
||||
- "forward_rate"
|
||||
learning:
|
||||
- "time_to_first_read"
|
||||
|
||||
start_conditions:
|
||||
- "both revisions verified against the private test channel"
|
||||
- "channel has at least 100 subscribers"
|
||||
|
||||
stop_conditions:
|
||||
- "hard_guardrail_violation"
|
||||
- "entry_content_loss"
|
||||
- "manual_stop"
|
||||
- "max_duration_reached"
|
||||
|
||||
max_duration_hours: 2160 # 90 days: roughly 12 entries at current cadence
|
||||
|
||||
result:
|
||||
state: "PLANNED"
|
||||
preferred_revision: null
|
||||
evidence_refs: []
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
# Competing hypothesis B, in group CG-tg-length.
|
||||
fluid_hypothesis:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "H-tg-serial"
|
||||
interface_id: "helix-forge-telegram-publishing"
|
||||
state: "DRAFT"
|
||||
|
||||
title: "The whole entry in the channel serves readers better than a link"
|
||||
|
||||
observation:
|
||||
summary: >
|
||||
85% of hall entries exceed Telegram's 4096-character message limit, with
|
||||
a median body of 5767 characters. The interface must choose a form for
|
||||
long entries before it can publish at all.
|
||||
evidence_refs:
|
||||
- "measurement:hall-of-helix/entries@2026-09-04"
|
||||
- "pressure:P-tg-length"
|
||||
affected_cohorts:
|
||||
- "telegram-subscribers"
|
||||
|
||||
pressure:
|
||||
classes:
|
||||
- "missing_interface_capability"
|
||||
pressure_refs:
|
||||
- "P-tg-length"
|
||||
severity: 0.85
|
||||
confidence: 1.0
|
||||
|
||||
explanation:
|
||||
claim: >
|
||||
A link out of Telegram is a cost most readers will not pay. Telegram's
|
||||
rich-text posts are built for long-form reading and the audience is
|
||||
already there. Serializing the entry across a small number of linked
|
||||
messages keeps the whole account where the reader is, and the median
|
||||
entry needs only two.
|
||||
reach:
|
||||
score: 0.5
|
||||
explains:
|
||||
- "P-tg-length"
|
||||
notes: >
|
||||
Medium reach. It answers the length constraint natively but does not
|
||||
transfer to platforms with harder limits.
|
||||
|
||||
proposed_adaptation:
|
||||
class: "presentation"
|
||||
summary: >
|
||||
Split the entry at section boundaries into a small number of rich-text
|
||||
messages, each linked to the previous, with the visual attached to the
|
||||
first.
|
||||
implementation_scope: "interface_only"
|
||||
|
||||
backend_requirements:
|
||||
required: false
|
||||
requirement_refs: []
|
||||
|
||||
expected_outcomes:
|
||||
- metric: "read_through_rate"
|
||||
cohort: "telegram-subscribers"
|
||||
target: 0.15
|
||||
direction: "higher"
|
||||
- metric: "messages_per_entry"
|
||||
cohort: "all"
|
||||
baseline: 1.0
|
||||
target: 2.0
|
||||
direction: "higher"
|
||||
|
||||
guardrails:
|
||||
- metric: "entry_content_loss"
|
||||
operator: "=="
|
||||
threshold: false
|
||||
- metric: "error_rate"
|
||||
operator: "<="
|
||||
threshold: 0.01
|
||||
- metric: "messages_per_entry"
|
||||
operator: "<="
|
||||
threshold: 4
|
||||
|
||||
fitness_dimensions:
|
||||
expected:
|
||||
client_utility: 0.5
|
||||
discoverability: 0.3
|
||||
simplicity: -0.2
|
||||
correctness: 0.0
|
||||
security: 0.0
|
||||
|
||||
complexity:
|
||||
expected_delta:
|
||||
operation_count: 0
|
||||
concept_count: 1
|
||||
alternative_path_count: 1
|
||||
cognitive_load: 0.2
|
||||
score: 0.3
|
||||
|
||||
risk:
|
||||
level: "LOW"
|
||||
reasons:
|
||||
- "presentation only"
|
||||
- "splitting can lose meaning at a bad boundary; guarded by content-loss check"
|
||||
|
||||
economics:
|
||||
estimated_experiment_cost: 0.0
|
||||
estimated_implementation_cost: 0.0
|
||||
currency: "EUR"
|
||||
expected_value_class: "MEDIUM"
|
||||
|
||||
learning_value:
|
||||
score: 0.8
|
||||
notes: >
|
||||
Tests whether an audience will read long-form in place, which is the
|
||||
question behind every publishing interface HelixForge will build.
|
||||
|
||||
success_criteria:
|
||||
expression: >
|
||||
read_through_rate >= 0.15 AND messages_per_entry <= 4 AND no guardrail violation
|
||||
|
||||
failure_criteria:
|
||||
expression: >
|
||||
any hard guardrail violation
|
||||
OR read_through_rate below the teaser alternative over the same window
|
||||
|
||||
outcome:
|
||||
status: null
|
||||
summary: null
|
||||
evidence_refs: []
|
||||
|
|
@ -1,125 +0,0 @@
|
|||
# Competing hypothesis A, in group CG-tg-length.
|
||||
#
|
||||
# Create with:
|
||||
# fluid hypothesis create --file hypotheses/H-tg-teaser.yaml
|
||||
# fluid hypothesis compete --group CG-tg-length H-tg-teaser H-tg-serial
|
||||
fluid_hypothesis:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "H-tg-teaser"
|
||||
interface_id: "helix-forge-telegram-publishing"
|
||||
state: "DRAFT"
|
||||
|
||||
title: "A teaser and a link serve readers better than the whole entry"
|
||||
|
||||
observation:
|
||||
summary: >
|
||||
85% of hall entries exceed Telegram's 4096-character message limit, with
|
||||
a median body of 5767 characters. The interface must choose a form for
|
||||
long entries before it can publish at all.
|
||||
evidence_refs:
|
||||
- "measurement:hall-of-helix/entries@2026-09-04"
|
||||
- "pressure:P-tg-length"
|
||||
affected_cohorts:
|
||||
- "telegram-subscribers"
|
||||
|
||||
pressure:
|
||||
classes:
|
||||
- "missing_interface_capability"
|
||||
pressure_refs:
|
||||
- "P-tg-length"
|
||||
severity: 0.85
|
||||
confidence: 1.0
|
||||
|
||||
explanation:
|
||||
claim: >
|
||||
A channel post is scanned, not read. A hall entry is a considered
|
||||
first-person account that rewards attention. Presenting an opening and a
|
||||
link lets a reader decide to give it that attention somewhere designed
|
||||
for reading, rather than committing to a wall of text in a feed.
|
||||
reach:
|
||||
score: 0.5
|
||||
explains:
|
||||
- "P-tg-length"
|
||||
notes: >
|
||||
Medium reach. It answers the length constraint and would answer the
|
||||
same constraint on X, but it does not address entries with visuals or
|
||||
entries readers might want to keep in the channel.
|
||||
|
||||
proposed_adaptation:
|
||||
class: "presentation"
|
||||
summary: >
|
||||
Publish the title, the display name, the opening of the entry, and a link
|
||||
to the full entry in hall-of-helix.
|
||||
implementation_scope: "interface_only"
|
||||
|
||||
backend_requirements:
|
||||
required: false
|
||||
requirement_refs: []
|
||||
|
||||
expected_outcomes:
|
||||
- metric: "read_through_rate"
|
||||
cohort: "telegram-subscribers"
|
||||
target: 0.15
|
||||
direction: "higher"
|
||||
- metric: "messages_per_entry"
|
||||
cohort: "all"
|
||||
baseline: 2.0
|
||||
target: 1.0
|
||||
direction: "lower"
|
||||
|
||||
guardrails:
|
||||
- metric: "entry_content_loss"
|
||||
operator: "=="
|
||||
threshold: false
|
||||
- metric: "error_rate"
|
||||
operator: "<="
|
||||
threshold: 0.01
|
||||
|
||||
fitness_dimensions:
|
||||
expected:
|
||||
client_utility: 0.4
|
||||
discoverability: 0.5
|
||||
simplicity: 0.6
|
||||
correctness: 0.0
|
||||
security: 0.0
|
||||
|
||||
complexity:
|
||||
expected_delta:
|
||||
operation_count: 0
|
||||
concept_count: 0
|
||||
cognitive_load: -0.1
|
||||
score: 0.05
|
||||
|
||||
risk:
|
||||
level: "LOW"
|
||||
reasons:
|
||||
- "presentation only; no contract or authorization change"
|
||||
- "fully reversible by replacing the revision"
|
||||
|
||||
economics:
|
||||
estimated_experiment_cost: 0.0
|
||||
estimated_implementation_cost: 0.0
|
||||
currency: "EUR"
|
||||
expected_value_class: "MEDIUM"
|
||||
|
||||
learning_value:
|
||||
score: 0.8
|
||||
notes: >
|
||||
Whichever way this resolves, the answer transfers directly to
|
||||
fluid-x and fluid-substack, which face the same problem under
|
||||
different constraints.
|
||||
|
||||
success_criteria:
|
||||
expression: >
|
||||
read_through_rate >= 0.15 AND no guardrail violation
|
||||
|
||||
failure_criteria:
|
||||
expression: >
|
||||
any hard guardrail violation
|
||||
OR read_through_rate below the serialized alternative over the same window
|
||||
|
||||
outcome:
|
||||
status: null
|
||||
summary: null
|
||||
evidence_refs: []
|
||||
122
hypotheses/H-tg-textonly.yaml
Normal file
122
hypotheses/H-tg-textonly.yaml
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
fluid_hypothesis:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "H-tg-textonly"
|
||||
interface_id: "helix-forge-telegram-publishing"
|
||||
state: "DRAFT"
|
||||
|
||||
title: "Text alone reaches further than a post constrained by a caption"
|
||||
|
||||
observation:
|
||||
summary: >
|
||||
Telegram limits a photo caption to 1024 characters, a quarter of the 4096 available to a text message. Attaching a portrait therefore constrains the composition it accompanies, and a post written to 1024 characters is a different piece of writing from one written to 4096.
|
||||
evidence_refs:
|
||||
- "canon:fluid-telegram/TelegramCapabilityCanon.md#MED-01"
|
||||
- "pressure:P-register"
|
||||
affected_cohorts:
|
||||
- "telegram-subscribers"
|
||||
|
||||
pressure:
|
||||
classes:
|
||||
- "repeated_expectation_mismatch"
|
||||
pressure_refs:
|
||||
- "P-register"
|
||||
severity: 0.6
|
||||
confidence: 0.7
|
||||
|
||||
explanation:
|
||||
claim: >
|
||||
The scarce resource in a condensed post is room to say something specific. A generated portrait is decorative and interchangeable across entries; a concrete detail about what someone actually did is not. Trading three quarters of the available text for a picture spends the thing that makes a post worth reading on the thing that makes it look like everything else in the feed.
|
||||
reach:
|
||||
score: 0.4
|
||||
explains:
|
||||
- "P-register"
|
||||
notes: >
|
||||
Narrow but clean. It settles one delivery question decisively, and the
|
||||
answer transfers to fluid-x and fluid-substack, which face the same
|
||||
question under different feeds.
|
||||
|
||||
proposed_adaptation:
|
||||
class: "presentation"
|
||||
summary: >
|
||||
Publish the post as a text message with a link to the source entry, and no attached visual.
|
||||
implementation_scope: "interface_only"
|
||||
|
||||
backend_requirements:
|
||||
required: false
|
||||
requirement_refs: []
|
||||
|
||||
expected_outcomes:
|
||||
- metric: "engagement_rate"
|
||||
cohort: "telegram-subscribers"
|
||||
target: 0.05
|
||||
direction: "higher"
|
||||
- metric: "source_link_rate"
|
||||
cohort: "telegram-subscribers"
|
||||
target: 0.02
|
||||
direction: "higher"
|
||||
|
||||
guardrails:
|
||||
- metric: "unreviewed_publication"
|
||||
operator: "=="
|
||||
threshold: false
|
||||
- metric: "missing_source_attribution"
|
||||
operator: "=="
|
||||
threshold: false
|
||||
- metric: "missing_subject_consent"
|
||||
operator: "=="
|
||||
threshold: false
|
||||
- metric: "error_rate"
|
||||
operator: "<="
|
||||
threshold: 0.01
|
||||
|
||||
fitness_dimensions:
|
||||
expected:
|
||||
client_utility: 0.2
|
||||
discoverability: 0.3
|
||||
simplicity: 0.3
|
||||
correctness: 0.0
|
||||
security: 0.0
|
||||
|
||||
complexity:
|
||||
expected_delta:
|
||||
operation_count: 0
|
||||
concept_count: 0
|
||||
cognitive_load: 0.1
|
||||
score: 0.05
|
||||
|
||||
risk:
|
||||
level: "LOW"
|
||||
reasons:
|
||||
- "presentation only; no contract, authorization or consent change"
|
||||
- "the simpler arm; nothing to attach and nothing to fail"
|
||||
|
||||
economics:
|
||||
estimated_experiment_cost: 0.0
|
||||
estimated_implementation_cost: 0.0
|
||||
currency: "EUR"
|
||||
expected_value_class: "MEDIUM"
|
||||
|
||||
learning_value:
|
||||
score: 0.7
|
||||
notes: >
|
||||
If this wins, the campaign stops producing portraits for publication and spends that effort on the writing instead.
|
||||
|
||||
success_criteria:
|
||||
expression: >
|
||||
engagement_rate >= 0.05 AND no guardrail violation
|
||||
|
||||
failure_criteria:
|
||||
expression: >
|
||||
any hard guardrail violation
|
||||
OR engagement_rate at or below the rival arm over the same window
|
||||
|
||||
competition:
|
||||
group_id: "CG-tg-delivery"
|
||||
alternatives:
|
||||
- "H-tg-visual"
|
||||
|
||||
outcome:
|
||||
status: null
|
||||
summary: null
|
||||
evidence_refs: []
|
||||
122
hypotheses/H-tg-visual.yaml
Normal file
122
hypotheses/H-tg-visual.yaml
Normal file
|
|
@ -0,0 +1,122 @@
|
|||
fluid_hypothesis:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "H-tg-visual"
|
||||
interface_id: "helix-forge-telegram-publishing"
|
||||
state: "DRAFT"
|
||||
|
||||
title: "A post carrying its portrait reaches further than text alone"
|
||||
|
||||
observation:
|
||||
summary: >
|
||||
72 of 94 hall entries already carry a generated portrait, produced from the entry's own visual prompt. Telegram renders images inline and its feed is scanned rather than read, so a post with a portrait occupies more of a scrolling reader's attention than one without.
|
||||
evidence_refs:
|
||||
- "measurement:hall-of-helix/visuals@2026-09-04"
|
||||
- "pressure:P-register"
|
||||
affected_cohorts:
|
||||
- "telegram-subscribers"
|
||||
|
||||
pressure:
|
||||
classes:
|
||||
- "repeated_expectation_mismatch"
|
||||
pressure_refs:
|
||||
- "P-register"
|
||||
severity: 0.6
|
||||
confidence: 0.7
|
||||
|
||||
explanation:
|
||||
claim: >
|
||||
Reach in a scanned feed is won by stopping the scroll. The portraits are already made, already tied to the specific person and stretch of work, and cost nothing further to attach. If they do not measurably help, the effort of producing them for publication is not justified and the campaign should know that.
|
||||
reach:
|
||||
score: 0.4
|
||||
explains:
|
||||
- "P-register"
|
||||
notes: >
|
||||
Narrow but clean. It settles one delivery question decisively, and the
|
||||
answer transfers to fluid-x and fluid-substack, which face the same
|
||||
question under different feeds.
|
||||
|
||||
proposed_adaptation:
|
||||
class: "presentation"
|
||||
summary: >
|
||||
Publish the post with its portrait attached as a photo message, with the post body as the caption.
|
||||
implementation_scope: "interface_only"
|
||||
|
||||
backend_requirements:
|
||||
required: false
|
||||
requirement_refs: []
|
||||
|
||||
expected_outcomes:
|
||||
- metric: "engagement_rate"
|
||||
cohort: "telegram-subscribers"
|
||||
target: 0.05
|
||||
direction: "higher"
|
||||
- metric: "source_link_rate"
|
||||
cohort: "telegram-subscribers"
|
||||
target: 0.02
|
||||
direction: "higher"
|
||||
|
||||
guardrails:
|
||||
- metric: "unreviewed_publication"
|
||||
operator: "=="
|
||||
threshold: false
|
||||
- metric: "missing_source_attribution"
|
||||
operator: "=="
|
||||
threshold: false
|
||||
- metric: "missing_subject_consent"
|
||||
operator: "=="
|
||||
threshold: false
|
||||
- metric: "error_rate"
|
||||
operator: "<="
|
||||
threshold: 0.01
|
||||
|
||||
fitness_dimensions:
|
||||
expected:
|
||||
client_utility: 0.2
|
||||
discoverability: 0.5
|
||||
simplicity: -0.1
|
||||
correctness: 0.0
|
||||
security: 0.0
|
||||
|
||||
complexity:
|
||||
expected_delta:
|
||||
operation_count: 0
|
||||
concept_count: 0
|
||||
cognitive_load: 0.1
|
||||
score: 0.15
|
||||
|
||||
risk:
|
||||
level: "LOW"
|
||||
reasons:
|
||||
- "presentation only; no contract, authorization or consent change"
|
||||
- "Telegram caps a photo caption at 1024 characters, a quarter of the text limit; this arm constrains the composition it carries"
|
||||
|
||||
economics:
|
||||
estimated_experiment_cost: 0.0
|
||||
estimated_implementation_cost: 0.0
|
||||
currency: "EUR"
|
||||
expected_value_class: "MEDIUM"
|
||||
|
||||
learning_value:
|
||||
score: 0.7
|
||||
notes: >
|
||||
Settles whether the portraits earn their place in publication, and the answer transfers to every other channel HelixForge publishes to.
|
||||
|
||||
success_criteria:
|
||||
expression: >
|
||||
engagement_rate >= 0.05 AND no guardrail violation
|
||||
|
||||
failure_criteria:
|
||||
expression: >
|
||||
any hard guardrail violation
|
||||
OR engagement_rate at or below the rival arm over the same window
|
||||
|
||||
competition:
|
||||
group_id: "CG-tg-delivery"
|
||||
alternatives:
|
||||
- "H-tg-textonly"
|
||||
|
||||
outcome:
|
||||
status: null
|
||||
summary: null
|
||||
evidence_refs: []
|
||||
|
|
@ -1,47 +0,0 @@
|
|||
# Seed pressure record.
|
||||
#
|
||||
# This is not derived from telemetry. It is a structural constraint measured
|
||||
# directly against hall-of-helix on 2026-09-04, and it is real before the
|
||||
# interface has served a single request. The intent (section 13) permits a
|
||||
# structural constraint to found a hypothesis without waiting for repetition.
|
||||
#
|
||||
# Record it with:
|
||||
# fluid pressure ... (or let the first classification pass confirm it)
|
||||
fluid_pressure:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "P-tg-length"
|
||||
interface_id: "helix-forge-telegram-publishing"
|
||||
|
||||
class: "missing_interface_capability"
|
||||
|
||||
first_seen: "2026-09-04T00:00:00Z"
|
||||
last_seen: "2026-09-04T00:00:00Z"
|
||||
|
||||
affected_cohorts:
|
||||
- "hall-publishing-jobs"
|
||||
- "helix-forge-agents"
|
||||
|
||||
frequency:
|
||||
observations: 80
|
||||
independent_consumers: 2
|
||||
|
||||
severity: 0.85
|
||||
confidence: 1.0
|
||||
|
||||
summary: >
|
||||
Telegram limits a single message to 4096 characters. Of the 94 entries in
|
||||
hall-of-helix on 2026-09-04, 80 have bodies exceeding that limit — 85%.
|
||||
The median body is 5767 characters and the largest is 10220. No entry of
|
||||
typical length can be published as one message, so the interface cannot
|
||||
fulfil its mission without deciding how a long entry becomes a post.
|
||||
|
||||
evidence_refs:
|
||||
- "measurement:hall-of-helix/entries@2026-09-04"
|
||||
- "canon:fluid-telegram/TelegramCapabilityCanon.md#PUB-02"
|
||||
|
||||
status: "ANALYZING"
|
||||
|
||||
linked_hypotheses:
|
||||
- "H-tg-teaser"
|
||||
- "H-tg-serial"
|
||||
45
pressure/P-register-mismatch.yaml
Normal file
45
pressure/P-register-mismatch.yaml
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
# Seed pressure record.
|
||||
#
|
||||
# Measured directly rather than observed from telemetry. The intent (section 13)
|
||||
# permits a structural constraint to found a hypothesis without waiting for
|
||||
# repetition, and this one is structural: it is a property of what a hall entry
|
||||
# is, not of how anyone has used the interface.
|
||||
fluid_pressure:
|
||||
schema_version: "0.1"
|
||||
|
||||
id: "P-register"
|
||||
interface_id: "helix-forge-telegram-publishing"
|
||||
|
||||
class: "repeated_expectation_mismatch"
|
||||
|
||||
first_seen: "2026-09-04T00:00:00Z"
|
||||
last_seen: "2026-09-04T00:00:00Z"
|
||||
|
||||
affected_cohorts:
|
||||
- "telegram-subscribers"
|
||||
|
||||
frequency:
|
||||
observations: 94
|
||||
independent_consumers: 1
|
||||
|
||||
severity: 0.8
|
||||
confidence: 1.0
|
||||
|
||||
summary: >
|
||||
A hall-of-helix entry is written for a colleague reading a repository: a
|
||||
considered first-person account under fixed headings, median body 5767
|
||||
characters, with 80 of 94 entries exceeding Telegram's 4096-character
|
||||
single-message limit. A channel post reaches a stranger scrolling. These
|
||||
are different pieces of writing for different readers, and publishing the
|
||||
first as the second serves neither. 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.
|
||||
|
||||
evidence_refs:
|
||||
- "measurement:hall-of-helix/entries@2026-09-04"
|
||||
- "canon:fluid-telegram/TelegramCapabilityCanon.md#PUB-02"
|
||||
|
||||
status: "ANALYZING"
|
||||
|
||||
linked_hypotheses:
|
||||
- "H-tg-visual"
|
||||
|
|
@ -22,8 +22,20 @@ state_hub_workstream_id: "a660ed65-700e-5b54-8d91-a556b73518f0"
|
|||
|
||||
# FT-WP-0001 — Telegram identity and the Hall of Helix channel
|
||||
|
||||
Establish a Telegram identity for HelixForge and a channel publishing
|
||||
hall-of-helix entries, as the first real FLUID interface.
|
||||
Establish a Telegram identity for HelixForge and a channel publishing composed
|
||||
posts about hall-of-helix work, as the first real FLUID interface.
|
||||
|
||||
**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 and is a different piece of writing; the entry is the
|
||||
source and the check, never the payload.
|
||||
|
||||
Composition happens before this interface is called. That boundary is
|
||||
architectural: composing is a probabilistic editorial task, and
|
||||
`ArchitectureBlueprint.md` §48.1 forbids an LLM in the mandatory request path.
|
||||
It is also editorial: a post speaks about a named person's work in HelixForge's
|
||||
voice, and that deserves a human read before it goes out.
|
||||
|
||||
Everything that could be prepared without credentials has been: the governing
|
||||
intent, the R-1 contract, the adapter's connector contract, the observation
|
||||
|
|
@ -115,12 +127,57 @@ Implement the three operations in `contracts/r1.openapi.yaml` against the
|
|||
connector contract in `docs/adapter-contract.md`. TypeScript with grammY is the
|
||||
natural choice; the wire contract makes the language irrelevant to fluid-core.
|
||||
|
||||
The two requirements that carry the most weight: publication is idempotent on
|
||||
`entry_id`, and an entry that cannot be converted faithfully is refused rather
|
||||
than approximated. `entry_content_loss` is a hard guardrail in both competing
|
||||
hypotheses and can only be honest if the adapter refuses.
|
||||
Three requirements carry the weight: publication is idempotent on `post_id`; a
|
||||
post arriving without `reviewed_by` or a consent basis is refused with 403; and
|
||||
a post that exceeds the limit for the form being published is refused rather
|
||||
than truncated. The adapter renders and delivers. It never writes.
|
||||
|
||||
## T06 — Publish one entry to the private channel
|
||||
## T05b — Establish the composition step
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T05b
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Build the step that turns a hall entry into a post: condensed, personal,
|
||||
entertaining, reach-optimized, carrying a link back to the source entry.
|
||||
|
||||
For now this lives here, as a script plus a review queue. It is temporary by
|
||||
design — T11 extracts it into `pr-hall-of-helix` — so keep the seam clean. The
|
||||
composition step's only interface to publishing is `POST /v1/channel-posts`,
|
||||
and it must not reach into the adapter or the evidence store.
|
||||
|
||||
Each composition records the `variant` label of the strategy that produced it.
|
||||
Without that label the campaign cannot later compare strategies, and inferring
|
||||
it from timestamps is the kind of reconstruction that quietly stops being
|
||||
accurate.
|
||||
|
||||
Human review is not a nicety here. A generated condensation can put a claim in
|
||||
someone's mouth that their own entry never made, and in the audit trail an
|
||||
unfaithful post looks exactly like a faithful one. The reviewer reads the post
|
||||
against the entry, and their name goes in `reviewed_by`.
|
||||
|
||||
## T05c — Record the consent basis for hall participants
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T05c
|
||||
status: todo
|
||||
priority: high
|
||||
```
|
||||
|
||||
Ninety-four entries already exist, written by people and agent sessions who
|
||||
took a seat in the hall without anyone mentioning a Telegram channel.
|
||||
|
||||
Decide and record the basis on which HelixForge may write publicly about that
|
||||
work. If `hall-of-helix/ENTRY.md` is to carry a standing agreement, add it and
|
||||
say plainly that it is revocable. Contributors who predate it should be asked
|
||||
rather than assumed into it.
|
||||
|
||||
Absence of objection is not consent, and the interface refuses a post carrying
|
||||
no basis, so this blocks T08.
|
||||
|
||||
## T06 — Publish one post to the private channel
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T06
|
||||
|
|
@ -129,15 +186,22 @@ priority: high
|
|||
state_hub_task_id: "317bef32-e82e-586a-8947-8cbb330f6c98"
|
||||
```
|
||||
|
||||
The first real test. Publish a single hall entry to the private channel and
|
||||
confirm, by looking at it:
|
||||
The first real test. Compose one post from one entry, have it reviewed, publish
|
||||
it to the private channel, and confirm by looking at it:
|
||||
|
||||
- the rendering is faithful and readable;
|
||||
- re-publishing the same entry edits rather than duplicates;
|
||||
- the evidence store contains no Telegram user identity and no bot token;
|
||||
- `fluid audit trace R-1` reconstructs the publication.
|
||||
- the post reads well and is worth a stranger's attention;
|
||||
- it does not claim anything the source entry does not support;
|
||||
- the source link works and the attribution is right;
|
||||
- re-publishing the same `post_id` edits rather than duplicates;
|
||||
- the evidence store holds the post text but no Telegram user identity and no
|
||||
bot token;
|
||||
- `fluid audit trace R-1` reconstructs the publication, naming the reviewer and
|
||||
the source entry.
|
||||
|
||||
## T07 — Implement both presentation forms as R-2 and R-3
|
||||
The second bullet is the one that needs a person. Everything else can be
|
||||
automated; whether a condensation is fair to the person it describes cannot.
|
||||
|
||||
## T07 — Implement both delivery forms as R-2 and R-3
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T07
|
||||
|
|
@ -146,11 +210,16 @@ priority: medium
|
|||
state_hub_task_id: "6a91ea08-8814-58f3-8297-a7ca63405294"
|
||||
```
|
||||
|
||||
R-2 implements `H-tg-teaser`, R-3 implements `H-tg-serial`. Both are
|
||||
presentation adaptations over the same contract, so neither needs a contract
|
||||
change. Verify both against the private channel before either sees a subscriber.
|
||||
R-2 implements `H-tg-visual` — portrait attached, and Telegram's 1024-character
|
||||
caption limit with it. R-3 implements `H-tg-textonly` — a text message with the
|
||||
full 4096 available. Both are presentation adaptations over the same contract,
|
||||
so neither needs a contract change. Verify both against the private channel
|
||||
before either sees a subscriber.
|
||||
|
||||
## T08 — Open the public channel and publish the backlog
|
||||
The two arms impose different length limits on the composer, which is the one
|
||||
place delivery and composition genuinely couple.
|
||||
|
||||
## T08 — Open the public channel and start publishing
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T08
|
||||
|
|
@ -159,14 +228,16 @@ priority: medium
|
|||
state_hub_task_id: "5e5cd89a-e7b1-5aaf-9331-ece424afe5e3"
|
||||
```
|
||||
|
||||
Point the production adapter at the public channel and publish the existing
|
||||
entries. 94 entries at once would be both a rate-limit problem and an unkind
|
||||
introduction for a new subscriber; publish them oldest-first at a human pace.
|
||||
Point the production adapter at the public channel. Blocked on T05c: no post
|
||||
goes out about anyone's work without a recorded consent basis.
|
||||
|
||||
Whichever of R-2 or R-3 is used here becomes the de facto control for the
|
||||
experiment, which is why `E-tg-length` names R-2 as control rather than R-1.
|
||||
Do not batch-publish the backlog. Ninety-four posts in a burst is a rate-limit
|
||||
problem, an unkind introduction for a new subscriber, and — more to the point —
|
||||
ninety-four compositions nobody has had time to read properly. Publish
|
||||
selectively and at a human pace, and let the backlog be a source to draw on
|
||||
rather than a queue to drain.
|
||||
|
||||
## T09 — Run the first experiment
|
||||
## T09 — Run the first delivery experiment
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T09
|
||||
|
|
@ -175,14 +246,14 @@ priority: low
|
|||
state_hub_task_id: "649e7df6-6c07-5cc8-a022-d1eadcdb7236"
|
||||
```
|
||||
|
||||
Start `E-tg-length` once the channel has roughly 100 subscribers. Before then
|
||||
Start `E-tg-delivery` once the channel has roughly 100 subscribers. Before then
|
||||
the evaluator will report INCONCLUSIVE, and that is the correct answer rather
|
||||
than a reason to shorten the window.
|
||||
|
||||
At one entry per week, a 90-day experiment yields about twelve entries. That is
|
||||
At one post per week, a 90-day experiment yields about twelve posts. That is
|
||||
thin, and the honest expectation is that the first run is inconclusive and
|
||||
informs a better-powered second one. Record that expectation in the experiment's
|
||||
amendments rather than discovering it as a disappointment.
|
||||
informs a better-powered second one. Record that expectation in the
|
||||
experiment's amendments rather than discovering it as a disappointment.
|
||||
|
||||
## T10 — Close the loop
|
||||
|
||||
|
|
@ -193,9 +264,43 @@ priority: low
|
|||
state_hub_task_id: "b469b9b8-a1a8-5f76-a1bb-dd51dd4d2ce5"
|
||||
```
|
||||
|
||||
Record the hypothesis outcome, resolve the competition group, and promote the
|
||||
winner. Then confirm `fluid audit trace` reconstructs the whole chain from the
|
||||
length-limit pressure through to the promoted revision.
|
||||
Record the hypothesis outcome, resolve `CG-tg-delivery`, and promote the winner.
|
||||
Then confirm `fluid audit trace` reconstructs the whole chain, from the register
|
||||
mismatch through to the promoted revision.
|
||||
|
||||
That trace is the deliverable. It is what makes this the first FLUID interface
|
||||
rather than a bot that posts to a channel.
|
||||
|
||||
## T11 — Extract the campaign into pr-hall-of-helix
|
||||
|
||||
```task
|
||||
id: FT-WP-0001-T11
|
||||
status: todo
|
||||
priority: medium
|
||||
```
|
||||
|
||||
Everything about *what to say and when* moves to a new repository,
|
||||
`pr-hall-of-helix`, under the `pr-` prefix for public-relations campaigns. What
|
||||
stays here is the deterministic delivery interface.
|
||||
|
||||
The split follows the boundary the intent already draws (§7):
|
||||
|
||||
| Moves to `pr-hall-of-helix` | Stays in `fluid-telegram` |
|
||||
|---|---|
|
||||
| Composition and the editorial voice | The R-1 contract and its revisions |
|
||||
| Choosing which work is worth a post | The adapter |
|
||||
| Variant strategies and their competition | Delivery experiments (`CG-tg-delivery`) |
|
||||
| Consent records and the review queue | Per-variant engagement reporting |
|
||||
| The publishing cadence | Bot and channel configuration |
|
||||
|
||||
The campaign becomes a *consumer* of this interface — cohort
|
||||
`hall-publishing-jobs` — and runs its own FLUID loop over its own artifacts,
|
||||
using the per-variant engagement this interface reports.
|
||||
|
||||
Two things this buys. `fluid-telegram` becomes reusable for any HelixForge
|
||||
campaign rather than being about the Hall of Helix specifically. And the Hall of
|
||||
Helix campaign becomes an ongoing offering with its own intent, budget and
|
||||
cadence, rather than living inside a workplan that was supposed to finish.
|
||||
|
||||
Do this once the loop has closed at least once (T10). Extracting a seam before
|
||||
you have used it is how you find out you cut it in the wrong place.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue