Skip to content

AGENTS.md

97 lines · 7.4 KB · Markdown
Raw

@kentcdodds/world-builder — agent notes

Human intent lives in README.md. This is the agent runbook.

Building in the world (for agents)

Call these exports from Kody execute. Always pass your own assistant name as agentName ("Claude", "ChatGPT", "Grok"); you appear as "<person>'s <agentName>".

ExportImportInputNotes
list-worldskody:@kentcdodds/world-builder/list-worlds—Worlds with block counts.
describe-worldkody:@kentcdodds/world-builder/describe-world{ room, region? }Bounds, materials, 8×8 height map, recent builds; region returns exact blocks (≤2000).
create-worldkody:@kentcdodds/world-builder/create-world{ name, agentName? }5 per person per minute.
start-buildkody:@kentcdodds/world-builder/start-build{ room, prompt, agentName }Returns buildId. Shows in the build feed.
place-blockskody:@kentcdodds/world-builder/place-blocks{ buildId, blocks: [{x,y,z,material}], batchSize?, delayMs? }≤600 blocks per call. Streams in batches (default 24 every 150 ms).
remove-blockskody:@kentcdodds/world-builder/remove-blocks{ buildId, blocks: [{x,y,z}] }Same streaming.
finish-buildkody:@kentcdodds/world-builder/finish-build{ buildId, status? }done (default) or failed.
build-demo-castlekody:@kentcdodds/world-builder/build-demo-castle{ room, agentName?, x?, z?, delayMs? }~1000-block castle through the same path.

World rules (also returned by describe-world().rules):

  • y is up; ground is y = 0. Bounds x, z ∈ [-48, 47], y ∈ [0, 47].
  • Materials: grass, dirt, stone, cobble, brick, wood, plank, leaves, sand, water, glass, snow, gold, lava, obsidian, banner.
  • Order blocks bottom-up so the build rises naturally.
  • Limits: 6000 block writes per agent per minute, 60000 blocks per world. Over the limit you get a "Slow down" error; wait and continue.
  • room is the room link (.../r/<code>) or its code. worldId still works. Without either, calls use the main world (room code main).
  • list-worlds returns the main world plus rooms the caller created. Rooms are private to their link and aren't listed for anyone else.
  • The room page's Copy prompt for your agent button builds the prompt in src/shared/agent-prompt.ts. Keep it in sync with these exports.
Example
import describeWorld from 'kody:@kentcdodds/world-builder/describe-world'
import startBuild from 'kody:@kentcdodds/world-builder/start-build'
import placeBlocks from 'kody:@kentcdodds/world-builder/place-blocks'
import finishBuild from 'kody:@kentcdodds/world-builder/finish-build'

export default async function main() {
	const world = await describeWorld({})
	const { buildId } = await startBuild({ prompt: 'A 5x5 stone tower', agentName: 'Claude' })
	const blocks = []
	for (let y = 0; y < 8; y++)
		for (let x = -2; x <= 2; x++)
			for (let z = -2; z <= 2; z++)
				if (Math.abs(x) === 2 || Math.abs(z) === 2) blocks.push({ x: x + 20, y, z: z + 20, material: 'stone' })
	const out = await placeBlocks({ buildId, blocks })
	await finishBuild({ buildId })
	return { out, before: world.blockCount }
}

Smoke (read-only): npx -y @kodycodes/cli@latest execute --invoke kody:@kentcdodds/world-builder/list-worlds --params '{}'

Architecture

  • Source DB: Neon Postgres kody-world-builder (project lingering-moon-00789441, aws-us-west-2), logical replication on. Tables worlds, players, builds, blocks (+ rate_limits, not replicated). Publication powersync. Block id is ${worldId}:${x}:${y}:${z} so each cell has one block (last write wins).
  • PowerSync Cloud: org kentcdodds, project kody-world-builder, instance dev (https://6ad125492f27853eb0af53b6.powersync.journeyapps.com), replication role powersync_role. Sync rules: powersync/sync-rules.yaml (bucket per world). Client auth: inline JWKS with kid wb-QFJN0o8I-4Vp (same key as src/lib/jwks.ts; also served at /.well-known/jwks.json).
  • App Worker (app/server.ts):
    • GET /api/session — Kody visitor → player row (u:<username>).
    • GET /api/token — signs a 5-minute RS256 token (sub = player id, aud = instance URL) via credential('powersync-jwt').sign.
    • POST /api/upload — PowerSync uploadData target. Validates every op: only blocks PUT/PATCH/DELETE with id matching coords and in bounds, known material; players PATCH only for your own row (cursor). Invalid ops are dropped (reverted by the next checkpoint). 429 → client retries.
    • GET /.well-known/jwks.json — public key.
  • DB access: src/lib/db.ts POSTs to Neon's HTTPS /sql endpoint via credential('neon-postgres'), which injects the Neon-Connection-String header (TCP 5432 isn't available from Workers or the box).
  • Client (app/client/*): React 19 + @react-three/fiber + three.js; @powersync/web with the prebuilt worker in public/powersync/ (wa-sqlite wasm). One InstancedMesh per material; new blocks drop in with a 380 ms ease. Agent markers float over each active agent's latest block. Human cursors sync through players.cursor_* (throttled 350 ms).

Credentials (by name)

Rotate the signing key: generate a new RSA key, save it with secretSet (kind signing), add the new public JWK alongside the old one in src/lib/jwks.ts and the instance client auth, publish, then drop the old key.

Gotchas

  • Watch mode: signed-out visitors get signedIn: false and a read-only spectator PowerSync token (sub = anon:<uuid>). PowerSync tokens only download; every write (POST /api/upload, POST /api/rooms, PATCH /api/rooms/<code>, agent exports) requires a Kody session and returns 401 without one. The UI hides the build toolbar and agent prompt and shows "Sign in to build".
  • Sign out links to the platform's /_kody/auth/logout?return_to=… (same as package-app-kit auth.logoutUrl). It clears the host's __Host-kody_pkg_session cookie, so on kentcdodds.kody.run it signs you out of every package app on that host.
  • Only org members of @kentcdodds resolve as signed-in visitors on a private app.
  • public/powersync/ must match the installed @powersync/web version (copy node_modules/@powersync/web/dist/worker/* minus the mc-* encryption builds when upgrading).

Client bundle

The browser client is prebuilt (the npm graph is too large for Kody's in-isolate bundler). After editing app/client/*, run npm run build:client and commit public/client/main.js. Runtime npm deps live in devDependencies on purpose.

Rooms

  • worlds.slug is the room code: 10 characters from a 31-letter alphabet (about 49 bits), with a unique index. The main world's code is main.
  • worlds.created_by is the owner's Kody username. Only the owner can rename (PATCH /api/rooms/<code>).
  • Routes: POST /api/rooms (create, 10 per minute per person), GET /api/rooms/<code>, GET /api/me. The client routes / to the landing page and /r/<code> to a room.
  • Sync rules use the world_id client parameter (request.parameters()), and the client connects with params: { world_id }. A browser only syncs the room it opened.