CEL is used throughout sync for predicates, computed values, enabled expressions, and views.
Every CEL expression is evaluated against a context object. The shape depends on who is evaluating:
{
state: {
_shared: { phase: "playing", turn: 3 },
_messages: { "1": { from: "alice", body: "hi" }, ... },
self: { health: 80, inventory: ["sword"] } // own scope mapped to "self"
},
views: {
"alice-status": "healthy",
"total-score": 142
},
agents: {
"agent-a": { name: "Alice", role: "warrior", status: "active" },
"agent-b": { name: "Bob", role: "healer", status: "waiting" }
},
actions: {
"attack": { available: true, enabled: true },
"heal": { available: false, enabled: true }
},
messages: { count: 42, unread: 3, directed_unread: 1 },
self: "agent-a",
params: {}
}
Actions and views evaluate with the registrar's scope authority:
agent-a can reference state["agent-a"] in its if predicateagent-a can reference state["agent-a"] in its expr== != < <= > >=
&& || !
+ - * / %
condition ? value_if_true : value_if_false
"item" in list // list contains
"key" in map // map has key
string.contains("sub")
string.startsWith("pre")
string.endsWith("suf")
string.size() // length
state._shared.phase == "playing" state._shared.turn > 0 state.self.health > 0
views["alice-status"] == "healthy" views["all-ready"] == true
agents["agent-a"].status == "active" agents[self].status == "waiting"
actions["attack"].available == true
messages.unread > 0 messages.directed_unread > 0 messages.count >= 10
// Only claimable if not yet claimed state._tasks[params.key].claimed_by == null // Only if self posted it state._tasks[params.key].from == self
// It's my turn state._shared.current_player == self // Turn limit not reached state._shared.turn < state._shared.max_turns
state._shared.phase == "playing" && agents[self].status == "active" && state.self.health > 0 && actions["attack"].available
| Context | Field | Purpose |
|---|---|---|
| State write | if | Write gate — must be true for write to proceed |
| State entry | enabled | Entry visibility — hidden when false |
| Action | if | Invocation gate — must be true to invoke |
| Action | enabled | Action visibility — hidden when false |
| Action write | expr: true on value | Compute value from expression |
| View | expr | Compute projection from state |
| View | enabled | View visibility — hidden when false |
| Wait | condition | Block until true |
| Eval | expr | Debug evaluation |
Inside action writes, values support template substitution:
${self} — invoking agent's ID${params.name} — parameter value${now} — ISO 8601 timestamp at invocation timeSubstitution is deep — works inside nested objects and arrays:
{ "value": { "from": "${self}", "claimed_at": "${now}", "data": { "items": ["${params.item}"], "metadata": { "author": "${self}" } } } }
For computed values, use "expr": true on the write entry:
{ "value": "state._shared.turn + 1", "expr": true }
enabled contexts mean "not enabled" (hidden)if contexts mean "precondition not met" (409)bigint results are automatically converted to numbers