Skip to content

Docs

Docs

Everything is one pure function: a hash goes in, an SVG comes out. The rest is where you call it from.

Proposed API. The host and the packages (@blockbuddies/*, PyPI blockbuddies) don't exist yet TBD. The renderer does: this site runs it, , in your browser.

Three layers

LayerWhat you getWhat it needsWho does it
FaceThe buddy for any hash, in any pose, at any size. Forever the same.Nothing. hash = 0x + sha256("blockbuddies/name/v1:" + nickname), where the nickname is generated from the agent idAnyone
LiveA public URL that shows the agent's real state, and a “linked” markA status key (Ed25519) held by the runtime, plus one anchorThe operator sets it up once; the runtime signs
OwnedThe token bound to the agent hash, an “owned” mark, the printA wallet that holds the tokenA person, or an agent wallet on the advanced path

The face never depends on the other two.

URL API coming with mainnet

GET /<hash>.svg                       idle face, 36px, theme auto
GET /<hash>.svg?state=needs-you&size=64&theme=dark&motion=calm
GET /<hash>.png?size=128              for Slack, Discord, email, MCP clients
GET /<hash>.json                      traits, anchors, owner mark, live state if published
GET /<hash>/art.svg                   the print (print, paper, mount)

GET /live/<hash>.svg                  the agent's real state (signed source only)
GET /live/<hash>.json                 {state, since, expires, source, mark}
GET /live/<hash>/events               Server-Sent Events, one event per change

GET /t/base/<tokenId>.svg             token -> agent param (or seed) -> face
GET /r/1.4/<hash>.svg                 pinned renderer version, immutable
  • <hash> is 0x + 64 hex. There's deliberately no /id/<agentId> route: ids would land in URLs, logs and referrers.
  • size snaps to 16, 18, 24, 28, 32, 36, 48, 64, 96, 128, 256, 512, 1024 or 2048.
  • state any state or mood. theme light, dark or auto. motion none, calm (default) or full. badge=1 draws the mark, only when an anchor is verified.
  • ?state= is a pose, not a claim. What can't be faked is /live/… and the mark.
  • Every hash has a buddy, so /<hash>.svg never 404s. SVGs carry no script and no external refs.
RouteCache-Control
/<hash>.svg, .pngpublic, max-age=86400, stale-while-revalidate=604800
/r/<ver>/…public, max-age=31536000, immutable
/live/<hash>.svg, .jsonpublic, max-age=5, stale-while-revalidate=30
/live/<hash>/eventsno-store

Core package TBD

Zero dependencies, no network, no DOM, no clock, no Math.random. These are the signatures of the renderer this site runs.

import { genome, hashFromId, render, STATES, MOODS } from "@blockbuddies/core";

const g = genome(hashFromId("01JAGENTULID"));       // the same buddy DorkOS shows
render(g, { size: 48 });                            // the still: idle
render(g, { size: 48, state: "needs-you" });        // a pose
render(g, { size: 48, mood: "glad" });              // a mood (shows only over idle)
render(g, { size: 48, state: "working", t: 900 });  // t = ms since the state was set
render(g, { art: true, size: 1000 });               // the print
  • t is passed in by you. The renderer never reads a clock, so the same inputs always give the same SVG.
  • from animates a change from the previous expression (200ms, 260ms through offline).
  • list: true (or any size ≤ 32) never loops. hover or focus lets a loop run.
  • motion: full, active-only or still. Use still under reduced motion: every pose and glyph stays, only movement goes.

React package TBD

import { BlockBuddy, useBlockBuddy, useLiveState } from "@blockbuddies/react";

// static: SSR-safe, no client JS needed for the still SVG
<BlockBuddy hash={hash} state="needs-you" size={32} />

// from an agent id: hashed, turned into its nickname, and the nickname seeds the buddy (never sent anywhere)
<BlockBuddy agentId="01JAGENTULID" state={status} size={28} motion="calm" />

// live, from the signed public status (needs @blockbuddies/live)
function AgentChip({ hash }) {
  const state = useLiveState(hash);            // 'idle' until the first event
  return <BlockBuddy hash={hash} state={state} size={24} />;
}

A state change must not re-render the tree: <BlockBuddy> renders once per hash and size and drives poses on refs. For Vue, Svelte, Solid and plain HTML, use the <block-buddy> custom element TBD.

Canvas draw

The same frame, drawn on a 2D canvas. Pure: sizes are relative and time is passed in. Good for p5 and generative work.

import { genome, draw } from "@blockbuddies/core";

const g = genome(hash);
function frame(ms) {
  ctx.clearRect(0, 0, 240, 240);
  draw(ctx, g, { size: 240, state: "thinking", t: ms, hover: true });
  requestAnimationFrame(frame);
}
requestAnimationFrame(frame);

This is that code, running.

MCP tool TBD

{ "mcpServers": { "blockbuddy": {
    "command": "npx", "args": ["-y", "@blockbuddies/mcp"],
    "env": { "BB_AGENT_ID": "01JAGENTULID", "BB_STATUS_KEY_FILE": "~/.config/blockbuddies/key" } } } }
ToolInputOutput
set_avatar_state{ mood | event, reason, ttlSeconds? }what shows after precedence
clear_avatar_state{}back to the runtime state
get_avatar_url{ state?, size?, format?: 'svg'|'png', live?: boolean }a URL
export_avatar{ format: 'svg'|'png'|'gif', state?, states?, size?, path }{ path, alt }
get_identity{}hash, traits, anchors, mark, token if any
  • The operator puts the key file in place. The agent calls tools; it never sees or holds the key.
  • Without a key, set_avatar_state still works locally and export_avatar works offline. Only publishing needs the key.
  • The reason is required for moods. It shows on hover: “Glad · shipped the style guide”.

States and moods

Each one reads by form, face and glyph, never by colour alone.

BuddyNameSet byFormFace
idleruntimestillblinks now and then
thinkingruntimeone block turns a quartereyes look at it
workingruntimea sliding-puzzle stepeyes follow the mover
speakingruntimethe face block pulsesa mouth appears
needs-youruntimethe tile parts along its seam, ! or ? on a warm sloteyes look into the gap. The loudest state.
errorruntimehalves slip, blocks land crookeddazed eyes
offlineruntimeblocks shrink under a muted squareeyes closed
gladagent, with a reasonthe tile lifts oncesmile arcs
curiousagent, with a reasonthe tile tilts toward its gazepupils a little bigger
sheepishagent, with a reasonthe tile shrinks to 0.96eyes look down
celebrateagent or operatorsmall blocks hop oncesmile arcs, then glad

Precedence

What shows when more than one thing is set, highest first:


    
RuleValue
A runtime state beats a moodalways; the mood waits underneath and comes back if its time is left
Mood lifetime10 min by default, 60 min at most
Celebrateone-shot, then glad for 2 min
Busy states (thinking, working, speaking)expire after 120 s unless the next runtime event refreshes them
needs-youheld until seen, up to 24 h; re-nudges every 12 s
A lapsed state falls back toidle, never offline
Loudness in a list (≤ 32px)no loops; busy holds a pose. Only needs-you moves.