Evolving The Data Model
TriliumNext/Trilium
A skill your agent uses when adding a DB migration or a new column/field to a Becca entity in Trilium ("add a migration", "new column on notes/attributes", "ALTER TABLE", "add a field to…
Read before writing or debugging a Malloy query. An agent skill from malloydata/publisher.
$ npx skills add malloydata/publisher --skill malloy-queries -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install malloydata/publisher malloy-queries --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .claude/skills && cp -r skills-src/skills/malloy-queries .claude/skills/malloy-queries && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "malloy-queries" agent skill from https://github.com/malloydata/publisher/tree/main/skills/malloy-queries into .claude/skills/malloy-queries/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "malloy-queries", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/malloydata/publisher/tree/main/skills/malloy-queriesType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add malloydata/publisher --skill malloy-queries -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install malloydata/publisher malloy-queries --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .agents/skills && cp -r skills-src/skills/malloy-queries .agents/skills/malloy-queries && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "malloy-queries" agent skill from https://github.com/malloydata/publisher/tree/main/skills/malloy-queries into .agents/skills/malloy-queries/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "malloy-queries", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add malloydata/publisher --skill malloy-queries -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install malloydata/publisher malloy-queries --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/skills/malloy-queries .cursor/skills/malloy-queries && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "malloy-queries" agent skill from https://github.com/malloydata/publisher/tree/main/skills/malloy-queries into .cursor/skills/malloy-queries/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "malloy-queries", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/malloydata/publisher.git --path skills/malloy-queries--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add malloydata/publisher --skill malloy-queries -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install malloydata/publisher malloy-queries --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/skills/malloy-queries .gemini/skills/malloy-queries && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "malloy-queries" agent skill from https://github.com/malloydata/publisher/tree/main/skills/malloy-queries into .gemini/skills/malloy-queries/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "malloy-queries", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install malloydata/publisher malloy-queriesInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add malloydata/publisher --skill malloy-queries -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .github/skills && cp -r skills-src/skills/malloy-queries .github/skills/malloy-queries && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "malloy-queries" agent skill from https://github.com/malloydata/publisher/tree/main/skills/malloy-queries into .github/skills/malloy-queries/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "malloy-queries", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add malloydata/publisher --skill malloy-queries -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install malloydata/publisher malloy-queries --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/malloydata/publisher.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/skills/malloy-queries .opencode/skills/malloy-queries && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "malloy-queries" agent skill from https://github.com/malloydata/publisher/tree/main/skills/malloy-queries into .opencode/skills/malloy-queries/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "malloy-queries", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
malloy-queriesRead before writing or debugging a Malloy query. An agent skill from malloydata/publisher.
Malloy Queries is an agent skill from malloydata/publisher. Read before writing or debugging a Malloy query. Dates, aggregates vs dimensions, joins, filters, strings, window functions, chart annotations, and the compile errors a SQL habit produces.
Its SKILL.md is about 5.1k tokens, which your agent loads only when the skill is triggered. It is a single SKILL.md file with no bundled scripts.
It sits in Databases, covering SQL. It works with SQL. The repository describes itself as: Publisher is the open-source analytics engine for Malloy. It lets you define data models once — and use them everywhere. The licence is MIT.
Read from SKILL.md and the folder at commit b9a1a19. It shows what the files ask for, not the result of running them.
Pre-approves nothing: there is no allowed-tools line, so your agent's usual permission prompts apply.
From allowed-tools in the SKILL.md frontmatter.
No scripts in the folder and no shell commands in SKILL.md (its code samples are malloy).
From the folder's file list and the shell code blocks in SKILL.md.
No URLs in SKILL.md.
From URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
Malloy Queries loads about 5.1k tokens when it runs. Until then it costs about 51 tokens; SKILL.md has 2,536 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check found no risky patterns in SKILL.md.
Automated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from malloydata/publisher at commit b9a1a19, republished under its MIT licence (© malloydata). 2,536 words, ~5,074 tokens.
.claude/skills/malloy-queries/SKILL.md (or your agent's skills folder).<!--
Copyright (c) Credible Data Inc.
SPDX-License-Identifier: MIT
-->
Only use field names defined in the model. Ground yourself first with get_context; never invent entities or guess field names.
Tool names are written bare here -
get_context,execute_query,search_malloy_docs. The exact prefixed name depends on the host surface; match each against the tools you actually have.
Simple aggregation:
run: source -> {
aggregate: total_revenue, order_count
}Group by dimension:
run: source -> {
group_by: category
aggregate: revenue
order_by: revenue desc
limit: 10
}Time trend:
# line_chart
run: source -> {
group_by: order_date.month
aggregate: revenue
order_by: 1
}Filtered query:
run: source -> {
where: status = 'active'
group_by: region
aggregate: count_orders, total_revenue
}Run a pre-built view:
run: source -> view_nameRefine a view with additional options:
run: source -> view_name + { limit: 10, where: region = 'US' }Percent of total: use all(), not parent().
run: source -> {
group_by: category
aggregate:
revenue
pct_of_total is revenue / all(revenue)
}Conditional dimensions with pick: pick is a keyword, not a function.
run: source -> {
group_by:
tier is pick 'Premium' when price > 100
pick 'Standard' when price > 50
else 'Budget'
aggregate: count()
}Wrong: pick('Premium') { ... } (that's not Malloy syntax).
Window functions with calculate:: running totals, lag(), lead(), and other window operations belong in calculate:, not aggregate:.
run: source -> {
group_by: order_month is order_date.month
aggregate: revenue
calculate: prev_month_revenue is lag(revenue)
order_by: order_month
}Use the joins the model declares. When you answer a question over a published model, every query is rooted on one source, and you reach a joined source's fields by a dotted path within the query body (for example stores.region). Do not write join_one or join_many in an answer query, and never invent a join key. (A join written in a query does compile, and model files and notebooks use them; the rule is that an answer uses the joins the model declares.) If the field you need is on a source the model does not join, say so and ask the user to add the join to the model.
The -> operator separates a source from a view (query transformation). It does NOT navigate between joined sources.
Wrong:
run: candidate -> hiring_manager -> { aggregate: employee_count }Right:
run: candidate -> { aggregate: hiring_manager.employee_count }Use the field paths defined in the model verbatim. If the model defines hiring_manager.employee_count, do not strip the hiring_manager. prefix; that prefix is the join namespace, not a separate source to navigate to.
@ literalsUse @ date literals for comparisons. Never compare a timestamp to a bare number.
Wrong (compares a timestamp to the integer 2020):
where: order_date.year >= 2020Right:
where: order_date >= @2020-01-01order_date.month returns a month-truncated timestamp (e.g., 2025-06-01 00:00:00), useful for group_by:. It is NOT a 1-12 integer.
Available truncations: .year, .quarter, .month, .week, .day, .hour, .minute, .second.
Anything else is an extraction function, not an accessor: day_of_year(), day_of_week() (1 = Sunday), day(), week(), month(), quarter(), year(), hour(), minute(), second().
Wrong: group_by: created_at.day_of_year → 'created_at' cannot contain a 'day_of_year'
Right: group_by: doy is day_of_year(created_at)
month, year, day, date, count and friends are reserved as namesThey cannot name an output field. The set is wider than it looks: the timeframes and their plurals (day/days, month/months, year/years, ...), type names (date, timestamp, number, string), aggregate names (count, sum, avg, min, max, all), and now, source, table, by, on, is, asc, desc. The functions are fine: month(order_date) extracts 1-12 anywhere, including where:.
Wrong: group_by: month is order_date.month → 'month' is a reserved word, so to use it as a name you must quote it
Right: group_by: order_month is order_date.month
Right (when a column is literally named month): group_by: `month`
For a single contiguous range, prefer the ? apply operator with a partial-date literal: it's the idiomatic Malloy form and works with date or timestamp fields:
where: order_date ? @2025 -- anywhere in 2025
where: order_date ? @2025-Q3 -- Q3 2025 (Jul-Sep)
where: order_date ? @2025-06 to @2025-09 -- June through August (upper bound excluded)
where: order_date ? @2025-06-01 for 3 months -- same range, duration form
where: order_date ? now - 1 year for 1 year -- the last full year~ is for strings, not dates: where: order_date ~ @2025 does not compile (Malloy reports mysterious error in range computation). Use ?, or = for a whole year, month or day.
Bounded >=/< with two literals also works and is sometimes clearer:
where: order_date >= @2025-06-01 and order_date < @2025-09-01Every bound names the field, and a range only goes with ?:
| Wrong | Error | Right |
|---|---|---|
d > @2021 and < @2022 | unexpected '<' | d ? @2021, or d >= @2021-01-01 and d < @2022-01-01 |
d > @2021 and @2022 | 'logical operator' Can't use type date | same |
d = (@2021 to @2022) | A Range is not a value | d ? @2021 to @2022 |
ts > @2021 to @2022 | none - it compiles, and dropped a 2021-03-04 row | ts ? @2021 to @2022 |
The last one is the dangerous one: a comparison operator in front of a range is accepted, so nothing warns you and the count is simply wrong. Cannot compare a timestamp to a boolean means the right-hand side of a comparison is itself a condition; split it into one comparison per bound.
where: filters rows before aggregation. having: filters aggregate results. Picking the wrong one is the single most common query error.
where: only sees dimensions / raw columns.having: only sees aggregates / measures.Wrong: where: total_revenue > 1000 (total_revenue is an aggregate)
Right: having: total_revenue > 1000
Wrong: having: region = 'US' (region is a dimension)
Right: where: region = 'US'
Prefer inline expressions in having: rather than defining an extra named aggregate just to filter on:
having: count() > 20Don't put aggregates in group_by:, or dimensions in aggregate:.
Wrong: group_by: total_sales (where total_sales is sum(price)) → Cannot use an aggregate field in a group_by operation
Right: group_by: category; aggregate: total_sales
A measure is not a calculate: field either. calculate: takes a window function over an aggregate, not the aggregate itself.
Wrong: calculate: t is total_sales → Cannot use an aggregate field in a calculate operation
Right: aggregate: total_sales (or calculate: prev is lag(total_sales) for a window)
Scalar functions are not aggregates. concat(), substr(), arithmetic on raw fields, etc. belong in group_by: or select:, never aggregate:.
Wrong: aggregate: full_name is concat(first_name, ' ', last_name)
Right: group_by: full_name is concat(first_name, ' ', last_name)
Counting:
count(): row count.count(field): distinct count of that field.count(distinct field) syntax, and count(*) is wrong.Wrong: count(distinct customer_id), count(*)
Right: count(customer_id), count()
There is no method form of count on a joined field either: shipments.shipment_id.count() fails with 'shipments.shipment_id' is not a source or join. Write count(shipments.shipment_id).
Aliases from group_by: aren't visible in where:. where: is evaluated before group_by:, so it can't see aliases defined there. Reference the source field directly.
Wrong:
group_by: region_alias is customer.region
where: region_alias = 'US'Right:
where: customer.region = 'US'
group_by: region_alias is customer.regionMalloy's ~ operator is regex, not SQL LIKE. Use the raw-string r'...' form; no % wildcards.
Wrong: where: name ~ '%Alonso%'
Right: where: name ~ r'Alonso'
Both sides must be strings. For multi-value equality, use the ? partial-match operator:
where: region ? 'US' | 'CA' | 'MX'order_by: can reference a group_by alias or a column position. It cannot reference a dotted join path; alias the field in group_by: first.
Wrong: order_by: customer.region
Right:
group_by: region is customer.region
aggregate: revenue
order_by: regionWhen the model exposes both a human-readable name and an internal code/ID (e.g., aircraft_model_name vs aircraft_model_code, customer_name vs customer_id), prefer the human-readable one for anything the user will see (group-bys in charts, labels, breakdowns). Check the #(doc) field descriptions in the model to disambiguate.
Chart annotations (e.g., # bar_chart, # line_chart, # big_value) go before run:, view:, or nest:, never inside curly braces. Field-level tags (# label, # currency, # x, # y) go above individual fields inside the query block:
# bar_chart
run: source -> {
group_by: category
aggregate:
# label="Revenue"
# currency
revenue
order_by: revenue desc
limit: 10
}Charts render only the first aggregate. For multiple measures on one chart, place # y above the aggregate: keyword or use the y=['a','b'] shorthand. A chart annotation left as the last line inside { } fails with "Parser enountered unexpected statement type 'unimplemented'" (the compiler's spelling); one after the closing } fails with "Object annotation not connected to any object".
Read the malloy-charts skill for chart types, properties, data shape requirements, and selection guidance.
Aggregating a joined field takes method syntax. sum, avg, min and max over a dotted path through a join_many fail with Join path is required for this calculation; use 'inventory_items.item_cost.sum()'. The message gives the fix. Over a join_one path both forms compile and mean different things: sum(o.total) adds each order's total once per base row (once per line item), o.total.sum() once per order. For the joined source's own total (order value, customers) you almost always want the second; the first is right only when the question is weighted per base row. min and max are unaffected.
Wrong: measure: cogs is sum(inventory_items.item_cost)
Right: measure: cogs is inventory_items.item_cost.sum()
count(joined.field) is the exception: it is the correct distinct count through a join, so keep it as written (see Counting above). Do not rewrite it as joined.count(field): that form does not compile (Expression illegal inside path.count()). joined.count() does compile, but it counts rows of the joined source, which is a different number from count(joined.field) when the field repeats.
Scalar functions never take method form, and nothing chains onto a function call. round, floor and ceil are always round(x, 2). The errors (something is missing before 'round', Cannot call function round(number, number) with source) name round without saying so, and read like a typo somewhere else.
Wrong: avg(price).round(2), price.avg().round(2), avg_price.round(2)
Right: round(avg(price), 2), round(price, 2)
sum and avg need a numeric field. avg(status) fails with Can't use type string. A name that reads numeric (order_number, zip, account_id) is often typed string, so check the type in the get_context result. Count a string field instead of averaging it.
A measure is already an aggregate. aggregate: busiest is max(flight_count) fails with Aggregate expression cannot be aggregate and does not name the field. Aggregate per group, then take the maximum in a second stage:
run: flights -> { group_by: carrier, aggregate: n is flight_count } -> { aggregate: busiest is max(n) }For only the top row, use order_by: n desc with limit: 1.
A window's braces bind to the function. Put { partition_by: ..., order_by: ... } directly after the window call, not after the division: sum_cumulative(n) { partition_by: region, order_by: n desc } / region_total. For a share within a group, compute the denominator in aggregate: (region_total is all(count(), region)); a window's partition_by does not apply to all().
A dotted path must name a join the source declares. If the source declares the join as carrier, carriers.name fails with 'carriers.name' is not a source or join. Confirm the join name and the field under it in a get_context result; do not infer either from a table name or a plural/singular guess.
order_by: can only name an output column. order_by: total fails with Unknown field total in output space when the query never emits total. group_by or aggregate it first, and alias it if it comes through a join.
Charts: one aggregate per view. A # bar_chart or # line_chart view renders only its first aggregate. For several metrics, nest separate chart views in a # dashboard, or use y=['revenue','cost'].
Define measures and dimensions in the source, and reference them in the view. aggregate: revenue in a view, not a fresh sum(total) there.
Truncate for charts, extract for comparisons. ts.month is the first day of the month (right for a time-series chart, which then orders correctly); month(ts) is the number 1 to 12 (right for comparing across years). year(ts) renders as 2,018; tag it # number=id, as for zip codes and IDs.
Combine an alternation with other filters using a comma. where: is_us = true, party ? 'Democrat' | 'Republican'. The ? alternation means "any of these values". With and it works only when the alternation comes second.
Strings. An apostrophe inside single quotes ends the literal (no viable alternative at input 's'), so use double quotes: "Mac's Diner". There is no concatenation operator (unexpected '+', no viable alternative at input '||'): write concat(origin, '-', destination).
A ; inside a clause ends it. Between clauses a newline, comma or ; all work. Within one clause, separate fields with commas or newlines, or the next field is orphaned (no viable alternative at input 'charters'). The same message appears for an unnamed aggregate after the first: aggregate: n, max(x) fails at max, so name every entry.
Query execution failed: ... means Malloy compiled and the warehouse refused the SQL, often over a type. The position it quotes is in the generated SQL, not in your query. BigQuery, for example, will not partition a window function on a FLOAT64 column; partition_by: takes only a field name, so cast in group_by: (season_key is season::string) and partition on season_key, or fix the type in the model.
Read the error against the tables above and below. Most failures match a known pattern and can be fixed directly. If the cause is not obvious after that, remove pieces (filters, joins, nested views) until the query compiles; whatever you removed last is the bug.
| Error message | Likely cause / fix |
|---|---|
Cannot compare a timestamp to a number | Comparing date.year to an integer. Use date >= @2020-01-01 instead. |
no viable alternative at input '<word>' | Often a ; between fields within one clause - fields under one aggregate:/group_by: are comma- or newline-separated (the error points at the field right after the ;). |
unexpected '<field>', expected 'not' or 'null' | A reserved word used as a name, in a multi-line aggregate: / group_by: list. second, minute, hour, day, week, month, quarter, year are reserved. On its own line the compiler says so plainly ('second' is a reserved word, so to use it as a name you must quote it), but as a later entry in a multi-line list the parser has already committed, and the error points at the NEXT field instead - so you read it as a problem with the line below. Rename the field or backtick it. Verified against the compiler both ways. |
'logical operator' Can't use type <string|date|...> | A ? apply that comes before and needs parentheses, not just the alternation form. `where: t ? 'a' |
Circular reference to '<name>' in definition | A measure aliased to its own name (aggregate: games is games), which compiles fine until a having: references it. Alias to a different name, or drop the alias entirely. |
Unknown function '<name>'. Use '<name>!(...)' to call a SQL function directly. | The function does not exist in Malloy (substring is substr; there is no median, and no percentile, either as percentile(x, 0.5) or as x.percentile(50)). If get_context lists a percentile or median measure, use that. In an ad-hoc query the suggested fix is a dead end: a query sent to a server is compiled in restricted mode, so median!(...) then fails with "direct SQL function calls are not permitted" and following the error message costs two round trips. A saved model file is not compiled that way, so the same call is allowed there and fails only on its own merits. Use the Malloy spelling, or express it another way (an ordered limit for a median-like value); reach for !(...) only in a model file, and only knowing it pins you to one dialect. |
Required filter "<name>" (dimension: <dim>) was not provided | The source declares a required #(filter). get_context lists it under source_info.filter_params with its name, type and required; pass a value through execute_query's filter-parameters argument, keyed by the filter's name (not the dimension). A where: on the dimension does not satisfy it. |
'<field_name>' is not defined | Field doesn't exist in the source. Re-check against the model definition; you may have stripped a join prefix. |
field is a bar chart, but is not a repeated record | Chart annotation placed inside { }. Move # bar_chart above run: / view: / nest:. |
Parser enountered unexpected statement | Spelled that way by the compiler. Most often a chart annotation left as the last line inside { } - move it above run:. Also syntax Malloy doesn't allow in that position (e.g., pick inside a nested view). |
| Query silently returns zero rows | Filter value mismatch (case, spelling, format). Run a distinct-values query on the dimension to confirm the literal. |
For anything not covered here, call search_malloy_docs with the topic (for example "string functions", "nested queries").
© malloydata, MIT. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
Just SKILL.md in skills/malloy-queries of malloydata/publisher.
Open the folder on GitHubat commit b9a1a19
Malloy Queries next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| Malloy Queries this skillmalloydata/publisher | 116 | — | ~5.1k | Automated safety check: Pass | MIT | |
| Evolving The Data ModelTriliumNext/Trilium | 38k | — | ~2.1k | Automated safety check: Pass | AGPL-3.0 | |
| Orchardcore Data MigrationOrchardCMS/OrchardCore | 8.2k | — | ~1.7k | Automated safety check: Pass | BSD-3-Clause | |
| SQL Optimization Patternsynulihao/AgentSkillOS | 617 | 11 repos | ~3.3k | Automated safety check: Pass | None | |
| SQL PortabilityHL7/sql-on-fhir | 151 | — | ~512 | Automated safety check: Pass | Custom licence | |
| StmoSAP/project-foxhound | 180 | 2 repos | ~1.8k | Automated safety check: Pass | GPL-3.0 |
TriliumNext/Trilium
A skill your agent uses when adding a DB migration or a new column/field to a Becca entity in Trilium ("add a migration", "new column on notes/attributes", "ALTER TABLE", "add a field to…
OrchardCMS/OrchardCore
Creates and updates OrchardCore data migrations (DataMigration classes with CreateAsync/UpdateFromX).
ynulihao/AgentSkillOS
Master SQL query optimization, indexing strategies, and EXPLAIN analysis to dramatically improve database performance and eliminate slow queries.
HL7/sql-on-fhir
Analyse whether a SQL query is portable across database implementations using sqlglot transpilation.
SAP/project-foxhound
Manage Redash queries and dashboards on Mozilla's STMO (sql.telemetry.mozilla.org) using stmo-cli.
kurealnum/dotfiles
A skill your agent uses when generating or regenerating Drizzle migration files, changing database schema tables or columns, resolving migration sequence conflicts after rebase, reviewing migration…
malloydata/publisher
Score one analytical answer against a verified golden, and score which of the entities the golden depends on retrieval delivered to the answerer.
malloydata/publisher
Fix a CRITICAL Trivy finding that is failing CI in this repo (a vulnerability, misconfiguration, or secret from security-scan.yml or image-scan.yml), or add, review, or retire an entry in…
malloydata/publisher
Turn a list of questions into an eval set, whatever shape it arrived in: a JSONL a customer sent, a CSV, a spreadsheet export, a markdown doc, an email thread, or a pull from production logs.
malloydata/publisher
Conduct a local Publisher evaluation loop in five steps: scrape/run, eval, diagnose, improve, checkpoint.
malloydata/publisher
Make the smallest safe Malloy model edit that closes a diagnosed model-owned gap, with a probe receipt for every factual claim.
malloydata/publisher
Decide whether ONE answer matches its golden, and say whether you believe the golden.
Works with
Categories
Read before writing or debugging a Malloy query. An agent skill from malloydata/publisher. Malloy Queries is an agent skill from malloydata/publisher. Read before writing or debugging a Malloy query.
Malloy Queries fits situations like: tasks that involve SQL.
Run `npx skills add malloydata/publisher --skill malloy-queries -a claude-code`. Or copy the skill folder (skills/malloy-queries in malloydata/publisher) into .claude/skills/malloy-queries in your project. Claude Code loads it when a task matches its description.
Run `npx skills add malloydata/publisher --skill malloy-queries -a codex`. Or copy the skill folder (skills/malloy-queries in malloydata/publisher) into .agents/skills/malloy-queries in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add malloydata/publisher --skill malloy-queries -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/malloy-queries, .gemini/skills/malloy-queries, .github/skills/malloy-queries and .opencode/skills/malloy-queries in your project.
SKILL.md names no scripts, command-line tools or credentials: Malloy Queries is instructions for the agent only.
SKILL.md contains no URLs. Any network use would come from the scripts or tools the agent runs. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found no risky patterns, such as piping downloads into a shell, reading credential files or hidden Unicode. It is not a guarantee. Review the folder before installing.
Malloy Queries is published under the MIT licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.1k tokens (SKILL.md is roughly 20k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full.
Skills that share tags, products or a category with Malloy Queries: Evolving The Data Model (TriliumNext/Trilium, 38k stars), Orchardcore Data Migration (OrchardCMS/OrchardCore, 8.2k stars), SQL Optimization Patterns (ynulihao/AgentSkillOS, 617 stars) and SQL Portability (HL7/sql-on-fhir, 151 stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
malloydata (a GitHub organization) maintains it in malloydata/publisher, which has 116 GitHub stars. The repository holds 29 skills in this directory. The repository was last updated on October 9, 2026.
Source: malloydata/publisher on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.