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

# CQL — the Context Query Language

> A compact string for scoping your graph by name. CQL is how you describe which entities belong to a slice or a unit of context.

**CQL (Context Query Language)** is a short string that names *which entities
belong* to a scope. You write a CQL string anywhere Penumbra asks for a scope:
to [preview context](/sdk/context#preview-a-scope), to
[author a slice](/sdk/slices#author-a-slice), or to
[compile context](/sdk/context#compile-a-scope-or-slice).

```
type:Company shape:"Account Intelligence" after:7d
```

That reads as: *Company entities, in the Account Intelligence shape, created in
the last 7 days.* No UUIDs, no SQL, no embeddings — just names.

## How CQL works

The mental model is the most important thing to hold:

<Note>
  **A scope is a set, and CQL describes its membership.** It answers "what is *in*
  this slice?" — not "how do I rank or search it?" Ranking and traversal are
  separate concerns (see [What CQL is not](#what-cql-is-not)).
</Note>

A CQL string is a sequence of `prefix:value` tokens (plus optional date bounds).
Each token names a dimension of membership:

| Token                | Means                                | Example                                              |
| -------------------- | ------------------------------------ | ---------------------------------------------------- |
| `type:`              | Entity type name                     | `type:Company`                                       |
| `shape:`             | Shape display name (one per slice)   | `shape:"Account Intelligence"`                       |
| `source:`            | Source file name                     | `source:"Q2 Deck.pdf"`                               |
| `source-shape:`      | Entities from a source *and* a shape | `source-shape:"Q2 Deck.pdf"->"Account Intelligence"` |
| `after:` / `before:` | Created-at bounds                    | `after:7d` `before:2026-01-01`                       |

Two rules govern how tokens combine:

* **Same kind → union (OR).** `type:Company type:Person` means *Company **or**
  Person.*
* **Different kind → intersection (AND).** `type:Company source:"Q2 Deck.pdf"`
  means *Company entities **that also** come from Q2 Deck.pdf.*

Values are **names, not IDs**. Quote any value with spaces:
`shape:"Account Intelligence"`. Penumbra resolves names to the underlying ids for
you, and tells you (via a warning) if a name doesn't resolve.

### Dates: absolute or relative

`after:` and `before:` bound the created-at timestamp. They accept an absolute
ISO date **or** a relative expression, resolved server-side in UTC:

| Form                                   | Meaning                                                     |
| -------------------------------------- | ----------------------------------------------------------- |
| `2026-01-01`                           | absolute ISO date                                           |
| `last-week`, `last-month`, `last-year` | a week / month / year ago                                   |
| `today`, `yesterday`, `now`            | start of today / yesterday / this instant                   |
| `7d` `-7d` `2w` `12h` `3mo` `1y`       | N units ago (the sign is ignored — scopes look at the past) |

```
type:Company after:last-month     # Companies from the last month
type:Insight after:7d             # Insights from the last 7 days
```

## Write your first CQL string

Build a scope one token at a time, previewing as you go so you see exactly what
it matches before you save it:

```bash theme={null}
# 1. Start broad
type:Company                       # every Company

# 2. Add a shape (intersect)
type:Company shape:"Account Intelligence"

# 3. Add a time bound
type:Company shape:"Account Intelligence" after:30d

# 4. Widen the type (union)
type:Company type:Person after:30d
```

[Preview](/sdk/context#preview-a-scope) returns a `matched` count and
any warnings, so each step gives immediate feedback:

```ts theme={null}
const { matched, scope_warnings } = await pb.context.preview({
  cql: "type:Company after:7d",
});
// matched: 4, scope_warnings: []
```

## CQL string vs. structured filter

Every scope can be written two ways. They express the same thing; pick by feel.

|                       | CQL string (`cql`)                        | Structured filter (`filter`)                   |
| --------------------- | ----------------------------------------- | ---------------------------------------------- |
| Shape                 | One terse line                            | A JSON object                                  |
| Best for              | Humans, quick scoping, the canonical form | Programmatic building, **property predicates** |
| Property predicates   | ✗ (not expressible)                       | ✓ `{ path, operator, value }`                  |
| `gt` / `lt` operators | ✗                                         | ✓                                              |

```ts theme={null}
// These two are equivalent:
{ cql: 'type:Company shape:"Account Intelligence" after:2026-01-01' }

{ filter: {
    types: ["Company"],
    shapes: ["Account Intelligence"],
    after: "2026-01-01",
} }
```

Property predicates have **no CQL syntax** — reach for the structured filter:

```ts theme={null}
{ filter: {
    types: ["Company"],
    properties: [{ path: "stage", operator: "eq", value: "alpha" }],
} }
```

## What CQL is *not*

CQL scopes *membership*. It is deliberately not a search or ranking language, so
a few things you might try are **not** part of a slice — and Penumbra tells you
so in `scope_warnings` rather than silently dropping them:

* **Free text** (`renewal risk`) — that's a search concern. Use
  [search](/sdk/search), then scope the results.
* **`edge:` and `agent:`** — traversal and provenance, not membership.
* **`sort:`** — ordering is applied when you read, not when you define the set.
* **A second `shape:`** — a slice resolves one shape; the rest are ignored with a
  warning.

This "honest warnings" behavior is intentional: a scope that under-resolves tells
you *why*, so you never get a silent `matched: 0`.

## Where to go next

* [Context and slices](/sdk/context) — preview, author, and compile scopes
* [Planes](/concepts/planes) — the layers CQL scopes across
* [Search](/sdk/search) — when you need ranking and free text, not membership
