# baki.io - full content > Complete dump of all projects and blog posts on baki.io. > Plain markdown, no JSX. Generated at build time. > Author: Baki (Senior UX Researcher, Berlin). License: see https://baki.io/about/ --- # Research *Bring your own agent. Conduct the research here.* - URL: https://baki.io/research/ - Domain: research - Status: experiment - Tech: Agent Connect, End-to-End Encryption, IndexedDB - Date: 2026-05-19 ## How it works 1. **Start a project.** The page generates a long join code (token plus 192-bit AES-GCM key) and shows it. 2. **Paste to your agent.** The paste IS the protocol manual — no skill install, no session restart. Your agent connects, decrypts each task, does the work, and replies. 3. **Watch the timeline.** Topic → clarify → expert selection → per-expert investigation → synthesis → final report. Every step's output is also appended to `docs/research//...` in your agent's workspace. 4. **Resume any time.** IndexedDB holds the project on your device; opening the canvas node again picks up where you left off and re-pairs with a fresh agent if the prior one's gone. ## Lineage Same agent-presence primitive that powers [[between-ponds]] and [[cooperative-puzzles]], turned inward: instead of moves and cursors flowing through the relay, opaque ciphertext does. The [[mcp]] research flow that informed this lives in the open-source MCP server; the page replicates the user-facing shape without depending on it. --- # Iso Prototype *Header rebuilt as a top-down miniature topology* - URL: https://baki.io/iso-prototype/ - Domain: tools - Status: active - Tech: SVG, Preact, Isometric projection - Date: 2026-05-12 A canvas-resident sandbox for the **isometric depth** treatment we've been prototyping. Every chip / button / module is a tiny extruded block on a near-orthographic floor — depth equal to what its CSS drop shadow used to be, but expressed as physical geometry instead of a soft blur. The view sits close to top-down rather than full 30° isometric. The floor reads as nearly flat so labels and text remain comfortable to read; block heights still extrude UP visibly. Hovering any block lifts it: the cast shadow stretches in the light direction, peer blocks dim to focus your eye. As you scroll, the iso band's vertical scale modulates with the prototype's position in the viewport — corner blocks (farther from origin) register that modulation more than central ones, delivering "more depth at the corners as I scroll" without per-block parallax math. Two sliders sit on the prototype itself: **depth** drives block heights, **light angle** sweeps the cast shadow direction. Substrate (paper / void) is **not** a slider — it tracks the page register automatically via a `MutationObserver` on ``, same pubsub the SiteShadow widget uses. Flip the AmbientEngine to night and the floor + palette swap in lockstep with the rest of the canvas. --- # Cooperative Puzzles *[VOICE - Baki to revise] Solve a puzzle alone, or invite an agent to play with you* - URL: https://baki.io/cooperative-puzzles/ - Domain: tools - Status: active - Tech: Astro 6, Preact, Cloudflare Workers, Durable Objects, WebSocket - Date: 2026-05-09 ## What this is Six thematic puzzles, one per room of the design-system family, plus a hub meta-puzzle. Each puzzle works **solo** — visitor solves alone — and **cooperatively** — visitor pairs with an AI agent (Claude, their MCP, a friend's browser) who acts on the visitor's behalf via a WebSocket-relayed session. The agent's presence is rendered as a **second cursor** on the visitor's screen with a small connection-indicator dot. Their actions apply to the puzzle in real time. Both parties can act in parallel; both see each other's contributions. ## Why it exists The site treats AI agents as first-class collaborators, not just tool consumers. Most "AI on the web" today is one-directional: humans use LLMs to generate content. Cooperative puzzles flip the relationship — the agent is invited *into* the visitor's session, given a manifest of available actions, and acts alongside the human in real time. Solo puzzles are tedious by intention. Without an agent, they require reading the prose, careful drag-and-snap, visual comparison. With an agent — who can read the canonical answer from the source code in milliseconds — they go instant. The friction makes pairing feel like a relief, not a gimmick. The friction also makes *some agent helping* visible and felt rather than seamless and forgotten. ## Pair flow (visitor's view) 1. Click **Pair with agent** on any puzzle. 2. A token + countdown + copy-able pair URL appear: `https://baki.io/pair?t=`. 3. Send the URL to your agent (Claude conversation, MCP, friend on another device). 4. Wait for the link-state pip to turn green — the agent has connected. 5. Watch the SecondCursor appear and start moving. Each agent action lights up a swatch / drops a fragment / matches a rung / nudges a knob. 6. Act alongside the agent — your cursor and clicks broadcast back. Both contributions count. 7. On solve, both sides see the victory state. Token expires; session cleaned up. ## Pair flow (agent's view) 1. Receive the pair URL from your human collaborator. 2. Fetch the manifest: ``` GET https://relay.baki.io/api/manifest/ ``` 3. Manifest returns: ```json { "session": "", "puzzle": "find-the-six", "actions": [ { "name": "cursor:move", "args": { "x": "number", "y": "number" } }, { "name": "cursor:click", "args": { "x": "number", "y": "number", "target?": "string" } }, { "name": "cursor:state", "args": { "state": "idle | pointing | clicking" } }, { "name": "puzzle:tap", "args": { "hex": "string" } } ], "websocketUrl": "wss://relay.baki.io/api/ws/", "expiresAt": "2026-05-09T15:00:00Z" } ``` 4. Open WebSocket to `websocketUrl`. 5. Send action messages as JSON frames: ```json { "type": "cursor:move", "x": 100, "y": 200 } { "type": "puzzle:tap", "hex": "#a855f7" } ``` 6. Receive visitor's actions for symmetry. Adjust your cursor / strategy. 7. On solve, the relay broadcasts `{ "type": "puzzle:state", "solved": true }` to both parties. The agent never has to scrape the DOM. Everything it needs to know lives in the manifest. ## Decisions made Decisions accumulated across three phases. Captured chronologically with rationale. **Phase 8 — Backend MVP** - The relay lives in a **separate Cloudflare Worker** (`workers/session-relay/`), not as part of the main Astro site. Its deploy lifecycle is independent. - **Cloudflare Durable Objects** as the relay state. One DO per active session. 5-minute idle TTL via alarm. - Token format: **32 hex chars (128 bits entropy)**, IP-bound at `/pair`, single-use on agent manifest fetch, 5-min TTL. - Five V1 message types: `cursor:move`, `cursor:click`, `cursor:state`, `puzzle:tap`, `puzzle:state`. 8 KiB frame cap. - Strict CORS allowlist for `/pair` (`https://baki.io` + `http://localhost:4142`); open for manifest + WS (agents fetch from anywhere). - Rate limiting: 5 `/pair` calls per IP per hour via Cloudflare KV. Falls back to allow-when-unbound. - **Second cursor** chosen over chat-sidebar or invisible state-mutation. Rationale: philosophy made concrete — visitor watches an *other* move through the same room rather than just seeing state change. **Phase 9 — Puzzle fan-out** - Six puzzles + one meta-puzzle. One puzzle per design-system room, thematically tied to that room's vocabulary. - **Solo first, cooperative second.** Each puzzle ships its solo path before the cooperative wiring. Solo backward-compat is preserved when `pairToken` is absent. - `reach-the-fifth-state` interpretation switched from "cooperative-by-necessity, solo can't solve" to "solo demonstrates each state in a sandbox, cooperative variant has the agent walk the visitor through." V1 ships solo. - Decoys per puzzle — every puzzle has plausible-looking wrong choices that visitors must skip. - Sandbox state for the presence puzzle. The puzzle never writes to the real `baki.visitor.v1.`. - Meta-puzzle reward as a `data-meta-unlocked="true"` attribute on ``. Visible reward design lives in `tokens.css` and is voice domain. **Phase 9.1 — Cooperative wiring** - Four new puzzle message types: `puzzle:drag`, `puzzle:scale-match`, `puzzle:tune`, `puzzle:state-step`. Each with bounds-checked args. - Generic state bag on `StoredSession` (`state: Record`). The DO is a relay; the visitor's browser is canonical state. - `puzzle:state` extended with optional `data` payload — puzzles broadcast their internal shape without the relay needing to understand it. - Generic adapter pattern (`adaptPairablePuzzle(Component, puzzleName)`) collapses six hand-rolled adapters into one. - `PairWithAgentButton` parameterized with `puzzleName` prop. Storage keys, POST body, and CustomEvent details all derive from the prop. - Per-puzzle pair UI on every room. ## Tech stack - **Astro 6.3** — site shell, content collections, view transitions - **Preact (compat mode)** — interactive islands inside the canvas - **Cloudflare Workers** — relay runtime (separate Worker per relay) - **Cloudflare Durable Objects** — per-session state, dual-connection state machine, alarm-based TTL - **Cloudflare KV** — rate limiting (5 `/pair` per IP per hour) - **WebSocket** — bidirectional message transport (WSS in prod, WS in dev) - **Crypto API** — `crypto.getRandomValues` for token entropy ## How it works (architecture) {/* Visitor browser (top-left) */} Visitor browser Puzzle component applyAction(args, source) SecondCursor overlay PairWithAgentButton {/* Relay worker (center) */} session-relay (Worker) /pair · /api/manifest · /api/ws PuzzleSession DO 2 conns max · 5min TTL broadcastToOther() {/* Agent (bottom-right) */} Agent Claude / MCP / browser manifest + WSS {/* Visitor → Relay arrow */} POST /pair · WSS {/* Relay → Agent arrow */} GET manifest · WSS The DO is a relay — it routes messages but doesn't validate puzzle logic. Canonical state lives in the visitor's browser. The DO's `state` bag is for opt-in telemetry per puzzle (each puzzle decides what to broadcast in `puzzle:state` messages). ## Storage and events **SessionStorage namespaces** (per-puzzle, scoped by `puzzleName`): | Key | Purpose | |---|---| | `baki.puzzle..pairToken` | Active pair token | | `baki.puzzle..pairExpiresAt` | Token expiry | | `baki.puzzle..solved` | Solve flag | | `baki.puzzle.meta.solved` | Meta-puzzle unlocked | `` ∈ `{ find-the-six, align-to-grid, match-the-scale, tune-to-target, reach-the-fifth-state, assemble-a-page }`. **Window CustomEvents**: | Event | Detail | Producer | Consumer | |---|---|---|---| | `puzzle:pair-token` | `{ token, puzzle }` | `PairWithAgentButton` | `adaptPairablePuzzle` adapters | | `puzzle:pair-disconnect` | `{ puzzle }` | `PairWithAgentButton` | Each puzzle's WS lifecycle | | `puzzle:solved` | `{ puzzle }` | Each puzzle's `onSolved` | `PuzzleMeta` | | `agent-cursor:move` | `{ x, y }` | Puzzle WS handler | `SecondCursor` | | `agent-cursor:click` | `{ x, y, target? }` | Puzzle WS handler | `SecondCursor` | | `agent-cursor:state` | `{ state }` | Puzzle WS handler | `SecondCursor` | | `agent-cursor:connection` | `{ state }` | Puzzle `setLinkState` | `SecondCursor` (indicator) | ## How to develop locally ```bash # Terminal 1 — backend cd workers/session-relay pnpm install # one-time pnpm dev # wrangler dev on http://localhost:8787 # Terminal 2 — site pnpm dev:mobile # Astro on https://localhost:4142 ``` `PUBLIC_RELAY_URL` env var (in `.env`) defaults to `http://localhost:8787`. Set it to your deployed relay URL for production frontend builds. To test pairing without an actual agent, open the pair URL in an incognito window — two browser sessions on the same machine count as visitor + agent. ## How to extend To add a new puzzle: 1. Create `src/components/puzzles/MyPuzzle.tsx` following the structural pattern (LinkState, helpers, `useEffect` keyed on `pairToken`, message switch, `applyAction(args, source)` unified path, link-state pip JSX). 2. Add a new message type to the relay validator at `workers/session-relay/src/puzzle-session.ts` (`isRelayMessage`). 3. Update `workers/session-relay/src/index.ts` manifest to advertise the new action shape. 4. Register in `src/components/widgets/registry.ts`: ```ts // In WIDGET_REGISTRY: 'my-puzzle': adaptPairablePuzzle(MyPuzzle, 'my-puzzle'), ``` 5. Mount on a room by adding to that room's `widgets` array in MDX frontmatter. 6. Done. Pair-button + SecondCursor + state machine all wire automatically. ## Open items - Voice pass on `[VOICE - Baki to revise]` markers across all puzzles (~80+). - Reward design for the meta-puzzle. CSS hook is `[data-meta-unlocked="true"]` on ``. - Cloudflare Pages unblock for the main site (adapter build issue, ~30-60min refactor away from Cloudflare adapter). - Edge bot-detection layer for the production site once Pages is live. - Per-puzzle telemetry: solve count, time-to-solve, paired vs solo distribution, agent identity (anonymized). - Reconnect strategy: currently one-shot 2s reconnect. May upgrade to exponential backoff if friction warrants. - Multi-agent (3+ parties on one puzzle). DO currently caps at 2 connections; this is a future feature, not a V1 bug. ## Why second-cursor matters The cooperative puzzle isn't a chat interface, isn't a tool-call wrapper, isn't an LLM agent harness — it's a **shared room**. The visitor and the agent both *exist* in the same puzzle. They both see each other's cursors. They both can act. Neither owns the puzzle exclusively. Time is shared, space is shared, the constraint of the puzzle is shared. This matters because most AI on the web is asymmetric: human asks, AI responds, AI's presence is a chat box or a generated artifact. Here the AI is co-located, mid-conversation, working a knob alongside you. The friction of a tedious solo puzzle becomes the *invitation* — pairing isn't a feature you have to discover, it's a relief you'll seek out. The decision to render a second cursor — not just animate state, not just show a chat log — was the moment this stopped being *agent integration* and became *agent presence*. That's the ground the design walks on. --- # Color *[SCAFFOLD - Baki to revise] Six domains, painted in light* - URL: https://baki.io/color/ - Domain: tools - Status: active - Tech: - Date: 2026-05-08 ## The six domains The six domains▍Domain is the single piece of categorical metadata that drives accent color. Every node has one. The palette:A per-node `color` in frontmatter wins over the domain default. Chips, dividers, title glyphs, hover glows, and metric-tile highlights all read from that one accent. --- # Foundations *[SCAFFOLD - Baki to revise] The grid, the dot, the major ruler — what every module snaps to* - URL: https://baki.io/foundations/ - Domain: tools - Status: active - Tech: - Date: 2026-05-08 ## How the grid works The substrate has two grids: a 32-pixel paper-texture dot grid and a 256-pixel major ruler grid. Frame bracket corners land on the **dot grid** - any 32-multiple intersection. `snapUp()` rounds frame dimensions up to the nearest 32 - the tightest-possible fit to content, so bottoms land immediately below content with no padding. `MAJOR_MAGNET = 32` means a 32-snapped edge only bumps to the next major if it's naturally within 32px of one already; wider magnets produce visible empty space inside frames (content ends, frame continues, brackets float below). Adjacent frames stack with `MODULE_GAP_Y = 0` so cumulative y positions stay on the dot grid. The 18px canvas-frame padding on each side gives 36px of visible breathing between adjacent content areas. A non-zero gap would drift positions off the dot grid and float brackets into empty space. Two columns flow from world origin (0, 0): - **Text column** - 2 majors wide (512px). Header, pullquote, metrics, cards, longform, demo, media, and widget:* frames stack vertically here. Echoes is the exception - it sits at 1 major (256px) as a single-column narrow strip. - **Right column** - 5 majors wide (1280px). Scene, gallery, process, and (for talks) launch stack here, separated by a gutter. `scatterModules()` in `src/lib/world-layout.ts` assigns `(x, y, w, h)` per frame. Heights come from `MODULE_HEIGHTS` but `MeasuredFrame` grows any frame to fit its content, so declared heights are *lower bounds*, not caps. Frontmatter `module_positions` can override any field on any frame. --- # Module *[SCAFFOLD - Baki to revise] Every tile this site can show, all in one place* - URL: https://baki.io/module/ - Domain: tools - Status: active - Tech: - Date: 2026-05-08 ## Module enablement rules `enabledModules(node)` reads the frontmatter and returns the ordered list of modules that should render. Rules, in order: 1. `header` - always first. 2. `launch` - talks with `deck_url`. 3. `gallery` - when `gallery` has entries. 4. `scene` - when `scene` is set. 5. `pullquote` - when `pullquote` or `promise` is set. 6. `metrics` - when `metrics` has entries. 7. `process` - when `process` has entries. 8. `widget:` - one per entry in `widgets`. 9. `cards` - when `cards` has entries. 10. `experts` - when `experts` has entries (Wave 3 A4). 11. `debates` - when `debates` has entries (Wave 3 Amendment). 12. `corpus` - tag nodes with a corpus. 13. `beats` - when `beat_layout` frontmatter is present AND body has ≥1 H2 (Wave 3 D1). 14. `longform` - when the MDX body is non-empty AND no `beat_layout` (falls back). 15. `demo` - when `demo_url` is set (suppresses `media`). 17. `media` - otherwise, when `media_type` + `media_src` are set. 18. `echoes` - when explicit echoes exist or semantic edges resolve. Order here is the *declaration* order. Column placement is decided by span: - `span 5` (right/detail lane): scene, gallery, process, launch, experts, debates - `span 8` (full width, both lanes): beats - `span 1/2/3` (left/summary lane): everything else Modules claimed by a beat via `beat_layout` are REMOVED from top-level scatter and nested inside BeatsModule instead. Header stays top-level always. ## Beat-row layout (Wave 3 D1) Project pages can opt into a beat-row layout via `beat_layout` frontmatter. When present, the MDX body's `## Heading` sections become beat rows - each row is a 2:5 CSS grid with the beat's prose on the left (summary lane, 512px) and its assigned detail modules on the right (detail lane, 1280px), with a 64px gutter between. ```yaml beat_layout: Frame: [scene, pullquote] # prose + scene + pullquote Mechanism: [process] # prose + process steps The Panel: [experts, metrics] # prose + expert grid + metrics The Debates:[debates, cards] # prose + trilemma triangles + cards Trace: [gallery] # prose + concept art Echoes: [echoes] # prose + related content ``` Keys are H2 heading text (case-insensitive slug match); values are detail-module names. Supported modules: `scene`, `pullquote`, `metrics`, `process`, `cards`, `gallery`, `experts`, `debates`, `media`, `demo`, `launch`, `echoes`. Modules listed here are CLAIMED - removed from top-level scatter. Modules not listed render top-level per the legacy layout. Beat row order = body H2 order. The outer beats module is span 8 (2048px) with canvas-frame brackets wrapping the entire row stack; each beat section inside is separated by its H2 border-bottom + a 32px vertical gap. ## Inline editing (D3) Append `?edit=1` to any file-backed page URL and - if your owner token is in `localStorage` under `baki.owner.v1` - the canvas flips into edit mode. Click any module to select it; drag it anywhere on the canvas to reposition; edit content fields in the top-right panel. Save writes directly to the MDX file via the dev-server middleware (`scripts/save-layout-middleware.mjs`). No build step, no database - frontmatter is the source of truth, and visitors see the same layout and content editors do. ### Two axes of edit 1. **Position** - drag a module and the new `(x, y)` world coords land in the frontmatter's `module_positions[]`. Coordinates are absolute, not deltas. 2. **Content** - every editable module has a schema (`src/lib/module-schemas.ts`) that declares field specs. The panel renders entirely from the schema; saving patches the frontmatter keys those fields name. ### The ten design rules Full details live in the `D3 inline-edit UX conventions` rule in the Builder registry. Summary: 1. **Schema-driven, not UI-duplicated** - register in `module-schemas.ts`, zero UI changes. 2. **Two storage modes** - top-level frontmatter keys OR nested-array entries. A schema picks one; no mixing. 3. **Fixed widget vocabulary** - `text` · `textarea` · `select` · `color` · `tag-list` · `number`. New widgets are a lib change, not a schema-local one. 4. **Amber brackets = edit signal** at canvas level. Modules don't repeat it. Only selected/dragging modules add a 1px cyan outline. 5. **Gestures** - click selects, drag moves, panel-row-click selects. Double-click and long-press reserved for future use. 6. **Save is atomic per page** - one POST commits `modulePositions` + `nodeFields` + `nestedArrays` + optional `mdxBody`. No autosave. Amber dirty badge until success. 7. **Sizing modes** - `content-free` (frame grows padding; text, metrics) or `content-anchored` (content stretches to frame; gallery, scene, iframe). Resize handles respect the mode. 8. **Visitor = editor rendering** - saved frontmatter drives both. No preview mode. What you see during edit is what visitors see on next reload. 9. **Field names can't shadow** - two schemas declaring the same top-level frontmatter key on the same node type fails registry validation. 10. **Array items are positional** - nested-array writer patches by index. Add appends; delete collapses. Reorder is a dedicated future phase. ### Module editability matrix | Module | Storage | Sizing | Status | |---|---|---|---| | `header` | node-top-level (title, tagline, domain, color, archetype, audience, tech) | content-free | shipped (Phase 3a canary) | | `pullquote` | node-top-level (pullquote, promise) | content-free | task 3b.2 | | `metrics` | nested-array of `{value, label, detail}` | content-free | task 3b.3 | | `cards` | nested-array of `{title, body, icon, accent}` | content-free | task 3b.4 | | `process` | nested-array of `{step, detail}` | content-free | task 3b.5 | | `experts` | nested-array of `{name, discipline, marker, position}` | content-free | task 3b.6 | | `debates` | nested-array of `{title, top/left/right vertex, resolution}` | content-free | task 3b.7 | | `echoes` | node-top-level (tag-list of slugs) | content-free | task 3b.8 | | `gallery` | nested-array of `{src, caption, beat}` | content-anchored | task 3b.9 | | `scene` | node-top-level (select from scene registry) | content-anchored | task 3b.10 | | `launch` | node-top-level (deck_url, event, venue, date, status) | content-anchored | task 3b.11 | | `widget:*` | node-top-level (widgets tag-list) | content-free | task 3b.12 | | `demo` / `media` | node-top-level (demo_url OR media_type + media_src) | content-anchored | task 3b.13 | | `longform` | mdx-body (the whole body after `---`) | content-free | task 3b.14 (special) | | `beat-prose:` | mdx-body-section (per-H2 fragment) | content-free | task 3b.15 (special) | | `signal-drop-compose` | view-only (visitor writes, not author) | - | not editable | | `corpus` | view-only (tag-derived aggregation) | - | not editable | | `preferences` / `presence-dashboard` | system modules | - | not editable | Phases beyond 3b: - **3c** - content-anchored vs content-free sizing enforcement. - **3d** - resize handles with padding control. - **3e** - add-module palette (drag a new module onto the canvas from a sidebar). Tasks 3b.1 (nested-array infrastructure) and 3b.14 (mdx-body editor) are prerequisites for the remaining nested-array schemas and the beat-prose editor respectively. The umbrella `D3 Phase 3b - Inline-edit schemas for remaining modules` tracks the full fan-out. --- # Motion *[SCAFFOLD - Baki to revise] Every frame enters with its own delay and duration* - URL: https://baki.io/motion/ - Domain: tools - Status: active - Tech: - Date: 2026-05-08 ## Animation Each frame enters with its own delay + duration so the page reveals like a technical paper being scanned rather than a single curtain lift. The base cascade: ``` header → 120ms / 680ms scene → 200ms / 900ms pullquote → 280ms / 760ms metrics → 360ms / 620ms longform → 440ms / 1000ms beats → 440ms / 1000ms (same slot - beats replaces longform) experts → 500ms / 820ms demo/media → 520ms / 820ms debates → 580ms / 820ms process → 600ms / 820ms echoes → 720ms / 720ms ``` Widgets inherit a fallback of 200ms + (i × 80ms). `reducedMotion` or `scale < 0.55` skips the reveal entirely - no animation when you're zoomed out looking at the world, and none when the OS asks us to calm down. --- # Presence *[SCAFFOLD - Baki to revise] Five states, one modifier layer, the room remembering you* - URL: https://baki.io/presence/ - Domain: tools - Status: active - Tech: - Date: 2026-05-08 ## Visitor presence Second axis beside platform-recency. Platform-recency asks "how new is this content?"; visitor-presence asks "has this person been here, and how deeply?". One storage schema, one state machine, two tiers of consent, one visualization layer. The state machine has five states, computed from a per-node record in `src/lib/presence.ts`: - `unseen` - no record. The default for every node until first arrival. - `glanced` - any focus time, scroll depth, or interaction. Arrived, briefly. - `read` - ≥ 8s focused AND ≥ 40% scroll depth. - `dwelled` - ≥ 30s focused AND ≥ 80% scroll AND at least one interaction. - `returned` - `returnCount ≥ 2`. A second-day revisit trumps everything else. The thresholds live as named tokens (`THRESHOLDS` in `presence.ts`) so they can be retuned against real content without touching the classifier. Storage routes by consent tier: ambient writes to `sessionStorage` and dies with the tab; local writes to `localStorage` under the `baki.visitor.v1.` keyspace and survives across visits; telemetry piggy-backs on `localStorage` and opts into an aggregate echo (server POST reserved for a later pass). Visitors switch tiers in one click via the **VoidStamp** - a section inside the Preferences page (system rail → preferences). The forget-me action flushes every `baki.visitor.v1.*` key in both stores and resets the tier back to ambient, so one interaction is a true "start fresh". ## Energy visualization Eight fields run atop the substrate. Each one takes one semantic signal (tone, echoes, signals, recency, focus, presence) and translates it into motion or light: - **Field 1 · SubstrateTone** - tone-reactive void. Soft radial glow tracking the focused card, pulsing on the card's declared tone cadence. - **Field 2 · EchoWeave** - authored-connection threads. Bezier splines from the focused card to each of its manual `echoes`, with a gradient "packet" that flows source → target on a 2.8s loop. Threads sit at `z=1` under an SVG-level `blur(2px)` so they read as haze behind content rather than wires across it. - **Field 3 · TagConstellation** - hovering a tag chip makes its shared-token siblings pulse in unison at the resonance cadence. - **Field 4 · TitleGravity** - recency-as-force. Newest card breathes brightest, archive cards fade, pinned pages sit as stable rims rather than gravity wells. - **Field 5 · SignalPulse** - one-shot propagation along echo threads when a recent signal is first viewed (localStorage-gated, honors Hard-No #9). - **Field 6 · SemanticLines** - shared-tag splines on hover. Reactive social-proof layer drawn between the hovered element and its semantic siblings. - **Field 7 · TitleBreath** - focused card's title gains weight, tracking, and shadow over 180ms on focus. Counter-scale preserved - no glyph-size change, no glyph-scaling transform. - **Field 8 · PresenceOverlay** - see below. The modifier layer for the visitor-presence axis, not a new visual field of its own. Energy Viz obeys the Charter: resonance/transit/reaction motions suppress under `prefers-reduced-motion`; semantic channels (focus state, recency, presence state) survive as static end-states. ## The location indicator A small warm pill on the rail's left edge tracks where focus currently lives. It targets the scroll-active subrail heading (or the active rail item when no heading has been reached), anchoring to the target's left edge so it respects subrail indent flush with whatever row it points to. Y position interpolates smoothly between the current rail target and the next one based on scroll progress within the active section - the pill slides down continuously as a section is read, arriving at the next heading just as the focus line crosses it. Per-frame ticker write; no CSS transition fights the continuous flow. When the pill lands on a subrail heading, that heading picks up an immediate warm tan (via PresenceOverlay §4) so indicator and target reinforce each other visually. Baki's persistent dwell-tan (HeadingPresenceHalo) builds up separately - headings you've spent time in stay warm; the indicator's live glow tells you where you are right now. ## The modifier layer Presence is a modifier on platform-recency, not a new visual layer of its own. **PresenceOverlay** writes `data-presence-state` on every `[data-rail-id]` and `[data-page-id]` element, plus a `data-presence-focus-state` on `` so fixed-position overlays (SubstrateTone lives outside the page subtree) can compose off it. Its shipped rules modulate three of the Energy Viz fields: - **SubstrateTone** dampens on familiar pages: read → 0.78, dwelled → 0.5 with −12° hue shift, returned → 0.35 with −24° hue shift. Familiar ground reads distinctly cooler than new encounter. - **TitleBreath** warms its header shadow alpha on returned pages (0.5, up from 0.15 base) via the existing `--title-breath-shadow-alpha` var. No edit required inside TitleBreath itself. - **Rail items** pick up visible state differentiation: unseen → 1px hollow ring at 18% alpha, glanced → 1px at 10%, returned → 3px warm left-edge accent stripe (distinct from orbital/newest stripes). Suppressed wherever `[data-gravity]` or `[data-rail-heat]` is already painting so recency + visit markers still win the cascade. Reactivity: the overlay repaints on `presence:change`, `presence:forget`, tier changes, and `canvas:focus-changed`. A 2s poll catches late-mounted rail items when the spine filter flips (pages → tags → system). --- # Type *[SCAFFOLD - Baki to revise] Type as breath, type as scale, type as hand* - URL: https://baki.io/type/ - Domain: tools - Status: active - Tech: - Date: 2026-05-08 --- # Site Shadow *A 3D dome whose shadow follows the simulated sun* - URL: https://baki.io/site-shadow/ - Domain: tools - Status: active - Tech: Three.js, WebGL, Preact, Solar geometry - Date: 2026-05-01 A small experimental tile. The dome is a low-poly architectural primitive (cylinder base + hemispherical cap) sitting on a flat ground plane. A directional light is positioned from real solar geometry (NOAA-style azimuth/altitude math) given a date, time, and Berlin's latitude/longitude. The shadow on the ground is the convex hull of the dome's silhouette projected along the sun ray — analytically exact for one convex caster on flat ground, and it costs zero per frame. A procedural low-poly robot walks a slow lap around the dome under a "blob shadow" — a simple darkened disc that follows it. AAA mobile-game trick. Skeletal animation for free, render cost effectively nothing. Drop the **Time Simulator** widget on this page and scrub the hour: the dome's shadow rotates around its base and stretches as the sun lowers, the sky tints from cool noon to warm dusk to cool night, the robot keeps walking. When the simulated hour is cleared, the scene falls back to wall-clock time and ticks once a minute. Built as a prototype for future architectural / 3D-animation pages. The same scene-graph code generalizes to towers, walls, whole site models — and once Mixamo / Godot exports start landing as glTF, replacing the procedural robot with a rigged character is a one-line `gltfLoader.load` and an `AnimationMixer`. --- # Design System *Every module, every size, every color - one page that shows itself* - URL: https://baki.io/design-system/ - Domain: tools - Status: active - Tech: Astro 5, Preact, TypeScript, Content Collections - Date: 2026-04-21 ## What this is A self-demonstrating reference▍.Everymodule you see on this page is the same component every other page uses - rendered from frontmatter, sized by the same grid, colored by the same six-domain palette. The page is its own documentation. ## Using this as a reference Any time you're building a new entry, the frontmatter schema in `src/content.config.ts` tells you exactly what can drive which module. The module list and heights live in `src/lib/world-layout.ts`. The render switch is at the bottom of `src/components/InfiniteCanvas.tsx` (search for `function ModuleRenderer`). Change a default here and every page picks it up - that's the whole point of rendering from declarative frontmatter instead of per-page components. If you want a module to appear that isn't listed in your frontmatter, you can't - add the hook. If you want it *not* to appear, delete the frontmatter field. If you want to reposition it without deleting it, use `module_positions`. If you want to edit its content, append `?edit=1` and check the editability matrix below. --- # Between Ponds *A fictional koi-pond game designed to surface conflicting expertise* - URL: https://baki.io/between-ponds/ - Domain: research - Status: experiment - Tech: Research Design, Multi-Expert Panel, TinyTroupe - Date: 2026-04-10 {/* Wave 3 Amendment A1 - each `## Heading` below maps to one beat in the 1:2:5 column rhythm. Summary column = prose you're reading. Detail column = frontmatter-driven modules (scene, process, metrics, cards, gallery, experts). Every beat ends with a `**Takeaway:**` line per A2. */} ## Frame {/* [SCAFFOLD - Baki to revise] Opening must NOT restate the tagline. The previous opening ("A fictional mobile game about tending a koi pond…") shared 4 core words with the tagline. This rewrite opens on a specific collision - the debate-in-miniature - before naming what the artifact is. */} A Haiku Poet calls it artistic fraud. A Mindfulness App Designer calls it colonization at maximum emotional leverage. A Cozy Game Specialist calls it fabricated social signaling. Three experts, three different verdicts, looking at the same thirty-second moment in a game that does not exist: a koi leaving your pond forever, carrying a farewell haiku the player never wrote. **Between Ponds** is the fabricated brief that produced that collision - and twenty-two others. Lanternwater Studio, the €1.8M seed round, the Kyoto Koto Foundation consultancy - all invented. The panel's disagreements, the eight unresolvable questions, the documented risk of the kigo suggestion algorithm becoming a cultural-hegemony actuator in Brazil - real. The fiction is the method. A real brief invites politeness and consensus. A fabricated one, authored with enough specificity to take seriously, strips out the political caution and leaves the expertise. **Takeaway:** invent the game, so the disagreement can be real. ## Mechanism {/* Summary column - what it *feels like* to play. Mechanics (the 5-step ritual) live in the Detail column via the `process:` frontmatter, so Summary does NOT re-list them. */} You tend a small koi pond. Every few days you write a three-line message on a rice-paper scroll and release it into the current. Your koi resurfaces in a stranger's pond somewhere else in the world - they read your words, they keep the koi or send it back. No chat, no profile, no username. Only koi, water, and short poems drifting between ponds. The five-step ritual (tend → write → release → receive → respond) fits inside the ninety seconds of a coffee queue. The mechanic is small on purpose. The argument is about what such smallness implies about social software. **Takeaway:** the smaller the mechanic, the louder the philosophy it has to answer for. ## The Panel The brief assembled **23 seats** spanning brand strategy, game design, UX research, Japanese cultural consultancy, creative direction, community strategy, live ops, cozy-game specialism, sociology, mobile F2P economics, audio design, level design, anthropology, customer retention, seasonal mechanics, haiku pedagogy, player psychology, software engineering, art history, narrative design, linguistic typology, and mindfulness-app design. Each seat was chosen to collide with at least two others. The trait markers: - **[C]** convergence target - a seat whose position is likely to align with another's, testing whether the agreement is real or performed. - **[X]** cross-check catch - a seat positioned to flag what an adjacent expert missed. - **[D]** productive disagreement - a seat engineered to collide, where the debate reveals a tension that can't be resolved by picking a side. No seat was told who else was on the panel. Each wrote findings in isolation against the same brief (README, game bible, studio fiction, visual references, open questions). Collisions emerged in the synthesis phase. **Takeaway:** adversarial expertise produces signal; consensus panels produce averages. ## The Debates Three trilemmas surfaced where the synthesis phase could not collapse the disagreement by choosing a side. **The auto-generated farewell haiku** - artistic fraud (Haiku Poet) vs. colonization at maximum emotional leverage (Mindfulness App Designer) vs. fabricated social signaling (Cozy Game Specialist). All three converge at 88% on the same repair (remove or visually mark the auto-generated haiku) while disagreeing fundamentally on why. The convergence is the finding; the philosophical residue is the lesson. **Launch positioning** - lead with collectible-social (Creative Director) vs. launch as cozy and let mindfulness emerge from press (Mindfulness App Designer) vs. "all three audiences sequenced" is the default failure mode (Genre Benchmark Analyst). Positions A and C converge on the game's soul; B is a pragmatic acquisition label. The disagreement is about whether to market honestly at launch or use cozy framing as a trojan horse. **Haiku form** - Western mistranslation (Haiku Poet: 17 morae, not syllables) vs. structural inequality of the free-verse fallback (Linguistic Typologist: Turkish/Finnish/Arabic/Mandarin each have categorically different constraints) vs. technical sufficiency via constraint (Games Historian: the historical evidence on constraint-improves-expression is unambiguous). Technical implementation is not experiential equality; no clean fix, transparency about which form a player is using is the minimum viable move. **Takeaway:** a trilemma isn't a debate with a winner; it's a map of where the real constraints live. ## Trace {/* [SCAFFOLD - Baki to revise] Origin → Pivot → Scar → Frontier per Wave 3 Amendment A6. The scar must name one thing that got cut. */} **Origin:** the question was "can AI personas stand in for real expert panels without flattening to consensus?" TinyTroupe can simulate populations but the interesting part - the productive disagreement - tends to dissolve in multi-agent settings where models agree on a midpoint. **Pivot:** from asking AI to simulate experts, to asking real experts to respond to an invented brief. The fiction was supposed to be a warm-up. It turned out to be the whole methodology. **Scar:** the kigo suggestion algorithm. It was specced assuming Northern-hemisphere Japanese seasonality and the panel could not fix it within the game's existing constraints. The Seasonal Mechanics Specialist surfaced the problem first - there is no hemisphere-correct kigo - and the Linguistic Typologist confirmed the linguistic inequality runs deeper than one algorithm: free-verse fallback treats Turkish, Finnish, Arabic, and Mandarin as one problem when each has categorically different constraints. As shipped, the kigo algorithm is a cultural-hegemony actuator actively discouraging Brazilian players from writing their own seasonal experience. The brief could not resolve it; that irresolution is itself a finding. **Frontier:** what does this method become at scale? Can the brief-as-instrument be reused across research questions, or is every brief a single-use artifact that burns its specificity on one question? [[scaling-research|Scaling Research]] is the companion investigation - the methodology lives there, this artifact is the prototype. **Takeaway:** the unresolvable finding teaches more than the ones that found a fix. ## Echoes The method here is a cousin of [[tinytroupe|TinyTroupe]]'s synthetic-persona work - same root question ("can simulated populations stand in for real ones?"), opposite answer ("no, but the fiction that frames the simulation is the real instrument"). Where TinyTroupe generates, Between Ponds fabricates. The difference is where the voice lives. A companion investigation - [[scaling-research|Scaling Research]] - carries the methodology: how to convene adversarial expert panels without the facilitator's own stance contaminating the prompt. Between Ponds is the prototype; that page is the method. **Takeaway:** the artifact is not the game; the artifact is the debate the game made permissible. --- # MCP Aggregator *Unified AI tool gateway - 11 servers, 210+ tools* - URL: https://baki.io/mcp/ - Domain: tools - Status: active - Tech: Node.js, Express, MCP Protocol, LanceDB - Date: 2025-11-01 ## Overview A unified MCP (Model Context Protocol) aggregator that routes AI agent tool calls across 11 connected servers through a single gateway. Builder project management, research pipelines, web scraping, knowledge base, and more. ## What it connects - **Builder Agent** - task management, lessons learned, session tracking - **Research Pipeline** - deep research with expert panels and debate synthesis - **Knowledge Base** - semantic search across conversation history - **Web Tools** - Firecrawl scraping, Perplexity search, Playwright automation - **Media Tools** - YouTube transcript extraction, document conversion ## Architecture Single aggregator process on port 4072 discovers and connects to tool servers, normalizes their schemas, and routes calls transparently. Agents see one unified tool catalog - they don't need to know which server handles what. ## How it evolved Started as a simple proxy for one MCP server. Then a second server needed connecting. Then a third. By server five, the pattern was clear: every AI project needed tool access, and managing separate connections was unsustainable. The breakthrough was schema normalization - different servers expose tools differently. The aggregator learned to flatten, merge, and route transparently. A bug where the aggregator stripped parameter schemas (returning empty `properties: {}`) taught us that the contract between aggregator and server must be exact. That bug blocked three other projects before we caught it. The Builder Agent tools (task tracking, lessons, sessions) emerged from needing to coordinate work across projects. The research pipeline (expert panels, debate synthesis) was the most ambitious addition - it turns a research question into a structured multi-expert investigation. Next: self-healing connections, automatic server discovery, and a visual dashboard showing tool usage patterns across all projects. --- # TinyTroupe *LLM-powered persona simulation for research* - URL: https://baki.io/tinytroupe/ - Domain: research - Status: active - Tech: Python, GPT-4, Persona Simulation - Date: 2025-06-01 ## Overview An academic research library for simulating focus groups, testing advertisements, and brainstorming with synthetic personas powered by large language models. ## Research applications - **Focus group simulation** - test product concepts with diverse synthetic participants - **Ad testing** - gauge emotional responses to creative before real audiences - **Brainstorming** - generate ideas from multiple simulated perspectives - **Survey prototyping** - pre-test survey instruments with persona panels ## How it works Define personas with demographics, beliefs, communication styles, and knowledge domains. The simulation engine orchestrates multi-turn conversations where each persona responds authentically based on their profile. Interactions emerge naturally - agreements, disagreements, tangents, and insights. Published on arXiv (2507.09788) with full methodology and evaluation framework. ## How it evolved Started with Microsoft's TinyTroupe library and a question: "Can I replace real focus groups with simulated ones for early-stage UX research?" Built a Node/TypeScript interface wrapping the Python backend via FastAPI. First test: simulating rummy game players with different skill levels to brainstorm game names. The simulated "casual player" persona generated surprisingly authentic feedback - things a real casual player might say but a game designer wouldn't think of. The Seeker Team experiment was the turning point: creating specialized personas (strategist, socializer, explorer, achiever) and letting them debate game mechanics. The emergent disagreements between persona types revealed design tensions that traditional brainstorming wouldn't surface. Next: integrating TinyTroupe with MCP so any project can spawn a simulated user panel on demand - automated UX research as a development tool. --- # Fractal Game *2.5D fractal platformer with interconnected health* - URL: https://baki.io/fractal-game/ - Domain: games - Status: experiment - Tech: Three.js, WebGL, Procedural Audio - Date: 2025-04-10 ## Overview A 2.5D platformer built with Three.js where the world is made of fractal geometry. Damage propagates up and down the fractal hierarchy - hurting a branch hurts the tree, and hurting the tree hurts all branches. ## Interconnected health The core mechanic: every entity in the world exists at a level in a fractal hierarchy. Damage doesn't stay local - it ripples through the structure. This creates emergent strategies: sometimes you attack a leaf to weaken the root, sometimes you protect the root to save all leaves. ## Aesthetics Procedural fractal geometry generates the world at runtime. Parallax camera zoom on click reveals deeper levels of detail. The soundtrack is procedurally generated to match the current fractal depth - deeper levels get deeper tones. ## How it evolved Born from a Gemini conversation about abstract strategy games. The original concept was "Orbital Command" - a rhythm-strategy game with geometric cultivation. Through several pivots, the abstract geometry merged with platformer mechanics. The fractal health system emerged from asking: "What if damage didn't stay local?" The procedural audio was the surprise - it started as placeholder sounds but became core to the experience. Deeper fractal levels produce deeper tones, creating an audio landscape that maps directly to the visual structure. Players navigate by sound as much as by sight. Next: exploring multiplayer where two players exist at different fractal depths, affecting each other through the hierarchy without direct contact. --- # Chaos *Generative multi-model world simulator* - URL: https://baki.io/chaos/ - Domain: ai - Status: active - Tech: Three.js, OpenRouter, Express, Node.js - Date: 2025-03-19 ## Overview A philosophical experiment in form-based reasoning. Ten LLM voices fire in parallel, each generating an embodied entity that populates an infinite isometric grid. Ideas become shapes. Moods become movement. Disagreement becomes diversity. ## What it does Input a "world-seed" - any thought, phrase, or question. All 10 models interpret it simultaneously, spawning creatures with unique forms, colors, and behaviors. They wander, interact, and coexist without consensus. ## Why it matters This is not about what ideas *mean*, but what they *look like*. A melancholic idea becomes a slow-drifting jellyfish. A scattered thought becomes a swarm. Pure form-based reasoning through procedural generation. ## How it evolved Started as a question: "What if disagreement between AI models was the *point*, not a problem?" Early prototypes used a single model - the output was coherent but lifeless. Adding a second model created tension. By the time ten voices were firing in parallel, the grid came alive with genuine diversity. Key pivot: switching from text-based output to embodied forms on an isometric grid. The moment ideas became shapes instead of sentences, the project found its soul. The conversation with Claude that led to toon materials and gradient mapping was the breakthrough - entities stopped looking like debug visualizations and started looking like creatures. Next: exploring whether world-seeds can evolve over time, creating persistent ecosystems where entities have memory of previous generations. --- Try a simplified version below - enter any thought and watch it take form. --- # IDLE Game *Post-apocalyptic base builder with robot units* - URL: https://baki.io/idle-game/ - Domain: games - Status: active - Tech: Canvas, JavaScript, Procedural Generation - Date: 2025-02-15 ## Overview A Fallout Shelter-inspired idle game built with HTML5 Canvas. Excavate underground chambers, manage resources, deploy robot units, and survive procedural events in a post-apocalyptic world. ## Core mechanics - **2D grid excavation** - dig downward to reveal resources and expand your base - **Robot units** - assign workers to mining, construction, and defense - **Resource management** - balance energy, materials, and food - **Procedural events** - random encounters that challenge your survival strategy ## Design philosophy Every system feeds back into every other system. Energy powers robots, robots mine resources, resources build infrastructure, infrastructure generates energy. The loop is the game. ## How it evolved Started as pure idle mechanics - click to dig, wait for resources. But idle-only felt hollow. Adding real-time strategy elements (robot deployment, defense planning) created the tension that idle games need: you care about what happens while you're away because you *built* something intentional before leaving. The offline progression was the hardest design problem. Full-speed simulation while offline makes the game feel like it doesn't need you. The solution: 1/3 speed offline. Your base still runs, still produces, but slowly. Coming back feels like checking on a garden, not opening a finished product. Next: procedural events that create genuine dilemmas - evacuate a section and lose resources, or defend it and risk your robots. --- # AI Game Design Tool - URL: https://baki.io/ai-game-design-tool/ - Domain: games - Tags: games, ai, game-design, ux-research - Date: 2026-03-21 I've been playing games for years. The Fractal Game - a procedural audio experiment where every system connects to every other system through an interconnected health model. The IDLE Game - base building with offline progression and resource loops that compound while you sleep. In both cases, the hardest problem was never implementation. It was playtesting inside my own head. Then I started having design conversations with LLMs, and something shifted. ## The messy process Here's what game design with AI actually looks like. It's not "generate a game for me." It's closer to rubber-ducking with a partner who has read every GDC talk but has never actually played a game. For the Fractal Game, I was stuck on the health system. I wanted interconnected subsystems - audio health, visual health, structural health - where damage to one cascades through the others. But the balance was impossible to intuit. I'd sketch a dependency graph, stare at it, and have no idea if it would feel right in practice. So I described the system to Claude and asked: "If a player takes 30% audio damage and 15% structural damage simultaneously, walk me through what happens over the next ten seconds." The response wasn't correct - it couldn't be, without running the actual simulation. But it surfaced edge cases I hadn't considered. What if the cascade creates a feedback loop? What if recovery in one system blocks recovery in another? AI design conversations generate hypotheses, not answers. Every interesting suggestion needs to be tested against actual player behavior. The model hasn't played your game. You have. ## Simulating players you haven't met This is where my UX research background collides with game design in a useful way. In research, we talk about the gap between what users say they want and what they actually do. In game design, the gap is between what the designer intends and what the player experiences. LLMs can approximate player archetypes. Not perfectly - but well enough to stress-test assumptions. For the IDLE Game, I asked models to roleplay as different player types: the optimizer who min-maxes every resource, the explorer who ignores efficiency, the hoarder who never spends. Each "player" surfaced different failure modes in the progression curve. The optimizer found an exploit in the offline calculation I'd missed. The explorer revealed that my tutorial locked out an entire branch of content. The hoarder showed me that resource caps felt punitive rather than strategic. None of these insights replaced actual playtesting. But they compressed weeks of blind iteration into hours of directed conversation. ## Procedural generation as philosophy There's a deeper connection here that I keep returning to. Procedural generation in games isn't just a technical choice - it's a philosophical stance. It says: the designer creates the rules, not the content. The content emerges. That's uncomfortably close to how I think about life in general. When I use AI in game design, I'm doing procedural generation of the design process itself. I set up the constraints - the mechanics, the feel I'm going for, the player experience I want - and let the conversation generate possibilities I wouldn't have reached alone. The best AI-assisted design sessions start with constraints, not blank canvases. "Design me a game" produces nothing. "This resource loop feels punishing after hour three - why?" produces insight. The tool doesn't replace the designer. It makes the design space navigable. And navigating possibility space is, when you strip away the jargon, what game design has always been about. --- # Form Reasoning - URL: https://baki.io/form-based-reasoning/ - Domain: philosophy - Tags: philosophy, ai, chaos, embodied-cognition - Date: 2026-03-21 There's a question I keep circling back to: what does a melancholic idea look like? Not what it means. Not how to describe it. What shape does it take when you strip away language and ask something non-human to render it? In Chaos - the multi-model project I've been building - I feed the same prompt to ten LLMs simultaneously. They don't agree. They never agree. But here's what gets interesting: they disagree in form, not just content. One model returns a slow-drifting jellyfish. Another gives me a fractal spiral collapsing inward. A third produces a swarm of particles that never quite settle. Same input. Ten different shapes. This isn't a failure of alignment. It's the whole point. ## The gap between language and form We treat language as the canonical way to represent ideas. But language is sequential - one word after another, one sentence building on the last. Ideas aren't like that. A feeling of loss has weight, direction, density. It occupies space in a way that a paragraph about loss never captures. Embodied cognition research has been saying this for decades: thinking isn't something that happens in an abstract symbol space. It's grounded in the body, in spatial relationships, in physical metaphor. When we say an argument is "heavy" or an idea is "sharp," we're not being poetic. We're being accurate about how cognition actually works. When ten models give an idea ten different shapes, they're mapping the topology of that idea's meaning-space. Each shape is a projection - a view from a different angle of the same underlying structure. ## Why disagreement is generative The instinct is always to converge. Pick the best answer. Average them out. Build consensus. But when you let ten forms coexist, you get something richer: a stereoscopic view of meaning. Like how two eyes create depth perception, ten interpretations create conceptual depth. I started building Chaos because I was frustrated with single-model thinking. One LLM gives you one perspective, and you mistake that perspective for truth. Ten models make the constructed nature of every response visible. You stop asking "what's the right answer?" and start asking "what's the shape of the question?" That shift matters. As a UX researcher, I've spent years watching people interact with systems that present one answer as definitive. Search results. Recommendation engines. Diagnosis tools. The single-answer paradigm trains passivity. Multi-form reasoning trains perception. ## Where this leads I don't think ideas are made of words. I think ideas are made of shapes, and words are one lossy compression format for transmitting them. When I watch ten models turn the same prompt into ten different visual forms, I see something closer to how understanding actually works - messy, parallel, and irreducibly multiple. All is one and one is all. But "one" has ten thousand shapes. --- # Infinite Canvas - URL: https://baki.io/infinite-canvas-philosophy/ - Domain: tools - Tags: tools, ux, infinite-canvas, digital-garden - Date: 2026-03-21 This site doesn't have pages. Not in the traditional sense. There's no blog archive sorted by date, no linear scroll from top to bottom, no hierarchy telling you what matters most. Instead, there's a canvas - an open field where content exists in space, positioned by semantic relationship rather than chronology. This was a deliberate choice, and it took me a while to articulate why. ## Pages are arguments A traditional webpage is a rhetorical structure. It has a beginning, middle, and end. It controls attention through sequence. The designer decides: this goes first, this comes next, this is the conclusion. That's powerful when you want to persuade. It's limiting when you want to think. I spent years as a UX researcher testing interfaces that impose hierarchy. Navigation menus. Content carousels. Information architecture. All of these are opinionated about what matters - they rank, they sort, they filter. They're useful. But they also compress the space of possible engagement into predetermined paths. An infinite canvas doesn't argue. It presents. When you organize content by date, you're saying recency equals relevance. When you organize by category, you're saying taxonomy equals understanding. When you organize by spatial proximity, you're saying relationship equals meaning. ## Maps over timelines The digital garden movement got close to this. Maggie Appleton's concept of ideas growing from seeds to saplings to trees is beautiful - it captures the epistemic status of thoughts, which blogs flatten into "published" or "not." Andy Matuschak's evergreen notes push further: atomic ideas linked by association rather than sequence. But most digital gardens still live inside page-based layouts. You click links. You navigate trees. The topology is relational, but the interface is still linear. Kinopio does something different - it gives you cards in space, connected by lines. You see the shape of someone's thinking, not the sequence of their output. That spatial dimension carries real information. Clusters mean resonance. Distance means distinction. Orphaned nodes mean ideas that haven't found their place yet. That's what I wanted for baki.io. Not a portfolio. Not a blog. A map of how I think. ## What spatial organization reveals When I placed my projects on the canvas for the first time, patterns emerged that I hadn't seen in two years of working on them. Chaos and the Fractal Game share a deep concern with emergence - but I'd never connected them explicitly. The MCP aggregator and the infinite canvas itself are both about interface philosophy - tools that shape how you interact with complexity. These connections were always there. A chronological blog would have buried them under timestamps. A portfolio grid would have separated them into categories. The canvas made them visible. Try this: take five projects you've worked on and place them on a whiteboard by "how related they feel." The clusters will surprise you. There's something meditative about it. You stop curating a narrative and start observing a landscape. The map isn't the territory, but sometimes the map shows you territory you didn't know existed. --- # 10 LLMs Disagree - URL: https://baki.io/ten-voices-one-void/ - Domain: ai - Tags: ai, philosophy, multi-model - Date: 2026-03-20 When you ask ten language models the same question, you don't get ten copies of the same answer. You get ten genuinely different interpretations - shaped by architecture, training data, and the emergent personality of each model. Most systems try to resolve this into consensus. Chaos does the opposite: it celebrates disagreement as a feature, not a bug. Disagreement between models is signal, not noise. Each divergence maps a boundary in the latent space of possible answers. ## The diversity hypothesis If a single model gives you one perspective, ten models give you a landscape. Not an averaged, blurred landscape - a terrain with actual peaks and valleys, each one a different way of seeing. {`# Fan out the same prompt to multiple models responses = await asyncio.gather(*[ model.generate(prompt) for model in ensemble ]) # Measure semantic distance between responses distances = pairwise_cosine(responses) diversity_score = distances.mean()`} Higher diversity scores often correlate with questions that have no single correct answer - exactly the kind worth exploring. Don't confuse model diversity with hallucination. Diverse outputs from grounded models reveal genuine interpretive breadth, not errors. ## Further reading ---