# RQL — Reflection Query Language

RQL (Reflection Query Language) is a compact pipeline language for querying and safely changing live object graphs — scenes, GameObjects, components and assets. Since Unity-MCP 1.0.0 it is the data layer of the Unity plugin: the agent reads and edits a Unity project through three tools, rql-query, rql-mutate and rql-schema, instead of one tool per operation. Unity is the only engine that uses RQL today; Unreal Engine and Godot do not yet, and Brutal is coming soon.

_Last updated: 2026-10-17_

Docs · Reference · RQL

**RQL (Reflection Query Language)** is a compact pipeline language for querying and safely changing live object graphs — scenes, GameObjects, components and assets. Since **Unity-MCP 1.0.0** it is the Unity plugin's data layer: the agent reads and edits your project through three tools, `rql-query`, `rql-mutate` and `rql-schema`.

## What RQL is

A query is a **source** followed by a pipeline of **stages**: `source | stage | stage …`. Every stage works on _live_ editor objects, not on a JSON copy of the scene. Only the rows that survive filtering and projection are serialized and sent to the agent.

RQL also defines a versioned, runtime-neutral QueryPlan protocol. Runtimes exist for .NET (the reference, published to NuGet as `com.IvanMurzak.Rql`), TypeScript and C++, and all of them are checked against one set of golden test vectors. Source and the normative spec: [github.com/IvanMurzak/RQL](https://github.com/IvanMurzak/RQL).

## Why the agent uses it

Unity-MCP 1.0.0 (October 15, 2026) replaced about 30 data and Editor-operation tools with `rql-query`, `rql-mutate` and `rql-schema`. The Unity plugin now exposes a small tool set — `package-*`, `rql-*`, `screenshot-*`, `script-*`, `tests-run`, `unity-tool-list` and `tool-set-enabled-state`. Everything else that reads or changes the project (GameObjects, components, assets, scenes, prefabs, console logs, the profiler, Editor state) goes through RQL.

RQL is also the data layer of the remaining tools: by default, complex tool parameters are RQL values (literals, `ref("GameObject#12")`, `unity.find("Name")`) coerced with the same rules as `set`.

- One precise call replaces many exploratory calls: filter, project, sort, count and walk hierarchies in a single query.
- Compact table results use far fewer tokens than object-by-object JSON dumps.
- Errors come back as diagnostics with hints (an unknown member suggests the closest names), so the agent corrects itself.
- Writes are all-or-nothing and undoable (Ctrl+Z), with a dry-run preview.

In the RQL repository's own evaluation (a DeepSeek agent on tasks with hidden answers; method and caveats in its [RESULTS.md](https://github.com/IvanMurzak/RQL/blob/main/RESULTS.md)), RQL tools solved 100% of a fresh 90-task set written after tuning was frozen. A conventional reflection-tool baseline (find / dump object as JSON / set / call) solved 66.7% of the same set and cost 40× more per successful task.

## The three tools

| Tool | Arguments | What it does |
| --- | --- | --- |
| `rql-query` | `query` | Read-only RQL over the live scene, assets and Editor state; returns compact rows. A query containing `set` or `call` fails with `permission-denied`. |
| `rql-mutate` | `query`, `dry_run` | A query whose last stage is `set` or `call`. All-or-nothing, undoable in the Editor, changed assets and scenes saved to disk; `dry_run: true` returns the receipt without changing anything. |
| `rql-schema` | `type` (optional) | Without a type: sources, asset functions and the component types in the loaded scenes. With a type name (e.g. `"Light"`, `"Enemy"`, `"Vector3"`): its members (`ro` = read-only) and callable methods. |

## Query syntax basics

### Sources (Unity)

`objects` (every GameObject in the loaded scenes, inactive ones included), `roots`, `scenes`, `selection`, `prefab` (the prefab open in prefab mode), `console` (Editor log entries), `shaders`, `componentTypes`, `profiler`, `tests`, `testResults` and `editor` (Editor state and operations). Assets: `unity.assets("t:Material")` or `unity.assets("t:Prefab", "Assets/Enemies")`, and `unity.asset("Assets/Mats/Red.mat")`. `ref("Light#1234")` resolves a stable reference; `unity.find("Player")` or `unity.find("Level/Pillar_1")` finds a GameObject by unique name or path. Installed extensions add their own sources.

### Stages

| Stage | Meaning |
| --- | --- |
| `where <predicate>` | keep items whose predicate is true |
| `select a b.c alias=(expr)` | project columns (wrap computed expressions in parentheses) |
| `sort <expr> [desc], …` | stable sort |
| `first N` · `skip N` · `count` | paging and counting |
| `get <path>` | replace each item by a member (lists are flattened) |
| `recurse children [max-depth=N] [include-self=false]` | walk the hierarchy |
| `distinct [expr]` · `group-by expr` · `sum / min / max / avg expr` | deduplicate and aggregate |
| `set path = expr, …` · `call Method(args)` | mutations (`rql-mutate` only) |

### Expressions, paths and meta members

- Operators: `== != < <= > >= and or not + - * / %`; `contains`, `starts-with`, `ends-with` (case-insensitive); `matches "regex"`; `x in [a, b]`; `x is null`; `$ is Light` (includes subclasses); `has(member)`.
- Literals: numbers, `"strings"`, `true` / `false` / `null`, lists `[a, b]`, objects `{x: 1, y: 2}`.
- Paths: `a.b.c`, `list[0]`, `map["key"]`; `.member` on a list maps over its elements. List methods: `.any(pred)` `.all(pred)` `.count([pred])` `.sum(e)` `.min(e)` `.max(e)` `.first([pred])`.
- Meta members: `$` (current item), `$ref` (stable id such as `Light#1234`), `$type`, `$path`, `$index`, `$depth`.
- Enums compare to strings (`type == "Spot"`); `==` on strings is case-sensitive. Vectors and colors are written `"(1, 2, 3)"`, `[1, 2, 3]` or `{x: 1, y: 2, z: 3}`; colors also `"#ff8800"`.

### Examples

```text
objects | where name contains "Enemy" and active | select $ref name transform.position
objects | get components | where $ is Light and intensity > 2 | select gameObject.name intensity color
roots | recurse children | where components.count() == 1 | select $path name
unity.assets("t:Material") | select name shader.name properties["_Color"]
console | where logType == "Error" | sort sequence desc | first 5 | select message
componentTypes | where name contains "Collider" | select name
```

## How results look to the agent

Results are compact text tables: optional diagnostic lines, a header line (`N results`, or `N of M (truncated; …)` when the row budget, 50 by default, is exceeded), then `col|col` and one row per line. Objects render as `Type#id(Name)`, structs and maps as `{k:v,…}`, lists as `[a,b]`. In Unity, vectors and colors render as `(x, y, z)`. The example below comes from the RQL README (sample runtime):

```text
objects | where $ is Enemy and Health < 20 | sort Health | first 5 | select $ref Name Health MaxHealth Faction Transform.Position

5 results
$ref|Name|Health|MaxHealth|Faction|Transform.Position
Enemy#233|Wraith_0232|1|90|Undead|{x:207.96,y:25.52,z:1.02}
```

Diagnostics use the form `warning unknown-member: … — hint: …`. Missing members, `null` values and errors stay distinct (three-valued logic), so `where not (Health > 5)` does not return objects that have no `Health` member — use `has(Health)`. A dry run starts with `DRY RUN — nothing changed;`. A receipt lists `$ref`, `member`, `old` and `new` for `set`, or `$ref`, `method` and `result` for `call`.

## Changing things

```text
objects | where name == "Player" | set transform.position = "(0, 1, 0)"
objects | get components | where $ is Light | set intensity = intensity * 2, color = "#ffcc88"
objects | where tag == "Enemy" | set active = false
unity.asset("Assets/Mats/Red.mat") | set properties["_Color"] = "(1, 0, 0, 1)"
editor | call CreateGameObject("Crate", ref("GameObject#1234"), "Cube")
objects | where name == "Crate" | call AddComponent("Rigidbody")
```

Every target and value is resolved and checked before anything is written; if one write fails, the writes already made are restored. A `call` cannot be rolled back, so its receipt reports how many calls completed. Unity-MCP records each mutation as one Undo step and allows at most 20 targets per call.

## Security and permissions

RQL treats query text from an agent as untrusted input that runs inside the editor process.

- **No arbitrary code execution.** There is no eval and no file-system or process access in the language. `call` only reaches methods the host allowlists (`[RqlCallable]` in .NET, or methods an adapter adds); any other method fails with `permission-denied`.
- **Only exposed members are read.** A getter that throws becomes an error value plus a `getter-threw` warning.
- **Writes need two switches:** mutations enabled on the host and a request that allows mutation. The read tool (`rql-query`) cannot write.
- **All-or-nothing writes** with rollback, dry-run receipts, read-only identity members, and a limit of 20 mutation targets unless the host raises it (never beyond its hard ceiling, 1000 by default).
- **Budgets bound every query:** `maxVisited` 1,000,000 (error), `maxRows` 50 (truncation), `maxListItems` 20, `maxDepth` 16, `timeoutMs` 2,000, `maxOutputBytes` 65,536. The .NET runtime also bounds each regex match to 50 ms.
- **Stale references are detected:** a destroyed Unity object reads as `null`, and `ref()` to it is `stale-ref`, instead of touching destroyed objects.

Residual risk

The policy limits _what_ can be written, not whether the change was wise. Hosts should allowlist destructive methods sparingly, and dry runs are worth using for bulk edits.

## Where RQL is available today

| Engine | Status |
| --- | --- |
| [Unity](https://ai-game.dev/engines/unity) | **Yes**, since Unity-MCP 1.0.0. Unity-MCP ships the RQL .NET assemblies (`Rql.Contracts`, `Rql.Parser`, `Rql.Execution`) as Editor-only plugins. Every Unity extension contributes an RQL module instead of its own MCP tools. |
| [Unreal Engine](https://ai-game.dev/engines/unreal) | Not yet. Unreal-MCP uses its own tool families. RQL has a C++ runtime and a documented Unreal integration path, but it is design only and not implemented. |
| [Godot](https://ai-game.dev/engines/godot) | Not yet. Godot-MCP uses its own tools. |
| Brutal Soon | Coming soon. No plugin yet. |

## Extensions are RQL modules

Since Unity-MCP 1.0.0, the Unity extensions (Animation, Cinemachine, Navigation, Particle System and the rest) add no MCP tools. Each one contributes an **RQL module**: type adapters, sources (for example `navigation`), functions, and an agent guide that is rendered into the `unity-rql` skill. You can extend the agent for your own project types the same way.

Guide Teach the agent your own types

Write a type adapter and an `IRqlModule` in your project's Editor code — see [Custom type adapters](https://ai-game.dev/docs/rql/custom-adapters).

## License

RQL is source-available under the [PolyForm Shield License 1.0.0](https://polyformproject.org/licenses/shield/1.0.0): free to use, including in commercial products, except to provide a product that competes with the licensor's products. It is not an OSI open-source license. Unity-MCP itself is Apache-2.0; the RQL assemblies it ships keep their PolyForm Shield license.

[Custom type adapters →](https://ai-game.dev/docs/rql/custom-adapters) [Install for Unity →](https://ai-game.dev/engines/unity) [Unity best practices →](https://ai-game.dev/docs/best-practices/unity)

---

Source (HTML): https://ai-game.dev/docs/rql
