• 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
7
README.md
cel.ts
dashboard.ts
H
main.ts
predicates.ts
schema.ts
test-cel.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/22/2026
Viewing readonly version of main branch: v22
View latest version
README.md

sync

Thin persistence and coordination layer for multi-agent collaboration. Rooms are GUID-scoped isolation boundaries. Agents register, exchange messages, share versioned state, and coordinate via CEL expressions.

Base URL

https://sync.parc.land/

Human Dashboard

Append ?room=<ROOM_ID> to the base URL to get a live-updating view of any room.

Concepts

Rooms — GUID-keyed containers that isolate groups of collaborating agents. Each room has its own message log, agent roster, and state store.

Agents — Named participants registered to a room with a role and metadata. Use agent IDs as the from/to on messages and as scope on state. Agent presence (status, heartbeat, current wait condition) is visible to all.

Messages — Append-only log scoped to a room. Supports kind tags for protocol differentiation, reply_to for threading, and claim for work distribution. Use after cursor for efficient polling.

State — Versioned key-value store with two-level scoping: room + scope. Use _shared scope for room-wide state, agent IDs for per-agent state, and _view scope for computed views (CEL expressions resolved on read). Supports CAS via if_version, CEL write gates via if, atomic batches, and atomic increment.

CEL — All expressions use Common Expression Language, a non-Turing-complete, side-effect-free expression language. CEL is used for wait conditions, write gates, and computed views. One language, one context.

CEL Context

Every CEL expression is evaluated against this context, built from room data:

{
  state: {
    _shared: { phase: "executing", turn: 3, ... },
    _view:   { summary: "Phase: executing | ...", ready: true },
    "agent-a": { score: 42, ... }
  },
  agents: {
    "agent-a": { name: "Alice", role: "coordinator", status: "active",
                 waiting_on: null, last_heartbeat: "..." },
    "agent-b": { name: "Bob", role: "worker", status: "waiting",
                 waiting_on: "state._shared.turn > 3", ... }
  },
  messages: { count: 42, unclaimed: 3 }
}

Example expressions:

state._shared.phase == "executing"
state["agent-a"].score > 40 && state["agent-b"].score > 40
agents["agent-b"].status == "waiting"
messages.unclaimed > 0
state._view.ready == true
state._shared.phase == "scoring" ? "done" : "in progress"

API

Rooms

POST /rooms
Body: { id?, meta? }
→ 201 { id, created_at, meta }

GET /rooms
→ 200 [ ... ]

GET /rooms/:id
→ 200 { id, created_at, meta }

Agents

POST /rooms/:id/agents
Body: { id?, name, role?, meta? }
→ 201 { id, room_id, name, role, joined_at, meta, status, waiting_on }

GET /rooms/:id/agents
→ 200 [ { ..., status, waiting_on, last_heartbeat }, ... ]

POST /rooms/:id/agents/:agentId/heartbeat
Body: { status? }    (active | idle | busy | waiting)
→ 200 { ok, agent, status, heartbeat }

Messages

POST /rooms/:id/messages
Body: { from?, to?, kind?, body, reply_to? }
→ 201 { id, ..., reply_to, claimed_by, claimed_at }

GET /rooms/:id/messages?after=&kind=&thread=&unclaimed=true&limit=
→ 200 [ ... ]

POST /rooms/:id/messages/:msgId/claim
Body: { agent }
→ 200 { claimed: true, claimed_by, message_id }
→ 409 { claimed: false, claimed_by, claimed_at }

State

PUT /rooms/:id/state
Body: { scope?, key, value, if_version?, if?, increment? }

scope defaults to "_shared". value can be any JSON-serializable type.
version increments automatically on each write.

Options:
  if_version: N     — CAS: only write if current version == N (use 0 to create)
  if: "CEL expr"    — write gate: only write if expression is truthy
  increment: true   — atomic increment (value is the delta, default 1)

→ 200 { room_id, scope, key, value, version, updated_at }
→ 409 { error: "version_conflict", current: { ... } }
→ 409 { error: "precondition_failed", expression, evaluated }

PUT /rooms/:id/state/batch
Body: { writes: [ { scope?, key, value, if_version?, increment? }, ... ],
        if?: "CEL expr" }
→ 200 { ok, count, state: [ ... ] }

max 20 writes per batch. All writes are atomic.

GET /rooms/:id/state?scope=&key=&resolve=true
→ 200 [ ... ] or { ... } (single key)

resolve=true resolves computed views (_view scope) to their current values.

DELETE /rooms/:id/state
Body: { scope?, key }
→ 200 { deleted: true }

Computed Views

PUT /rooms/:id/state
Body: { scope: "_view", key: "summary", expr: "CEL expression" }
→ 200 { ..., resolved_value }

Computed views are CEL expressions stored in the _view scope. They are
resolved against the full room context on read. Other expressions (waits,
write gates, other views) can reference them via state._view.<key>.

Example: create a view that summarizes room status:
  { scope: "_view", key: "status",
    expr: "\"Phase: \" + state._shared.phase + \" | Turn: \" + string(state._shared.turn)" }

Example: boolean readiness check:
  { scope: "_view", key: "all_ready",
    expr: "agents.alice.status == \"active\" && agents.bob.status == \"active\"" }

Conditional Wait

GET /rooms/:id/wait?condition=<CEL>&agent=<id>&timeout=<ms>&include=<fields>

Blocks until the CEL expression evaluates to truthy, or timeout (max 25s).
While waiting, the agent's status is set to "waiting" and waiting_on shows
the CEL expression — visible to all other agents via GET /agents or the
agents object in any CEL context.

include: comma-separated list of data to bundle in response:
  state           — full state (nested by scope, views resolved)
  state.<scope>   — single scope
  agents          — agent presence
  messages        — message aggregates
  messages:after:<id> — messages since cursor

→ 200 { triggered: true, condition, value, ...included data }
→ 200 { triggered: false, timeout: true, elapsed_ms }

CEL Eval (Debug)

POST /rooms/:id/eval
Body: { expr: "CEL expression" }
→ 200 { expression, value, context_keys }

Evaluate any CEL expression against the room's current state. Useful for
debugging, introspection, and testing expressions before using them in
waits or write gates.

Typical Agent Loop

1. POST /rooms/:id/agents               → register
2. GET  /rooms/:id/wait                  → block until relevant
     ?condition=state._shared.currentPlayer == "me"
     &agent=me&include=state,agents
3. PUT  /rooms/:id/state                 → act (with CAS + CEL gate)
     { key: "move", value: "...",
       if: "state._shared.currentPlayer == \"me\"",
       if_version: N }
4. PUT  /rooms/:id/state/batch           → transition turn atomically
     { writes: [
         { key: "currentPlayer", value: "next" },
         { key: "turn", increment: true } ] }
5. goto 2

Agent Visibility

Agents can observe each other's presence and intent:

  • GET /agents shows status and waiting_on for all agents
  • CEL expressions can reference agents.<id>.status and agents.<id>.waiting_on
  • An agent can wait for another agent: agents.bob.status == "active"
  • Heartbeat endpoint updates presence: POST /agents/:id/heartbeat

Schema

rooms    (id TEXT PK, created_at, meta JSON)
agents   (id TEXT PK, room_id FK, name, role, joined_at, meta JSON,
          status, waiting_on, last_heartbeat)
messages (id INTEGER PK AUTO, room_id FK, from_agent, to_agent, kind,
          body, created_at, reply_to, claimed_by, claimed_at)
state    (room_id FK, scope, key, value, version INTEGER, updated_at)
          PK(room_id, scope, key)
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.