> ## Documentation Index
> Fetch the complete documentation index at: https://docs.inworld.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Common Expression Language

## Overview

CEL (Common Expression Language) enables dynamic decision-making in your Inworld Agent Runtime graphs. Use CEL expressions to control when edges fire, creating intelligent routing based on data content, user properties, confidence scores, and more.

## Basic Syntax

CEL expressions are used in the `conditionExpression` parameter when adding edges to your graph:

```ts theme={"system"}
.addEdge(sourceNode, targetNode, {
  conditionExpression: 'input.confidence > 0.8'
})
```

## Understanding the Input Object

In CEL expressions, `input` refers to the data output from the source node that the edge is connected to. When an edge is evaluated, CEL receives the previous node's output as the `input` object.

#### Example flow

```ts theme={"system"}
// UserAuthNode outputs: { user: { id: "123", tier: "premium" }, isAuthenticated: true }
// The edge condition receives this as 'input'
.addEdge(userAuthNode, premiumFeatureNode, {
  conditionExpression: 'input.user.tier == "premium" && input.isAuthenticated == true'
})
```

#### Key Points

* `input` = output data from the source node of the edge
* Structure varies based on what the source node produces
* Always verify the source node's output format before writing CEL expressions

## The Data Store Object

In addition to `input`, CEL expressions also have access to a `data_store` object that provides persistent storage across graph execution. The data store allows you to share state between different nodes and maintain context throughout the graph's lifecycle.

### Data Store Operations

#### Checking if a Variable Exists

```ts theme={"system"}
data_store.contains('variable_name')
```

#### Getting a Variable Value

```ts theme={"system"}
data_store.get('variable_name')
```

**Note:** Data store values are stored as objects with a `text` property, so you need to access `.text` to get the actual value:

```ts theme={"system"}
data_store.get('my_bool_var').text == 'true'
```

### Data Store vs Input

| Aspect        | `input`                | `data_store`                    |
| :------------ | :--------------------- | :------------------------------ |
| **Scope**     | Current edge only      | Entire graph execution          |
| **Source**    | Previous node's output | Persistent storage across nodes |
| **Lifecycle** | Per edge evaluation    | Graph lifetime                  |
| **Use Case**  | Node-to-node data flow | Cross-node state management     |

## Data Type Limitations

**Important:** The Inworld Agent Runtime only supports simple data types in node inputs and outputs. Complex JavaScript objects and class instances are not supported.

### Supported Types:

* Primitives: `string`, `number`, `boolean`
* Arrays of primitives: `string[]`, `number[]`, `boolean[]`
* Plain objects with primitive properties: `{ name: string, age: number }`
* Nested plain objects: `{ user: { id: string, tier: string } }`

### Unsupported Types:

* Class instances (e.g., `new Date()`, custom classes)
* Functions
* Symbols
* Complex objects with methods

## 1. Fundamental Operations

### Boolean Comparisons

#### Equality Checks

**Expression:** `input.is_safe == true`\
**Use Case:** Safety Gate - Route conversation through safety checker before proceeding to main chat flow. If content is flagged as unsafe, redirect to the safety response node.

```ts theme={"system"}
.addEdge(safetyNode, mainChatNode, {
  conditionExpression: 'input.is_safe == true'
})
.addEdge(safetyNode, safetyResponseNode, {
  conditionExpression: 'input.is_safe == false'
})
```

**Expression:** `input.status == "approved"`\
**Use Case:** Content Moderation - Only allow approved content to proceed to LLM generation. Rejected content gets routed to the moderation review queue.

#### Inequality Checks

**Expression:** `input.user_tier != "banned"`\
**Use Case:** User Access Control - Block banned users from accessing premium features. Route them to account restriction notice instead.

**Expression:** `input.language != "en"`\
**Use Case:** Multi-language Routing - Route non-English inputs to translation service first, then proceed to the main processing pipeline.

### Numeric Comparisons

#### Greater Than / Less Than

**Expression:** `input.confidence_score > 0.8`\
**Use Case:** High-Confidence Intent Routing - Only route to specialized handlers if confidence is high enough. Low confidence goes to the general fallback handler.

```ts theme={"system"}
.addEdge(intentNode, specializedHandler, {
  conditionExpression: 'input.confidence_score > 0.8'
})
.addEdge(intentNode, fallbackHandler, {
  conditionExpression: 'input.confidence_score <= 0.8'
})
```

**Expression:** `input.token_count < 100`\
**Use Case:** Token Limit Enforcement - Short queries go to fast processing pipeline. Long queries get chunked or use different LLM settings.

#### Greater/Less Than or Equal

**Expression:** `input.user_credits >= 10`\
**Use Case:** Premium Feature Access - Users with sufficient credits access premium LLM models. Others get routed to standard models.

**Expression:** `input.response_time_ms <= 5000`\
**Use Case:** Performance SLA Monitoring - Fast responses proceed normally. Slow responses trigger performance alerts and fallback routes.

### String Operations

#### String Contains/StartsWith/EndsWith

**Expression:** `input.message.startsWith("Hello")`\
**Use Case:** Greeting Detection - Route greeting messages to warm welcome flow. Other messages go to standard conversation flow.

**Expression:** `input.email.endsWith("@company.com")`\
**Use Case:** Internal User Detection - Company employees get access to internal features. External users follow standard customer journey.

**Expression:** `input.query.contains("urgent")`\
**Use Case:** Priority Request Handling - Urgent requests bypass normal queue and go to fast-track processing. Regular requests follow a standard processing timeline.

### Boolean Logic

#### AND Operations

**Expression:** `input.verified == true && input.age >= 18`\
**Use Case:** Age-Gated Content Access - Only verified adult users can access mature content. Others get redirected to age-appropriate alternatives.

**Expression:** `input.is_premium && input.feature_enabled`\
**Use Case:** Feature Flag + Subscription Check - Premium users with beta features enabled get new functionality. Others continue with the standard feature set.

#### OR Operations

**Expression:** `input.role == "admin" || input.role == "moderator"`\
**Use Case:** Administrative Access - Admins and moderators get access to management tools. Regular users follow standard user flow.

**Expression:** `input.emergency == true || input.priority == "high"`\
**Use Case:** Emergency Response Routing - Emergency or high-priority requests bypass normal processing. Route directly to rapid response handlers.

#### NOT Operations

**Expression:** `!input.maintenance_mode`\
**Use Case:** Maintenance Mode Check - During maintenance, route to "service unavailable" message. Normal operations proceed when not in maintenance.

## 2. Data Structure Navigation

### Object Property Access

#### Simple Property Access

**Expression:** `input.user.tier`\
**Use Case:** User Tier Routing - Route based on subscription level: free, premium, enterprise. Each tier gets different LLM models and response limits.

```ts theme={"system"}
.addEdge(inputNode, premiumLLMNode, {
  conditionExpression: 'input.user.tier == "premium"'
})
.addEdge(inputNode, standardLLMNode, {
  conditionExpression: 'input.user.tier == "free"'
})
```

**Expression:** `input.session.language`\
**Use Case:** Language Preference - Route to language-specific LLM models and response templates. Maintain conversation context in user's preferred language.

#### Nested Property Access

**Expression:** `input.user.profile.preferences.voice_enabled`\
**Use Case:** Voice Feature Toggle - Users who enabled voice in their profile settings get TTS output. Others receive text-only responses.

**Expression:** `input.conversation.metadata.sentiment.score`\
**Use Case:** Sentiment-Based Response Adjustment - Negative sentiment routes to empathetic response templates. Positive sentiment continues with standard conversation flow.

### Array Operations

#### Array Size

**Expression:** `size(input.intent_matches) > 0`\
**Use Case:** Intent Detection Success - If intents were detected, route to intent-specific handlers. No intents detected routes to general conversation flow.

```ts theme={"system"}
.addEdge(intentDetectionNode, intentHandlerNode, {
  conditionExpression: 'size(input.intent_matches) > 0'
})
.addEdge(intentDetectionNode, generalChatNode, {
  conditionExpression: 'size(input.intent_matches) == 0'
})
```

**Expression:** `size(input.knowledge_results) >= 3`\
**Use Case:** Knowledge Base Confidence - Multiple knowledge matches indicate high-confidence answers. Route to direct answer generation vs. "I'm not sure" response.

#### Array Element Access

**Expression:** `input.intents[0].name == "booking"`\
**Use Case:** Primary Intent Classification - Top-ranked intent determines conversation flow. Booking intents route to reservation system integration.

**Expression:** `input.search_results[0].score > 0.9`\
**Use Case:** High-Confidence Search Results - Highly relevant results get featured answer treatment. Lower confidence results trigger clarification questions.

#### Array Contains/Membership

**Expression:** `"admin" in input.user.roles`\
**Use Case:** Role-Based Access Control - Users with admin role get access to system management features. Regular users are limited to standard functionality.

**Expression:** `input.detected_language in ["en", "es", "fr"]`\
**Use Case:** Supported Language Check - Supported languages proceed with native processing. Unsupported languages route to translation service first.

### Complex Data Filtering

#### Filter with Conditions

**Expression:** `size(input.intent_matches.filter(x, x.score >= 0.8)) > 0`\
**Use Case:** High-Confidence Intent Filtering - Only consider intents with high confidence scores. Route to specialized handlers vs. general conversation.

```ts theme={"system"}
.addEdge(intentNode, highConfidenceHandler, {
  conditionExpression: 'size(input.intent_matches.filter(x, x.score >= 0.8)) > 0'
})
.addEdge(intentNode, lowConfidenceHandler, {
  conditionExpression: 'size(input.intent_matches.filter(x, x.score >= 0.8)) == 0'
})
```

**Expression:** `size(input.knowledge_records.filter(r, r.verified == true)) >= 2`\
**Use Case:** Verified Knowledge Validation - Only use verified knowledge sources for answers. Insufficient verified sources trigger "needs verification" flow.

#### Complex Object Filtering

**Expression:** `input.messages.filter(m, m.role == "user" && m.timestamp > input.last_hour)`\
**Use Case:** Recent User Message Analysis - Analyze only recent user messages for context. Ignore older conversation history for focused responses.

**Expression:** `input.messages.filter(m, m.role == "user" && m.timestamp.seconds > input.current_time.seconds)`\
**Use Case:** Recent User Message Analysis - Analyze only messages sent after session start. Requires timestamp objects with seconds/nanos fields.

**Expression:** `input.features.filter(f, f.enabled == true && f.beta == false)`\
**Use Case:** Stable Feature Selection - Only route to production-ready features. Beta features require special opt-in handling.

### Existence Checks

#### Property Existence

**Expression:** `has(input.user.subscription)`\
**Use Case:** Subscription Status Check - Subscribed users get premium features. Non-subscribers see upgrade prompts and limited functionality.

**Expression:** `has(input.conversation.context)`\
**Use Case:** Conversation Context Availability - Continue existing conversation if context exists. Start a fresh conversation flow if there is no prior context.

#### Complex Existence Patterns

**Expression:** `has(input.user.preferences) && has(input.user.preferences.notifications)`\
**Use Case:** Notification Preference Check - Users with notification preferences get personalized alerts. Others receive default notification settings.

**Expression:** `has(input.session.auth_token) && input.session.auth_token != ""`\
**Use Case:** Authentication Validation - Authenticated users access full functionality. Unauthenticated users limited to public features only.

## Production Examples from Character Engine

### Safety Pipeline Routing

```ts theme={"system"}
// Route based on safety check results
.addEdge(safetySubgraphNode, safetyResultTransformNode, {
  conditionExpression: 'input.is_safe == true'
})
.addEdge(safetySubgraphNode, safetyResponseNode, {
  conditionExpression: 'input.is_safe == false'
})
```

### Intent Confidence Thresholding

```ts theme={"system"}
// High confidence intent matches
.addEdge(strictMatchNode, topNFilterNode, {
  conditionExpression: 'size(input.intent_matches) >= 1',
  optional: true
})
// Low confidence fallback to LLM
.addEdge(intentEmbeddingMatchesAggregatorNode, llmPromptVarsNode, {
  conditionExpression: 'size(input.intent_matches.filter(x, x.score >= 0.88)) < 1'})
```

### Behavior Action Discrimination

```ts theme={"system"}
// Different behavior types
.addEdge(behaviorProducerNode, dialogPromptVariablesNode, {
  conditionExpression: 'input.type == "SAY_INSTRUCTED"'
})
.addEdge(behaviorProducerNode, behaviorActionTransformNode, {
  conditionExpression: 'input.type == "SAY_VERBATIM"'
})
```

### Message Validation

```ts theme={"system"}
// Ensure messages exist before processing
.addEdge(flashMemoryPromptBuilder, flashMemoryLLMChat, {
  conditionExpression: 'size(input.messages) > 0'
})
```

## Best Practices

1. **Keep expressions simple and readable**, Complex logic should be broken into multiple edges when possible
2. **Use meaningful variable names,** `input.user.tier` is clearer than `input.t`
3. **Handle edge cases**, Always provide fallback routes for when conditions aren't met
4. **Test thoroughly**, Verify your expressions work with different input data types
5. **Performance considerations**, Avoid expensive operations in frequently evaluated expressions
6. **Understand your data flow** - Always know what structure the source node outputs before writing CEL expressions
7. **Document expected input formats** - Comment your edge conditions with expected input structure for maintainability
8. **Use only simple data types** - Ensure all node outputs use primitives and plain objects, not class instances
9. **Convert complex types** - Transform timestamps, custom classes, and complex objects to plain object representations
10. **Validate data serialization** - Test that your data can be JSON.stringify'd without loss of information
11. **Always check data\_store.contains() first** - Prevent runtime errors by verifying variables exist before accessing them
12. **Remember the .text property** - Data store values are objects; always access .text to get the actual stored value
13. **Use data\_store for cross-node communication** - When you need to share state between non-adjacent nodes, use data\_store instead of passing through intermediate nodes

## Common Patterns

### Confidence-Based Routing

```ts theme={"system"}
// High confidence → specialized handler
// Medium confidence → general handler  
// Low confidence → fallback handler
conditionExpression: 'input.confidence >= 0.9'  // High
conditionExpression: 'input.confidence >= 0.7 && input.confidence < 0.9'  // Medium
conditionExpression: 'input.confidence < 0.7'  // Low
```

### Multi-Condition Validation

```ts theme={"system"}
// Ensure all requirements are met
conditionExpression: 'has(input.user) && input.user.verified && input.user.credits > 0'
```

### Array Processing

```ts theme={"system"}
// Check if any items meet criteria
conditionExpression: 'size(input.items.filter(x, x.active)) > 0'
// Check if all items meet criteria  
conditionExpression: 'size(input.items.filter(x, x.active)) == size(input.items)'
```

### Data Store State Management

```ts theme={"system"}
// Safe data store access pattern
conditionExpression: "data_store.contains('flag_name') && data_store.get('flag_name').text == 'expected_value'"
// Multiple data store conditions
conditionExpression: "data_store.contains('var1') && data_store.contains('var2') && data_store.get('var1').text == 'true' && data_store.get('var2').text != 'disabled'"
```

## Debugging Tips

1. **Use simple expressions first**, Start with basic comparisons and add complexity gradually
2. **Check data types**, Ensure your input data structure matches your CEL expression expectations
3. **Test with sample data**, Create test cases with known input values to verify expression behavior
4. **Use parentheses**, Make complex boolean logic explicit with parentheses: `(A && B) || (C && D)`
5. **Inspect actual input data** - Log or debug the actual `input` object structure before writing complex expressions
6. **Validate source node outputs** - Ensure source nodes produce the expected data structure that your CEL expressions assume
7. **Check for unsupported types** - If CEL expressions fail unexpectedly, verify that input data contains only supported primitive types
8. **Use JSON.stringify for validation** - Test your node outputs with `JSON.stringify()` - if it fails or loses data, the types aren't supported
9. **Debug data store state** - Log data\_store contents to understand what variables are available and their current values
10. **Test data store persistence** - Verify that data store variables persist correctly across different graph execution paths
