SKILL.md
Cubes
A cube is one stored definition in the org's semantic model: which warehouse
table it reads (sql_table), the dimensions you group and filter by,
the measures you aggregate, and the joins to other cubes. Tables,
segments, and every semantic-model query are built on top of these.
You reach them through the RevOS MCP server's generic tools on the cubes
resource:
| Call | What it does |
|---|---|
api_read { "resource": "cubes", "params": { "fields": "id,name,updatedAt" } } | list the stored definitions |
api_read { "resource": "cubes", "id": "<cube id>" } | one stored definition, under definition |
api_read { "resource": "cubes", "method": "meta", "params": { "filter": "name == \"crm_deals\"" } } | the compiled cube as the org can query it |
api_write { "resource": "cubes", "data": { "name", "definition" } } | create |
api_write { "resource": "cubes", "id": "<cube id>", "data": { "definition" } } | update |
api_delete { "resource": "cubes", "id": "<cube id>" } | delete |
The record always goes under data. A body under any other key may be
ignored without an error. api_details({ resource: "cubes", operation: "create" }) is the authority on the definition's JSON schema.
The one thing to keep in mind
The server stores whatever you send, and Cube compiles all of the org's
cubes together. A definition that doesn't compile doesn't fail on save.
It fails the whole org's semantic model: every table, segment, and query
errors until someone fixes it. Common causes are a join to a cube that
doesn't exist, a ${…} reference to a member that isn't there, or a removed
dimension that a table column still uses. A wrong column name or bad SQL is
quieter: the model compiles, and only queries touching that member fail.
So every write follows the same loop:
- Keep the before-state. For an edit, hold the full current
definitionyou just read. For a create, remember the new id. - Write.
- Verify at once with
metafiltered to the cube's name. An error there means the org's model no longer compiles. Restore immediately: write the saved definition back, or delete the cube you just created. Then tell the user what happened, and don't keep editing on a broken model. Ifmetastill shows the old version, wait a few seconds and read again; Cube polls for changes. - Prove the SQL with one small query over the members you added or
changed:
api_read { "resource": "cubes", "method": "query", "body": { "query": { "dimensions": ["crm_deals.stage"], "measures": ["crm_deals.count"], "limit": 3 } } }. An error here names the bad column or expression. Fix it the same way.
Writes need the Developer role. A 403 means the connected user doesn't have it, so say so rather than retrying.
Before the first write: two checks
Is the org in cubes mode? If the stored list is empty but meta returns
cubes, the org is still on the legacy, auto-generated model. Creating its
first stored cube switches the org to cubes mode, and every auto-generated
cube disappears with it. Don't create one. Tell the user it needs a
deliberate migration in RevOS instead.
Are the cubes managed as code? Some orgs ship cubes from a repo with the
RevOS CLI (revos apply). An edit made here is overwritten on their next
apply. If the user mentions a repo or the CLI, point them there instead.
Reading
- What's defined, and how: list with
fields: "id,name,updatedAt", thengetby id for the fulldefinition. The list'sfilteris CEL onname(name == "crm_deals",name.startsWith("hubspot_")). - What's actually queryable:
meta. It differs from the stored list:- It is compiled. Members are fully qualified, such as
crm_deals.amount, and use camelCase keys. - It adds the cubes RevOS generates itself, which you never write:
- a cube per table that has stored columns or scores, named
model_stream_…___local(a table that only reads cubes has none); - a cube per static segment, named
segment_…; - a view per table.
- a cube per table that has stored columns or scores, named
- It is compiled. Members are fully qualified, such as
When the user asks how a cube is defined, show the relevant part of the stored definition: the table, the dimensions with their types, the measures, and the joins. Explain it in plain words. Don't dump the JSON. To answer questions about the data, switch to query-semantic-model.
Who depends on a cube
Before you rename or remove anything, or delete a cube, find its users. Each reference to the old name or member breaks:
- Other cubes: a
joinskey equal to this cube's name, or${name.…}in any join, dimension, or measure SQL. Get each stored definition and search it. Joins are declared on either side. - Tables: list them with
api_read { "resource": "tables", "params": { "fields": "id,name,streams,objectsColumns" } }. Each table has:streams[].id(a cube name) andstreams[].joinPath(dotted cube names);objectsColumns[], whosestreamId+pathpoint at a cube's dimension.
- Segments: list them, then
geteach by id. Each hascubes[].joinPathand themembers in itsfilter.
Report what you found to the user before acting: "crm_deals is used by the Deals table (7 columns) and 2 segments".
Creating, editing, deleting
Each has its own reference. Read the one you need before writing:
- New cube: references/create.md
- Change a cube (add or fix members, joins, rename): references/edit.md
- Delete a cube: references/delete.md
- Definition syntax (dimensions, measures, joins, SQL references): references/definition.md
Reporting back
After a write, tell the user in two or three sentences:
- what changed, in their terms ("added Amount as a sum measure on Deals");
- that
metacompiled and a test query ran; - any judgment call you made, such as a join's relationship or a dimension's type.
If you had to restore, say so plainly, along with the error that caused it.