Skip to content

parleml Analyst — workflow

How to go from a plain-English question to a correct answer. Read overview.md first for the iron rules.

Contents

  • The tools at a glance
  • The lifecycle (with a builder token)
  • Step 0 — resolve names to ids
  • The decision ladder (which tool for which question)
  • Input-table resolution recipe
  • Worked end-to-end example

The tools at a glance

Everything runs through the parleml MCP tools (the hosted /mcp path — nothing to install). ParleML computes, you reason: never re-implement a number a tool returns, and if one you need is missing, say so. A token is offered only its scope's tools: analyst the read tools, compute adds the compute tools, builder (bound to its workspaces) adds the builder tools.

Move Tool Scope
Playbook / schema reference get_playbook(topic?) read
Where a workspace stands; resume work get_workspace_brief(workspace_id) read
List resources (+ running jobs) list_resources(kind, workspace_id?, limit?) read
Get one resource get_resource(kind, id) read
Describe queryable tables describe_catalog(workspace_id) read
One experiment's result tables + keys describe_catalog(workspace_id, experiment_id=…) read
First rows of a table / source object describe_catalog(workspace_id, preview=…, knowledgebase_id?) read
Profile one table profile_table(workspace_id, table, columns?) read
Run SQL query_sql(workspace_id, sql, limit?, knowledgebase_id?) read
Dataset EDA / leakage / column roles get_eda(dataset_id, section?, column?) read
Model evaluation (numbers) get_model_evaluation(id, split?, class_key?) read
Computed insights get_insight(id, kind, params?) — kind="kinds" lists them read
Per-row why get_insight(id, "case", {"row_index": N}) read
Rule cards / rule redundancy get_rules(id, kind?, engine?, class_key?) read
AutoDoc model card get_autodoc(experiment_id, fmt?) read
Capabilities + ready kinds get_capabilities(experiment_id) read
Chart help / data get_chart_data(family) / get_chart_data(family, kind, subject_id, params?, render?) read
Run status, failure, config, files get_run(experiment_id, view?, since?, limit?) read
Platform vocabulary describe_platform(kind, name?, task?, context?) read
Compare like with like compare(kind, ids, include_production_model?, reference_id?, feature?) read (datasets: compute)
Dataset probes probe_dataset(dataset_id, kind, params?, refresh?) compute
What-if one row what_if(experiment_id, row, base?, calibrated?, explain?) compute
Operating points (nothing applied) simulate_decision(experiment_id, kind, params?) compute
Column roles, EDA, leakage triage prepare_data(dataset_id, action, params?) builder
Training-table SQL (recipes) author_recipe(knowledgebase_id, action, recipe_id?, params?) builder
Plan, launch, explain, publish run_experiment(action, experiment_id?, plan_id?, params?) builder
Batch scoring score_batch(experiment_id, action, job_id?, params?) builder
Register a model, record a release, import predictions register_model(action, experiment_id?, workspace_id?, params?, model_id?) builder
Record intended use and risk level update_workspace(workspace_id, params) builder

Every builder tool returns its idempotency_key — pass the key back only to retry a call whose response was lost.

The lifecycle (with a builder token)

Use the relevant steps, reusing existing work; each operation is a ParleML object (get_playbook("deliver")):

Step Tool Whose
frame describe_catalog, query_sql — the target, population, prediction moment, metric and costs agent proposes, a person agrees
shape author_recipe — your own SELECT with its prediction moment; features are SQL columns agent
validate prepare_data (roles, leakage triage), probe_dataset (signal, floor) agent proposes, a person confirms triage
plan run_experiment(action="plan") — the question and comparison first agent
run run_experiment(action="launch", plan_id), then poll get_run agent
evaluate get_model_evaluation, compare(include_production_model=True), get_insight agent
decide simulate_decision — what each operating point yields; nothing applied agent simulates, a person decides
register register_model — registration is not release agent registers, a person promotes in ParleML

Step 0 — resolve names to ids

Users speak in names ("the insurance fraud workspace", "the churn experiment"); the API speaks in ids. Resolve before anything else:

  1. list_resources("workspaces") → match the workspace by name (case-insensitive) → take its workspace_id.
  2. list_resources("experiments", workspace_id=…) (or "datasets") → match by name → take its id.
  3. If several match, disambiguate by workspace + most-recent, or ask the user.

The decision ladder

Pick the tool from the question type — this table is the routing rule:

For per-row explanations, N below is an explanation-profile position. Locate a known case with get_insight(id, "case_list", {"search": "<key or value>"}) and verify its identity before reading case. The literal search may match multiple observations or none. Published predictions and input rows use different positions; join published rules through the matched prediction's catalog keys, never by equating those positions.

The question is about… Use
which surfaces an experiment even supports get_capabilities (before deep reads)
rows, cohorts, aggregates, joins describe_catalog → query_sql
one experiment's result tables, roles and join keys describe_catalog(workspace_id, experiment_id=…)
"what's in this table?" (before any SQL) profile_table
a dataset's shape / quality / leakage get_eda (+ eda.* charts)
a dataset's columns: types, target, key, ignored get_eda(dataset_id, section="columns")
"is it running? why did it fail? what config ran?" get_run (view="log" after a failure, view="config" for the resolved config)
"how good is the model?" (numbers) get_model_evaluation
"how does it compare with production / with run B?" compare("experiments", ids, include_production_model=True)
trust, fairness, drift, errors, leaderboard, … get_insight(id, kind) (get_insight(id, "kinds") lists them)
"why did the model say X for THIS row?" get_insight(id, "case", {"row_index": N}) + *__rules_fired SQL
the model's IF-THEN behaviour get_rules
a curated series to show get_chart_data(family) (how to read) → get_chart_data(family, kind, subject_id)
which presets, templates, algorithms, metrics exist describe_platform
the big picture in prose get_autodoc
"is there signal for target T?" (fresh idea) probe_dataset(dataset_id, "signal", {"target": T}) (compute scope)
"is this series forecastable?" (before training) probe_dataset(dataset_id, "forecast_signal", {…}) (compute scope)
"what does a trivial predictor score on metric M?" probe_dataset(dataset_id, "baseline_floor", {…}) (compute scope)
"how would a temporal run split these rows?" probe_dataset(dataset_id, "temporal_split", {…}) (compute scope)
"has the new data drifted from the reference?" compare("datasets", ids, reference_id=…) (compute scope)
"what would it predict for this hypothetical row?" what_if (compute scope)
"what if this case's X were Y?" what_if(id, row={X: Y}, base={"key": {…}} or {"row_index": n})

Habits that keep answers correct:

  • Catalog before SQL — describe_catalog names are the only valid SQL identifiers; describe_catalog(workspace_id, experiment_id=…) adds roles and keys for one experiment's tables (404 = not published).
  • Numbers over prose — prefer get_model_evaluation / get_insight to quoting AutoDoc; cite the metric, link the ui_url. For a comparison, quote compare rather than subtracting numbers yourself.
  • Graceful notes are answers — get_insight returning available: false means "not computed", not "error"; say so (quoting its reason when it says why) and move on. A kind marked needs_explanations waits for the Explain step.
  • Read the log before explaining a failure — get_run(id) gives the status, the failed stage and its error; get_run(id, view="log") the console tail around it. Never guess a cause the log does not show.
  • Read the axis before the verdict — the forecast_signal probe returning grid: "ordinal" means the timestamps had no regular spacing, so the series were scored in observation order. A weak verdict there is not evidence of no signal: calendar seasonality cannot appear on that axis. Re-run with resample_to ('D', 'W', 'ME') to score on a real grid before concluding anything (api.md has the details).

Knowledge bases: where the data comes from

A knowledge base is a source a person connected in Knowledge bases: a database (SQLite, PostgreSQL, MySQL, SQL Server, Oracle), workbook files (xlsx, xls, csv, tsv) or a demo. Each workspace also has one data knowledge base: the lakehouse of its datasets and published results. list_resources("knowledgebases", workspace_id) names them. You read them; you never create, connect, describe or relate them — that stays with the person, in the UI.

  • See the source. describe_catalog(workspace_id, knowledgebase_id=…): its tables, columns and sample values, the person's descriptions of tables and columns, and its relationships. A description is the person's word on what a column means and when it is filled: read it before you choose a column or a join. A relationship with status other than approved is a proposal, not a fact. preview="<table>" shows 20 real rows.
  • Investigate the source. query_sql(workspace_id, sql, knowledgebase_id=…): one read-only DuckDB SELECT over the source tables it names, row-capped like the lakehouse query, before any recipe exists. Use it to find the grain, check a join's fan-out, count a population or see how a label is distributed. Nothing is written. query_sql without knowledgebase_id reads the lakehouse instead: datasets and published results, not source tables.
  • Shape a training table. A recipe (author_recipe, builder) is the SELECT that turns source tables into one table at the playbook's grain; preview it, then materialise it into a dataset that keeps the knowledge base, the recipe and the SQL as its provenance. An agent's recipe always runs in DuckDB. A person's recipe that runs on the source database (source_db) is theirs: you may read it, never edit, preview or run it. The point-in-time check reads every recipe against its prediction moment (get_playbook("deliver"), Point in time).
  • Write DuckDB SQL (get_playbook("overview"), DuckDB idioms) on every one of these paths, whatever the source database's own dialect.

Input-table resolution recipe

To join a fact back to its input dataset:

  1. get_resource("experiment", id) → read dataset_id.
  2. describe_catalog(workspace_id) → find the table whose dataset_id matches and is_result == false; its name is the input SQL view.
  3. Join on the declared key column(s) the fact carries under the input's own names (catalog role key) — never by position (RULE 3).

Worked end-to-end example

Question: "In the insurance-fraud workspace, who do we predict will defraud, by region?"

  1. list_resources("workspaces") → {workspace_id: "ws_ccab", name: "Insurance Fraud"}.
  2. list_resources("experiments", workspace_id="ws_ccab") → {id: "exp_ab12", name: "Claim fraud", type: "supervised"}.
  3. describe_catalog("ws_ccab", experiment_id="exp_ab12") → the predictions fact name="insurance_claim__predictions" (canonical_id="exp_ab12_predictions", role="fact", primary_key=["row_index"]); columns include claim_id (role key — the input's declared key), row_role, predicted_class, predicted_proba.
  4. The input is not a result table: get_resource("experiment", "exp_ab12") → dataset_id="ds_1", and in describe_catalog("ws_ccab") the table with that dataset_id and is_result=false is name="insurance_claims_fraud".
  5. query_sql("ws_ccab", sql):
SELECT s.region, COUNT(*) AS n, AVG(s.claim_amount) AS avg_claim
FROM "insurance_claims_fraud" s
JOIN "insurance_claim__predictions" p USING (claim_id)
WHERE p.predicted_class = 1
GROUP BY s.region ORDER BY n DESC;
  1. Read {columns, rows, n_returned, has_more}. If has_more=true, the result was truncated — add a tighter filter or aggregate further.
  2. To close with quality context: get_model_evaluation("exp_ab12") for the headline metrics, and include the ui_url links in the answer.