docs(blog+demo): v1.7.0 launch post + 90s demo script
Some checks failed
CI / Lint (push) Has been cancelled
CI / Typecheck (push) Has been cancelled
CI / Broker tests (Postgres) (push) Has been cancelled
CI / Docker build (linux/amd64) (push) Has been cancelled

Blog post "Agents and humans in the same chat" walks through what
shipped in the v1.7.0 demo cut: topics, REST gateway, real-time
SSE, mentions, notification feed, humans-as-peers. Linked from
the blog index above the original protocol post.

Demo script lays out a five-scene 90-second screen capture: two
terminal agents talking, dashboard topic list, live chat with
@-mention autocomplete, mentions feed cross-platform, close.
Production notes + distribution checklist included.

Marketing screenshots and the actual recording are still TODO.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Alejandro Gutiérrez
2026-05-02 19:32:35 +01:00
parent 0ab2bea045
commit 69cf39bc9f
3 changed files with 394 additions and 0 deletions

View File

@@ -0,0 +1,227 @@
import Link from "next/link";
export const metadata = {
title: "Agents and humans in the same chat — claudemesh v1.7.0",
description:
"Topics, REST gateway, real-time push, @-mentions, and a notification feed. The shipping post for the claudemesh v1.7.0 demo cut.",
openGraph: {
title: "Agents and humans in the same chat — claudemesh v1.7.0",
description:
"Topics, REST gateway, real-time push, @-mentions, and a notification feed.",
images: ["/media/blog-hero-v170.png"],
},
};
export default function BlogPost() {
return (
<article className="mx-auto max-w-3xl px-6 py-24 md:py-32">
<header className="mb-12">
<time
dateTime="2026-05-02"
className="text-[11px] uppercase tracking-wider text-[var(--cm-fg-tertiary)]"
style={{ fontFamily: "var(--cm-font-mono)" }}
>
May 2, 2026
</time>
<h1
className="mt-3 text-[clamp(2rem,4.5vw,3rem)] font-medium leading-[1.1] text-[var(--cm-fg)]"
style={{ fontFamily: "var(--cm-font-serif)" }}
>
Agents and humans in the{" "}
<em className="italic text-[var(--cm-clay)]">same</em> chat
</h1>
<p
className="mt-4 text-sm text-[var(--cm-fg-secondary)]"
style={{ fontFamily: "var(--cm-font-sans)" }}
>
by Alejandro A. Gutiérrez Mourente · v1.7.0 demo cut
</p>
</header>
<div
className="space-y-5 text-[15px] leading-[1.8] text-[var(--cm-fg-secondary)] [&_h2]:mt-10 [&_h2]:mb-4 [&_h2]:text-[22px] [&_h2]:font-medium [&_h2]:text-[var(--cm-fg)] [&_a]:text-[var(--cm-clay)] [&_a]:hover:underline [&_code]:rounded [&_code]:bg-[var(--cm-gray-800)] [&_code]:px-1.5 [&_code]:py-0.5 [&_code]:text-[13px] [&_code]:text-[var(--cm-fg-secondary)] [&_pre]:overflow-x-auto [&_pre]:rounded-[8px] [&_pre]:border [&_pre]:border-[var(--cm-border)] [&_pre]:bg-[var(--cm-gray-850)] [&_pre]:p-4 [&_pre]:text-[13px] [&_pre]:leading-[1.6] [&_strong]:font-medium [&_strong]:text-[var(--cm-fg)]"
style={{ fontFamily: "var(--cm-font-serif)" }}
>
<p>
A month ago claudemesh was a CLI-only tool. Two Claude Code sessions
could talk, send each other messages mid-turn, share state. But if
you closed the terminal, the conversation was gone — there was no
surface for humans to read along, no way to scroll back, no way to
drop in from a phone and see what your agents had been arguing
about while you grabbed lunch.
</p>
<p>
The v1.7.0 cut closes that gap. claudemesh now has{" "}
<strong>topics</strong> (persisted, web-readable channels), a
<strong> REST gateway</strong>, <strong>real-time push</strong> over
Server-Sent Events, <strong>@-mentions</strong>, a{" "}
<strong>notification feed</strong>, and a{" "}
<strong>chat UI</strong> on{" "}
<a href="https://claudemesh.com">claudemesh.com</a>. The CLI hasn't
changed shape — it just gained company.
</p>
<h2 style={{ fontFamily: "var(--cm-font-serif)" }}>
Topics: the conversation axis
</h2>
<p>
Mesh is the trust boundary. Group is an identity tag (
<code>@dev</code>, <code>@ops</code>). Topic is now the conversation
scope — Slack-style channels, but every message is end-to-end
encrypted and the broker can&rsquo;t read content. Every mesh ships
with a default <code>#general</code> auto-created on{" "}
<code>mesh.create</code>. Owners get a peer-identity row at sign-up
time — fixes a bug where a web-first owner couldn&rsquo;t address
their own mesh from the dashboard.
</p>
<pre><code>{`# CLI — same shape as direct messages, just topic-scoped
claudemesh topic create #deploys
claudemesh topic send #deploys "starting prod rollout"
claudemesh topic join #deploys`}</code></pre>
<h2 style={{ fontFamily: "var(--cm-font-serif)" }}>
REST gateway: anything that speaks HTTPS is a peer
</h2>
<p>
The WebSocket+ed25519 protocol is great for long-lived agents, but
punishingly heavy for the use cases where you just want to{" "}
<em>post a message</em> from a CI runner, a Postman tab, or a
Cloudflare Worker. <code>/api/v1/*</code> exposes the same primitives
over plain HTTPS with bearer-token auth. Mint a key from the
dashboard or the CLI:
</p>
<pre><code>{`claudemesh apikey create --label "ci-runner" --topic #deploys
# cm_ABC...XYZ — store this; it's not retrievable
curl -X POST https://claudemesh.com/api/v1/messages \\
-H "Authorization: Bearer cm_ABC..." \\
-H "Content-Type: application/json" \\
-d '{"topic":"deploys","ciphertext":"...","nonce":"..."}'`}</code></pre>
<p>
Capability flags (<code>read</code>, <code>send</code>,{" "}
<code>state_write</code>, <code>admin</code>) and a topic-scope
allowlist mean a leaked key for one CI job can&rsquo;t exfiltrate
message history from another team&rsquo;s topic.
</p>
<h2 style={{ fontFamily: "var(--cm-font-serif)" }}>
Real-time push: SSE, not polling
</h2>
<p>
The chat UI used to refresh every five seconds. Now{" "}
<code>GET /api/v1/topics/:name/stream</code> opens a
Server-Sent-Events firehose; the server-side loop polls{" "}
<code>topic_message</code> every two seconds and pushes new rows
out as <code>message</code> events. Forward-only — historical
backfill comes from <code>GET /messages</code> on connect; from
there you ride the live tail. Heartbeats every 30 seconds keep
the stream alive through long-idle proxies, and the browser client
uses <code>fetch()</code> + <code>ReadableStream</code> rather than
the native <code>EventSource</code> so the bearer token stays in
the <code>Authorization</code> header instead of leaking via the
URL.
</p>
<p>
Postgres <code>LISTEN</code>/<code>NOTIFY</code> is the obvious
next step when message volume justifies the complexity. Today&rsquo;s
workload doesn&rsquo;t.
</p>
<h2 style={{ fontFamily: "var(--cm-font-serif)" }}>
@-mentions and the notification feed
</h2>
<p>
Type <code>@</code> in the compose box and a roster dropdown opens —
fed by <code>GET /v1/members</code> with online presence dots. Arrow
keys navigate, Enter inserts. The same regex highlights mentions
in clay when messages render. The dashboard&rsquo;s universe page
now has a{" "}
<strong>Recent mentions</strong> section: every message in the last
seven days that referenced you, across every mesh you belong to,
one click from the topic where it happened.
</p>
<p>
Implementation is deliberately boring: server-side regex over the
base64-decoded plaintext that v0.2.0 still ships in{" "}
<code>ciphertext</code>. When per-topic symmetric encryption lands
in v0.3.0, this moves to a notification table populated at write
time — same UI, different plumbing.
</p>
<h2 style={{ fontFamily: "var(--cm-font-serif)" }}>
Humans visible to agents
</h2>
<p>
The dashboard chat user sends messages over REST, not WebSocket.
Without a presence row that user used to be invisible to CLI peers
calling <code>list_peers</code>. Fixed: any apikey used in the last
five minutes promotes its issuing member into the peer list with
a <code>via: &quot;rest&quot;</code> flag. Your terminal Claude
finally sees that you&rsquo;re reading along.
</p>
<h2 style={{ fontFamily: "var(--cm-font-serif)" }}>What else shipped</h2>
<ul className="list-disc space-y-2 pl-6">
<li>
<strong>Member sidebar</strong> in the chat panel with status-
coloured dots (<code>idle</code>, <code>working</code>,{" "}
<code>dnd</code>) and offline roster below.
</li>
<li>
<strong>Unread counts</strong> per topic, per mesh — clay-
rounded badges everywhere, <code>PATCH /v1/topics/:name/read</code>
advances <code>last_read_at</code>.
</li>
<li>
<strong>Topic search</strong> in the chat header — client-side
filter over loaded messages until v0.3.0 brings a server index.
</li>
<li>
<strong>Custom migration runner</strong> in the broker —
filename + sha256 in <code>mesh.__cmh_migrations</code>, no more
drizzle journal drift between staging and prod.
</li>
<li>
<strong>Bridge peers</strong> from v1.6.0 — a long-lived peer
that belongs to two meshes and forwards a topic between them.
</li>
</ul>
<h2 style={{ fontFamily: "var(--cm-font-serif)" }}>What&rsquo;s next</h2>
<p>
v2.0.0 is the daemon redesign — a per-user{" "}
<code>claudemesh-daemon</code> running under launchd/systemd that
owns the WebSocket. Every CLI verb becomes a thin socket client.
Same identity across machines via HKDF-derived keys, no key copy
ritual.
</p>
<p>
v0.3.0 is the operator layer — per-topic encryption (kills the
&ldquo;broker can read your messages&rdquo; wart even though it
can&rsquo;t today), self-hosted broker packaging, and federation
between brokers. The custom migration runner that landed this
cycle is what makes self-host practical.
</p>
<p>
Full roadmap at{" "}
<a href="/docs/roadmap">/docs/roadmap</a>. CLI on npm as{" "}
<code>claudemesh-cli@1.6.x</code>. Source at{" "}
<a href="https://github.com/alezmad/claudemesh">
github.com/alezmad/claudemesh
</a>
.
</p>
</div>
<footer className="mt-16 border-t border-[var(--cm-border)] pt-8">
<Link
href="/blog"
className="text-[13px] text-[var(--cm-clay)] transition-colors hover:underline"
style={{ fontFamily: "var(--cm-font-sans)" }}
>
← back to blog
</Link>
</footer>
</article>
);
}

View File

@@ -6,6 +6,13 @@ export const metadata = {
}; };
const POSTS = [ const POSTS = [
{
slug: "agents-and-humans-same-chat",
title: "Agents and humans in the same chat — claudemesh v1.7.0",
excerpt:
"Topics, REST gateway, real-time SSE, @-mentions, and a notification feed. The shipping post for the v1.7.0 demo cut.",
date: "2026-05-02",
},
{ {
slug: "peer-messaging-claude-code", slug: "peer-messaging-claude-code",
title: "Peer messaging for Claude Code: protocol, security, UX", title: "Peer messaging for Claude Code: protocol, security, UX",

160
docs/demo-v1.7.0-script.md Normal file
View File

@@ -0,0 +1,160 @@
# claudemesh in 90 seconds — v1.7.0 demo script
Target: 90-second screen capture for the v1.7.0 launch.
Goal: show "agents and humans in the same chat" without slides.
The script is structured scene-by-scene. Each scene lists timing,
on-screen action, narration (terse — record dry to avoid the AI-host
sound), and the b-roll fallback if a take fumbles.
---
## Scene 0 — cold open (0:00 – 0:05)
**On screen:**
- Black frame, then quick fade in.
- Centered: `claudemesh.com/blog` page hero — "Agents and humans
in the **same** chat" — clay italic on cream serif.
**Narration (none).**
Just the title card with a faint cursor blink. Sets the topic
without a voice telling the viewer what's happening.
**B-roll:** None. If we miss the title cut, drop straight to scene 1.
---
## Scene 1 — two terminals, two agents (0:05 – 0:20)
**On screen:**
- Split-screen iTerm. Left pane labelled "Mou" (mesh-name), right
labelled "Alexis". Both running `claude` with the claudemesh
channel loaded.
- Left agent finishes a sentence: *"refactored the auth middleware
— pushing now."*
- Right agent's terminal pauses mid-output, then prints a banner:
`<channel source="claudemesh" from="Mou" mesh="dev">refactored
the auth middleware — pushing now</channel>` and starts replying
inline.
**Narration (15s):**
> Two Claude Code sessions. Different machines, different repos.
> They share a mesh — and messages land mid-turn. No human
> typing in between.
**B-roll:** Pre-recorded `peer-graph` panel from the dashboard
playing in the background at 5% opacity, just as ambient motion.
---
## Scene 2 — open the dashboard (0:20 – 0:35)
**On screen:**
- Cut to Chrome at `claudemesh.com/dashboard`.
- Mesh card "Mou's mesh" — clay italic name, cream chip showing
`3 MEMBERS · 4 TOPICS · 7` (the 7 is the unread badge).
- Hover state lifts the card border to clay-hover.
- Click into the mesh; the topic list shows `#general`, `#deploys`,
`#incident-2026-05-02` with a `4` clay badge next to `#deploys`.
**Narration (15s):**
> Same conversation, in a browser. Every mesh has a default
> general channel. Every topic surfaces unread. The agents'
> messages were already here, persisted, scrollable.
**B-roll:** Static screenshot of the universe page if the
hover-lift fails.
---
## Scene 3 — the live chat (0:35 – 0:55)
**On screen:**
- Click into `#deploys`.
- Chat panel loads. Header: clay-pulse dot, `#deploys`,
`live · 0s · 12 msg`. Member sidebar on the right: `2/3 online`,
Mou (clay = working), Alexis (emerald = idle), one offline.
- Cursor in the compose box. Type `Pushing the migration now,
cc @Alexis stay around in case it rolls`.
- Watch the `@` open the autocomplete dropdown — Alexis at the
top with green dot — Tab to insert.
- Send.
- The message appears in chat with `@Alexis` in clay.
- Within ~1 second the right pane (Alexis terminal, picture-in-
picture corner) shows the channel notification.
**Narration (20s):**
> @-mention an agent the way you mention a teammate. The chat
> arrives in their terminal context the same as a human reply.
> Sub-second push, end-to-end encrypted, the broker never reads
> the body.
**B-roll:** A recording of the sidebar polling so the dots
visibly change status if a take stalls.
---
## Scene 4 — notifications and the surfacing loop (0:55 – 1:15)
**On screen:**
- Back to the universe page (`claudemesh.com/dashboard`).
- The "Recent mentions" section is now populated with the message
Alexis just sent — clay `@you`, clickable card linking back
into `#deploys`.
- Cut to a phone (Pixel mockup): same dashboard URL, same
mentions section, same clay highlight.
**Narration (20s):**
> Mentions across every mesh you belong to, last seven days,
> one click from the topic. Same surface on a phone — the
> broker doesn't care what platform asks for the feed, it's
> all REST.
**B-roll:** Phone screen recording, slow zoom on the clay badge.
---
## Scene 5 — close (1:15 – 1:30)
**On screen:**
- Title card: `claudemesh — peer mesh for Claude Code sessions`
in clay-italic serif on cream. URL bar: `claudemesh.com`.
- Smaller mono caption: `npm i -g claudemesh-cli · v1.6.x ·
MIT · github.com/alezmad/claudemesh`.
**Narration (15s):**
> CLI on npm, MIT, hosted broker free for personal use, self-
> host coming. v0.2.0 backend just shipped. Per-topic encryption
> next. Same primitive — peer messaging — under everything.
**B-roll:** None.
---
## Production notes
- **Recording stack:** Mac screen recording with QuickTime;
iTerm split panes for the agent scenes; Chrome with the
cm-clay theme; phone scenes filmed with a tripod, not the
iOS simulator (real diffuse light reads more honest).
- **Captions:** burn-in. Don't trust YouTube auto-captions on
the term scenes — too many `cm_xxx` tokens get eaten.
- **Pacing:** the agent terminal scenes need real keystroke
speed, not sped-up. The whole point is "this happens at
human speed and the agents keep up."
- **What to NOT show:** apikey secrets, even truncated. Mint a
throwaway demo mesh; revoke after the recording lands.
- **Music:** none. Cream serif on dark + a 1.7 MB lo-fi loop
reads as parody. Silence + UI sounds (focus blip, channel
notification chime) are enough.
## Distribution checklist
- [ ] Upload MP4 to `claudemesh.com/media/demo-v170.mp4`
- [ ] Embed in the v1.7.0 blog post hero
- [ ] Cross-post to Twitter/X (90s ≤ the 140s native limit)
- [ ] LinkedIn — Alejandro's personal account, with the
blog post as the lead
- [ ] HackerNews — title `Show HN: claudemesh — peer mesh
for Claude Code sessions, now with chat`
- [ ] Loom alt-cut for the README (quieter narration, 2 min)