Version: 0.0.1
Hush is a lightweight, legible C11 implementation of core Nostr relay functionality.
Hush began as a fork of Buzz. It is now a standalone C11 relay and hive. We do not track, fetch, or sync Buzz. One-way import notes live in IMPORT.md.
Developed with the Codex AI agent. All development uses worktrees inside this repository (worktrees/<slug>), never under /opt/repo/worktrees or other external paths.
- Written in strict C11 following the machine-legibility standard (write-legible-c).
- Single binary:
hush-relay - Designed for set-and-forget self-hosting and embedding.
- License: GPLv3
- Version source of truth: top-level
VERSION(currently0.0.1)
- Nostr NIP-01 basics for chat (kind 0 profiles, kind 1 notes). Kind 5
e-tag deletions are applied; kind 7 reactions are stored but not rendered - EVENT ingestion + a bounded event store persisted to
store.ring+store.log - REQ with filter matching (kinds, ids, authors, since/until, and
#e/#p/#h/#dtag values, up to four filters) - CLOSE
- Wire
EVENTframes are BIP-340 verified before store and fan-out; NIP-42 AUTH challenges authenticate connections and gate private hives. See NOSTR.md and SECURITY.md - Token-bucket rate limits on every ingress path (wire, HTTP, provider
quotas) with honest
rate-limited/429 rejections and graceful overload - RFC 6455 WebSocket transport for stock Nostr clients (
ws://on the same port: handshake, masked frames, fragmentation, ping/pong, close) - Simple TCP newline-delimited JSON protocol (debug/dev transport)
poll(2)single-threaded server- Same port also serves the chat PWA over HTTP (
GET /, manifest, service worker, icons) - Optional STUN/TURN (coturn) from Settings, including systemd daemon mode
- Vibes are public (discoverable) or private: wire reads, publishes, and fan-out require member AUTH or the join token, and only a hash of the token is persisted
- HTTP API gated by a per-hive session token (
$HUSH_HOME/session.token); the listener binds127.0.0.1unless--listensays otherwise - Mesh conference calling (humans and AI agents; agent voice needs Whisper)
- Strict build:
-std=c11 -Wall -Wextra -Werror -Wconversion -Wshadow
The chat UI is a Progressive Web App served by the relay itself
(hush-c/demo/: index.html, manifest.webmanifest, sw.js, icons).
On first launch a feather splash detects identity and vibe, then a
numbered wizard walks Identity → Backup (pass checked by default;
retrieve with pass show hush/identity/nsec) → Vibe (public or private)
→ Meet Major. Right-click (macOS: meta+click) a robot in inventory
and choose edit to change name, avatar, system prompt, optional
voice, and equipped skills. Profile holds first/last name, email,
organization, theme (dark / light / color-blind / dracula /
desert / monochrome / christmas), and Logout. From the hive you
can create channels and projects, invite humans, and raise agents
from the tool rail (plaintext/Markdown context only). The left nav
keeps channels and robot cards only. Edit-robot actions sit on one
compact line (Save Robot / Close / Delete Robot). @ in the composer mentions humans
and robots as pills; the wire still uses NIP-27 nostr:npub1….
Channels carry a UUID, sit in optional Groups (NIP-29 parent), and can
be deleted or managed from a right-click menu. Manage Channel adds and
removes people with + / − pills and sets a policy leash (open /
humans / robots / mixed; robots reply off, when mentioned, or confirm
first). Chatty multi-send bursts coalesce; robots confirm they heard
the ask before spending a Grok turn. Install, Profile, Settings, Call,
Close, and Exit live on a movable tool rail that collapses to a
hamburger. Install puts Hush on the app launcher as its own window; it
does not start a second hive. When Whisper is on PATH (or
HUSH_WHISPER is set), robot cards show a 1:1 Call icon and channels
show a Voice icon; mute any tile in the conference. After Grok/Codex
OAuth the matching provider box shows authenticated (Grok needs
~/.grok/auth.json, Codex needs ~/.codex/auth.json or config.toml)
and tells you to close the extra windows. Mention a Grok Build robot
to start a thread; a thinking chip shows while it works, a Thread
button opens a resizable hive chat (1:1 or 1:n). In a 1:1 pane a
follow-up without a new @ still addresses that sole robot. The tool rail is a
free-drag hamburger (no docks); double-click parks it left of the
brand. Expanded, it is compact two-column pairs (Profile/Settings,
Call/Invite, Add Channel/Configure Providers, New Robot/New Project,
Minimize/Maximize, Close/Exit) under Install; Profile, Settings,
Call, Invite, Channel, Providers, Robot, and Project each have an
i help popover. Configure Providers is the hive-wide desk
for credentials and OAuth; each robot still picks which runtime
it uses from left-nav Edit. Minimize iconifies the window;
Maximize toggles the WM maximized state. The composer is a six-line wrapping
box that scrolls after that. A live thread you have scrolled up
stays put across the 1 s poll. The reply is one short grok -p note
from an empty cwd (no desktop AGENTS.md, --no-memory). A joke ask
gets exactly one joke; the follow-up transcript flattens a prior
multi-line note so Happy cannot repeat a joke it already told.
Fenced code paints as a block; Canvas opens a right-hand editor
that colors popular languages (including Go), downloads, or saves
into a recorded project. Ctrl+K on a canvas selection asks Grok to
rewrite just that span (no extra hive note). Pause while typing
and Tab accepts a dim ghost completion at the caret. JSON event bodies
escape TAB and other C0 so a Go snippet cannot freeze the thinking
chip.
Click relay live for stored / projects / sockets. Hive metadata persists in ~/.hush/config/vibe.json so
make clean && make install or Exit does not force a new vibe after
you import the same nsec. (For upgrades from the older Buzz layout, the
relay still reads a legacy copy at ~/.config/hush/vibe.json as a
fallback when the canonical file is absent — delete both to fully reset
a hive.) Exit (--quit) stops the relay and the
browser / login children it forked. Close leaves the hive standing.
If --open attaches to a leftover listener, quit that process before a
new install can take the port. Secrets stay in pass. See
docs/pass-integration.md.
Install it from the tool rail (Chromium “Install”, or iOS Share → Add to Home Screen)
while the relay is running at http://127.0.0.1:<port>/.
Importing identities and channels from the predecessor project? See IMPORT.md.
Law: PRIME_DIRECTIVE.md — also AGENTS.md, BRANCHING.md.
- Always create a worktree:
git worktree add -b gb/<slug> worktrees/<slug>(inside this repo only). - Commit and push on that
gb/*branch. - Land on
mainonly via Pull Request → review → auto-merge. - After merge, delete the worktree.
- Writing directly to
mainis strictly prohibited.
./scripts/install-hooks.sh # blocks commit/push on main./configure
make
make testSTUN/TURN support is on by default. To omit it:
./configure --disable-stun-turn
makeHush does not vendor coturn. It writes a
config and starts the turnserver binary when you click Enable STUN/TURN
in Settings.
# Debian / Ubuntu / Pop!_OS
sudo apt install coturn- Child mode (no root):
hush-relayforksturnserveron port 3478 (root) or 13478 (user) with a generated long-term username/password. - Daemon mode (systemd): requires a system install so the unit is in
/lib/systemd/system/:
sudo make install PREFIX=/usr
# then either:
sudo systemctl enable --now hush-turn
# or open Settings → Daemon modeOpen the firewall for 3478/tcp, 3478/udp, and the relay range
49152-49251/udp. Set Public host / IP if the machine is behind NAT.
A vibe (this relay) can be public or private. Public vibes are discoverable and joinable. Private vibes hide from discovery and share a join token.
Conference calls are a WebRTC mesh on the current channel (kind 25000
signaling). Supported mixes: human↔human, many humans, human↔agent,
agent↔agent, and mixed. AI agents need a speech model such as Whisper
(HUSH_WHISPER=1 or whisper on PATH) to hear; otherwise they join as
signaling-only.
./hush-relay # default port 10555; opens a standalone app window if a display is available
./hush-relay --open # same, and always open the app window (used by the .desktop launcher)
./hush-relay --no-open 10555The process is a server: it prints the listen URL and stays running until
Exit, --quit, or Ctrl+C. --open (the default on a graphical session)
launches a frameless standalone app window (Chromium/Chrome/Brave/Edge
--app= plus --ozone-platform=x11, or Epiphany application mode) with
no browser tab strip, URL bar, or OS title-bar ×. Firefox-as-default
is not used, because it cannot hide chrome. The same port
also speaks the newline-delimited Nostr JSON protocol (see
.agents/skills/relay/SKILL.md).
These are two different verbs. Rail Close and Exit open one
chooser: Exit the application, Close the window, or Cancel.
The OS/PWA window × belongs to the --app window. Hush cannot put
those three buttons on that close-box. Closing the last live --app
window raises a follow-up (zenity when present): the same three
verbs. Launch does not raise that dialog. Cancel re-opens the window.
Close leaves the hive standing.
| Verb | In the hive | CLI | What happens |
|---|---|---|---|
| Close | chooser Close the window | hush-relay --close |
GUI goes away. The relay keeps listening. |
| Exit | chooser Exit the application | hush-relay --quit or Ctrl+C |
Every process stops. Exit code 0. |
| Cancel | chooser Cancel | — | Stay, or re-attach if the --app window already closed. |
Click the launcher (hush-relay --open) while the hive is already up to
re-attach a window. POST /api/close acknowledges Close and does not stop
the process. POST /api/exit sets the same shutdown flag as SIGTERM.
Every note is transcribed to $HUSH_HOME/threads/<root>.log (keyed by the
thread's root event) and each robot reply rolls that thread's brief forward.
Transcripts are private (0600, no symlink follow), survive restart, and are
what an agent reads when the live in-memory ring has moved on.
While a robot is answering, the relay streams the provider's deltas into the thread, so text paints as it arrives instead of after the whole answer:
| Endpoint | Body | Answer |
|---|---|---|
POST /api/reply |
{"root": "<hex>", "robot": "<name or hex>"} |
{"ok":true,"running":true,"text":"…"} while the job is live, running:false once it is gone. |
POST /api/cancel |
same | {"ok":true,"stopped":true} after SIGTERM to the job's process group (SIGKILL follows if it will not exit), then the robot posts an honest "stopped on request" note. A second cancel answers stopped:false. |
Configure Providers on the tool rail is the hive-wide desk. It lists every runtime, who uses it, and opens the same tailored drawer the Raise-robot pencil does. Credentials are global; a robot only stores which id it uses.
Selecting an AI provider on Raise a robot still reveals a pencil. That opens the same tailored drawer:
- Grok Build and Codex are OAuth-only: Log in with OAuth starts
grok login --oauthorcodex loginin a terminal. Authenticated means that provider’s own auth file exists — a leftover~/.codexdirectory does not count. Hush does not implement the browser dance and does not write~/.grokor~/.codex. - Goose reuses
~/.config/gooseor accepts an override key. - Gemini / xAI / OpenAI / Anthropic / Deepseek take an API key, host
URL, and a scanned or typed model. Those fields use the same
+/−pills as the robot name and system prompt. Deepseek host ishttps://api.deepseek.com. - Cline shows an honest empty state if the editor extension is missing.
Secrets Hush accepts for a provider (API key, username, password,
token, passkey) live only in pass:
pass show hush/providers/<id>/api_key
pass show hush/providers/<id>/username
pass show hush/providers/<id>/password
pass show hush/providers/<id>/token
pass show hush/providers/<id>/passkey
Host and model live in ~/.hush/config/providers.json.
The named vibe, channels, projects, profile (no email), members, and
raised-robot labels live in ~/.hush/config/vibe.json (0600).
Skills live under ~/.hush/skills/. make clean only deletes build
products; it does not touch that tree. Tests set HUSH_CONFIG_DIR
and/or HUSH_HOME.
GET /api/provider never returns the values. Goose / Grok / Codex
home secrets stay in those homes and are never copied.
Cline authenticates with ClinePass or a bring-your-own provider key,
not a Grok/Codex-style OAuth-first CLI.
The application launcher entry (hush-relay.desktop) starts or attaches the
GUI. The Quit Hush desktop action runs --quit.
Hush is available through multiple package managers. Choose the one that fits your distribution:
# Build from source
./configure
make deb
# Install the resulting .deb
sudo dpkg -i ../hush-relay_*.deb# Build from source
make rpm
# Install the resulting .rpm
sudo dnf install ~/rpmbuild/RPMS/*/hush-relay-*.rpm# Build from source
make flatpak
# Or install from Flathub (when available)
flatpak install flathub io.github.coldcanuk.hush# On OpenBSD (pkg_add gmake first):
./configure --prefix=/usr/local
make openbsd
doas pkg_add ./dist/openbsd/hush-relay-*.tgz
# Or drop the port into the ports tree:
# doas cp -R openbsd/net/hush-relay /usr/ports/net/hush-relay
# cd /usr/ports/net/hush-relay && make makesum && make packageEveryday commands (FAQ 15):
pkg_info -aQ hush, doas pkg_add -u, doas pkg_delete hush-relay.
Details: openbsd/README.md.
# On FreeBSD (pkg install -y gmake first):
./configure --prefix=/usr/local
gmake freebsd
pkg add ./dist/freebsd/hush-relay-*.pkg
# Or drop the port into the ports tree:
# cp -R freebsd/net/hush-relay /usr/ports/net/hush-relay
# cd /usr/ports/net/hush-relay && make makesum && make packageEveryday commands (pkg reference):
pkg search hush, pkg info hush-relay, pkg update && pkg upgrade,
pkg delete hush-relay.
Details: freebsd/README.md.
./configure
make
make installmake install installs to ~/.local/bin/ by default — no sudo required.
For a system-wide install:
./configure
make
sudo make install PREFIX=/usrPlans and research live under docs/. Do not leave PLAN_*.md or RESEARCH*.md at the repo root.
| Kind | Path |
|---|---|
| Plans | docs/plan/ |
| Research | docs/research/ |
pass |
docs/pass-integration.md |
Codex is the supported development agent. Read AGENTS.md and
Codex for Hush. The complete
write-legible-c skill is checked in,
including its normative reference and upstream MIT license. Invoke
$write-legible-c for C work; spawned development agents follow the same skill.
Hush also exposes this skill in the isolated working directory of each Codex
runtime job. make install installs the complete skill under
share/hush/codex/skills/write-legible-c. HUSH_CODEX_SKILL_DIR can point to
another complete copy. Missing or conflicting skill files prevent Codex dispatch.
The agy integration is removed. Select codex and run codex login;
saved robot and Payne provider selections migrate on restore. New API requests
reject the retired id. Other providers keep their existing runtime roles.
Core skills in .agents/skills/:
- worktree, c-build, c-test, write-legible-c, legible-c, relay, acoder-init, publish
See CODE_OF_CONDUCT.md — SQLite's Code of Ethics (Rule of St. Benedict).