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:
list_resources("workspaces")→ match the workspace by name (case-insensitive) → take itsworkspace_id.list_resources("experiments", workspace_id=…)(or"datasets") → match by name → take its id.- 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_catalognames 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_insightto quoting AutoDoc; cite the metric, link theui_url. For a comparison, quotecomparerather than subtracting numbers yourself. - Graceful notes are answers —
get_insightreturningavailable: falsemeans "not computed", not "error"; say so (quoting itsreasonwhen it says why) and move on. A kind markedneeds_explanationswaits 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_signalprobe returninggrid: "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 withresample_to('D', 'W', 'ME') to score on a real grid before concluding anything (api.mdhas 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 withstatusother thanapprovedis 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_sqlwithoutknowledgebase_idreads 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:
get_resource("experiment", id)→ readdataset_id.describe_catalog(workspace_id)→ find the table whosedataset_idmatches andis_result == false; itsnameis the input SQL view.- 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?"
list_resources("workspaces")→{workspace_id: "ws_ccab", name: "Insurance Fraud"}.list_resources("experiments", workspace_id="ws_ccab")→{id: "exp_ab12", name: "Claim fraud", type: "supervised"}.describe_catalog("ws_ccab", experiment_id="exp_ab12")→ the predictions factname="insurance_claim__predictions"(canonical_id="exp_ab12_predictions",role="fact",primary_key=["row_index"]); columns includeclaim_id(rolekey— the input's declared key),row_role,predicted_class,predicted_proba.- The input is not a result table:
get_resource("experiment", "exp_ab12")→dataset_id="ds_1", and indescribe_catalog("ws_ccab")the table with thatdataset_idandis_result=falseisname="insurance_claims_fraud". 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;
- Read
{columns, rows, n_returned, has_more}. Ifhas_more=true, the result was truncated — add a tighter filter or aggregate further. - To close with quality context:
get_model_evaluation("exp_ab12")for the headline metrics, and include theui_urllinks in the answer.