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

# CEL Expressions

> Reference guide for writing CEL policy expressions in Devctrl.

Devctrl policies use the [Common Expression Language (CEL)](https://cel.dev/) — a lightweight, fast expression language designed for security policy evaluation. CEL expressions are evaluated on every tool call.

## Available variables

Every CEL expression has access to three top-level objects:

### identity

Information about the agent making the request.

| Variable          | Type               | Description                                 |
| ----------------- | ------------------ | ------------------------------------------- |
| `identity.name`   | `string`           | The identity name (e.g., `"support-agent"`) |
| `identity.labels` | `map[string, any]` | Key-value labels assigned to the identity   |

### task

Information about the active task session. Only available when the request includes an `X-Task-Token` header.

| Variable       | Type               | Description                                                    |
| -------------- | ------------------ | -------------------------------------------------------------- |
| `task.name`    | `string`           | The task definition name (e.g., `"resolve-ticket"`)            |
| `task.labels`  | `map[string, any]` | Key-value labels assigned to the task definition               |
| `task.context` | `map[string, any]` | The runtime context provided when the task session was created |

### request

Information about the current tool call.

| Variable            | Type               | Description                       |
| ------------------- | ------------------ | --------------------------------- |
| `request.tool.name` | `string`           | The name of the tool being called |
| `request.tool.args` | `map[string, any]` | The arguments passed to the tool  |

## Operators

CEL supports standard operators:

| Operator             | Example                           | Description     |
| -------------------- | --------------------------------- | --------------- |
| `==`                 | `identity.name == "bot"`          | Equality        |
| `!=`                 | `request.tool.name != "delete"`   | Inequality      |
| `&&`                 | `a == 1 && b == 2`                | Logical AND     |
| `\|\|`               | `a == 1 \|\| b == 2`              | Logical OR      |
| `!`                  | `!identity.labels.restricted`     | Logical NOT     |
| `in`                 | `request.tool.name in ["a", "b"]` | List membership |
| `>`, `<`, `>=`, `<=` | `task.context.amount < 1000`      | Comparison      |

## String functions

| Function       | Example                                  | Description         |
| -------------- | ---------------------------------------- | ------------------- |
| `startsWith()` | `request.tool.name.startsWith("get_")`   | String prefix check |
| `endsWith()`   | `request.tool.name.endsWith("_read")`    | String suffix check |
| `contains()`   | `request.tool.name.contains("customer")` | Substring check     |
| `matches()`    | `request.tool.name.matches("^get_.*")`   | Regex match         |

## Map access

Access nested map values using dot notation or index notation:

```cel theme={null}
// Dot notation
identity.labels.team

// Index notation (useful for keys with special characters)
identity.labels["team-name"]

// Check if a key exists using 'has'
has(identity.labels.admin)
```

<Warning>
  Accessing a map key that doesn't exist causes an error, which results in a **deny**. Use `has()` to check for optional keys before accessing them.
</Warning>

## Common patterns

<Note>
  Tool names are namespaced as `serverName__toolName` (e.g., `github__list_issues`). Use the full namespaced name in your expressions. The examples below use short names for readability.
</Note>

### Allow specific tools

```cel theme={null}
request.tool.name in ["github__get_issue", "github__list_issues", "github__add_comment"]
```

### Check identity labels

```cel theme={null}
identity.labels.team == "engineering"
```

### Scope access by task context

```cel theme={null}
task.context.customer_id == request.tool.args.customer_id
```

### Combine multiple conditions

```cel theme={null}
identity.labels.team == "support"
  && request.tool.name in ["get_issue", "get_customer", "add_comment"]
  && task.context.customer_id == request.tool.args.customer_id
```

### Block dangerous tools

```cel theme={null}
// As a deny rule — blocks these tools for everyone
request.tool.name in ["drop_table", "delete_database", "rm_rf"]
```

### Allow based on optional label

```cel theme={null}
// Check if label exists before reading it
has(identity.labels.admin) && identity.labels.admin == true
```

## Evaluation rules

1. **Deny rules** are evaluated first. If any deny rule's expression returns `true`, the request is blocked.
2. **Allow rules** are evaluated next. If any allow rule's expression returns `false`, the request is blocked.
3. If all rules pass, the request is allowed.
4. If **no active release** exists, the project's default action applies.
5. If a CEL expression **errors** (e.g., accessing a missing key), it's treated as a deny.

<Note>
  Order matters. Place deny rules before allow rules for clear, predictable behavior.
</Note>

## Next steps

<CardGroup cols={2}>
  <Card title="Policy examples" icon="book-open" href="/policies/examples">
    Real-world policy patterns you can copy and adapt.
  </Card>

  <Card title="Write policies" icon="pencil" href="/console/write-policies">
    Author policies in the console's policy editor.
  </Card>
</CardGroup>
