Weft Namespace 0.1 (Draft)
Weft (Web-External Federated Threads) builds on Dave Winer's textcasting.
Oct 12, 2026 · @Henry · revised Nov 9, 2026
Status and purpose
This is a first working draft of an RSS 2.0 namespace for Weft, which builds on Dave Winer's textcasting: two-way, Markdown-based publishing where every post lives in its author's own feed. It defines how a post marks itself as a reply, how threads are reassembled, how likes are counted, and how feeds move or get edited.
The spec only adds what RSS 2.0 lacks. Anything RSS 2.0, Winer's Source namespace or Webmention already covers is reused, not redefined. Elements are marked required, recommended or optional.
Design principles
- Titles are optional but allowed. A post may be a one-line note with no
<title>, or a long titled essay. Apps must handle both equally well. RSS 2.0 already permits this: an item needs at least one of<title>or<description>. In textcasting,<description>is always present, so<title>is truly optional. - Markdown is the source of truth. Authors write Markdown. The feed carries both rendered HTML (for any reader) and the original Markdown (for Weft apps).
- No length limit. There are no threads of numbered posts; a long thought is one post.
- Posts are editable. Edits update the item in place and notify anyone the post replied to.
- You own your words. Every post, reply and like lives in its author's own feed at its author's own URL.
- Public by design. Everything in a Weft feed is public. Private posts and messages are out of scope.
- Degrade gracefully. A plain RSS reader that ignores this namespace still shows every post correctly.
Namespace declaration
The namespace prefix is weft. Parsers match on the URI, not the prefix.
<rss version="2.0"
xmlns:weft="https://weftprotocol.org/ns/weft/0.1"
xmlns:source="http://source.scripting.com/"
xmlns:atom="http://www.w3.org/2005/Atom">
Reused namespaces:
| Prefix | Used for |
|---|---|
source: |
source:markdown, the post's original Markdown; source:inReplyTo and source:comments, written next to weft:inReplyTo and weft:replies (Dave Winer's Source namespace) |
atom: |
atom:link rel="self" and rel="hub" for WebSub pings |
| RSS 2.0 core | <cloud> for rssCloud pings, <category> for keywords, <enclosure> for media, and the channel <image> (its <url>) as the author's avatar |
Channel elements
The author's profile uses standard RSS 2.0: <title> is the author or publication name, <description> the bio, <image> the avatar, <link> the homepage. The namespace adds:
| Element | Status | Meaning |
|---|---|---|
<weft:webmention> |
Required | URL of the author's Webmention endpoint, where replies and likes are sent |
<weft:following> |
Recommended | URL of an OPML file listing the feeds this author follows; the public follow graph used for trust weighting |
<weft:likesFeed> |
Recommended | URL of the author's separate likes feed |
For update pings, publish either an RSS 2.0 <cloud> element (rssCloud) or <atom:link rel="hub"> (WebSub). At least one is recommended.
Item elements
Every post, reply and like is an RSS 2.0 <item>.
Core elements
| Element | Status | Rule |
|---|---|---|
<guid isPermaLink="false"> |
Required | The post's identity: urn:uuid: plus a UUID (version 7 recommended) made once when the post is created. It is not a URL and never changes, so the post stays the same post if its author moves to another host. |
<link> |
Required | The post's HTML permalink: the author's host plus a slug, such as https://alice.example/posts/the-reader-half.k3x9. This URL is the post's address in replies (weft:inReplyTo, weft:root), likes and Webmentions. It is the only URL that serves the post. The slug is built from the title or the first sentence of a note plus a short random suffix (the words may be ones the author chose when making the post: lowercase letters, digits and hyphens; the suffix is always added), is unique in the author's namespace, and never changes after publishing, even if the post is edited. A deleted post keeps its slug reserved. |
<description> |
Required | Rendered HTML of the post. Pictures and links in it are full addresses (https://alice.example/media/<hash>.jpg), not paths, since a feed is read away from the site. The same holds for <source:markdown>. An author's own pictures may be stored on the author's server and referred to there; a reader may show them from that address. |
<title> |
Optional | Include only if the author gave one. Apps must not invent titles from the first line. |
<source:markdown> |
Recommended | The original Markdown, unaltered. |
<source:inReplyTo> |
Optional | The address of the post this one answers, as text, with feedUrl when the parent's feed is known. Weft writes it with every weft:inReplyTo, for readers that know only the source namespace. A reader that finds no weft:inReplyTo uses it; an identifier that is not an address (isPermaLink="false") cannot be followed and is ignored. |
<source:comments> |
Optional | count and feedUrl of the item's replies feed. Written with every weft:replies. Weft does not read it. |
<pubDate> |
Required | First publication time. |
<weft:canonical href="…"/> |
Optional | Present on a repost: the address where the content first appeared. An original has none. A reader shows "Originally published at …" and may prefer the original when it shows the same text twice. href must be an http or https address. Atom readers can use <link rel="canonical"> for the same thing. |
<weft:updated> |
Optional | Time of the latest edit, RFC 822 format like pubDate. Present only on edited items. |
<category> |
Optional | Keywords: one element per keyword or phrase, as typed by the author (not Markdown, not hashtags). Writers should publish at most 10 of up to 50 characters, with no commas, and readers should apply the same limits to what other hosts publish. A reader decides how to show them; Weft shows them as chips under the post, and serves a topic feed at feed.xml?category=<keyword> (the posts with that keyword, matched ignoring case, paged like the main feed). The query is a convenience, not part of the format: a reader follows the feed address it is given. |
<enclosure> |
Optional | Attached media, as in podcasting. |
Reply elements
<weft:inReplyTo> marks the item as a reply. It is the child's upward link, and it survives even if the parent rejects or deletes the reply.
<weft:inReplyTo href="https://alice.example/posts/rss-plus-links-already-gives-you.w3kp"
feed="https://alice.example/feed.xml"/>
href(required): the parent's permalink, equal to the parent's<link>.feed(optional): the parent's feed URL. A Weft reader does not need it: it reads the parent from its permalink (see Post representations). It matters when the parent is not a textcast post, since a plain RSS item can only be found through its feed. Required in that case (see Replying to any RSS 2.0 item).
<weft:root> names the post that started the thread, like email's References header. Any reply can be placed in its conversation even if middle posts disappear.
<weft:root href="https://alice.example/posts/rss-plus-links-already-gives-you.w3kp"
feed="https://alice.example/feed.xml"/>
- Required on every reply. Omitted on the root post itself.
- When replying directly to the root,
weft:rootandweft:inReplyTopoint to the same URL.
<weft:context> carries a short snippet of the parent, so the reply still makes sense if the parent goes offline.
<weft:context href="https://alice.example/posts/rss-plus-links-already-gives-you.w3kp"
retrieved="Mon, 12 Oct 2026 22:30:00 -0400">
RSS plus links already gives you a social web.
</weft:context>
- Recommended. In its minimal form it is an empty element carrying only
href(andretrieved): the URL of the parent. A text snippet is optional: plain text, kept brief, no more than about 300 characters. - Optional attributes
title(up to 200 characters),description(up to 300, the same as the text snippet) andimage(an absolute http or https address) say what the parent page looked like when it wasretrieved: single-line plain text, so a reader can show a link card for a page that publishes no feed and cannot be looked up. Readers apply the same limits and ignore animagethat is not an http(s) address. Weft writes these when a reply is started from the bookmarklet and shows them as a card. Readers that do not know them still have thehref.
<weft:context href="https://blog.example/posts/web-of-links" retrieved="Mon, 12 Oct 2026 22:30:00 -0400"
title="The web of links" description="Why links still beat feeds." image="https://blog.example/cover.jpg"/>
- It is a quote for context, not a copy. Apps must not display it as the parent post when the parent is reachable.
- A reply to a plain RSS 2.0 item or any other page treats the parent as if it were already deleted: the link (and the snippet, when present) are all a reader can rely on. The parent may be any URL, textcast or not.
- Optional
originattribute:author(written by the replier, the default) orgenerated(summarized by an app, for example with AI). Readers may label generated context, and authors should review it before publishing.
Like elements
<weft:likeOf href="…"/> marks the item as a like of the post at href.
href(required): the permalink of the post liked. It may be any page, Weft or not.title,description,image(optional): what the liked page looked like, with the same limits and cleaning as onweft:context. They let a reader name a page that publishes no feed ("Bob liked “The web of links”") instead of showing a bare address.
A like is a single positive signal, with no emoji. It names who liked with <weft:feed href="…">Display name</weft:feed>, the same element a replies feed uses, because the likes feed's own title is something like "Bob likes". There are no dislikes. Anything a person wants to say beyond approval is a reply, and a reply may be nothing but an emoji; readers may show such replies compactly. A like item has no <title> and an empty <description>. A user has at most one active like per post. Withdrawing it deletes the item (the permalink then returns 410). Likes are published in the likes feed, not the main feed.
Reply and like count elements
<weft:replies href="…" count="7"/>(recommended): the URL of this post's replies feed and the number of approved replies.<weft:likes count="12" href="…"/>(optional): the number of verified likes received.href(optional) is the post's liked-by feed,<permalink>/likes.xml: an RSS 2.0 feed with one item per like received, each with<weft:likeOf href="…"/>the permalink and<weft:feed href="…">Name</weft:feed>the liker. A reader that follows only some of the likers uses it to show who the others are and offer to follow them. The likers already publish these likes in their own likes feeds. It is a cache; the likes themselves live in the likers' feeds and can be recounted.
Replies feed
Each post may publish a replies feed: an RSS 2.0 feed of the replies its author has approved. It is the parent's downward link to its children, and it lets readers rebuild a thread one level at a time.
Channel elements of a replies feed:
<weft:repliesTo href="…"/>(required): the permalink of the post these replies belong to.<weft:depth>(optional, default 1): how many levels of descendants this feed caches.<lastBuildDate>(required): when the cache was last refreshed.
Each <item> is a reference to a reply, not a full copy:
<guid>and<link>: the reply's permalink. Theguidmay be that permalink withisPermaLink="true"; theurn:uuid:identity stays in the author's own feed.<description>: a short excerpt for display.<author>or<weft:feed href="…">Display name</weft:feed>: who wrote it, by feed URL. The element text is an optional display name.<weft:inReplyTo>: the reply's parent. Required whenweft:depthis greater than 1, so deeper items can be placed in the tree.<weft:replies href="…" count="…"/>(optional): where the reply's own replies are published and how many the feed's host has cached, so a reader can show a closed branch's size without opening it. Items one level down need not carry it.<weft:likes count="…"/>: the reply's like count, if known.
Cached subtrees
A replies feed fills its second level from the replies feeds of its own replies. Reference implementation: it caches one level below each approved reply (weft:depth of 2) and keeps it fresh with the bubble-up rule below, so the cache only changes when a real change was reported. Deeper levels are found by reading the replies feed of a reply, one level at a time.
With weft:depth of 2 or 3, one fetch returns most of a conversation. Readers rebuild the tree from each item's weft:inReplyTo. A post's cache goes stale when a deeper reply arrives; the update rule under Processing rules keeps it fresh.
weft:depth is a statement by the server that publishes the feed about what its feed holds: how many levels it chose to cache. It is a setting of that server, not something the protocol fixes; the reference implementation uses 2. A reader takes the feed as it is and finds anything deeper by reading the replies feed of a reply (weft:replies). A reader also keeps its own limit on how many levels and items it will accept from one feed, so that a feed cannot make it do unbounded work; that limit is a setting of the reader and is not published.
Curated and raw views
The replies feed is the curated view: only approved replies. The raw view is every item anywhere whose weft:inReplyTo or weft:root points into the thread, as an index sees it. Apps should let readers switch between them.
Likes feed
Likes go in a separate feed, linked from the main feed's weft:likesFeed, so they don't bury posts for subscribers. Each like is an item with a guid permalink of its own (an HTML page) and a weft:likeOf. Each like is also sent to the liked post's Webmention endpoint.
A person's feeds share one identity: the channel <link> (homepage) of the main feed and the likes feed are the same. Receivers use it to recognise that a like comes from someone they follow, even though they follow the main feed.
Likes are public. There is no dislike, and nothing private is sent to a post owner; a reader may let its user hide a post locally, which is not published.
Post representations
A thread is built from posts, not from feeds. A feed lists an author's newest posts for subscribers, and an old post may no longer be in it. Every post therefore stands on its own at its permalink, which serves these representations by content negotiation (the URL is the same; Vary: Accept):
- HTML (the default, and for any
Acceptthat includestext/htmlor*/*): the page described below. Accept: application/rss+xml: the post as a one-item RSS 2.0 feed, with everyweft:element a feed item has:weft:inReplyTo,weft:root,weft:replies(where its approved replies are published),weft:likes. This gives a reader the post, its parent and the way to its replies in one request, whatever the age of the post.Accept: text/markdown: the post's markdown source.
A deleted post answers 410 Gone in every representation.
A reader that has a reply and wants its context needs only the permalinks: the reply's weft:inReplyTo for the parent, and weft:replies for the approved replies below it.
Paged feeds
A feed shows its newest posts (100 in this implementation) and links to older ones as in RFC 5005: <atom:link rel="next"> to the next page of older items, with rel="previous" and rel="first" on later pages. Readers that follow a feed for the first time should read the next links (on the same host) to get the history, and afterwards poll only the first page. A cached item newer than the oldest item on the first page that has disappeared from it was deleted; older cached items are kept.
Permalink page requirements
Every item's <link> must resolve to an HTML page. Webmention verification, search engines and people without feed readers all depend on it. The page must include:
<link rel="alternate" type="application/rss+xml" href="…">pointing to the author's feed.<link rel="webmention" href="…">pointing to the author's endpoint.- For a reply or like, a visible
<a href>to the parent or liked post. Webmention receivers check for this link. <link rel="alternate" type="application/rss+xml" href="…">to the post's own URL (the one-item representation),<link rel="in-reply-to" href="…">to the parent for a reply, and<link rel="replies" type="application/rss+xml" href="…">to the post's replies feed, so a reader that starts from the page can find the thread. The author's feed link comes first.
Recommended for IndieWeb compatibility: microformats2 markup (h-entry, with u-in-reply-to on replies and u-like-of on likes).
Processing rules
Publishing a post
- Render the Markdown to HTML and write the permalink page.
- Add the item to the feed, with
source:markdown, andweft:inReplyTo,weft:rootandweft:contextif it is a reply. - If it is a reply, send a Webmention to the parent. Sending one to the root as well is optional.
- Ping subscribers and indexes through rssCloud or WebSub.
Receiving a Webmention
- Fetch the source URL and confirm it links to the target post. If not, reject it.
- Read the source's feed item. If it has
weft:inReplyTomatching the target, treat it as a reply; if it hasweft:likeOfmatching the target, treat it as a like; otherwise it is a plain mention and is ignored. - Apply moderation: auto-approve senders the author follows (from
weft:following), queue others, drop blocked domains. Likes are recorded once verified and all of them count. A receiver may also use the optional Webmention Vouch extension as a signal for senders it does not follow: avouchlink to a page, from a source the receiver trusts, that links to the sender. This spec does not require it, and a sender that sends none is treated like any other stranger. - On approval, add the reply to the post's replies feed and update the
weft:repliesandweft:likescounts. - Bubble up. If this post is itself a reply, re-send its own Webmention to its parent. Under the Webmention spec, a re-sent Webmention means the source changed, so the parent refreshes its cached subtree. Repeat up to the root, or stop at the parent's
weft:depth.
Editing a post
Update the item in place, set weft:updated, and re-send Webmentions to every post it replied to or liked. The guid and the link never change.
Deleting a post
The permalink returns HTTP 410 Gone, ideally with a small tombstone page. Remove the item from the feed and re-send Webmentions; receivers seeing 410 remove the reply or like. Replies to the deleted post keep their own place in the tree.
Moving a feed
A feed moves when its address changes: the author moves to another server, or starts using a domain of their own. The author's guids do not change (a Weft guid is urn:uuid:…, not a URL), so the posts are the same posts at a new address. The signal is the one HTTP already has; Weft adds no element for it.
The old address. Answer every request, the feed included, with a permanent redirect to the same path at the new address: 301 for GET and HEAD, 308 for any other method (the only permanent redirect that keeps the method and the body, so a Webmention sent to the old endpoint still arrives). Keep doing so for at least 90 days; there is no reason to stop, and the name should stay reserved so nobody else can take it and receive the replies and likes still sent to the old permalinks. A short Cache-Control: max-age lets a mistake be undone.
Readers. When a feed is fetched and the whole chain of redirects to it was permanent, and what it leads to is a feed, the reader replaces the subscription with the final address and drops the posts it cached for the old one, so each post is seen once. A temporary redirect (302, 307), or a permanent one to something that is not a feed, changes nothing. A reader follows at most a few moves in one refresh, so feeds that point at each other cannot loop it.
Replies and likes already published refer to the old permalinks; they keep working through the redirect.
Orphaned replies
Readers handle a reply whose parent is unavailable as follows:
| Parent state | How readers show the reply |
|---|---|
| Rejected (parent online, reply not in its replies feed) | As an unapproved branch, visible in the raw view, collapsed or hidden in the curated view |
| Offline (no response) | Under its weft:root, with its weft:context snippet standing in for the parent |
| Deleted (410 Gone) | Under a tombstone reading "original post removed"; showing weft:context is the app's choice |
In every case, replies further down the branch stay attached to their own parents.
Replying to any RSS 2.0 item
A Weft app can reply to an item in any RSS 2.0 feed, not just Weft feeds. The parent needs no weft: elements and its author needs no Weft app. The reply is built as if the parent were already deleted:
- Point up.
weft:inReplyTo hrefis the item's<link>, or itsguidwhenisPermaLinkis true and there is no link. Thefeedattribute is required here, since nothing else identifies the source feed. - Set the root. The non-Weft item has no thread ancestry, so it is the root:
weft:rootequalsweft:inReplyTo. - Carry context.
weft:contextis required. The app can draft it from the item's title and description, by excerpting or summarizing with AI, markedorigin="generated". The author can edit it before publishing. - Notify if possible. If the item's page advertises a Webmention endpoint, send one. If not, the parent's author is never notified; the reply is still found through indexes and by readers following both feeds.
This makes the whole existing RSS web, including blogs, news sites and podcasts, something you can reply to on day one.
Examples
Alice's feed, with an untitled root post
<rss version="2.0"
xmlns:weft="https://weftprotocol.org/ns/weft/0.1"
xmlns:source="http://source.scripting.com/">
<channel>
<title>Alice</title>
<link>https://alice.example/</link>
<description>Notes on the open web.</description>
<weft:webmention>https://alice.example/webmention</weft:webmention>
<weft:following>https://alice.example/following.opml</weft:following>
<weft:likesFeed>https://alice.example/likes.xml</weft:likesFeed>
<cloud domain="rpc.example.org" port="80" path="/rsscloud/pleaseNotify"
registerProcedure="" protocol="http-post"/>
<item>
<!-- No <title>: a short note -->
<guid isPermaLink="false">urn:uuid:0194f3a0-5c1e-7a52-9d0b-3c7a5e2f1b44</guid>
<link>https://alice.example/posts/rss-plus-links-already-gives-you.w3kp</link>
<description><p>RSS plus links already gives you a social web. Who's building the <em>reader</em> half?</p></description>
<source:markdown>RSS plus links already gives you a social web. Who's building the *reader* half?</source:markdown>
<pubDate>Mon, 12 Oct 2026 21:00:00 -0400</pubDate>
<category>textcasting</category>
<category>open web</category>
<weft:replies href="https://alice.example/posts/rss-plus-links-already-gives-you.w3kp/replies.xml" count="1"/>
<weft:likes count="1"/>
</item>
</channel>
</rss>
Bob's reply, with a title
<item>
<title>The reader half is the hard part</title>
<guid isPermaLink="false">urn:uuid:0194f3b8-90aa-7c13-8e21-6d4f0a9b7c25</guid>
<link>https://bob.example/posts/reader-half</link>
<description><p>Search and threads are what's missing. Here's a sketch…</p></description>
<source:markdown>Search and threads are what's missing. Here's a sketch…</source:markdown>
<pubDate>Mon, 12 Oct 2026 22:15:00 -0400</pubDate>
<weft:updated>Mon, 12 Oct 2026 22:40:00 -0400</weft:updated>
<weft:inReplyTo href="https://alice.example/posts/rss-plus-links-already-gives-you.w3kp"
feed="https://alice.example/feed.xml"/>
<weft:root href="https://alice.example/posts/rss-plus-links-already-gives-you.w3kp"
feed="https://alice.example/feed.xml"/>
<weft:context href="https://alice.example/posts/rss-plus-links-already-gives-you.w3kp"
retrieved="Mon, 12 Oct 2026 22:10:00 -0400">RSS plus links already gives you a social web.</weft:context>
</item>
Carol's like, in her likes feed
<item>
<guid isPermaLink="true">https://carol.example/likes/2026-10-12-1</guid>
<link>https://carol.example/likes/2026-10-12-1</link>
<description/>
<pubDate>Mon, 12 Oct 2026 22:20:00 -0400</pubDate>
<weft:likeOf href="https://alice.example/posts/rss-plus-links-already-gives-you.w3kp"/>
</item>
Alice's replies feed for the post, caching two levels
<rss version="2.0" xmlns:weft="https://weftprotocol.org/ns/weft/0.1">
<channel>
<title>Replies to: RSS plus links already gives you a social web</title>
<link>https://alice.example/posts/rss-plus-links-already-gives-you.w3kp</link>
<description>Approved replies</description>
<lastBuildDate>Mon, 12 Oct 2026 22:45:00 -0400</lastBuildDate>
<weft:repliesTo href="https://alice.example/posts/rss-plus-links-already-gives-you.w3kp"/>
<weft:depth>2</weft:depth>
<item>
<title>The reader half is the hard part</title>
<guid isPermaLink="false">urn:uuid:0194f3b8-90aa-7c13-8e21-6d4f0a9b7c25</guid>
<link>https://bob.example/posts/reader-half</link>
<description>Search and threads are what's missing.</description>
<weft:feed href="https://bob.example/feed.xml"/>
<weft:inReplyTo href="https://alice.example/posts/rss-plus-links-already-gives-you.w3kp"/>
<weft:likes count="3"/>
</item>
<item>
<!-- Depth 2: a reply to Bob, untitled -->
<link>https://dana.example/n/8812</link>
<guid isPermaLink="true">https://dana.example/n/8812</guid>
<description>Agreed. Webmention covers notification.</description>
<weft:feed href="https://dana.example/feed.xml"/>
<weft:inReplyTo href="https://bob.example/posts/reader-half"/>
</item>
</channel>
</rss>
Compatibility
- Plain RSS readers ignore the
weft:elements and show every post normally, titled or not. - IndieWeb sites interoperate through Webmention and microformats on permalink pages.
- Atom Threading (RFC 4685) users can map
thr:in-reply-totoweft:inReplyTo.weft:adds the root pointer and context snippet that Atom Threading lacks. - Search uses OpenSearch with RSS results; it is outside this namespace.
Out of scope
- Private posts and direct messages.
- Search and index protocols.
- Moderation policy. The spec carries signals; each app and index decides what to do with them. That includes whom a receiver trusts and how it weighs likes.
Deferred
- Whether replies also send a Webmention to the root, so root authors see all thread activity. Held off for now (Nov 9, 2026); a reply sends one to its parent only, and the bubble-up rule carries the news upward.
Open questions
None at present.