This website is meant to be read and understood quickly by humans, but is only fully parsable, on a technical level, with the aid of an AI system. Read why →
Loop MMT
Memorable IDs, Safe by Constructionsource← all gifts

Callsigns

A random identifier you can read aloud, remember for the length of a standup, and paste anywhere without escaping. Every token is word-word-hash (e.g. sunny-champion-8h3kq7): two human-readable words plus a six-character disambiguating hash. The point is that all three parts are ref-, path-, URL-, and shell-safe by construction — not “usually fine,” but safe as a proven property of the alphabet each part draws from, so a callsign drops straight into a git branch, a directory name, a URL segment, or a shell argument with no quoting. The hash alphabet is confusable-free (digits + a-z minus i/l/o/u) and lowercase-only, so there are no case-fold collisions. Seed it and the same seed yields the same token on any machine, forever.

The honest edge
A callsign is a memorable, SAFE identifier — not a guaranteed-unique one. The hash makes an accidental collision astronomically unlikely, but “unlikely” is not “impossible”: if your correctness depends on uniqueness, pair a callsign with a real uniqueness source (a timestamp, a sequence, a registry that rejects duplicates). It buys memorability and paste-safety, not a uniqueness authority.
Run it
python3 callsigns.py --demo test_callsigns.py (2061 checks / 10 tests, mutation-bitten, pinned golden batch hash) Python standard library only, deterministic under --seed, headless
The code — every file that ships
callsigns.py203 lineson GitHub →
#!/usr/bin/env python3
"""callsigns — memorable IDs that are safe by construction.

A random unique identifier you can read aloud, remember for the length of a
standup, and paste anywhere without escaping. Every token has the shape

    word-word-hash          e.g.  sunny-champion-8h3kq7

Two human-readable words drawn from a curated pool, then a six-character
disambiguating hash. The point is that all three parts are *ref-, path-, URL-,
and shell-safe by construction* — not "usually fine," but safe as a proven
property of the alphabet each part draws from, so a callsign drops straight into
a git branch name, a directory name, a URL segment, or a shell argument with no
quoting and no surprises.

WHY IT'S HONEST

- **Safe by construction, not by hope.** The words pass a lowercase-ASCII
  allowlist; the hash draws from a confusable-free, case-safe base32 alphabet
  (digits + a-z minus i/l/o/u). Every character clears git refs, Windows and
  macOS filenames, RFC-3986 URL segments, and the shell. There is no escaping
  step to forget because there is nothing to escape.

- **No case-fold collisions.** Everything is lowercase, so two callsigns can
  never collide only because a filesystem folded their case.

- **The namespace is a stated number, not a vibe.** 64 x 64 word-pairs = 4096
  memorable prefixes; the six-char hash adds 32^6 (~1.07e9) per prefix, for
  ~4.4e12 total. You can reason about collision odds because the size is exact.

- **No degenerate pairs.** A token is never `word-word` with the two words
  equal; the draw rejects and redraws, so every callsign reads as two distinct
  words.

- **Seed it and it's deterministic.** Pass a seed and the same seed yields the
  same callsign on any machine, forever — so a demo, a test, or a reproducible
  fixture is byte-identical. Leave the seed off and it draws from the system CSPRNG.

THE HONEST EDGE

A callsign is a *memorable, safe* identifier, not a *guaranteed-unique* one.
The hash makes an accidental collision astronomically unlikely, but "unlikely"
is not "impossible": if your system's correctness depends on uniqueness, pair a
callsign with a real uniqueness source (a timestamp prefix, a sequence, a
registry that rejects duplicates) — exactly as the Loop MMT session floor does,
joining a callsign to a UTC timestamp. Callsigns buy you memorability and
paste-safety; they do not replace a uniqueness authority.

USAGE
    python3 callsigns.py                 # one callsign
    python3 callsigns.py --n 5           # five callsigns, one per line
    python3 callsigns.py --seed 42       # deterministic: same seed, same token
    python3 callsigns.py --demo          # a short, reproducible demonstration

MIT licensed. Python standard library only. Deterministic under --seed; headless.
"""
from __future__ import annotations

import argparse
import json
import os
import random
import sys

HERE = os.path.dirname(os.path.abspath(__file__))
WORDLIST = os.path.join(HERE, "wordlist.json")

# A drawn word must be ref-/path-/URL-/shell-safe by construction.
SAFE = frozenset("abcdefghijklmnopqrstuvwxyz")

# Hash field: confusable-free, case-safe base32 — digits + a-z minus i,l,o,u.
# Lowercase-only (no case-fold collision); every char clears git refs,
# Windows/macOS filenames, RFC-3986 unreserved URL chars, and the shell.
# Length is the lever, not the alphabet: 32^6 ~= 1.07e9 per word-pair.
HASH_ALPHABET = "0123456789abcdefghjkmnpqrstvwxyz"  # 32 chars: a-z drop i,l,o,u
HASH_LEN = 6

_MAX_REDRAW = 64  # attempts to avoid a degenerate equal-word pair before forcing


def _is_safe(word: str) -> bool:
    """True iff word is non-empty and every char is in the safe allowlist."""
    return bool(word) and all(c in SAFE for c in word)


def draw_hash(rng: random.Random, length: int = HASH_LEN) -> str:
    """Draw a `length`-char hash from the case-safe base32 alphabet."""
    if length < 0:
        raise ValueError("hash length must be non-negative")
    return "".join(rng.choice(HASH_ALPHABET) for _ in range(length))


def load_pools(path: str = WORDLIST) -> tuple[list[str], list[str]]:
    """Load and safety-filter the two word pools from the wordlist JSON.

    Raises ValueError if either pool is empty after the safety filter — a
    wordlist that can't produce a safe word is a loud failure, never a silent
    empty draw.
    """
    with open(path, "r", encoding="utf-8") as fh:
        data = json.load(fh)
    pool_one = [w for w in data["pool_one"] if _is_safe(w)]
    pool_two = [w for w in data["pool_two"] if _is_safe(w)]
    if not pool_one or not pool_two:
        raise ValueError("wordlist pools are empty after the safety filter")
    return pool_one, pool_two


def draw(
    rng: random.Random | None = None,
    path: str = WORDLIST,
    pools: tuple[list[str], list[str]] | None = None,
) -> str:
    """Draw one callsign: `word-word-hash`, the two words always distinct.

    `rng`   — a random.Random (seed it for determinism); defaults to the CSPRNG.
    `pools` — optional pre-loaded (pool_one, pool_two); loaded from `path` if omitted.
    """
    rng = rng or random.SystemRandom()
    pool_one, pool_two = pools if pools is not None else load_pools(path)
    # Reject the degenerate identical-word pair and redraw.
    for _ in range(_MAX_REDRAW):
        a, b = rng.choice(pool_one), rng.choice(pool_two)
        if a != b:
            return f"{a}-{b}-{draw_hash(rng)}"
    # Vanishingly unlikely fallthrough: force a distinct pair.
    a = rng.choice(pool_one)
    alt = [w for w in pool_two if w != a]
    if not alt:
        raise ValueError("cannot form a distinct word pair from these pools")
    b = rng.choice(alt)
    return f"{a}-{b}-{draw_hash(rng)}"


def draw_many(
    count: int,
    rng: random.Random | None = None,
    path: str = WORDLIST,
) -> list[str]:
    """Draw `count` callsigns, reusing one loaded pool and one RNG."""
    if count < 0:
        raise ValueError("count must be non-negative")
    rng = rng or random.SystemRandom()
    pools = load_pools(path)
    return [draw(rng=rng, pools=pools) for _ in range(count)]


def demo(rng: random.Random | None = None) -> str:
    """A short, reproducible demonstration rendered as text.

    Deterministic when `rng` is seeded — the same seed prints the same block.
    """
    rng = rng or random.Random(42)
    pools = load_pools()
    lines = [
        "callsigns — memorable IDs safe by construction",
        "",
        "Five draws (seed=42):",
    ]
    for _ in range(5):
        lines.append("  " + draw(rng=rng, pools=pools))
    lines += [
        "",
        "Same seed, same tokens — reproducible anywhere.",
        f"Namespace: {len(pools[0])} x {len(pools[1])} pairs x {len(HASH_ALPHABET)}^{HASH_LEN}"
        f" ~= {len(pools[0]) * len(pools[1]) * len(HASH_ALPHABET) ** HASH_LEN:.2e} total.",
    ]
    return "\n".join(lines)


def _build_arg_parser() -> argparse.ArgumentParser:
    p = argparse.ArgumentParser(
        prog="callsigns",
        description="Draw memorable, safe-by-construction identifiers (word-word-hash).",
    )
    p.add_argument("--n", type=int, default=1, help="how many callsigns to draw (default 1)")
    p.add_argument("--seed", type=int, default=None, help="seed for deterministic output")
    p.add_argument("--demo", action="store_true", help="print a short reproducible demonstration")
    return p


def main(argv: list[str] | None = None) -> int:
    args = _build_arg_parser().parse_args(argv)
    try:
        if args.demo:
            rng = random.Random(args.seed) if args.seed is not None else random.Random(42)
            sys.stdout.write(demo(rng=rng) + "\n")
            return 0
        if args.n < 0:
            sys.stderr.write("callsigns: FATAL: --n must be non-negative\n")
            return 1
        rng = random.Random(args.seed) if args.seed is not None else random.SystemRandom()
        pools = load_pools()
        for _ in range(args.n):
            sys.stdout.write(draw(rng=rng, pools=pools) + "\n")
        return 0
    except Exception as exc:  # loud, never silent
        sys.stderr.write(f"callsigns: FATAL: {exc}\n")
        return 1


if __name__ == "__main__":
    raise SystemExit(main())
wordlist.json35 lineson GitHub →
{
  "_meta": {
    "name": "callsign-wordlist",
    "version": "v1",
    "produced": "S05.1342 (Mature Maple)",
    "product_of": "Wes (FWW(C) vocabulary) + Tamar (structural guarantee)",
    "purpose": "Source vocabulary for The Callsign Convention. A session callsign is one word drawn from pool_one and one from pool_two, joined by a hyphen, e.g. sunny-champion, taco-disco, feral-biscuit.",
    "structure": "Cartesian product pool_one x pool_two. Draw is uniform-random over the product; reject and redraw the degenerate case where word_one == word_two (Tamar's exclusion rule).",
    "namespace": 4096,
    "namespace_note": "|pool_one| (64) x |pool_two| (64) = 4096 distinct callsigns, minus the handful of identical-word pairs the draw rejects. Same-minute twin-collision probability is 1/namespace ~ 0.024%; scaling is additive (add words to either pool).",
    "token_rule": "Every token is lowercase ASCII [a-z] only: no spaces, no punctuation, no path/ref-hostile characters. The drawn callsign 'word-word' is safe as a git branch segment and a directory name by construction. (Nyx: the callsign is never free user input, so there is no injection surface — the wordlist IS the allowlist.)",
    "display_form": "Stored/path form is lowercase-hyphenated (sunny-champion). Spoken/display form is Title-Case (Sunny-Champion).",
    "fwwc": "Pools are curated HEAVY with FWW(C) per operator directive: Taco-Disco energy, not Server-Node-7. Both pools individually vetted clean; the product of two clean pools reads clean (no slur, no unfortunate phrase) for this vocabulary."
  },
  "pool_one": [
    "sunny", "turbo", "velvet", "feral", "cosmic", "disco", "rowdy", "mellow",
    "plucky", "saucy", "zesty", "wobbly", "snazzy", "brisk", "crispy", "nimble",
    "goblin", "thunder", "mossy", "taco", "rascal", "dapper", "jazzy", "breezy",
    "fuzzy", "sneaky", "peppy", "glossy", "rugged", "swift", "gilded", "frosty",
    "hasty", "lucky", "moody", "nifty", "prickly", "quirky", "rusty", "spunky",
    "tidal", "vivid", "witty", "amber", "bouncy", "chunky", "drowsy", "eager",
    "frantic", "gallant", "husky", "ironic", "jolly", "keen", "lanky", "merry",
    "noble", "oaken", "pesky", "quiet", "ruddy", "stout", "twangy", "untamed"
  ],
  "pool_two": [
    "champion", "disco", "taco", "walrus", "comet", "biscuit", "bandit", "lighthouse",
    "mango", "otter", "tuba", "cyclone", "pickle", "falcon", "wizard", "noodle",
    "dynamo", "llama", "kraken", "sprocket", "pumpkin", "raccoon", "satchel", "thunder",
    "vortex", "whistle", "anvil", "beacon", "cactus", "domino", "ember", "fjord",
    "gadget", "harpoon", "igloo", "jukebox", "kazoo", "lantern", "marmot", "nugget",
    "octopus", "pelican", "quartz", "rocket", "saxophone", "tangerine", "ukulele", "viper",
    "waffle", "xylophone", "yeti", "zeppelin", "badger", "conch", "dragon", "ferret",
    "gizmo", "hatchet", "jamboree", "koala", "lobster", "mammoth", "narwhal", "oyster"
  ]
}
test_callsigns.py166 lineson GitHub →
#!/usr/bin/env python3
"""test_callsigns.py — the certifying properties of the callsigns gift.

Run:  python3 test_callsigns.py         (exit 0 = all pass, 1 = a failure)

The tests are chosen to be MUTATION-BITTEN: each one is here because a plausible
mutation of callsigns.py makes it fail loud. In particular the determinism test
pins a GOLDEN sha256 of a seeded batch rather than checking self-equality — a
weak self-equality test passes benign reorders (the Loop MMT sudoku lesson), a
pinned golden does not.
"""
import hashlib
import random
import sys

import callsigns as c

# --- pinned golden: a seeded batch must reproduce this exact signature -------
# Regenerate ONLY on an intentional change:
#   python3 -c "import hashlib,random,callsigns as c; \
#     rng=random.Random(1234); p=c.load_pools(); \
#     b=[c.draw(rng=rng,pools=p) for _ in range(50)]; \
#     print(hashlib.sha256('\n'.join(b).encode()).hexdigest())"
GOLDEN_SEED = 1234
GOLDEN_N = 50
GOLDEN_SHA256 = "815a0dc83fa371ebb1df70e9a82961aeeea11cd3ad05b80e9e73a90c2e10448f"

_FAILURES: list[str] = []
_PASSES = 0


def check(cond: bool, msg: str) -> None:
    global _PASSES
    if cond:
        _PASSES += 1
    else:
        _FAILURES.append(msg)


def _batch(seed: int, n: int) -> list[str]:
    rng = random.Random(seed)
    pools = c.load_pools()
    return [c.draw(rng=rng, pools=pools) for _ in range(n)]


# --- 1. determinism: seeded draw reproduces the pinned golden ----------------
def test_golden_signature():
    batch = _batch(GOLDEN_SEED, GOLDEN_N)
    sig = hashlib.sha256("\n".join(batch).encode()).hexdigest()
    check(
        sig == GOLDEN_SHA256,
        f"golden signature drifted: got {sig[:16]}... expected {GOLDEN_SHA256[:16]}...",
    )
    # kills a mutation that drops the redraw and lets equal-word pairs through:
    for tok in batch:
        a, b, _ = tok.split("-")
        check(a != b, f"golden batch contains a degenerate equal-word pair: {tok}")


# --- 2. same seed => same first token; different seed => (almost surely) not --
def test_seed_repeatability():
    r1 = random.Random(7)
    r2 = random.Random(7)
    p = c.load_pools()
    check(c.draw(rng=r1, pools=p) == c.draw(rng=r2, pools=p), "same seed produced different tokens")
    r3 = random.Random(8)
    # different seeds SHOULD differ (namespace ~4.4e12, collision astronomically rare)
    p2 = c.load_pools()
    check(
        c.draw(rng=random.Random(7), pools=p) != c.draw(rng=r3, pools=p2),
        "different seeds produced identical tokens (namespace or seeding is broken)",
    )


# --- 3. shape: exactly three dash-parts, word-word-hash ----------------------
def test_shape():
    for tok in _batch(99, 200):
        parts = tok.split("-")
        check(len(parts) == 3, f"token is not word-word-hash: {tok!r}")


# --- 4. every char of the words is in the safe allowlist ---------------------
def test_words_safe():
    for tok in _batch(101, 200):
        a, b, _ = tok.split("-")
        check(all(ch in c.SAFE for ch in a), f"word 1 has an unsafe char: {a!r}")
        check(all(ch in c.SAFE for ch in b), f"word 2 has an unsafe char: {b!r}")


# --- 5. the hash never contains a confusable (i/l/o/u) or an out-of-alphabet char
def test_hash_alphabet():
    banned = set("ilou")
    for tok in _batch(202, 300):
        h = tok.split("-")[2]
        check(len(h) == c.HASH_LEN, f"hash wrong length: {h!r}")
        check(all(ch in c.HASH_ALPHABET for ch in h), f"hash has out-of-alphabet char: {h!r}")
        check(not (set(h) & banned), f"hash contains a confusable char (i/l/o/u): {h!r}")
    # kills a mutation that swaps HASH_ALPHABET for a plain base32 including i/l/o/u
    check(not (set(c.HASH_ALPHABET) & banned), "HASH_ALPHABET must not contain i/l/o/u")


# --- 6. no degenerate equal-word pair, ever, across a large sample -----------
def test_no_equal_pairs():
    for tok in _batch(303, 500):
        a, b, _ = tok.split("-")
        check(a != b, f"degenerate equal-word pair produced: {tok}")


# --- 7. empty-pool wordlist is a loud failure, not a silent empty draw -------
def test_empty_pool_is_loud():
    import json
    import os
    import tempfile

    with tempfile.TemporaryDirectory() as d:
        bad = os.path.join(d, "wordlist.json")
        with open(bad, "w") as fh:
            json.dump({"pool_one": [], "pool_two": ["x"]}, fh)
        raised = False
        try:
            c.load_pools(bad)
        except ValueError:
            raised = True
        check(raised, "empty pool did not raise ValueError (silent-empty-draw regression)")


# --- 8. namespace math is the stated exact number ---------------------------
def test_namespace_is_exact():
    p1, p2 = c.load_pools()
    total = len(p1) * len(p2) * len(c.HASH_ALPHABET) ** c.HASH_LEN
    check(len(p1) == 64 and len(p2) == 64, f"pools not 64x64: {len(p1)}x{len(p2)}")
    check(len(c.HASH_ALPHABET) == 32, f"hash alphabet not 32 chars: {len(c.HASH_ALPHABET)}")
    check(total == 64 * 64 * 32 ** 6, f"namespace math changed: {total}")


# --- 9. draw_many honors count and reuses one pool --------------------------
def test_draw_many():
    got = c.draw_many(0, rng=random.Random(1))
    check(got == [], "draw_many(0) should be empty")
    got5 = c.draw_many(5, rng=random.Random(1))
    check(len(got5) == 5, f"draw_many(5) returned {len(got5)}")


# --- 10. demo is deterministic under a seed ---------------------------------
def test_demo_deterministic():
    d1 = c.demo(rng=random.Random(42))
    d2 = c.demo(rng=random.Random(42))
    check(d1 == d2, "demo(seed=42) is not reproducible")


def run() -> int:
    tests = [v for k, v in sorted(globals().items()) if k.startswith("test_") and callable(v)]
    for t in tests:
        t()
    total = _PASSES + len(_FAILURES)
    if _FAILURES:
        print(f"FAIL — {len(_FAILURES)} of {total} checks failed:")
        for f in _FAILURES:
            print(f"  ✗ {f}")
        return 1
    print(f"OK — {_PASSES}/{total} checks passed ({len(tests)} tests).")
    return 0


if __name__ == "__main__":
    sys.exit(run())
Take the whole folder → MIT Python standard library only, deterministic under --seed, headless