Agent Skills
Agent Skills are markdown files with YAML frontmatter that live in
saiku-home/skills/. The launcher scans them lazily on every request
and injects the catalogue into the LLM system prompt, so any
AI Ask turn — MDX or Ossie — can
route to an admin-authored workflow instead of freewheeling.
Small feature, disproportionate value: it lets operators codify their team’s canonical questions (“this quarter’s exec rollup”, “the churn cohort”, “the store-comp report”) without owning yet another tool. The skill lives in the repo, versions with the semantic model, and gets code-reviewed like everything else.
Shipped in saiku v4.7 as saiku#1426.
Two invocation paths
File format
---name: weekly-foodmart-rollupdescription: | Weekly revenue rollup for the FoodMart Sales cube: total Store Sales by Product Family for the last 7 days, compared to the prior 7 days. Flags any family with a >20% swing.cube: unknown_foodmart/FoodMart/FoodMart/Sales---
## Steps
1. Query total `[Measures].[Store Sales]` and `[Measures].[Unit Sales]` broken down by `[Product].[Product Family]` for the last 7 days (`[Time].[Weekly].[Week].&[latest]`).
2. Query the same shape for the prior 7 days (`[Time].[Weekly].[Week].&[latest - 1]`).
3. Present the result as a two-column table with a `Δ vs prior` percentage column derived per family.
4. Highlight any Product Family whose `Δ vs prior` swings by more than 20% in either direction.Frontmatter
| Field | Required | Type | Notes |
|---|---|---|---|
name | yes | string | kebab-case, [a-z][a-z0-9-]{0,63}. Used as the slash slug. |
description | yes | string | One line or block scalar. Shown in /ai/skills and the LLM prompt. |
cube | no | string | connection/catalog/schema/cubeName ref. Scopes the skill. |
Unknown top-level keys are rejected. A typo (descripton) surfaces
as a structured UNKNOWN_FIELD error — never as a silent nameless
skill.
Body
Everything after the closing --- fence is the body. Markdown is
recommended but not required; the body is treated as opaque text and
pasted verbatim into the LLM prompt on a slash-command match. Keep it
tight — the LLM has to read the whole file.
Slash command
-
User (or the DimSum widget on your behalf) posts an ask starting with
/:Terminal window curl -sS -X POST -H 'Content-Type: application/json' \-u admin:admin \http://localhost:8080/saiku/api/ai/ask \-d @- <<'EOF'{"question": "/weekly-foodmart-rollup for Q4 instead of this week","cube": {"connectionName":"unknown_foodmart","catalog":"FoodMart","schema":"FoodMart","cubeName":"Sales"}}EOF -
The service parses the slash and looks up
weekly-foodmart-rollupin the catalogue. -
Match → the ask sent to the LLM is:
Skill: weekly-foodmart-rollup## Steps1. Query total [Measures].[Store Sales] ……User follow-up: for Q4 instead of this week -
The LLM runs the steps, applies the follow-up (“Q4 instead of this week”) to the time filter, and emits an
AiQueryRequestas usual. -
Miss → the ask travels unchanged. The LLM sees it as an ordinary prompt with a leading slash. Nothing breaks.
Natural language
The catalogue lands in the LLM system prompt as:
Available skills (admin-authored workflows). When the user's questionclosely matches one of these — or when the message starts with`/<skill-name>` — use the skill's steps to structure your responseinstead of freewheeling:- /weekly-foodmart-rollup: Weekly revenue rollup for the FoodMart Sales cube…- /store-comp-report: Store-level comp vs same period last year…- /churn-cohort: 30/60/90 day churn cohort split by acquisition channel…The model routes on its own. If the user asks “how did stores compare
to last year?”, the natural-language matcher picks
/store-comp-report; the user never sees the routing decision.
REST surface
All three endpoints sit under the standard AI Ask base path.
GET /rest/saiku/api/ai/skills
The catalogue — compact {name, description, cube} summaries.
curl -sS -u admin:admin \ http://localhost:8080/saiku/api/ai/skills | jq{ "skills": [ { "name": "weekly-foodmart-rollup", "description": "Weekly revenue rollup for the FoodMart Sales cube: total Store Sales …", "cube": "unknown_foodmart/FoodMart/FoodMart/Sales" } ]}GET /rest/saiku/api/ai/skills?errors=true
Same shape, plus a errors[] array listing every file that failed to
parse this scan. Each entry carries a stable machine code so operators
fix bad frontmatter without reading server logs.
{ "skills": [ /* … */ ], "errors": [ { "path": "broken.md", "code": "MISSING_FRONTMATTER", "message": "expected leading `---` YAML frontmatter block" } ]}Stable error codes:
| Code | When |
|---|---|
EMPTY_SKILL | File is empty or all whitespace. |
MISSING_FRONTMATTER | No leading --- fence. |
EMPTY_BODY | Frontmatter parsed but no markdown after the closing fence. |
MALFORMED_YAML | YAML parser rejected the frontmatter. |
MISSING_FIELD | Required field (name / description) not present. |
BLANK_FIELD | Required field present but empty / whitespace-only. |
TYPE_MISMATCH | Field present but wrong type (name: 42). |
INVALID_NAME | name doesn’t match [a-z][a-z0-9-]{0,63}. |
UNKNOWN_FIELD | Frontmatter contains a field not in the schema. |
DUPLICATE_NAME | Two skill files declared the same name. |
IO_ERROR | Could not read the file (filesystem-level). |
GET /rest/saiku/api/ai/skills/{name}
Full body of one skill — the raw markdown. Handy for a UI slash-menu that previews the workflow before the user hits send.
POST /rest/saiku/api/ai/skills/refresh
Force a rescan (bypasses the mtime signature check). Returns the fresh counts so operators can eyeball the reload.
curl -sS -u admin:admin -X POST \ http://localhost:8080/saiku/api/ai/skills/refresh{ "skills": 4, "errors": 0 }Bundled example
Fresh Saiku installs stage a working example on first boot — see weekly-foodmart-rollup.md. Operators add their own alongside; the seeded one is idempotent (only lands when the target file doesn’t already exist).
How the scan works
- Subdirectories are walked recursively.
- Non-
.mdfiles are ignored (a strayREADME.txtwon’t crash the scan). - Broken files don’t take down the catalogue — a
ParseExceptionon one file leaves the others intact. - Duplicates on
nameproduce aDUPLICATE_NAMEerror on the second file to load; the first one wins.
Ossie asks pick these up too
The skill catalogue is a single per-launcher store served at
/rest/saiku/api/ai/skills. Any skill whose cube: field names an
Ossie ref (e.g. pharma/Pharma/Pharma/Sales) is naturally scoped by
that ref when routed through
/ai/ossie/ask. MDX and Ossie
share the same primitive.