Skip to content

schwentker/celestial-signals

Repository files navigation

Celestial Signals

An ATProto labeler that watches the Bluesky firehose for astrology content and annotates it with cryptographically signed labels — in real time.

Live visualizer: https://sandboxlabs.ai/celestial-signals

Built as a learning project + meetup demo to understand the labeler primitive and explore the two-pass NLP pattern at the core of Attie.

What this is

A labeler, not a feed generator. Key difference:

Feed Generator Labeler
What it emits Post URIs Signed label objects
Protocol primitive getFeedSkeleton com.atproto.label.*
Travels with content ❌ app-local ✅ cross-app, portable
User action Subscribe to feed Subscribe to labeler

Labels are protocol-level metadata. When a user subscribes to Celestial Signals, posts about astrology get annotated inside any atproto client — not just Bluesky.

Architecture

Jetstream (JSON WebSocket)
         │
         ▼
Pass 1: Keyword filter          ← cheap gate, ~O(n keywords)
         │ match
         ▼
Pass 2: Claude Haiku classifier ← semantic verification
         │ true
         ▼
server.createLabel()            ← CBOR-signed, broadcast via
         │                        com.atproto.label.subscribeLabels
         ▼
Subscribers see labels in app

This two-pass pattern mirrors what Attie is doing at scale: cheap filter first, LLM understanding only where it counts.

Labels

identifier description
astrology Post meaningfully engages with astrological concepts
mercury-retrograde Specifically discusses Mercury retrograde
natal-chart Birth charts, rising signs, house placements

Why Jetstream not raw firehose

The raw com.atproto.sync.subscribeRepos stream emits CBOR-encoded MST blocks. Jetstream proxies this to plain JSON WebSocket — same data, no binary decoding, filterable by collection and DID. For labelers and feed generators that don't need full sync semantics, Jetstream is the right layer.

Stack

  • Runtime: Bun
  • Labeler: @skyware/labeler — handles XRPC endpoints, CBOR signing, label distribution
  • Firehose: @skyware/jetstream — typed Jetstream client
  • Classifier: Claude Haiku (claude-haiku-4-5-20251001) via Anthropic SDK

Setup

git clone https://github.com/YOUR_HANDLE/celestial-signals
cd celestial-signals
bun install
cp .env.example .env

# Convert a dedicated Bluesky account into a labeler
bunx @skyware/labeler setup
# → copies DID + SIGNING_KEY into .env

# Set SETUP_COMPLETE=true, then:
bun run dev

Verify:

curl http://localhost:4100/xrpc/com.atproto.label.queryLabels
# → {"cursor":"0","labels":[]}

Env vars

var description
DID did:plc of your labeler account
SIGNING_KEY secp256k1 private key for label signing
BSKY_IDENTIFIER labeler account handle
BSKY_PASSWORD app password
USE_LLM_CLASSIFIER true = two-pass, false = keywords only
ANTHROPIC_API_KEY required if USE_LLM_CLASSIFIER=true

What I learned building this

  • Labelers require a DID document mutation to register #atproto_label signing key and #atproto_labeler service endpoint — this is the one-time ceremony that makes your server recognizable to the network
  • Label values must be published to the ATProto record before the server can issue them — bunx @skyware/labeler label add, not runtime registration
  • Jetstream cursors are unix microseconds — time-based, portable across instances, resumable without sequence coordination
  • The neg: true pattern for label retraction is elegant — negation is a first-class protocol concept, not an out-of-band delete

Related


Built for the ATProto SF meetup — Sandbox Labs AI

About

ATProto labeler that annotates astrology content on the Bluesky firehose with cryptographically signed labels. Two-pass architecture: keyword filter + Claude Haiku semantic verification

Resources

License

Contributing

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors