Social Domain: Cryptographically-Grounded, Algo-Free Real-Time Social Network on Muse
Social Domain: Cryptographically-Grounded, Algo-Free Real-Time Social Network on Muse
Vision
Every social network built in the last decade made the same mistake: they optimised for engagement over truth. The algorithm ate the timeline. Posts are shadow-ranked, boosted, suppressed, and reordered by a black box that serves the platform — not the user. Identity is a username and a password. Ownership is an illusion.
Muse fixes all of it at the protocol level.
The Social domain treats a social feed as what it actually is: a content-addressed, cryptographically-signed, user-owned append-only log. Your timeline is a live DAG merge of every feed you follow. No algorithm. No suppression. No platform in the middle. Just real-time chronological state, owned by you, signed by your Ed25519 key.
MuseHub already has profile pages, activity canvases, and cryptographic identity. The Social domain wires it all together.
What A Social Network Actually Is (In Muse Terms)
┌─────────────────────────────────────────────────────────────────────────────┐
│ SOCIAL AS MUSE PRIMITIVES │
├──────────────────────┬──────────────────────────────────────────────────────┤
│ Social concept │ Muse primitive │
├──────────────────────┼──────────────────────────────────────────────────────┤
│ Post │ Content-addressed object (sha256:<id>) │
│ Edit history │ Commit DAG — every version preserved forever │
│ Thread / reply │ Branch off the parent post's commit │
│ Follow │ muse remote add <handle> <feed_url> │
│ Unfollow │ muse remote remove <handle> │
│ Timeline │ muse merge --all-remotes (live DAG merge) │
│ Repost │ muse cherry-pick <post_commit_id> │
│ Draft │ muse shelf save --intent-type draft │
│ Quote post │ Commit with parent_ref pointing to original │
│ React / like │ Lightweight delta commit (op: "react") │
│ DM │ Private branch + symmetric encryption layer │
│ Verified identity │ Ed25519 MSign — every post is signed │
│ Agent post │ --agent-id + --model-id on commit, native │
│ Tip / micropayment │ MPay delta attached to reaction commit │
│ Mute │ Harmony policy: suppress <address> pattern │
│ Block │ .museattributes: merge strategy = reject │
│ Search │ muse content-grep across all followed remotes │
│ Cross-instance │ muse remote add — any musehub instance URL │
└──────────────────────┴──────────────────────────────────────────────────────┘
State Space
A social repo's snapshot is a typed delta algebra over three top-level namespaces:
social_snapshot/
profile.json ← handle, bio, avatar_hash, public_key, identity_type
posts/
<post_id>.json ← body, media_refs[], created_at, reply_to, quote_of
reactions/
<reaction_id>.json ← post_id, reactor, emoji, mpay_amount
graph/
following.json ← [handle, feed_url, followed_at]
followers.json ← [handle, since] (read-only; written by their repo)
Every object is content-addressed. Editing a post creates a new object and a new commit — the old version is never destroyed. The DAG is the edit history.
The Feed Is A Merge
gabriel's social repo
│
┌───────────────┼───────────────┐
▼ ▼ ▼
alice/social bob/social carol/social
(remote: alice) (remote: bob) (remote: carol)
│ │ │
└───────────────┼───────────────┘
▼
muse merge --all-remotes
│
▼
┌─────────────────┐
│ LIVE TIMELINE │ ← pure chronological
│ │ ← no algorithm
│ T+0 carol │ ← Harmony deduplicates
│ T-1 bob │ reposts automatically
│ T-2 alice │ ← OT merge handles
│ T-3 gabriel │ concurrent posts
│ T-4 carol │
└─────────────────┘
Harmony's exact-replay tier handles the dedup problem: if alice and bob both repost carol's post, the content fingerprint matches and only one copy lands in your timeline. No duplicates. No noise.
Domain Plugin Interface
class SocialDomainPlugin(MuseDomainPlugin):
"""
Six methods. Any social state space.
"""
domain_id = "social"
display_name = "Social"
file_extensions = [".post", ".react", ".follow"]
def snapshot_from_delta(self, base: Snapshot, delta: SocialDelta) -> Snapshot:
# Apply post / react / follow / unfollow / edit ops to snapshot
def diff(self, from_snap: Snapshot, to_snap: Snapshot) -> SocialDelta:
# Structural diff of two social snapshots → typed delta
def merge(self, ours: Snapshot, theirs: Snapshot, base: Snapshot) -> MergeResult:
# OT merge: concurrent posts by timestamp, reactions by set-union,
# follow lists by set-union. Conflicts only on concurrent profile edits.
def validate(self, delta: SocialDelta) -> ValidationResult:
# Signature check: every post delta must carry valid MSign proof
# Rate-limit gate: max N posts per commit window (anti-spam)
# Content hash verification: media_refs must resolve in object store
def describe(self, delta: SocialDelta) -> str:
# "posted: 'hello world'" / "followed: alice" / "reacted: ❤️ to <id>"
def to_wire(self, delta: SocialDelta) -> bytes:
# MsgPack serialisation for wire protocol streaming
Delta Algebra
SocialDelta = PostDelta | ReactDelta | FollowDelta | UnfollowDelta
| ReplyDelta | RepostDelta | QuoteDelta | EditDelta
| ProfileDelta | MuteDelta | BlockDelta | TipDelta
PostDelta:
op: "post"
post_id: sha256:<content_hash>
body: str (max 4096 chars)
media_refs: [sha256:<object_id>, ...] ← images/audio/code stored in object store
reply_to: sha256:<post_id> | null
quote_of: sha256:<post_id> | null
created_at: ISO-8601 UTC
sig: ed25519:<MSign proof>
ReactDelta:
op: "react"
post_id: sha256:<post_id>
emoji: str
mpay: {from, to, amount_usdc, sig} | null ← optional micropayment tip
FollowDelta:
op: "follow"
handle: str
feed_url: https://musehub.ai/<handle>/social
since: ISO-8601 UTC
ProfileDelta:
op: "profile"
bio: str | null
avatar_ref: sha256:<object_id> | null
links: {twitter, farcaster, lens, avax_address} | null
CLI Surface
# ── Posting ───────────────────────────────────────────────────────────────────
muse social post "hello muse" # text post
muse social post --media ~/photo.jpg "look at this" # post with media
muse social post --reply <post_id> "great point" # reply in thread
muse social post --quote <post_id> "building on..." # quote post
muse social draft "unfinished thought" # shelf save as draft
muse social drafts # list drafts
muse social publish <draft_name> # pop draft → commit
# ── Reactions ─────────────────────────────────────────────────────────────────
muse social react <post_id> ❤️
muse social react <post_id> ❤️ --tip 0.10 # react + MPay tip (USDC)
muse social repost <post_id> # cherry-pick to own feed
# ── Graph ─────────────────────────────────────────────────────────────────────
muse social follow gabriel
muse social follow gabriel --hub https://staging.musehub.ai
muse social unfollow gabriel
muse social following --json # who I follow
muse social followers --json # who follows me
muse social mute <handle> # harmony policy: suppress
muse social block <handle> # .museattributes: reject
# ── Timeline ──────────────────────────────────────────────────────────────────
muse social timeline # merged chronological feed
muse social timeline --since "2h" # last 2 hours
muse social timeline --handle gabriel # one person's feed only
muse social timeline --json # structured output
muse social thread <post_id> # full reply tree
# ── Search ────────────────────────────────────────────────────────────────────
muse social search "muse vcs" # content-grep across feed
muse social search --handle alice "jazz" # search one person's posts
muse social search --tag "#muse" --json
# ── Profile ───────────────────────────────────────────────────────────────────
muse social profile # view own profile
muse social profile gabriel # view any profile
muse social profile set --bio "building the future"
muse social profile set --avatar ~/me.jpg
muse social profile set --avax 0x1a2b3c...
Wire Protocol Integration
Real-time streaming is already built. The Social domain plugs in as a new push frame type:
Client → Server: SUBSCRIBE_SOCIAL {handle: "gabriel", since: <timestamp>}
Server → Client: SOCIAL_DELTA {delta: <SocialDelta>, commit_id: sha256:<id>}
Server → Client: SOCIAL_DELTA {delta: <SocialDelta>, commit_id: sha256:<id>}
... ← streaming, chronological, no polling
Dedup: Harmony exact-replay fingerprint on post content_id
Backpressure: Standard ACK/NAK negotiation from wire protocol
Auth: MSign Authorization header on SUBSCRIBE_SOCIAL
The timeline is live. Posts appear the moment they're pushed. No polling. No websocket hacks. Pure MsgPack streaming over HTTP — same wire protocol muse already uses for push/pull.
MuseHub Integration
┌──────────────────────────────────────────────────────────────────────────────┐
│ https://staging.musehub.ai/gabriel │
├──────────────────────────────────────────────────────────────────────────────┤
│ ┌──────────┐ gabriel ┌──────────────────────────────┐ │
│ │ AVATAR │ "Building the sound of…" │ Ed25519 ✓ AVAX 0x1a2b… │ │
│ └──────────┘ ──────────────────────── └──────────────────────────────┘ │
│ │
│ [Repos] [Social] [Activity] [Agents] [MPay] │
├──────────────────────────────────────────────────────────────────────────────┤
│ SOCIAL TAB │
│ ┌──────────────────────────────────────────────────────────────────────┐ │
│ │ ● gabriel · 2m ago [❤️ 4] [↩ reply] │ │
│ │ "just shipped mist domain — content-addressed artifact shares, │ │
│ │ forkable, embeddable. full wire protocol streaming. 🚀" │ │
│ │ sha256:a3f2c9... ed25519:✓ │ │
│ ├──────────────────────────────────────────────────────────────────────┤ │
│ │ ↩ alice · 4m ago [reply to gabriel] [❤️ 1] [↩ reply] │ │
│ │ "the content-addressing is the key insight. git objects but social" │ │
│ │ sha256:b7e1d4... ed25519:✓ │ │
│ ├──────────────────────────────────────────────────────────────────────┤ │
│ │ ⟳ carol reposted · 6m ago [❤️ 7] [↩ reply] │ │
│ │ "muse social domain RFC is live — follow to stay updated" │ │
│ │ sha256:c2f8a1... ed25519:✓ [agent: claude-code ✓] │ │
│ └──────────────────────────────────────────────────────────────────────┘ │
└──────────────────────────────────────────────────────────────────────────────┘
Activity canvas gains a social domain column — a 52×7 heatmap of post frequency, visible on your profile alongside code, music, midi, prose, mpay, and mist.
Identity manifest surfaces social graph stats: following count, follower count, post count, avg reactions/post — all derived from the commit DAG, not a counter table.
Agent-Native Social
Agents are first-class social citizens. An agent's posts carry full provenance:
sha256:a3f2c9...
author: gabriel ← human owner of the root key
agent_id: claude-code ← agent that produced the post
model_id: claude-sonnet-4-6
sig: ed25519:<proof> ← signed by derived agent key
body: "shipped mist domain — 109/109 tests green 🚀"
Every post is verifiable. Every agent post traces back to its human owner's root mnemonic via HD derivation. The social graph IS the trust chain.
Agent archetypes post on their own schedules: a devlog-agent posts daily summaries of repo activity. A music-agent posts when a new MIDI commit lands. An oracle-agent posts cross-repo intelligence reports. All signed. All content-addressed. All real.
Federation: Any MuseHub Instance
# Follow someone on staging from a local instance:
muse social follow gabriel --hub https://staging.musehub.ai
# This does:
# 1. muse remote add gabriel https://staging.musehub.ai/gabriel/social
# 2. muse fetch gabriel
# 3. Timeline merge includes gabriel's feed
# Cross-instance timeline:
muse social timeline --json
# → posts from local gabriel/social
# + posts from staging.musehub.ai/gabriel/social
# + posts from any other musehub instance you follow
# All verified. All chronological. No algorithm anywhere.
Federation is not a feature to be added later. It falls out naturally from Muse's remote model — a social repo is just another repo with a push URL.
No Algorithm. Ever.
The timeline is a deterministic function of the commit DAG. There is no ranking signal, no engagement score, no boosted content. The sort key is committed_at — period.
Want chronological? Default.
Want reverse-chronological? --reverse.
Want only posts from one person? --handle alice.
Want only posts with media? --has-media.
Want to filter by keyword? muse social search "jazz".
Want to mute a topic? muse harmony policy-add --action suppress --content-pattern "#politics".
The user controls the filter. The platform doesn't.
Phased Implementation Plan
┌─────────────────────────────────────────────────────────────────────────────┐
│ SOCIAL DOMAIN — 8 PHASES │
├──────┬──────────────────────────────┬────────────────────────────────────── ┤
│ 00 │ Core Plugin │ SocialDomainPlugin, snapshot schema, │
│ │ │ delta algebra, OT merge, validate │
├──────┼──────────────────────────────┼───────────────────────────────────────┤
│ 01 │ CLI Surface │ post, reply, react, repost, follow, │
│ │ │ unfollow, timeline, search, profile │
├──────┼──────────────────────────────┼───────────────────────────────────────┤
│ 02 │ Wire Protocol │ SUBSCRIBE_SOCIAL frame, streaming │
│ │ │ deltas, Harmony dedup, ACK/NAK │
├──────┼──────────────────────────────┼───────────────────────────────────────┤
│ 03 │ MuseHub UI │ Social tab on profile, timeline view,│
│ │ │ thread view, activity canvas column │
├──────┼──────────────────────────────┼───────────────────────────────────────┤
│ 04 │ Agent-Native Posts │ Agent provenance on post commits, │
│ │ │ devlog/music/oracle agent archetypes │
├──────┼──────────────────────────────┼───────────────────────────────────────┤
│ 05 │ MPay Integration │ Tip reactions, pay-to-boost (local, │
│ │ │ user-controlled), USDC micropayments │
├──────┼──────────────────────────────┼───────────────────────────────────────┤
│ 06 │ Cross-Instance Federation │ Follow across any musehub instance, │
│ │ │ remote fetch + merge, DID resolution │
├──────┼──────────────────────────────┼───────────────────────────────────────┤
│ 07 │ Encrypted DMs │ Private branches, X25519 key │
│ │ │ exchange, encrypted delta blobs │
└──────┴──────────────────────────────┴───────────────────────────────────────┘
Phase 00 — Core Plugin
Implement muse/plugins/social/plugin.py:
SocialDomainPluginsatisfying the 6-methodMuseDomainPlugininterface- Snapshot schema:
{profile, posts, reactions, graph}namespaces - All eight delta types with Pydantic models and MsgPack codecs
- OT merge: concurrent posts → sort by
created_at; concurrent reactions → set-union; concurrent follows → set-union; concurrent profile edits → last-writer-wins with conflict detection validate(): MSign signature check on every PostDelta; rate-limit gate (max 100 posts per push); media object hash verificationdescribe(): human-readable one-liner per delta type- Full unit test suite: 100% delta round-trip coverage, merge idempotency, validation rejection paths
Phase 01 — CLI Surface
muse social <subcommand> dispatch in the muse CLI:
post,reply,quote,repost— create PostDelta, commit, pushreact— create ReactDelta, optional MPay attachmentfollow,unfollow,mute,block— graph mutationstimeline— fetch all remotes, merge, render chronologicallysearch—content-grepacross all fetched social reposprofile,profile set— view/update ProfileDeltadrafts,draft,publish— shelf-backed draft workflowthread <post_id>— walk reply DAG, render tree- All commands accept
--jsonfor agent-readable output
Phase 02 — Wire Protocol
Extend the MsgPack streaming protocol with social frame types:
SUBSCRIBE_SOCIAL— client subscribes to a handle's feed since a timestampSOCIAL_DELTA— server streams deltas as they're pushed- Harmony integration: content fingerprint dedup prevents duplicate reposts
- MSign
Authorizationheader required onSUBSCRIBE_SOCIAL - Backpressure: standard ACK/NAK from existing wire implementation
Phase 03 — MuseHub UI
- Social tab on profile pages (
/gabriel?tab=social) - Timeline view — paginated, chronological, with reaction counts + reply counts
- Thread view — full reply tree rooted at any post
- Compose UI — inline post form, media upload to object store, draft save
- Activity canvas —
socialcolumn (post frequency heatmap, 52×7) - Identity manifest — follower/following/post counts from commit DAG
- Reaction bar — emoji reactions + MPay tip button per post
Phase 04 — Agent-Native Posts
- Agent provenance fully rendered in the UI:
[agent: claude-code ✓]badge on posts devlog-agentarchetype: posts daily commit summary for a repomusic-agentarchetype: posts when a MIDI commit lands in a music repooracle-agentarchetype: posts cross-repo code intelligence reports (hotspots, churn, blast radius)- All agent posts signed with derived Ed25519 sub-key, traceable to root identity
Phase 05 — MPay Integration
ReactDeltacarries optionalmpay: {from, to, amount_usdc, sig}payloadmuse social react <post_id> ❤️ --tip 0.10— react + send $0.10 USDC tip- Profile page shows cumulative tips received (MPay ledger aggregate)
- Pay-to-boost: user-controlled local feed weighting based on tip amounts (optional, never platform-imposed)
- AVAX address required on profile to receive tips
Phase 06 — Cross-Instance Federation
muse social follow gabriel --hub https://staging.musehub.ai— follow across instances- DID document resolution:
/.well-known/did.jsonper handle on any musehub instance muse social timelinemerges local + all remote instance feeds seamlessly- MSign proofs verified against originating instance's key registry
Phase 07 — Encrypted DMs
- Private branches:
social/dm/<recipient_handle>— not pushed to public remotes - X25519 key exchange derived from sender + recipient Ed25519 keys (standard conversion)
- Delta blobs encrypted before commit — server stores ciphertext, never plaintext
muse social dm gabriel "hey"— encrypt + commit to private DM branchmuse social dms— list DM threads; decrypt + render locally
Why Now
Every serious attempt at a decentralised social network has failed for the same reason: they built the social layer first and tried to add content-addressing and cryptographic identity as an afterthought. ActivityPub has identity problems. Nostr is close but lacks the commit DAG — there's no history, no merge, no branching. Farcaster is on-chain but the UX is hostile. Bluesky has the AT Protocol but it's centralised in practice.
Muse has the right primitives already. The object store is there. The DAG is there. The cryptographic identity is there. The wire protocol is there. The domain plugin interface is there. MuseHub profile pages are there. The only missing piece is a SocialDomainPlugin and CLI subcommands.
This isn't a new product. It's a 5-line domain_id = "social" declaration and eight phases of wiring.
The algo-free timeline isn't a differentiator. It's the default. Chronological order falls out of committed_at sort. The algorithm has to be actively built and imposed — without one, you just get the truth.
Success Metrics
muse social post "hello"commits a signed PostDelta to the social repomuse social follow aliceadds alice's feed as a remotemuse social timelinemerges all followed feeds chronologically with zero duplicates- Every post in the timeline is verifiable with
muse verify-commit <commit_id> - Real-time streaming: post lands in subscriber's timeline in < 500ms of push
- MuseHub profile social tab renders timeline with correct provenance badges
- Cross-instance follow works:
muse social follow gabriel --hub https://staging.musehub.ai - Agent posts carry full provenance:
agent_id,model_id, verified signature - MPay tip completes: ReactDelta + USDC transfer in one commit
- Encrypted DM: only sender + recipient can decrypt; server stores ciphertext
✅ Phase 01 Complete — muse social CLI Surface
Commit: sha256:ad9018c59bfe0cae40106e518
Branch: merged to dev on muse repo
Tests: 62/62 GREEN (148 passing across social + mist + registry suites — zero regressions)
What landed
| File | Description |
|---|---|
muse/cli/commands/social.py |
Full muse social subcommand tree + pure state helpers |
muse/cli/app.py |
social_cmd registered under "social" |
tests/test_social_cli.py |
62 tests: shape, state helpers, post, follow/unfollow, timeline, react, profile, docstrings |
CLI surface shipped
muse social post "hello muse" # write posts/<post_id>.json
muse social post --reply-to <post_id> "reply" # reply thread
muse social follow alice # graph/follows/alice.json
muse social unfollow alice # deletes the file
muse social timeline # sorted newest-first
muse social timeline --limit 20 --json # structured output
muse social react <post_id> ❤️ # reactions/<id>.json
muse social profile --set-bio "building..." # update profile.json
muse social profile --json # read profile
Key design decisions
- Posts are content-addressed on (body + reply_to + quote_of + created_at) — same inputs always produce the same
post_id. Posting the same message twice is idempotent. - Reactions are content-addressed on (post_id + emoji) — reacting with ❤️ twice produces one file, not two.
- Follows are per-handle files (
graph/follows/<handle>.json) — set-union merge with no JSON parsing required. This design decision from Phase 00 pays off immediately. - All commands write state to the working tree. Stage and commit separately with
muse code add . && muse commit --sign.
Up next: Phase 02 — Wire Protocol (SUBSCRIBE_SOCIAL frame, real-time streaming deltas, Harmony dedup)
📝 Phase 02 — Design Correction
Original framing was wrong. MWP is transport — domain-agnostic by construction. Adding SUBSCRIBE_SOCIAL frames to MWP would couple domain semantics into the transport layer.
Corrected Phase 02: MuseHub Social API
The push path already works — social commits are just commits, MWP carries them exactly like code commits. Phase 02 is the server-side that makes social repos first-class on the hub:
Scope
| Endpoint | Description |
|---|---|
GET /api/social/{handle} |
Paginated posts from a handle's social repo. Reads the social repo's snapshot manifest, loads post objects, returns sorted feed. |
GET /api/social/{handle}/stream |
SSE stream — fires social_delta events when a social-domain push lands for {handle}. Real-time subscription without polling. |
| Push handler extension | Detect domain="social" on push completion, extract post deltas from the commit, fan out to active SSE subscribers. |
Why this is right
- MWP stays transport-only. No domain semantics in the wire layer.
- The feed endpoint is a pure read path over existing content-addressed objects.
- The SSE fan-out reuses the same pattern as MCP streaming — no new infrastructure.
- A UI (Phase 03) can be built on top of these two endpoints with no further backend changes.
✅ Phase 02 Complete — MuseHub Social API
28/28 tests GREEN across 5 test classes.
What shipped
Three new endpoints in musehub/api/routes/musehub/social.py:
| Endpoint | Purpose |
|---|---|
GET /api/social/{handle} |
Cursor-paginated posts feed — reads social-domain repo snapshot manifest, loads posts/*.json objects from the Muse store, returns sorted newest-first |
GET /api/social/{handle}/stream |
SSE real-time subscription — delivers social_delta events when new social pushes land for a handle |
New service layer in musehub/services/musehub_social.py:
get_social_feed(session, handle, *, limit, cursor)— full pagination with cursor supportsubscribe_handle / unsubscribe_handle— in-process SSE subscriber registry (per-handle asyncio.Queue sets)fan_out_to_subscribers(handle, event)— push events to all active SSE connections for a handle; no-op when no subscribers
Design notes
- Social state lives entirely in the Muse object store — no new DB tables. Posts are
posts/*.jsoncontent-addressed files in adomain_id="social"repo. - The subscriber registry is module-level in-process state; intentionally not distributed (single node). Horizontal scaling would need Redis pub/sub — not needed yet.
BaseHTTPMiddleware+ SSE body streaming doesn't work with ASGI in-process test transport (well-known limitation). SSE tests verified via route registration check + service-level fan-out tests.
Next
Phase 03 — Social domain UI in musehub: profile feed page (/{handle}/social), real-time timeline view using the SSE stream, post composer.
✅ Phase 00 Complete — SocialPlugin Core
Commit:
sha256:6296849dd5188cfda81467810Branch: merged todevonmuserepo Tests: 43/43 GREEN (0 regressions in 176 related existing tests)What landed
muse/plugins/social/plugin.pySocialPlugin— full 6-methodMuseDomainPluginprotocolmuse/plugins/registry.py"social"tests/test_social_plugin.pyKey design decision made during TDD
Follows are stored as
graph/follows/<handle>.json(one file per follow), not a singlefollowing.jsonarray. This means set-union merge is automatic — following alice on one branch and bob on another has zero conflicts, identical to how posts work. No special JSON merge logic needed.Merge rules implemented
posts/— set-algebraic, independent. Same content hash = same post = no conflict. Reposts deduplicate naturally.reactions/— set-algebraic, independent. Concurrent reactions always merge cleanly.graph/follows/— set-algebraic, independent. Per-handle files, set-union on merge.profile.json— non-independent. Conflict when both branches edit independently; last-writer-wins when only one side changed.Up next: Phase 01 —
muse socialCLI surface (post,follow,timeline,react,reply,repost,unfollow,profile,drafts).