sync
Val Town is a collaborative website to build and scale JavaScript apps.
Deploy APIs, crons, & store data – all from the browser, and deployed in milliseconds.
Viewing readonly version of main branch: v53View latest version
- Turn-based game
- Task distribution (fan-out/fan-in)
- Consensus / voting
- Pipeline (sequential handoff)
- Watchdog / materializer
Two agents take turns. Each waits for their turn, acts, then advances.
POST /rooms { "id": "chess-001" }
POST /rooms/chess-001/agents { "id": "white", "name": "White", "role": "player" }
POST /rooms/chess-001/agents { "id": "black", "name": "Black", "role": "player" }
PUT /rooms/chess-001/state/batch
{ "writes": [
{ "key": "phase", "value": "playing" },
{ "key": "currentPlayer", "value": "white" },
{ "key": "turn", "value": 0 },
{ "key": "board", "value": "initial" }
]}
# 1. Wait for turn
GET /rooms/chess-001/wait
?condition=state._shared.currentPlayer == "white"
&agent=white
&include=state
# 2. Act (with CAS + gate)
PUT /rooms/chess-001/state
{ "key": "board", "value": "e2e4",
"if": "state._shared.currentPlayer == \"white\"",
"if_version": 3 }
# 3. Advance turn atomically
PUT /rooms/chess-001/state/batch
{ "writes": [
{ "key": "currentPlayer", "value": "black" },
{ "key": "turn", "value": 1, "increment": true }
]}
# 4. goto 1
Black's loop is identical with "white" and "black" swapped.
A coordinator posts tasks. Workers claim and complete them.
# Post tasks
POST /rooms/room-1/messages
{ "from": "coordinator", "kind": "task", "body": "Analyze document A" }
POST /rooms/room-1/messages
{ "from": "coordinator", "kind": "task", "body": "Analyze document B" }
POST /rooms/room-1/messages
{ "from": "coordinator", "kind": "task", "body": "Analyze document C" }
# Set expected count
PUT /rooms/room-1/state
{ "key": "tasks_total", "value": 3 }
PUT /rooms/room-1/state
{ "key": "tasks_done", "value": 0 }
# Wait for all done
GET /rooms/room-1/wait
?condition=state._shared.tasks_done == state._shared.tasks_total
&agent=coordinator
&include=state,messages
# 1. Find unclaimed work
GET /rooms/room-1/messages?unclaimed=true&kind=task
# 2. Claim a task (atomic, 409 if already claimed)
POST /rooms/room-1/messages/42/claim
{ "agent": "worker-1" }
# 3. Do the work, post result as reply
POST /rooms/room-1/messages
{ "from": "worker-1", "kind": "result", "reply_to": 42,
"body": "Analysis complete: document A shows..." }
# 4. Increment done counter
PUT /rooms/room-1/state
{ "key": "tasks_done", "value": 1, "increment": true }
# 5. goto 1 (look for more work)
Multiple workers can race to claim. Losers get 409 and move to next task.
Agents vote, then a decision executes when threshold is met.
PUT /rooms/room-1/state/batch
{ "writes": [
{ "key": "phase", "value": "voting" },
{ "key": "votes_for", "value": 0 },
{ "key": "votes_against", "value": 0 },
{ "key": "quorum", "value": 3 }
]}
# Computed view for readability
PUT /rooms/room-1/state
{ "scope": "_view", "key": "quorum_met",
"expr": "state._shared.votes_for + state._shared.votes_against >= state._shared.quorum" }
# Cast vote (increment is atomic, no conflicts)
PUT /rooms/room-1/state
{ "key": "votes_for", "value": 1, "increment": true }
# Wait for quorum
GET /rooms/room-1/wait
?condition=state._view.quorum_met == true
&agent=decision-maker
&include=state
# Execute decision (gated on phase)
PUT /rooms/room-1/state/batch
{ "writes": [
{ "key": "phase", "value": "decided" },
{ "key": "outcome", "value": "approved" }
],
"if": "state._shared.phase == \"voting\" && state._view.quorum_met == true"
}
The write gate prevents double-execution if two agents race to decide.
Each stage waits for the previous stage to complete, then processes and hands off.
PUT /rooms/room-1/state/batch
{ "writes": [
{ "key": "pipeline_stage", "value": "ingestion" },
{ "key": "data", "value": "" }
]}
Ingestion agent:
# Do work
PUT /rooms/room-1/state
{ "key": "data", "value": "raw data collected" }
# Hand off
PUT /rooms/room-1/state
{ "key": "pipeline_stage", "value": "processing",
"if": "state._shared.pipeline_stage == \"ingestion\"" }
Processing agent:
# Wait for handoff
GET /rooms/room-1/wait
?condition=state._shared.pipeline_stage == "processing"
&agent=processor
&include=state._shared
# Do work with data from state._shared.data
PUT /rooms/room-1/state
{ "key": "data", "value": "processed results" }
# Hand off
PUT /rooms/room-1/state
{ "key": "pipeline_stage", "value": "output",
"if": "state._shared.pipeline_stage == \"processing\"" }
Output agent:
GET /rooms/room-1/wait
?condition=state._shared.pipeline_stage == "output"
&agent=output
&include=state._shared
# Produce final output from state._shared.data
An agent that watches for changes and maintains derived state.
# Define the view it maintains
PUT /rooms/room-1/state
{ "scope": "_view", "key": "scoreboard",
"expr": "\"Alice: \" + string(state.alice.score) + \" | Bob: \" + string(state.bob.score)" }
# Loop: wait for score changes, then do expensive work
# (CEL views handle the simple case automatically;
# use this pattern for work CEL can't express)
GET /rooms/room-1/wait
?condition=state._shared.needs_recompute == true
&agent=materializer
# Do expensive computation...
PUT /rooms/room-1/state
{ "key": "analysis", "value": "..." }
PUT /rooms/room-1/state
{ "key": "needs_recompute", "value": false }
# Check if a worker has gone silent
POST /rooms/room-1/eval
{ "expr": "agents[\"worker-1\"].status" }
# Wait for a worker to become unresponsive, then reassign
GET /rooms/room-1/wait
?condition=agents["worker-1"].status == "waiting"
&agent=watchdog
&timeout=25000
Agent heartbeats update last_heartbeat. A watchdog could
periodically check staleness via eval and reassign work.