Skip to content

Commit e5d7f51

Browse files
markmnlclaude
andauthored
Add FMSG-006 Indicators standard (#33)
Ephemeral, unacknowledged signals such as "composing a reply", sent host to host as single UDP datagrams on port 4930: version, subject message hash, participant index, ttl, and a single emoji or empty to clear. The indicating participant is named by position among the subject's wire fields, so no address appears on the wire; the receiver resolves the index from its stored copy and verifies the source address against fmsg.<domain> of that participant. Sending hosts rate-limit per receiving domain; receivers fan out to connected clients and discard. Kinds are open-ended emoji, the ephemeral twin of FMSG-005 reactions, with a recommended vocabulary and no change to the core specification or FMSG-001. Claude-Session: https://claude.ai/code/session_01MW4FXDGacoWXeKPU5cQTHS Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
1 parent a97632f commit e5d7f51

3 files changed

Lines changed: 324 additions & 0 deletions

File tree

STANDARDS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ Each standard is prefixed with a unique identifer following the format: "FMSG-##
2424
| [FMSG-003](standards/fmsg-003-webapi.md) | HTTP API spec client to perform CRUD operations on messages for a specific address |
2525
| [FMSG-004](standards/fmsg-004-a2a-binding.md) | A2A protocol binding over fmsg |
2626
| [FMSG-005](standards/fmsg-005-reactions.md) | Emoji reactions to messages, carried as terminal fmsg messages |
27+
| [FMSG-006](standards/fmsg-006-indicators.md) | Ephemeral indicators such as composing a reply, as unacknowledged UDP datagrams |
2728

2829

2930

mkdocs.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ nav:
6464
- FMSG-003 — Web API: standards/fmsg-003-webapi.md
6565
- FMSG-004 — A2A binding: standards/fmsg-004-a2a-binding.md
6666
- FMSG-005 — Reactions: standards/fmsg-005-reactions.md
67+
- FMSG-006 — Indicators: standards/fmsg-006-indicators.md
6768
- White paper: white-paper.md
6869
- Implementations: IMPLEMENTATIONS.md
6970
- Background: show-hn.md

standards/fmsg-006-indicators.md

Lines changed: 322 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,322 @@
1+
# FMSG-006 Indicators Standard
2+
3+
## Status
4+
5+
| Revision | Date | Summary |
6+
|----------|------------|---------------|
7+
| v0.1.0 | 2026-09-02 | Initial draft |
8+
9+
This standard defines indicators: short-lived, unreliable signals such as
10+
"composing a reply" that a participant of a message shows to the message's
11+
other participants. Indicators travel host to host as UDP datagrams, are never
12+
acknowledged, and are never stored.
13+
14+
## Requirements Language
15+
16+
The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHALL**, **SHALL NOT**,
17+
**SHOULD**, **SHOULD NOT**, **RECOMMENDED**, **NOT RECOMMENDED**, **MAY**, and
18+
**OPTIONAL** in this document are to be interpreted as described in BCP 14
19+
([RFC 2119](https://www.rfc-editor.org/rfc/rfc2119) and
20+
[RFC 8174](https://www.rfc-editor.org/rfc/rfc8174)) when, and only when, they
21+
appear in all capitals.
22+
23+
## Scope
24+
25+
This standard defines:
26+
27+
- the indicator datagram and its UDP binding;
28+
- how the indicating participant is identified without placing an address on
29+
the wire;
30+
- the verification a receiving host performs before accepting a datagram;
31+
- the sending host's fan-out and rate limits;
32+
- how a participant's indicator state is derived and when it expires; and
33+
- a recommended vocabulary of indicator emoji.
34+
35+
It does not change the fmsg host-to-host protocol or the message format, does
36+
not define a client API, and does not require a host to implement it. A host
37+
that does not implement this standard is unaffected: it does not listen, and
38+
datagrams sent to it are lost, which is the defined outcome. Client API
39+
exposure of indicators is expected in a future revision of
40+
[FMSG-003](fmsg-003-webapi.md).
41+
42+
## Normative References
43+
44+
- [fmsg Specification](../SPECIFICATION.md) v0.6.0 or later
45+
- [FMSG-001 TCP+TLS Transport and Binding Standard](fmsg-001-transport-and-binding.md)
46+
- [FMSG-005 Reactions Standard](fmsg-005-reactions.md)
47+
- [RFC 768: User Datagram Protocol](https://www.rfc-editor.org/rfc/rfc768)
48+
- [RFC 2827 (BCP 38): Network Ingress Filtering](https://www.rfc-editor.org/rfc/rfc2827)
49+
- [UTS #51: Unicode Emoji](https://www.unicode.org/reports/tr51/)
50+
51+
## Terminology
52+
53+
**subject** is the message an indicator refers to via its message hash. It may
54+
be a thread's first message, a reply, or an add-to batch message.
55+
56+
**indicating participant** is the participant of the subject whose state the
57+
indicator conveys.
58+
59+
**indicator state** is the emoji, if any, that an indicating participant
60+
currently shows on a subject, determined per
61+
[Indicator State](#indicator-state).
62+
63+
**participant index** is the position of an address among the subject's wire
64+
fields, defined in [Participant Index](#participant-index).
65+
66+
Other terms have the meanings given in the fmsg Specification.
67+
68+
## Introduction
69+
70+
This section is non-normative.
71+
72+
Messaging clients show that someone is composing a reply, that an agent is
73+
working on one, or that a file is being attached. These signals are useful for
74+
a few seconds and worthless afterwards. Carrying them as fmsg messages would
75+
give them delivery guarantees, storage, and hashes they do not need and would
76+
fill every host's store with noise. Indicators are instead sent as single UDP
77+
datagrams: fire and forget, verified enough to prevent forgery, fanned out to
78+
whoever is connected at that moment, and then discarded.
79+
80+
An indicator is the ephemeral twin of a reaction. [FMSG-005](fmsg-005-reactions.md)
81+
defines a participant's durable state on a message as a single emoji, empty to
82+
clear, latest wins. An indicator is the same shape with a lifetime: a single
83+
emoji that is a participant's transient state on a message, expiring unless
84+
refreshed. A client that recognises ✍️ shows the familiar composing bubble; one
85+
that does not still shows the emoji beside the participant's name. New kinds of
86+
indicator therefore need no change to this standard.
87+
88+
No address appears in a datagram. The indicating participant is named by their
89+
position among the subject's participants, which only a host holding the
90+
subject can resolve. The subject's hash is likewise meaningful only to hosts
91+
that hold it. A datagram observed on the network reveals which two hosts are
92+
exchanging one and nothing more.
93+
94+
## Datagram
95+
96+
All integers are little-endian. Fields are read sequentially.
97+
98+
| # | Field | Type | Notes |
99+
|---|-------|------|-------|
100+
| 1 | version | uint8 | 1 for this revision. |
101+
| 2 | hash | 32 bytes | Message hash of the subject, per the Specification's Computing Message Hash. |
102+
| 3 | index | uint16 | Participant index of the indicating participant, see [Participant Index](#participant-index). |
103+
| 4 | ttl | uint8 | Seconds the state remains valid unless refreshed. 0 clears the state immediately. |
104+
| 5 | data | uint8 length + UTF-8 | The indicator emoji, or empty to clear. Length MUST NOT exceed 64. |
105+
106+
A datagram is exactly these fields; any trailing bytes make it invalid. A
107+
receiving host MUST silently drop a datagram it cannot parse, whose version it
108+
does not support, whose data length exceeds 64, or whose non-empty data is not
109+
a single emoji per [Emoji](#emoji).
110+
111+
An empty data field with any _ttl_ clears the indicating participant's state.
112+
A _ttl_ of 0 with non-empty data is treated the same as empty data: the state
113+
is cleared.
114+
115+
### Emoji
116+
117+
Non-empty _data_ MUST be the UTF-8 encoding of exactly one element of the
118+
Unicode `RGI_Emoji` set defined by UTS #51, subject to the same rules and the
119+
same leniency as FMSG-005 [Emoji](fmsg-005-reactions.md#emoji). Implementations
120+
compare emoji as exact byte strings; ✍ and ✍️ are different indicators.
121+
122+
### Participant Index
123+
124+
The participants of the subject are the addresses in its wire fields, in
125+
field order:
126+
127+
1. _from_;
128+
2. each address in _to_, in order;
129+
3. if the subject has _has add to_ set, _add to from_;
130+
4. if the subject has _has add to_ set, each address in _add to_, in order.
131+
132+
The participant index is the zero-based position in that sequence: _from_ is
133+
0, the first _to_ address is 1, and so on. Because _add to from_ repeats an
134+
address already in _from_ or _to_, and an address MAY appear in both _to_ and
135+
_add to_, one address can have several indices. They are equivalent; a sender
136+
SHOULD use the lowest. A receiving host MUST drop a datagram whose index is
137+
out of range for the subject.
138+
139+
A host holds every recipient of a stored message exactly as transmitted (the
140+
Specification's Verifying Message Stored), so any host holding the subject can
141+
resolve an index. A host that does not hold the subject cannot, and drops the
142+
datagram.
143+
144+
## UDP Binding
145+
146+
Indicators are sent as UDP datagrams to port 4930, the same port number
147+
FMSG-001 assigns to TCP. A host implementing this standard MUST listen on UDP
148+
port 4930 on every address in its `fmsg.<domain>` A and AAAA record set, and
149+
MUST send indicators from an address in that set, because receivers verify the
150+
source address against it.
151+
152+
The Sending Host resolves `fmsg.<domain>` per the Specification's Domain
153+
Resolution and sends one datagram to one resolved address. It MAY alternate
154+
between addresses on successive datagrams. Nothing is ever sent in reply. A
155+
sending host MUST ignore ICMP errors beyond using them to pause sending to
156+
that host.
157+
158+
Datagrams are not encrypted. Implementations MUST NOT place anything other
159+
than the fields above in a datagram.
160+
161+
## Sending
162+
163+
A participant's client tells its host that the participant has a state on a
164+
subject. The host is the Sending Host only when the indicating participant's
165+
address belongs to its domain; a host MUST NOT send an indicator for an address
166+
it does not host.
167+
168+
The Sending Host MUST send the datagram to every unique participant domain of
169+
the subject other than its own domain. The subject's participant domains are
170+
the domains of every address in the sequence defined by
171+
[Participant Index](#participant-index). Participants on the Sending Host's own
172+
domain are informed through the host's client API directly, never over the
173+
wire.
174+
175+
### Refresh and Clear
176+
177+
A client SHOULD refresh a continuing state before its _ttl_ lapses, and SHOULD
178+
send a clearing datagram when the state ends, for example when the composed
179+
reply is sent or discarded. Both are best effort: a receiving host expires the
180+
state on its own when no refresh arrives.
181+
182+
### Rate Limits
183+
184+
Indicators are cheap to send and cheap to drop, so limits are enforced on the
185+
sending side, per receiving host, before anything reaches the network. A
186+
Sending Host MUST:
187+
188+
- send at most one datagram per (subject, participant index) every
189+
INDICATOR_MIN_INTERVAL seconds, coalescing more frequent state changes into
190+
the latest; and
191+
- send at most INDICATOR_HOST_RATE datagrams per second to any one receiving
192+
domain, dropping the excess.
193+
194+
| Variable | Description | Recommended |
195+
|----------|-------------|-------------|
196+
| INDICATOR_MIN_INTERVAL | Minimum seconds between datagrams for one participant on one subject. | 2 |
197+
| INDICATOR_HOST_RATE | Maximum datagrams per second to one receiving domain. | 20 |
198+
| INDICATOR_TTL_MAX | Longest _ttl_ a host accepts; larger values are clamped. | 120 |
199+
200+
A clearing datagram (empty data) SHOULD be exempt from INDICATOR_MIN_INTERVAL
201+
so that an ended state clears promptly.
202+
203+
## Receiving
204+
205+
A Receiving Host performs the following steps for each datagram and MUST drop
206+
the datagram silently at the first step that fails. Steps are ordered so the
207+
cheapest checks run first.
208+
209+
1. Enforce a per-source-address rate limit; drop the excess.
210+
2. Parse the datagram per [Datagram](#datagram).
211+
3. Look up the subject by _hash_ among the messages the host has stored,
212+
including add-to batch messages by batch hash (the Specification's
213+
Verifying Message Stored). Not found: drop.
214+
4. Resolve _index_ per [Participant Index](#participant-index). Out of range:
215+
drop.
216+
5. Resolve `fmsg.<domain>` for the domain of the address at _index_, per the
217+
Specification's Domain Resolution, and verify the datagram's source address
218+
is in the resolved set. DNS results SHOULD be cached. Not in set: drop.
219+
6. Clamp _ttl_ to INDICATOR_TTL_MAX.
220+
7. Deliver the indicator to every participant of the subject hosted on this
221+
domain other than the indicating participant, through the host's client
222+
API, and then discard it.
223+
224+
A Receiving Host MUST NOT store an indicator beyond what delivery to currently
225+
connected clients requires, MUST NOT send anything in response, and MUST NOT
226+
generate a push notification or any other durable notification from an
227+
indicator.
228+
229+
Because the hash is known only to hosts holding the subject and the source
230+
address must match the indicating participant's domain, forging an indicator
231+
requires both possession of the message and a spoofed source address that
232+
survives ingress filtering (BCP 38). That is the level of assurance an
233+
unacknowledged, unencrypted, disposable signal warrants, and no more.
234+
235+
## Indicator State
236+
237+
An indicating participant has at most one state on a subject. The most
238+
recently received datagram from that participant on that subject determines
239+
it: its _data_ is the state, empty means none, and the state expires _ttl_
240+
seconds after receipt unless a later datagram replaces it. Datagrams may be
241+
lost or reordered; a client displays whatever the latest received datagram
242+
says and relies on expiry to correct anything stale.
243+
244+
State is held wherever it is displayed, normally in the client. Hosts need not
245+
track it.
246+
247+
## Recommended Indicators
248+
249+
This section is non-normative. Any single emoji is a valid indicator; the
250+
following meanings are recommended so that clients interoperate for the common
251+
cases and can style them specially. Suggested TTLs assume the sender refreshes
252+
at INDICATOR_MIN_INTERVAL.
253+
254+
| Emoji | Meaning | Suggested TTL |
255+
|-------|---------|---------------|
256+
| ✍️ | Composing a reply to the subject | 6 s |
257+
|| Processing: an agent is working on a reply | 60 s |
258+
| 🎙️ | Recording a voice reply | 10 s |
259+
| 📎 | Attaching a file to a reply | 15 s |
260+
| 👀 | Viewing the subject | 30 s |
261+
262+
Clients SHOULD show an indicator they do not recognise as the emoji itself
263+
beside the participant's address, and SHOULD render ✍️ as a composing
264+
indication. Clients SHOULD treat sending 👀 as opt-in, since it discloses
265+
attention rather than activity.
266+
267+
## Interoperability
268+
269+
| Peer | Behaviour |
270+
|------|-----------|
271+
| Host implementing this standard | Verifies and fans out to its connected participants. |
272+
| Host not implementing this standard | Does not listen on UDP 4930; datagrams are discarded by the network stack. The sender neither knows nor needs to. |
273+
| Host holding the subject but not the indicating participant's message hash form | Cannot occur: a host that holds the subject holds its hash. |
274+
275+
Indicators require no change to the Specification or to FMSG-001. Firewalls
276+
in front of a host implementing this standard need UDP 4930 open inbound on
277+
the addresses in `fmsg.<domain>`, and outbound from them.
278+
279+
## Security Considerations
280+
281+
- **Forgery:** mitigated by the hash-as-capability and source-address checks
282+
in [Receiving](#receiving). A host on the same domain as a participant could
283+
forge that participant's indicators; it could also forge their messages, so
284+
this adds no new trust.
285+
- **Amplification and reflection:** none. A host never responds to a datagram,
286+
never resolves DNS before the cheaper hash lookup fails, and rate-limits per
287+
source address.
288+
- **Disclosure:** the wire carries a hash, an index and an emoji. An observer
289+
learns that two hosts exchanged an indicator, which the TCP connection for
290+
the eventual reply would reveal anyway. A host that later obtains the
291+
subject could correlate captured datagrams with it; the emoji is the only
292+
content exposed, and senders SHOULD NOT convey anything sensitive through
293+
their choice of it.
294+
- **Presence leakage:** 👀 and similar states disclose attention. Clients
295+
SHOULD require the user to opt in to sending them.
296+
- **Resource use:** the per-datagram work before the hash lookup is parsing
297+
only; the lookup is one indexed query. Hosts SHOULD bound the number of
298+
indicators fanned out per second per client.
299+
300+
## Examples
301+
302+
This section is non-normative.
303+
304+
Alice at `example.com` sent a message to Bob at `example.edu` and Carol at
305+
`example.org`. Bob starts composing a reply. His host sends, to
306+
`fmsg.example.com` and `fmsg.example.org`, a datagram whose fields are:
307+
308+
```
309+
version 1
310+
hash <message hash of Alice's message>
311+
index 1 (Bob is the first address in to)
312+
ttl 6
313+
data "✍️" (length 6, bytes E2 9C 8D EF B8 8F)
314+
```
315+
316+
Alice's host looks up the hash, finds the message, resolves index 1 to Bob,
317+
resolves `fmsg.example.edu`, confirms the source address, and forwards
318+
"Bob ✍️" to Alice's connected clients. Bob's host repeats the datagram every
319+
two seconds while he types. When he sends the reply, his host sends the same
320+
datagram with an empty data field and Alice's client removes the indication;
321+
had that datagram been lost, the indication would have lapsed six seconds
322+
after the last refresh.

0 commit comments

Comments
 (0)