# 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_

## 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: https://github.com/IvanMurzak/RQL (spec/semantics.md is the normative contract).

## 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 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 members the host's policy exposes 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 | 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 | 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 | Not yet. Godot-MCP uses its own tools. |
| Brutal | 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 in the same way — 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: 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.

---

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