• 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
10
docs
1
reference
3
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
/
docs
/
2026-02-25-v5-unified-architecture.md
Code
/
docs
/
2026-02-25-v5-unified-architecture.md
Search
2/25/2026
Viewing readonly version of main branch: v107
View latest version
2026-02-25-v5-unified-architecture.md

agent-sync v5: Unified Architecture Spec

Date: 2026-02-25 Status: Approved for implementation Context: Consolidation of design discussion around five core refinements


Motivation

The v4 architecture has four sibling resource types (state, messages, actions, agents) each with independently bolted-on timer, enabled, and CEL support. This creates:

  • Expanding API surface area (20+ endpoints)
  • Repeated timer/enabled columns across four tables
  • Dashboard that can't keep pace with new primitives
  • Legacy auth bypass undermining the identity model
  • No structural encouragement toward action-first coordination

Core Insight

State is not a sibling of messages, actions, etc — it is the substrate on which all resources are defined. Messages are append-only state. Actions are state entries with invocation semantics. Agent presence is auto-managed state. The platform provides the substrate and an invocation engine; room designers provide domain models via actions and views.

The Four Peers

1. State — The Substrate

Versioned key-value storage with scoped namespaces. Single table, two modes.

Scope privacy model:

  • Agent scopes are private by default. An agent can only read its own scope.
  • _shared is communal — readable by all, writable only by privileged agents or through actions.
  • Views project private state into public results (see Views below).

Scope modes:

  • Mutable — key-value map. Point reads/writes. CAS via version. Caller-specified keys.
  • Append — log-structured. Auto-assigned sort_key (incrementing seq within scope). No key overwrites, but merge (partial update) is allowed on existing entries.

Mode is determined per-write ("append": true flag), not per-scope declaration. The platform observes patterns; it doesn't enforce scope purity.

Merge semantics:

  • "value": {...} — full replacement (current behavior)
  • "merge": {...} — shallow merge into existing value. On nonexistent key, behaves as insert.
  • "expr": true continues to work on both — CEL result becomes the value or merge payload.

Storage schema (single table):

CREATE TABLE state ( room_id TEXT NOT NULL, scope TEXT NOT NULL, key TEXT NOT NULL, sort_key INTEGER, -- NULL for mutable, auto-inc for append value TEXT NOT NULL, -- JSON blob, opaque to platform version INTEGER DEFAULT 1, updated_at TEXT DEFAULT (datetime('now')), -- Timer (universal) timer_json TEXT, timer_expires_at TEXT, timer_ticks_left INTEGER, timer_tick_on TEXT, timer_effect TEXT, timer_started_at TEXT, -- Enabled (universal) enabled_expr TEXT, PRIMARY KEY (room_id, scope, key) ); CREATE INDEX idx_state_scope_sort ON state(room_id, scope, sort_key) WHERE sort_key IS NOT NULL;

Filtering on value fields (kind, from, claimed_by, etc.) uses json_extract() in WHERE clauses. No promoted metadata columns. Expression indexes added later as an ops decision if specific patterns become hot. Rooms are bounded coordination contexts, not analytics stores.

Built-in scope conventions:

  • _shared — communal mutable state (setup, game state)
  • _messages — append-only communication log
  • {agent-id} — private agent state
  • User-defined scopes as needed

2. Actions — Write Capabilities

Registered operations that agents can invoke. Actions carry the registrar's write authority and define structured, predicated state mutations.

Key properties:

  • id — unique within room
  • scope — owner agent or _shared. Determines write authority for the action's writes.
  • if — CEL predicate evaluated at invocation. Gates execution.
  • enabled — CEL expression gating visibility/availability.
  • params — parameter schema (types, enums). Validated at invocation.
  • writes — array of state mutations to execute atomically.
  • timer / on_invoke — timer lifecycle and cooldown.

Write authority model:

  • An action registered by agent-a with scope agent-a can write to agent-a's scope and _shared.
  • Writes to another agent's scope require the action's scope to match that agent, or the invoker to be that agent.
  • _shared and append scopes (_messages, etc.) are communal for writes.

Param substitution in writes:

  • ${params.item} in keys and string values
  • ${self} resolves to invoking agent's ID
  • "expr": true evaluates value as CEL with params in context

Merge in action writes:

  • { "scope": "_messages", "key": "${params.key}", "merge": { "claimed_by": "${self}" } }
  • Partial update on existing entry without clobbering other fields

Invocation logging:

  • Every invocation auto-appends to _messages scope with kind action_invocation

3. Views — Read Capabilities

Registered CEL expressions that project state (including private agent state) into public, queryable results. Peer of actions — actions are write capabilities, views are read capabilities.

Key properties:

  • id — unique within room
  • scope — owner agent or _shared. Determines read authority for the expression.
  • expr — CEL expression evaluated live on every query.
  • enabled — CEL expression gating visibility.
  • timer — lifecycle.

Read authority model:

  • A view registered by agent-a with scope agent-a can read state["agent-a"] in its expression.
  • The result is public — any agent can query it.
  • The agent controls what's visible by authoring the CEL expression.

Evaluation context for views:

  • The CEL context includes the registrar's private scope (because they authored the view).
  • The result is the projection, not the raw state.

Example:

{ "id": "agent-a-health-status", "scope": "agent-a", "expr": "state[\"agent-a\"].health > 50 ? \"healthy\" : \"wounded\"" }

Other agents see "healthy" or "wounded". They never see the raw health value.

Views in CEL context:

views.agent-a-health-status == "healthy"

Agents can wait on view results, use them in action predicates, or reference them in other views.

4. Auth — Identity and Authority

Three layers of identity and access control.

Room token:

  • Returned at room creation: POST /rooms → { "token": "room_..." }
  • Represents * scope authority (admin)
  • Used for: initial setup, agent promotion, recovery
  • Separate prefix (room_) from agent tokens (as_)
  • Not tied to an agent identity — can be held by a human or orchestrator

Agent tokens:

  • Returned at agent join: POST /rooms/:id/agents → { "token": "as_..." }
  • Proves agent identity on mutations
  • Default authority: own scope only

Scope grants:

  • Room token holder can grant additional scopes to agents
  • PATCH /rooms/:id/agents/:id { "grants": ["_shared", "_messages"] }
  • Agent with grants can direct-write to those scopes
  • * grant = full admin (equivalent to room token authority)

Enforcement:

  • All mutations require authentication (no legacy bypass)
  • Direct writes check: does the agent have authority over the target scope? (own scope, granted scopes, or *)
  • Action invocations bypass scope checks for defined writes (registrar's authority is baked in)
  • View queries bypass scope checks for defined reads (registrar's authority is baked in)

Privilege model summary:

IdentityDefault scopePromotionMechanism
Room token holder*n/aRoom creation
Agent (default)own scopeadditional scopesRoom token holder patches agent
Agent (promoted)own + grantsmore grantsRoom token holder patches agent

Three access modes:

  1. Direct — agent has scope authority (own, granted, or *)
  2. Delegated write — action's registrar had authority, agent invokes it
  3. Delegated read — view's registrar had authority, agent queries it

API Surface

POST   /rooms                            Create room (returns room token)
GET    /rooms/:id                        Room info
GET    /rooms                            List rooms (auth required)

POST   /rooms/:id/agents                 Join room (returns agent token)
GET    /rooms/:id/agents                 List agents (public presence)
POST   /rooms/:id/agents/:id/heartbeat   Update presence
PATCH  /rooms/:id/agents/:id             Update agent (grants, role — room token only)

GET    /rooms/:id/state                  Read state (respects scope privacy)
PUT    /rooms/:id/state                  Direct write (requires scope authority)
PUT    /rooms/:id/state/batch            Batch direct write (same)

PUT    /rooms/:id/actions                Register action
GET    /rooms/:id/actions                List actions (with availability)
GET    /rooms/:id/actions/:id            Get action detail
POST   /rooms/:id/actions/:id/invoke     Invoke action
DELETE /rooms/:id/actions/:id            Remove action

PUT    /rooms/:id/views                  Register view
GET    /rooms/:id/views                  List views (with resolved values)
GET    /rooms/:id/views/:id              Get single view
DELETE /rooms/:id/views/:id              Remove view

GET    /rooms/:id/wait                   Conditional wait (CEL)
POST   /rooms/:id/eval                   CEL debug

~18 endpoints. Down from 20+ with clearer separation of concerns.

CEL Context Shape (Per-Agent)

{ "state": { "_shared": { "phase": "active", "turn": 3 }, "self": { "health": 80, "inventory": ["sword"] } }, "views": { "agent-a-status": "alive", "total-score": 142, "all-ready": true }, "agents": { "agent-a": { "name": "Alice", "role": "warrior", "status": "active" }, "agent-b": { "name": "Bob", "role": "healer", "status": "waiting" } }, "actions": { "attack": { "available": true }, "heal": { "available": false }, "claim-task": { "available": true } }, "messages": { "count": 42, "unread": 3 }, "self": "agent-a", "params": {} }
  • state.self replaces state["agent-id"] — always your own scope
  • Other agent scopes absent from state
  • Agent presence (name, role, status) is public metadata, not private state
  • messages.unread computed from per-agent last_seen_seq
  • params populated during action invocation

Dashboard

Renders scopes uniformly:

  • Mutable scopes as key-value tables
  • Append scopes as logs (entries with sort_key)
  • Actions as invocation cards with availability indicators
  • Views as live-evaluated results
  • Timers as countdown/tick badges on entries
  • Enabled expressions as conditional visibility indicators
  • Agent presence with scope grants visible

Single rendering model, scope-specific affordances layered on top.

Migration Path

Phase 1: Storage Unification

  1. Add sort_key column to state table
  2. Add merge support to write path
  3. Add scope grants column to agents table
  4. Add room token to rooms table

Phase 2: Auth Enforcement

  1. Room creation returns room token
  2. Agent join enforces scope authority on writes
  3. Remove legacy auth bypass — all mutations require auth
  4. Implement PATCH /agents/:id for grant management (room token only)

Phase 3: Views as Peer

  1. Create views table (or _views in state with special handling)
  2. PUT/GET/DELETE /views endpoints
  3. Views carry registrar scope for read authority
  4. Views resolved in CEL context as views.*

Phase 4: Message Migration

  1. Built-in "post message" action writing to _messages scope with append
  2. Built-in "claim" action with merge semantics
  3. Migrate message data into state _messages scope
  4. Deprecate /messages/* endpoints (thin wrappers during transition)

Phase 5: Dashboard + Ergonomics

  1. Rebuild dashboard against scopes, actions, views
  2. Per-agent last_seen_seq for unread tracking
  3. GET /actions as primary agent onboarding (what can I do?)
  4. include param on wait bundles state + actions + views + messages-since

Phase 6: Cleanup

  1. Remove legacy message endpoints
  2. Remove legacy _view scope convention (replaced by views endpoints)
  3. Update SKILL.md, reference docs, examples

Design Principles

  1. State is the substrate. Everything is state. Actions operate on it. Views project it.
  2. Private by default. Agent state is invisible to others. Views are the controlled projection.
  3. Actions are the front door. Unprivileged agents write through actions, not direct state access.
  4. Authority flows from room token. Room token → scope grants → action/view delegation.
  5. Platform has zero opinions about value structure. JSON blobs are opaque. Filtering uses json_extract. Schema lives in action/view definitions, not the platform.
  6. Rooms are bounded. Performance assumes hundreds-to-low-thousands of entries per scope. Archives and new rooms for unbounded data.
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.