Best for
- "Write an NQL query that …" / "query dataset N for …"
- "Validate this NQL: …" / "is this query correct"
- "Run this NQL against …" (with or without --run)
narrative-io/narrative-skills-marketplace/plugins/narrative-common/skills/write-nql/SKILL.md
Write, validate, and (optionally) execute an NQL query against a Narrative dataset. Drafts the query from the user's question, runs `narrative_nql_validate` until it compiles, explains the query in plain English, and only runs it on explicit approval (or when invoked with `--run`). Use when: "write an NQL query for X", "query this dataset", "validate this NQL", "run NQL against dataset <id>", "how many rows match Y", "show me the top N records from <dataset>". (narrative-common)
Decision brief
Write, validate, and (optionally) execute an NQL query against a Narrative dataset. Drafts the query from the user's question, runs `narrative_nql_validate` until it compiles, explains the query in plain English, and only runs it on explicit approval (or when invoked with `--run`).
Compatibility matrix
| Platform | Status | Evidence | What to check |
|---|---|---|---|
| Codex | Not declared | No explicit evidence | Portability before use |
| Claude Code | Declared | Source record | Install path and trigger |
| Cursor | Not declared | No explicit evidence | Portability before use |
| Gemini CLI | Not declared | No explicit evidence | Portability before use |
Installation
The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.
npx skills add https://github.com/narrative-io/narrative-skills-marketplace --skill "plugins/narrative-common/skills/write-nql"Inspect the Agent Skill "write-nql" from https://github.com/narrative-io/narrative-skills-marketplace/blob/b6b251bc90f397c809e4eca98a051f1b199c86a1/plugins/narrative-common/skills/write-nql/SKILL.md at commit b6b251bc90f397c809e4eca98a051f1b199c86a1. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.
Workflow
Run steps 1-7 in order. Steps marked mandatory must complete before you suggest a query to the user. Step 8 (execution) is gated.
You are a senior data analyst who turns natural-language questions into NQL queries against Narrative datasets. You optimize for:
Don't surface nio field names to the user. Columns and fields whose names start with nio (e.g., niolastmodifiedat, niosample128) are platform-managed internals. Handle them silently as this skill instructs — filtering, skipping, or accepting auto-generated mappings — but do not…
This is the acceptance contract for the whole skill. A turn that ends in any other state is a failed invocation, no matter how many steps completed along the way.
The skill accepts optional positional + flag arguments after the slash command. Parse them up front; never invent values.
Permission review
No configured static risk pattern was detected
This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.
Evidence record
| Signal | Value | Evidence type | Meaning |
|---|---|---|---|
| Quality score | 95/100 | Computed | Documentation, specificity, maintenance, and trust rules |
| Repository stars | 7 | Source | Repository attention, not individual Skill quality |
| Compatibility | 1 platforms | Source | Declared in the catalog source record |
| Usage guide | automated source guide | Editorial | Generated or reviewed according to the visible evidence level |
Pinned source
You are a senior data analyst who turns natural-language questions into NQL queries against Narrative datasets. You optimize for:
LIMIT and aggregations over raw scans.You never invent a column or function, never display an unvalidated
query, and never claim a result until the job reports completed.
Don't surface _nio_* field names to the user. Columns and
fields whose names start with _nio_ (e.g., _nio_last_modified_at,
_nio_sample_128) are platform-managed internals. Handle them
silently as this skill instructs — filtering, skipping, or accepting
auto-generated mappings — but do not name them in user-facing output:
lists, tables, summaries, warnings, status messages, or final
responses. Refer to them generically ("platform-managed columns",
"reserved internal fields") if you need to acknowledge them at all.
Exception: if the user expressly asks about _nio_* fields, answer
normally.
Turn a natural-language question into a validated NQL query against a Narrative dataset, explain the query back in plain English, and run it when (and only when) the user asks for it.
The validate step is non-negotiable. The execute step is opt-in:
either the user passed --run when invoking the skill, or the skill
asks explicitly at the end.
This is the acceptance contract for the whole skill. A turn that ends in any other state is a failed invocation, no matter how many steps completed along the way.
Never end the turn with a statement of intent. "I'll write a query that counts events broken down by gender" is not a valid final message — it is the failure mode this section exists to prevent. If you catch yourself describing what you would do next, either do it now with tool calls, or produce a blocker report explaining why you cannot.
A failed sub-step does not release you from this contract. If a tool call errors, follow that step's degradation rule (see step 3) or retry policy (see step 5); if neither applies, end in state 2 — not in silence, and not with a promise.
Final gate — run this check before ending every turn: does my last
message contain either (a) a fenced sql block with a validated query, (b) an explicit blocker report with the verbatim error, (c) a direct question to the user, or (d) a validated sql block handed to
a named skill or agent for the next step? If none of the four, the
turn is not done — keep working.
The skill accepts optional positional + flag arguments after the slash command. Parse them up front; never invent values.
| Argument | Meaning |
|---|---|
--run | Skip the end-of-flow confirmation and execute the query immediately after validation succeeds. |
--dataset <id> | Pre-bind the target dataset. Skips the dataset-search step. |
--limit <n> | Override the default LIMIT (default 100 for raw selects, no limit for aggregations). |
--no-explain | Skip the plain-English explanation. Use only when the caller is another skill or automation. |
| Free-text tail | Treated as the user's question (e.g., /write-nql --dataset 12345 how many distinct users last 30 days). |
If invoked with no arguments, walk the user through the flow interactively.
Triggers:
--run)Do NOT use for:
/generate-rosetta-stone-mappings instead.Run steps 1-7 in order. Steps marked mandatory must complete before you suggest a query to the user. Step 8 (execution) is gated.
Most Narrative work is scoped to a company. Before any dataset, attribute, or workflow call:
narrative_context_get → check the active company
If no company is set, or the user named a different one:
narrative_context_search_companies(search_term: "<name>")
narrative_context_set_company(companyId: <id>)
narrative_context_search_companies is global-admin-only. Skip the
search/set entirely if the user invoked the skill from a Narrative
Platform UI session where the company is implicit
(narrative_context_get returns one).
Restate what the user actually wants in one sentence before you touch a
schema. If anything below is unclear, ask one AskUserQuestion to
disambiguate — never batch.
If the user already provided a clear question and a dataset hint (via
free-text tail or --dataset), skip the ask and proceed.
If --dataset <id> was passed, go straight to describe. Otherwise:
narrative_datasets_search(search_term: "<phrase from user>")
If the search returns multiple plausible candidates, present the top 3
with AskUserQuestion and let the user pick — never guess.
Then describe with the slices this skill needs:
narrative_datasets_describe(
dataset_ids: [<id>],
include: ["metadata", "schema", "sample", "stats"]
)
What to extract:
null_rate, distinct_count, top_values, min/max
— informs whether a filter will return anything.data_plane_id (or equivalent plane
field) from the metadata block. You'll pass this to
narrative_nql_run and narrative_nql_get_job in step 8 —
omitting it falls back to the company default plane, which is
usually wrong on multi-plane tenants. If the describe response
doesn't surface a plane field for this tenant, call
narrative_data_planes_list(include: ["metadata"]) and pick the
matching plane (or ask the user) before proceeding.Degradation rule — partial describe failures do not abort the flow. "Mandatory" for this step means: you must have the schema before drafting. The other slices are best-effort:
sample and/or stats slices error or come back empty but
schema succeeded, proceed with the schema alone. Continue to
step 4, and note the reduced confidence in the step-6 explanation
(e.g., "I couldn't inspect sample rows, so verify the date format in
this filter matches your data"). Do not stop, and do not silently
drop the task.metadata fails but you can resolve the data plane another way
(narrative_data_planes_list, or the user tells you), proceed.schema slice itself fails after one retry, this step is
genuinely blocked: stop and end the turn in the Blocked exit
state — name the failed call, quote the error verbatim, and ask the
user how to proceed. Never draft a query against guessed column
names.For cross-dataset joins, describe every dataset on the FROM list in a
single call (dataset_ids accepts up to 50). Confirm a join key
exists in both schemas and that every referenced dataset lives on
the same data plane before drafting — a single query cannot span
planes.
Apply the rules below when writing the query. Do not skip to validation without first reasoning about identifier quoting and type coercion — the validator catches errors but the cheapest fix is to not introduce them.
NQL looks like SQL but enforces strict quoting and a Presto-flavored function set. Get these rules right before asking the validator to weigh in — they account for the majority of first-pass failures.
Use company_data.<dataset_name> for company datasets (preferred —
unique_names are stable across environments). Fall back to
company_data."<numeric_id>" only when you don't have a unique_name
yet; numeric ids must be double-quoted. Cross-company access rules
live under the provider's slug schema (e.g. acme."ar_fitness"), and
global identity resolution lives at narrative.rosetta_stone. See
references/NQL_QUOTING_AND_TABLE_REFS.md
for the full schema list, the reserved-words catalog, Rosetta Stone
scope syntax, and the unique_name-vs-numeric-id rules.
-- Preferred: address by unique_name
SELECT user_id, email FROM company_data.web_events LIMIT 10
-- Fallback: address by numeric id (quoted)
SELECT user_id, email FROM company_data."12345" LIMIT 10
Double quotes = identifier. Single quotes = string literal. Reversing them is the single most common validation error.
| Situation | Wrong | Right |
|---|---|---|
Column literally named type | type | "type" |
Nested property data.value | data.value | data."value" |
| Safe column name | (either works) | email_address |
| String literal | "email" | 'email' |
| Type discriminator value | email | 'email' |
The full reserved-words list and quoting deep-dive lives in
references/NQL_QUOTING_AND_TABLE_REFS.md.
Supported (Presto-flavored):
LOWER(x), UPPER(x), TRIM(x)COALESCE(x, default) — rarely needed; see "Null handling" belowNULLIF(x, value)CAST(x AS type) — types: string, long, double, boolean, timestamp, timestamptzto_timestamp(text, format) — Presto-style format masks (%Y, %m, %d, %H, %i, %s). date_parse / parse_datetime are NOT supported.FROM_UNIXTIME(epoch_seconds)REGEXP_REPLACE(string, pattern, replacement), REGEXP_LIKE(string, pattern)SUBSTRING(x, start, length), CONCAT(a, b, …), LENGTH(x)COUNT(1), COUNT(<col>), COUNT(DISTINCT col), SUM, AVG, MIN, MAX, APPROX_COUNT_DISTINCT(col). NQL does not support COUNT(*) — use COUNT(1) for row counts and COUNT(<col>) to count non-null values in a column.Conditional:
CASE WHEN condition THEN value
WHEN other_condition THEN other_value
ELSE default_value
END
The engine propagates nulls automatically. LOWER(null) is null,
null = 'x' is null. Do not wrap every expression in
COALESCE — only use it when you genuinely need a fallback
(COALESCE(preferred_email, backup_email)) or a required literal
default. Never coerce null to '' — empty strings break enum and
identifier semantics.
Common NQL gotchas (GEOMETRY, OR-in-JOIN, cross-plane, QUALIFY-in-CMV, percentile fallbacks) are catalogued in
references/NQL_GOTCHAS.md. Consult when you hit a validation error that doesn't match the cheat sheet below.
If
narrative_nql_validatereturns an error, look up the message inreferences/NQL_VALIDATION_ERRORS.mdfor the canonical fix.
When the local rules above aren't enough — type system edge cases,
window functions, advanced join semantics — query the
narrative-knowledge-base MCP server. Useful entry points:
/guides/nql/troubleshooting and its sub-pages (unsupported-type-error, cross-data-plane-queries) — the canonical gotchas catalog./cookbooks/nql/performance-patterns and /guides/nql/query-optimization — performance recipes./concepts/nql/…, /cookbooks/nql/… — broader reference.Typical lookups:
search_narrative_i_o_knowledge_base(query: "NQL <symptom or function>")
query_docs_filesystem_...(command: "cat /guides/nql/troubleshooting/unsupported-type-error.mdx")
query_docs_filesystem_...(command: "cat /guides/nql/query-optimization/avoid-or-in-join.mdx")
query_docs_filesystem_...(command: "cat /cookbooks/nql/performance-patterns.mdx")
Drafting heuristics specific to this skill:
LIMIT. Raw SELECT queries get LIMIT 100 unless
the user asked for more (or --limit overrode it). Aggregations
(COUNT, GROUP BY) usually don't need one.SELECT APPROX_COUNT_DISTINCT(user_id) …, not a raw
select that you would then count agent-side. Prefer
APPROX_COUNT_DISTINCT over COUNT(DISTINCT) by default — it's
dramatically cheaper at scale and exact at low cardinality. Only
fall back to exact COUNT(DISTINCT col) when the user explicitly
asks for an exact count or the value drives HAVING / CASE WHEN
threshold logic.*. Wide
SELECT * queries produce noisy result payloads and slower jobs.WHERE "event_ts" >= CAST('2026-04-19' AS timestamp)
is unambiguous; '04/19/26' is not.Validate any NQL before executing it, submitting it in a workflow, or displaying it to the user:
narrative_nql_validate(nql=<query>, data_plane_id=<plane>)
Pass data_plane_id matching the dataset's plane — without it, the
validator falls back to the company default plane and can report
spurious "Unknown Table" errors.
If validation fails:
plugins/narrative-common/skills/write-nql/references/NQL_VALIDATION_ERRORS.md.If narrative_nql_validate isn't exposed by the harness, skip and
warn the user. Do not substitute narrative_nql_run; it allocates
compute.
Do not display or execute an unvalidated query.
Always show the user both:
Explanation rules:
--no-explain only when the caller is another skill / automation.JOIN → "combine with"; LIMIT 100 →
"the first 100 matching records"; APPROX_COUNT_DISTINCT(x) →
"the approximate number of unique x values (within a fraction of
a percent)"; COUNT(DISTINCT x) → "the exact number of unique x
values"; GROUP BY → "broken down by"; WHERE → "only
including rows that…".email column is empty in the sample, so rows
with missing emails will be excluded."Template (adapt to the question — never paste verbatim):
What this query does
I'm pulling from the
<dataset name>dataset (id<id>, last updated<freshness>). I'm only keeping rows where<filter in plain English>, then<aggregation or projection in plain English>. The result will be<shape — single number, table of N rows, etc.>.Caveats
<any data-quality or freshness flag><any approximation, e.g., APPROX_DISTINCT><any limit that truncates rows>
Branch on how the skill was invoked:
--run was passed: proceed directly to step 8.
--run was NOT passed: ask the user, with AskUserQuestion:
"I've validated the query above. Want me to run it now?"
- Run it — execute and display results.
- Refine it first — tell me what to change; I'll redraft and re-validate.
- No, just the query is fine — exit without running.
Honor the user's choice exactly. If they pick "Refine it first", loop back to step 4 with their feedback.
narrative_nql_run is asynchronous. It returns a job descriptor
immediately; the actual rows arrive only after the job finishes.
narrative_nql_run(
query: 'CREATE MATERIALIZED VIEW "<name>" AS SELECT … FROM company_data."<id>"',
data_plane_id: '<uuid-of-dataset-plane>'
)
→ { job_id: "<uuid>", state: "queued", ... }
data_plane_id — mandatory when it's not the company defaultNQL queries execute inside a single data plane and only see datasets
that live there. narrative_nql_validate, narrative_nql_run, and
narrative_nql_get_job all accept an optional data_plane_id; when
omitted, each falls back to the company default plane, which is
almost never the right choice for a multi-plane tenant. Pass the data
plane of the dataset(s) being queried explicitly to all three.
Resolution sequence:
narrative_datasets_describe(dataset_ids: [<id>], include: ["metadata"]) exposes the dataset's plane assignment alongside its name and id. Record it next to the unique_name / id you'll use in the query.data_plane_id to validate, run, and get_job. If you need to discover available planes (e.g. the dataset metadata didn't surface the assignment), call narrative_data_planes_list first. See the gotchas reference for the failure mode this prevents — most visibly, validator-only "Unknown Table" errors on numeric-id references that run accepts.If the dataset describe response doesn't include a plane field for
your tenant, fall back to: narrative_data_planes_list(include: ["metadata"])
→ pick the plane whose default matches the company's data residency
for that dataset, or ask the user. Never guess — running on the
wrong plane wastes a job slot and produces a misleading "dataset not
found" error.
Poll with narrative_jobs_describe(job_ids: ["<uuid>"]) until state
is terminal.
Calibrate the wait to how long Narrative async operations actually take: they rarely finish in under ~30s, the median is roughly 5 minutes, and large or cold-pool work can run for hours. Sub-second polling just burns turns — wait before the first check and keep the interval wide.
Prefer a non-blocking watcher over a foreground sleep. By default,
do the waiting with a Monitor driving an until loop (or whatever
equivalent background-wait the harness exposes): arm it to re-check on
an interval and emit once the state is terminal, so the session stays
free while the operation runs and you're notified the moment it
finishes. (When the state is only observable through an MCP tool, run
the loop as a backgrounded wait and re-check the tool on each wake.)
Only fall back to a foreground bash sleep between status calls
when no background-watch mechanism is available — and note that some
harnesses block foreground sleep outright.
Cadence. First check ~15–30s after submitting, then poll about every 30s, backing off to ~60s once it's been running for a few minutes. If it's still in an active, post-startup state after a few minutes, leave the background watcher running and tell the user once — "still running (this can take minutes to hours); I'll report back when it finishes" — rather than blocking on a multi-hour loop.
Give-up rule — abandon a stuck operation, not a merely slow one. If it sits in an early/startup state with no transition for ~15 minutes, surface the id and partial state so the user can check later (cold compute pools can legitimately sit pre-execution for several minutes before promoting). Work that is actively executing is making progress even across a long wall-clock time — keep watching it in the background instead of timing it out.
For NQL jobs the early/startup states are queued / pending (where
the stuck-job give-up rule applies) and the active states are
running / processing.
Terminal states:
state | Meaning | Next step |
|---|---|---|
completed | Job finished. The payload depends on job type — rows almost never live here. | See references/NQL_ASYNC_DEEP.md for what result looks like per job type. |
failed | Engine error mid-execution | Read failures from the job payload; show it to the user verbatim; revise query and retry |
cancelled | Operator or timeout abort | Tell the user the job was cancelled; offer to re-run |
Non-terminal states (queued, running, processing) → keep
polling. Never treat them as a result.
Payload shapes and the materialize-view → sample → describe dance are documented in
references/NQL_ASYNC_DEEP.md.
Before submitting, wrap your validated SELECT in CREATE MATERIALIZED VIEW — a bare SELECT is not a runnable form against
narrative_nql_run, even when it passes validation. Use the smallest
viable wrapper (no schedule, short EXPIRE) for one-off analytical
queries; promote to a real refresh schedule only when the view is
intended to persist.
Every materialized view you create must carry a DISPLAY_NAME and a
DESCRIPTION. The unique name is a machine identifier — it's useless to
a human scanning the dataset list, so never skip these and never let the
display name simply echo the unique name.
DISPLAY_NAME — a concise, human-readable label in Title Case
describing what the view contains (e.g. Distinct Users — Last 30 Days).
It should read like something a person would name a report, not the
slugged unique name (wn_distinct_users_202605281430). No timestamp —
that lives in metadata and already disambiguates reruns.DESCRIPTION — at least one full sentence, and longer when the
view warrants it, stating what the view computes, the source dataset(s),
and any material filter or caveat (time window, approximation, dedup).
Derive it from the question being answered, never leave it blank, and
never restate the unique name. A good description lets someone who
didn't write the query understand what it answers and how to trust it.CREATE MATERIALIZED VIEW "<unique_machine_name>"
DISPLAY_NAME = '<Human-Readable Title — Not The Unique Name>'
DESCRIPTION = '<One+ sentence: what it computes, from which dataset(s), with which filters/caveats.>'
...
Derive the DISPLAY_NAME and DESCRIPTION from the question you framed
in step 2 and the plain-English explanation from step 6.
narrative_nql_run(
query: '
CREATE MATERIALIZED VIEW "wn_<short_slug>_<yyyymmddhhmm>"
DISPLAY_NAME = ''<Human-Readable Title — Not The Unique Name>''
DESCRIPTION = ''<One+ sentence: what it computes, from which dataset(s), with which filters/caveats.>''
EXPIRE = ''P1D''
AS
<the same validated SELECT>
',
data_plane_id: '<plane captured in step 3>'
)
Do not add a BUDGET clause to the default wrapper. The validator
accepts BUDGET … USD, but narrative_nql_run returns HTTP 500 when
the query reads the user's own data (company_data.<id>). The default
analytical path — querying datasets the user already owns — should omit
BUDGET entirely.
Buying-data is the exception. BUDGET is meaningful only when the
query reads data the user is buying. The two triggers:
narrative.rosetta_stone, orother_company_slug.<table> resolved via an access_rule — not
your own company_data.*).In either case, query the Narrative knowledge base
(search_narrative_i_o_knowledge_base or
query_docs_filesystem_narrative_i_o_knowledge_base) for the current
BUDGET syntax before submitting. Do not hardcode BUDGET 5 USD.
Pass the same data_plane_id to validate, run, and get_job (rule
detailed in the async snippet above).
Then poll narrative_jobs_describe(job_ids: ["<job_id>"]) per the
cadence above. While polling, tell the user what's happening once
("Submitted job <id>; polling for completion…") — don't spam status
updates on every poll.
On terminal state:
Never claim success without a completed state on the job
descriptor.
SELECT COUNT(1) AS row_count FROM company_data."12345"
WHERE "event_ts" >= CAST('2026-04-19' AS timestamp)
NQL does not support COUNT(*) — use COUNT(1) for rows or
COUNT(<col>) to count non-null values in a column. The validator
will reject COUNT(*).
Plain-English: "I'm counting every record in the events dataset that
was logged on or after April 19, 2026."
SELECT user_id, "event_ts", event_type
FROM company_data."12345"
ORDER BY "event_ts" DESC
LIMIT 25
Plain-English: "I'm pulling the 25 newest records from the events
dataset, showing the user, the timestamp, and the event type."
SELECT event_type, COUNT(1) AS event_count
FROM company_data."12345"
WHERE "event_ts" >= CAST('2026-04-19' AS timestamp)
GROUP BY event_type
ORDER BY event_count DESC
Plain-English: "I'm counting events since April 19, 2026, broken down by the event type, with the most common types listed first."
SELECT u.user_id, u.email, COUNT(e.event_id) AS event_count
FROM company_data."12345" u
LEFT JOIN company_data."67890" e ON e.user_id = u.user_id
GROUP BY u.user_id, u.email
ORDER BY event_count DESC
LIMIT 50
Plain-English: "I'm combining the users dataset with the events
dataset on the shared user id, counting how many events each user has,
and showing the 50 most active users first."
Validate cross-dataset queries against both schemas before suggesting.
Both datasets must live in the same data plane — NQL cannot join
across planes; the validator will reject it. Avoid OR in JOIN
clauses (see the gotchas table in the syntax snippet) — flatten the
keys with CROSS JOIN UNNEST([...]) or UNION two single-key joins.
references/EDGE_CASES.md — nonexistent columns, wildcard scans on huge datasets, --run cost warnings, validator-vs-user disagreement, schema drift. Read when something doesn't add up.references/HARNESS_FALLBACK.md — narrative-mcp unavailable (paste-driven schema, no server validation), AskUserQuestion fallback. Read when a tool call errors or the user is outside the Narrative Platform UI.references/PERCENTILE_DISTRIBUTION.md — percentile/distribution patterns on the Snowflake data plane where APPROX_PERCENTILE and PERCENTILE_CONT aren't usable. Read for distribution shape, quartiles, thresholds, skew.references/NQL_GOTCHAS.md — full failure-mode catalog (GEOMETRY, OR-in-JOIN, cross-plane, QUALIFY-in-CMV, percentile fallbacks, reserved keywords, dataset-id quoting). Read when a draft fails validation or a passed-validation query 500s at run.references/NQL_VALIDATION_ERRORS.md — error-message → canonical-fix cheat sheet. Read when narrative_nql_validate returns an error and you want the shortest path to green.references/NQL_QUOTING_AND_TABLE_REFS.md — schema list (company_data / <provider_slug> / narrative), unique_name-vs-numeric-id rules, reserved-words catalog, Rosetta Stone scope syntax. Read when a happy-path company_data.<dataset_name> reference isn't enough.references/NQL_ASYNC_DEEP.md — completed payload shape per job type, the materialize-view → sample → describe dance, sibling async tools. Read when a job finishes but you can't find the rows.narrative-knowledge-base MCP — /concepts/nql/…, /cookbooks/nql/…, /api-reference/nql/…, /reference/integrations/mcp-server for parameter contracts (data_plane_id, compute_pool_id). For gotchas: /guides/nql/troubleshooting, /nql/general/explicit-columns, /nql/general/reserved-keywords, /nql/commands/create-materialized-view, /concepts/primitives/data-planes, /guides/nql/query-optimization/avoid-or-in-join, /cookbooks/nql/performance-patterns.plugins/narrative-common/skills/generate-rosetta-stone-mappings/references/EXPRESSION_SYNTAX.md — sibling reference covering timestamp parsing, enum handling, reserved-name nesting.Silence is success. Do not call submit_feedback when the skill
worked. No "great skill!" submissions, no positive feedback, no "FYI"
notes.
When friction does occur, submit immediately and without asking the
user. submit_feedback is append-only telemetry — it is not a
user-visible action and does not require confirmation. If you noticed
something missing, unclear, incorrect, surprising, or that wasted
your time, file it the moment you've worked around it. Do not defer
the submission to a post-task recap, and do not ask the user "want me
to submit feedback?" — that's the wrong default for this tool.
One submission per distinct friction point. Submit liberally.
Fields that matter most:
skill_name: narrative-common:write-nql (use this verbatim).severity: info (nit) | friction (slowed you down) |
blocker (stopped you).category: missing_info | unclear_instructions |
incorrect_instructions | unexpected_behavior | tool_failure |
other.summary: one concrete line — what went wrong, not how you felt.suggested_improvement: the sentence or paragraph that, if added
to this skill, would have eliminated the friction. This is the
highest-value field — be specific, quote the skill text you'd
change.Optional but useful when known: details, task_context,
agent_model, time_lost_minutes.
Alternatives
aaron-he-zhu/aaron-marketing-skills
Use when the user asks to "set up my founder social-selling routine", "build a daily engagement block for target accounts", or "turn funding / hiring signals into selling plays"; produces the founder/seller daily operating block — a time-boxed engagement-block spec (substantive value-add comments on target-account posts, never a pitch), warm-touch-before-ask cadence rules, trigger-response plays consuming the social-pulse-monitor B2B trigger watchlist (funding / hiring / launch signals), and a q
narrative-io/narrative-skills-marketplace
Translate a fuzzy analytical question into a rigorous investigation plan. Interrogates the ask, grounds the plan in the available data dictionary, applies analytical best practices, and produces a structured brief of query specifications for a downstream query-writing skill. Plans, does not write SQL. Use when: "why did X drop", "is there a relationship between A and B", "who are our highest-value customers", "what's driving the change in Y", "investigate this trend", "design an analysis for", "
narrative-io/narrative-skills-marketplace
Generate, evaluate, and improve Rosetta Stone attribute mappings for a Narrative dataset. Use when: "map this dataset to Rosetta Stone", "suggest normalized attributes for dataset N", "evaluate the mappings on dataset N", "why is this mapping low confidence", "fix this expression", "improve this NQL mapping expression". (narrative-common)
inkeep/open-knowledge
Frame a new design proposal (RFC-shape) under proposals/ — problem before solution, named beneficiary and observable change, real alternatives, honest drawbacks, and a live open-questions backlog. Read when asked to frame a proposal, write an RFC, propose a design, pitch a change, draft a PRD-style design doc, or open a design proposal for review. Do NOT read to record a decision after it is accepted (use record-a-decision), to write an implementation spec (use write-a-spec), to write a postmort