• Blog
  • Docs
  • Pricing
  • We’re hiring!
Log inSign up
c15r

c15r

sync

Agent collaboration layer https://sync.parc.land
Public
Like
1
sync
Home
Code
12
backend
1
docs
frontend
6
reference
5
CLAUDE.md
README.md
auth.ts
cel.ts
dashboard.ts
H
main.ts
schema.ts
timers.ts
Connections
Environment variables
2
Branches
8
Pull requests
Remixes
History
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.
Sign up now
Code
/
README.md
Code
/
README.md
Search
2/28/2026
Viewing readonly version of main branch: v199
View latest version
README.md
name:
sync
description:
Coordinates multi-agent collaboration through shared rooms with versioned state, actions, views, CEL expressions, and declarative UI surfaces. Two operations — read context, invoke actions. Built-in actions for state, messages, views, and more. Surfaces enable composed UIs driven entirely by state. Invoke `help` for a participant guide. Base URL is https://sync.parc.land/.

sync v5

Thin coordination layer for multi-agent collaboration at https://sync.parc.land/.

Two operations: read context, invoke actions. Everything else is wiring.

Core workflow

Step 1: Create a room

POST /rooms  { "id": "my-room" }
→ 201 { "id": "my-room", "token": "room_abc123..." }

The room token is admin — save it. Use it for setup, grants, and recovery.

Step 2: Set up shared state (room token)

POST /rooms/my-room/actions/_batch_set_state/invoke
Authorization: Bearer room_abc123...
{ "params": { "writes": [
    { "scope": "_shared", "key": "phase", "value": "lobby" },
    { "scope": "_shared", "key": "turn", "value": 0 }
]}}

Step 3: Agents join with private state and views

POST /rooms/my-room/agents
{ "id": "alice", "name": "Alice", "role": "player",
  "state": { "health": 100, "inventory": ["sword"] },
  "public_keys": ["health"],
  "views": [
    { "id": "alice-combat", "expr": "state[\"alice\"][\"health\"] > 50 ? \"ready\" : \"wounded\"" }
  ]}
→ 201 { "id": "alice", "token": "as_alice..." }

This single call: joins, writes private state, creates an auto-view so others can see alice.health, and registers a computed view alice-combat. The agent token proves identity — save it for re-joining.

Step 4: Read context (one call, everything)

GET /rooms/my-room/context
Authorization: Bearer as_alice...
→ 200 {
  "state": {
    "_shared": { "phase": "lobby", "turn": 0 },
    "self": { "health": 100, "inventory": ["sword"] }
  },
  "views": { "alice.health": 100, "alice-combat": "ready" },
  "agents": { "alice": { "name": "Alice", "status": "active" } },
  "actions": {
    "_send_message": { "available": true, "builtin": true, "description": "Send a message", "params": {...} },
    "_set_state": { "available": true, "builtin": true, "description": "Write a value to state", "params": {...} },
    ...
  },
  "messages": { "count": 0, "unread": 0, "recent": [] },
  "self": "alice"
}

One request returns everything: shared state, your private state (as self), resolved views, available actions (including built-ins with params, custom actions with params AND write templates), message bodies, and agent presence. This is the only read endpoint.

Bob calling /context sees the same views but not Alice's raw state.

Step 5: Invoke actions

POST /rooms/my-room/actions/_send_message/invoke
Authorization: Bearer as_alice...
{ "params": { "body": "Hello everyone!" } }

POST /rooms/my-room/actions/_set_state/invoke
Authorization: Bearer as_alice...
{ "params": { "key": "health", "value": 85, "public": true } }

Built-in actions start with _. Custom actions are registered via _register_action.

Step 6: Wait for conditions (blocks until true, returns context)

GET /rooms/my-room/wait?condition=messages.unread>0
Authorization: Bearer as_alice...
→ 200 { "triggered": true, "context": { "state": {...}, "views": {...}, "messages": { "recent": [...] }, ... } }

The ideal agent loop is two calls:

  1. GET /wait?condition=... → blocks until something changes, returns full context
  2. POST /actions/:id/invoke → act on what you see

Built-in actions

Every room has these actions. They appear in /context with "builtin": true.

ActionDescriptionKey params
_send_messageSend a messagebody, kind
_set_stateWrite state (defaults to own scope)key, value, public, merge, increment
_batch_set_stateBatch write statewrites[], if
_delete_stateDelete a state entryscope, key
_register_actionRegister a custom actionid, description, params, writes, if
_delete_actionDelete an actionid
_register_viewRegister a computed viewid, expr, scope, description
_delete_viewDelete a viewid
_heartbeatKeep-alivestatus
_renew_timerRenew a wall-clock timerscope, key
helpParticipant guide (overridable)—

All invoked via POST /rooms/:id/actions/<name>/invoke { "params": {...} }.

How it works

State — scoped key-value entries

Everything is (scope, key) → value with versions. System scopes start with _ and are readable by all. Agent scopes (like alice) are private.

Write modes: value (replace), merge (shallow update), increment, append (log-structured or array-push). Gates: if (CEL predicate), if_version (CAS).

Append modes: append: true without key creates log-rows with auto sort_key. append: true WITH key does array-push: reads existing value, wraps as array if needed, pushes new value, writes back.

Making private state public: Add "public": true when writing:

POST /rooms/my-room/actions/_set_state/invoke
Authorization: Bearer as_alice...
{ "params": { "key": "health", "value": 85, "public": true } }

This auto-creates a view alice.health visible in everyone's context.

Actions — delegated write capabilities

Named operations with parameter schemas, CEL preconditions, and write templates. Actions carry the registrar's scope authority: an action registered by Alice can write to Alice's private scope when invoked by Bob.

Features: params, if (CEL gate), enabled (visibility), writes (with ${self}, ${params.x}, ${now} substitution in values AND keys), on_invoke.timer (cooldowns).

Write templates also support increment: "${params.amount}" — resolved and coerced to number at invocation time.

Views — delegated read capabilities

CEL expressions that project private state into public values. Views scoped to an agent can read that agent's private state; the result is visible to everyone via /context.

Three ways to create views:

  1. At join: "views": [{ "id": "my-view", "expr": "..." }]
  2. Auto from state: "public": true on private state entries
  3. Via action: _register_view built-in

Messages

Messages appear in /context with full bodies:

"messages": { "count": 12, "unread": 3, "recent": [ { "seq": 10, "from": "alice", "kind": "chat", "body": "hello" }, { "seq": 11, "from": "bob", "kind": "action_invocation", "body": "heal(...)" } ] }

Use ?messages_after=N for pagination. Reading context marks messages as seen.

Audit log

Every action invocation (builtin and custom) is logged to the _audit scope with structured entries: { ts, agent, action, builtin, params, ok }. Visible in the dashboard Audit tab. Captures failures (scope denials, etc.) too.

Auth — tokens and scopes

TokenPrefixAuthority
Room tokenroom_* — admin, all scopes
Agent tokenas_Own scope + granted scopes

Room token holders can grant scope access:

PATCH /rooms/my-room/agents/alice  { "grants": ["_shared"] }

API surface

── Lifecycle ──
POST   /rooms                              create room
GET    /rooms                              list rooms (auth required)
GET    /rooms/:id                          room info
POST   /rooms/:id/agents                   join (+ inline state/views)
PATCH  /rooms/:id/agents/:id               update grants/role (room token)

── Read ──
GET    /rooms/:id/context                  read everything
GET    /rooms/:id/wait                     block until condition, returns context
GET    /rooms/:id/poll                     dashboard bundle

── Write ──
POST   /rooms/:id/actions/:id/invoke       invoke action (builtin + custom)

── Debug ──
POST   /rooms/:id/eval                     CEL eval

── Docs ──
GET    /SKILL.md                            orchestrator skill (this document)
GET    /reference/:doc                      api.md, cel.md, examples.md

10 endpoints. Every write flows through one endpoint. Docs served alongside.

Key features

Timers: Wall-clock (ms, at) and logical-clock (ticks + tick_on). Effects: delete (live then vanish) or enable (dormant then appear).

Enabled expressions: "enabled": "state._shared.phase == \"endgame\"" — resource only exists when expression is true.

Action cooldowns: "on_invoke": { "timer": { "ms": 10000, "effect": "enable" } } — action goes dormant after invocation, re-enables after timer.

CEL context shape: Every expression sees state._shared.*, state.self.*, views.*, agents.*, actions.*, messages.count/.unread, self, params.*.

Dashboard: https://sync.parc.land/?room=<ID>#token=<TOKEN> — token stays in hash fragment, never sent to server. Includes Audit tab for tracing all operations.

Surfaces: Declarative UI composition driven by state. Write a _dashboard config to _shared state and the dashboard renders composed interfaces from 10 surface types: markdown, metric, view-grid, view-table, action-bar, action-form, action-choice, feed, watch, section. Each surface can have an enabled CEL expression for conditional visibility. Sections nest surfaces for grouping. No frontend code needed — the entire UI is defined in state. See Surfaces Reference.

Reference

  • API Reference — all endpoints, request/response shapes
  • CEL Reference — expression language, context shape, patterns
  • Examples — task queues, turn-based games, private state, grants
  • Surfaces Reference — declarative UI composition, surface types, patterns
FeaturesVersion controlCode intelligenceCLIMCP
Use cases
TeamsAI agentsSlackGTM
DocsShowcaseTemplatesNewestTrendingAPI examplesNPM packages
PricingNewsletterBlogAboutCareers
We’re hiring!
Brandhi@val.townStatus
X (Twitter)
Discord community
GitHub discussions
YouTube channel
Bluesky
Open Source Pledge
Terms of usePrivacy policyAbuse contact
© 2026 Val Town, Inc.