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
| Layer | What you get | What it needs | Who does it |
|---|---|---|---|
| Face | The 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 id | Anyone |
| Live | A public URL that shows the agent's real state, and a “linked” mark | A status key (Ed25519) held by the runtime, plus one anchor | The operator sets it up once; the runtime signs |
| Owned | The token bound to the agent hash, an “owned” mark, the print | A wallet that holds the token | A 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>is0x+ 64 hex. There's deliberately no/id/<agentId>route: ids would land in URLs, logs and referrers.sizesnaps to 16, 18, 24, 28, 32, 36, 48, 64, 96, 128, 256, 512, 1024 or 2048.stateany state or mood.themelight, dark or auto.motionnone, calm (default) or full.badge=1draws 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>.svgnever 404s. SVGs carry no script and no external refs.
| Route | Cache-Control |
|---|---|
/<hash>.svg, .png | public, max-age=86400, stale-while-revalidate=604800 |
/r/<ver>/… | public, max-age=31536000, immutable |
/live/<hash>.svg, .json | public, max-age=5, stale-while-revalidate=30 |
/live/<hash>/events | no-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
tis passed in by you. The renderer never reads a clock, so the same inputs always give the same SVG.fromanimates a change from the previous expression (200ms, 260ms through offline).list: true(or any size ≤ 32) never loops.hoverorfocuslets a loop run.motion:full,active-onlyorstill. Usestillunder 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" } } } }
| Tool | Input | Output |
|---|---|---|
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_statestill works locally andexport_avatarworks 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.
| Buddy | Name | Set by | Form | Face |
|---|---|---|---|---|
idle | runtime | still | blinks now and then | |
thinking | runtime | one block turns a quarter | eyes look at it | |
working | runtime | a sliding-puzzle step | eyes follow the mover | |
speaking | runtime | the face block pulses | a mouth appears | |
needs-you | runtime | the tile parts along its seam, ! or ? on a warm slot | eyes look into the gap. The loudest state. | |
error | runtime | halves slip, blocks land crooked | dazed eyes | |
offline | runtime | blocks shrink under a muted square | eyes closed | |
glad | agent, with a reason | the tile lifts once | smile arcs | |
curious | agent, with a reason | the tile tilts toward its gaze | pupils a little bigger | |
sheepish | agent, with a reason | the tile shrinks to 0.96 | eyes look down | |
celebrate | agent or operator | small blocks hop once | smile arcs, then glad |
Precedence
What shows when more than one thing is set, highest first:
| Rule | Value |
|---|---|
| A runtime state beats a mood | always; the mood waits underneath and comes back if its time is left |
| Mood lifetime | 10 min by default, 60 min at most |
| Celebrate | one-shot, then glad for 2 min |
| Busy states (thinking, working, speaking) | expire after 120 s unless the next runtime event refreshes them |
| needs-you | held until seen, up to 24 h; re-nudges every 12 s |
| A lapsed state falls back to | idle, never offline |
| Loudness in a list (≤ 32px) | no loops; busy holds a pose. Only needs-you moves. |