# Daggy MCP reference

> For agents using Daggy, the MCP server for statistical modeling and what-if analysis on the user's own data. Generated from the server's tool list; do not edit by hand.

## Connecting

- Endpoint: `https://mcp.daggydata.com/mcp` (Streamable HTTP).
- Auth: log in with OAuth when your client prompts, or send a Daggy API key, made at https://app.daggydata.com/api-keys, as `Authorization: Bearer daggy_...`.
- Every tool needs an authenticated user with an active subscription.

## How the tools fit together

Daggy stores the user's datasets and Bayesian models. Usual order: list_items (kind "datasets"), then describe_item for columns and stats; query_data_sql to answer a question, or create_dataset to prepare data (joins, filters, new columns, train/test splits) and run_statistical_model to model it (list_items kind "models" first for an unused model_name). Each dataset has a dataset_name, for tool arguments, and a sql_name, for use inside SQL; they differ when the name has an extension or special characters. For AI search: add_search_pages puts the user's live pages and competitor pages in a collection, then score_search_content scores a draft against it (retrieval first with reader false); drafts are never saved, so rewrite and score again. query_data_sql, create_dataset, run_statistical_model, add_search_pages and score_search_content run as jobs: if a call returns a job_id without a result, call check_job_status with it right away. Errors name the call that fixes them; follow it rather than retrying unchanged.

## Troubleshooting

- **A call returned a `job_id` but no result.** Call check_job_status with that job_id right away; the server holds the request while the job runs, so there is no need to wait first.
- **"No dataset named X. list_items(kind="datasets") shows the exact names."** Call list_items with kind "datasets" and pass the dataset_name exactly as listed (case and file extension included).
- **SQL fails because a table is not registered or not found.** Inside SQL, name tables by their sql_name from list_items, not their dataset_name (they differ when the name has an extension or special characters, e.g. receipts.csv is receipts).
- **"a dataset named X exists" or "already exists in Daggy".** Pick a new name. Pass overwrite=true only when the user wants the old rows replaced.
- **delete_item (kind "datasets") answers "X is used by: ...".** Views or models depend on it. Delete each listed view or model first with the call the message names, or pass force=true only if the user agrees those views stop working.
- **"Job timed out after ...".** The job ran out of time. Simplify the request or split it into smaller steps, then call the same tool again.
- **The query needed more memory and temporary disk than the server has.** Filter rows first, group by lower-cardinality columns or aggregate in steps. Whole-column mean, min, max and null counts are already in describe_item.
- **A query_data_sql or create_dataset job is FAILED with the reason under error.** Read error: it names the bad table, column or SQL; fix it and call again. A query that runs and matches no rows is COMPLETED with zero rows.
- **describe_item shows `status: broken` for a view.** A source changed under it (a column it reads is gone). Recreate the view with create_dataset (materialize=false, overwrite=true) and SQL that matches its sources now, or delete it. An upload or create_dataset that overwrites a source names the views it broke.

## Marketing-mix panels

create_dataset's panel argument saves the weekly panel: every Monday week for every (group, channel) cell, spend 0 where no rows, KPIs and the outcome NULL with a `<column>_missing` flag. Everything else is a create_dataset sql call over a dataset (here `leads_panel`, with columns week, program, channel, spend, leads); keep the panel's week grain and never fill a KPI with 0.

- Wide spend (a column per channel) to long, before the panel:

  ```sql
  SELECT * FROM spend_wide UNPIVOT (spend FOR channel IN (google, meta))
  ```

- Linear trend (weeks since the panel's first week):

  ```sql
  SELECT *, date_diff('week', min(week) OVER (), week) AS trend FROM leads_panel
  ```

- Event flags from a calendar dataset, plus a built-in month flag:

  ```sql
  SELECT p.*, coalesce(c.deadline, 0) AS deadline, (month(p.week) = 12)::INT AS is_december FROM leads_panel p LEFT JOIN (SELECT CAST(date_trunc('week', CAST(day AS DATE)) AS DATE) AS week, max(deadline) AS deadline FROM calendar GROUP BY ALL) c ON c.week = p.week
  ```

- A daily control averaged to weeks, NULL plus a flag where absent:

  ```sql
  SELECT p.*, d.demand_index, (d.demand_index IS NULL)::INT AS demand_index_missing FROM leads_panel p LEFT JOIN (SELECT CAST(date_trunc('week', CAST(day AS DATE)) AS DATE) AS week, avg(demand_index) AS demand_index FROM demand GROUP BY ALL) d ON d.week = p.week
  ```

- Holdout: the last 12 weeks as test, counted back from the panel's last week:

  ```sql
  SELECT *, CASE WHEN week > (SELECT max(week) FROM leads_panel) - INTERVAL 84 DAY THEN 'test' ELSE 'train' END AS split FROM leads_panel
  ```


## Marketing-mix models

run_statistical_model fits a marketing-mix model (model_type mmm) on a panel: adstock, saturation and ROI per channel, leads per cell, a lagged outcome per group. A fit takes minutes; poll check_job_status. replay answers what-if and ROI questions, predict forecasts future weeks, and optimize splits a budget across series and months; relay its warnings (a prior-dominated channel it moves needs a pulse test before acting). Copy channel and series names from the training result's channels and cells keys. A forecast that covers every series and week from the training end gives the full picture; a series left out is forecast at spend 0.

- Train on the panel (leads per channel and program, applications per program; the panel's cell_active as active):

  ```json
  {
    "tool": "run_statistical_model",
    "arguments": {
      "action": "train",
      "model_type": "mmm",
      "model_name": "mix_fy26",
      "table_name": "leads_panel",
      "target": "applications",
      "edges": [
        {
          "from": "spend",
          "to": "leads"
        },
        {
          "from": "leads",
          "to": "applications"
        }
      ],
      "mmm": {
        "time": "week",
        "channel": "channel",
        "group": "program",
        "media": "spend",
        "kpi": "leads",
        "active": "cell_active",
        "outcome": {
          "column": "applications",
          "grain": [
            "program"
          ],
          "lag_weeks": 26
        },
        "holdout_weeks": 8
      }
    }
  }
  ```

- What-if: halve one channel's spend, over the last quarter of the panel:

  ```json
  {
    "tool": "run_statistical_model",
    "arguments": {
      "action": "replay",
      "model_name": "mix_fy26",
      "scenarios": [
        {
          "name": "meta_half",
          "multipliers": {
            "Meta": 0.5
          }
        }
      ],
      "period": {
        "start": "2026-04-06"
      }
    }
  }
  ```

- Forecast planned weeks (one row per channel, program and week after the training end; no leads or applications):

  ```json
  {
    "tool": "run_statistical_model",
    "arguments": {
      "action": "predict",
      "model_name": "mix_fy26",
      "target": "applications",
      "predict_data": [
        {
          "week": "2026-07-06",
          "channel": "Meta",
          "program": "MBA",
          "spend": 1200
        },
        {
          "week": "2026-07-06",
          "channel": "Google",
          "program": "MBA",
          "spend": 900
        }
      ]
    }
  }
  ```

- Budget allocation: split $200k over the six months after the training end, at least $4,000 a month on one program and a fixed $1,900 a month on one series:

  ```json
  {
    "tool": "run_statistical_model",
    "arguments": {
      "action": "optimize",
      "model_name": "mix_fy26",
      "optimize": {
        "budget": 200000,
        "horizon": {
          "start": "2026-07",
          "months": 6
        },
        "minimums": {
          "MBA": 4000
        },
        "fixed": {
          "Google|Brand": 1900
        }
      }
    }
  }
  ```


## Tools

### list_items

List what this user has in Daggy, by kind. kind="datasets": every dataset (table of data) and view they have, with its name, description and columns. Each has a dataset_name (for tool arguments such as name or table_name) and a sql_name (the table name to use inside SQL). They differ when the name has a file extension or special characters, and then dataset_name does not work in SQL. Call it first to discover which tables exist and what data each holds, before query_data_sql, create_dataset or run_statistical_model, or whenever the user asks what data is available or what data they have. kind="models": every trained model or DAG, with its model_name, target and training data. kind="search": the AI search pages (name, title, url) in each collection, and the queries scored before.

Arguments:

- `kind` (string ("datasets" | "models" | "search"), required): "datasets" lists the datasets and views (tables, columns, dataset_name and sql_name); "models" lists the trained models (DAGs, model_name, target, training data); "search" lists the AI search collections, their pages and saved queries.
- `collection` (string): For kind "search" only: list just this collection. Omit to list every collection.

### describe_item

Describe one dataset or model by its exact name from list_items. kind="datasets": the dataset's schema, its columns, their types (numeric or categorical) and simple univariate statistics (mean, min, max, category values); for a view, its SQL, its sources and whether it still works. Its stats cover every row (for a live view, as computed when it was created), so read a whole-column mean, min, max or null count here instead of scanning a large table with query_data_sql. kind="models": a trained model's metadata: its DAG, target, training data, type, metrics and next actions. kind="search": one AI search page's stored text (as markdown) and details.

Arguments:

- `kind` (string ("datasets" | "models" | "search"), required): "datasets" for a dataset or view schema; "models" for a trained model's metadata; "search" for an AI search page.
- `name` (string, required): Exact dataset_name, model_name or search page name from list_items.
- `collection` (string): For kind "search" only: the page's collection. Default "default".
- `levels` (string): For kind "models" only: an input column (or a list) whose per-level coefficients to list in full. Metadata lists the 60 largest per input and target, and its model_parameters_note names any column that was cut. "all" lists every column; a column with many levels can be large, so name one.

### query_data_sql

Answers questions about the user's Daggy datasets with SQL: counts, sums, averages, top-N, lookups and descriptive comparisons. It returns the query's rows and the SQL it ran. It does not save data (use create_dataset to keep a result, for example to model it) and it does not model: inference, hypothesis tests, forecasts and scenario analysis belong to run_statistical_model.

Ask in plain English in user_query ("average sales by region in sales_2024"), naming the dataset; a SQL query included there is treated as intent and corrected against the real schema. Or pass sql to run your own DuckDB SELECT exactly as written, naming tables by their sql_name from list_items: no agent rewrites it, and errors come back verbatim so you can fix the query. Give exactly one of user_query or sql.

One task per call. Use list_items to see what exists and describe_item for columns and simple univariate stats. In a workspace with many datasets, pass tables with the ones the request needs.

Arguments:

- `user_query` (string): A natural-language question naming the dataset(s) involved, optionally including a DuckDB/PostgreSQL SQL query. Omit when passing sql.
- `sql` (string): A single DuckDB SELECT naming tables by their sql_name, run exactly as written and answered with its rows. Omit when passing user_query.
- `tables` (string): The dataset names the question uses, as a list, for example ["sales", "product_info"]. Needed only with user_query in large workspaces; sql names its own tables.

### create_dataset

Save the result of a query as a new Daggy dataset, ready for run_statistical_model: joins, filters, subsets, new columns (ratios, logs, date parts), scenario grids, train/test splits and marketing-mix panels. Use it whenever the user wants data prepared for modeling or kept as a table; use query_data_sql when they only want an answer.

Describe the dataset in plain English in user_query ("sales joined to product_info with revenue = price * units_sold"), naming the source datasets, or pass sql with your own DuckDB SELECT and a description. Give exactly one of user_query, sql or panel. The name must be new unless overwrite is true. The dataset is saved automatically; do not call upload_dataset on the result. Set materialize to false only when the user asks for a view or the sources are large.

Train/test splits are two calls. Random data: "training data (80%) from <dataset>", then "test data from <dataset> excluding rows already in <training_table>". Ordered or time-series data: split at a date ("up to <DATE>", then "from <DATE> onwards"). The second call must name the training table or the date boundary so no row lands in both. No need to subset columns or clean data for modeling; run_statistical_model uses only the columns named in its edges.

Arguments:

- `name` (string, required): The dataset's name; letters, digits and underscores (other characters become underscores). Must be new unless overwrite is true.
- `user_query` (string): A plain-English description of the rows and columns to save, naming the source dataset(s). Omit when passing sql or panel.
- `sql` (string): A single DuckDB SELECT naming tables by their sql_name, whose result is saved as written. Omit when passing user_query or panel.
- `tables` (string): The source dataset names, as a list. Needed only with user_query in large workspaces; sql names its own tables.
- `description` (string): What the dataset holds, stored with it. Required with sql; with user_query it replaces the description the agent writes.
- `materialize` (boolean): Default true, which saves the rows as a dataset. false saves a view instead, only when the user asks for a view or the sources are large: a view stores no copy of the data and always reads the current version of its sources (a later overwrite of a source changes it; keep the default for a frozen copy), and models trained on it use a seeded sample of its rows. A view using now() or current_date changes as time passes.
- `overwrite` (boolean): Replace the existing dataset of this exact name. Default false. Set true only when the user wants the old rows replaced. A view is replaced only by a view and a dataset only by a dataset; delete the other kind first with delete_item. Replacing a view is refused while a live view or a trained model that reads it would lose a column; overwriting a dataset commits and warns.
- `panel` (string): A weekly marketing-mix panel built for you, as a JSON object: {"source": dataset_name in long form (a row per group, channel and date), "time", "channel", "group" (optional), "spend": [columns, 0 in weeks with no rows], "kpis": [columns, NULL plus <column>_missing], "no_fill": [spend columns to leave NULL instead; never the spend a marketing-mix model trains on, which must have no NULLs], "outcome": {"dataset", "column", "time"} (a coarser outcome per group and week, repeated on each channel row; finer rows, such as one per channel, are summed to group and week), "start" and "end" (ISO dates, optional), "cell_active" (default true: 1 inside each cell's first and last week, 0 before a cell starts or after it stops; false omits it; pass it as mmm.active when training a marketing-mix model on the panel)}. Every Monday week from start to end appears for every cell; rows with no time, group or channel are dropped. For a holdout, split it at a date counted back from its last week, not from today. Add trend, calendar flags or controls afterwards with sql over the panel, keeping one row per week and cell and never filling a KPI with 0 (trend: date_diff('week', min(week) OVER (), week); a daily control: average it to date_trunc('week', day) and LEFT JOIN, NULL plus a flag where absent). Wide data (a column of spend per channel) goes long first with sql: SELECT * FROM t UNPIVOT (spend FOR channel IN (tv, search, social)); every other column (the KPI, controls) repeats on each channel row of its week.

### upload_dataset

Upload a new dataset into Daggy from an outside source: a file the user provides (CSV, Excel), records fetched from other tools, APIs or websites, or a small table you create from scratch. Do not use it for anything derived from existing Daggy datasets: create_dataset saves query results, and run_statistical_model saves its own outputs.

Three ways to provide the rows, use exactly one:
- filename: starts a file upload. Returns a presigned PUT URL for the user; after they upload, call again with the returned uploaded_key to finish.
- file_content: the file's text, for small text files.
- data: a list of row dicts for a small table you create. Requires data_types.

table_name must not already exist unless overwrite or append is true (check list_items; a duplicate fails). With overwrite, the new rows are written under that name first and the existing dataset's files are removed once they are; models trained on the old rows keep their fit. Pass overwrite or append on both steps of the file upload flow. The rows may be modeled later, so prefer long format over wide.

Arguments:

- `table_name` (string, required): Name to store the rows under. Must be unused unless overwrite or append is true.
- `data_description` (string): What the data represents.
- `data` (string): List of row dicts for a small dataset you created. Requires data_types.
- `data_types` (string): Column types, required with data. Example {"age": "int", "salary": "float", "is_active": "boolean", "user_id": "category"}. Supported: int, float, boolean, category, datetime (stored as a native timestamp; ISO strings such as "2024-03-01" or "2024-03-01T10:30:00" parse).
- `filename` (string): Name of a file the user will upload (step 1 of the upload flow). CSV, JSON, parquet or xlsx; an xlsx workbook contributes its first sheet only.
- `uploaded_key` (string): The key returned after the upload, to finish processing (step 2 of the upload flow).
- `file_content` (string): File contents as a string, for small text data.
- `overwrite` (boolean): Replace an existing dataset of the same name. Default false. Set true only when the user wants the old rows gone. It always commits: live views and models that read the dataset and lose a column are listed in the result's warning.
- `append` (boolean): Add these rows to the dataset of this name (creating it if new) instead of refusing an existing name. Default false. Use it when collecting data over several calls. The rows take the dataset's column types (data_types only applies when append creates it), columns missing from a batch are left empty, and a batch identical to one already appended is skipped as a retry. data_description only applies on creation; use update_item to change it later.

### run_statistical_model

Builds and runs Bayesian statistical models on a Daggy dataset: training, A/B comparisons, predictions, Monte Carlo simulation, scenario and stress testing, and probabilistic forecasting with uncertainty. It also calibrates a list of probabilities (Platt / log-odds scaling) with a fixed coefficient or one fitted to a history table. Use it for any question about relationships, effects, predictions or comparisons under uncertainty; never attempt these in SQL.

Train one of three model types, set with model_type: a linear predictor per node (default) with interpretable coefficients, a small Bayesian neural network per node capturing non-linear effects and interactions, or a marketing-mix model (mmm) of spend on a weekly panel. Train the neural network for predictive questions, or when the linear model fits poorly.

If the model needs a join, a filter, new columns or a train/test split, create_dataset first and model the new dataset (an existing table's columns are used directly, no preparation needed). Training on a view or a large dataset uses a seeded sample of its rows, capped at a fixed size, plus one row for each category level.

Actions
- train: fit a new model. Needs table_name, a model_name not already in use (train refuses a name that exists; delete it with delete_item first to retrain under it), edges and target. Optionally model_type ("linear" or "nn") and nn_options, or pooled for a multilevel model. Returns fit summary and metrics.
- ab: compare two scenarios on a trained model. Needs model_name, target and ab_data. Returns comparative statistics for groups a and b.
- predict: predictions from a trained model, either for every row of table_name or for the scenarios in predict_data. Returns means, intervals and uncertainty.
- replay: what-if and ROI on a trained mmm model: contributions, ROI, mROI, response curves and spend scenarios. Needs model_name; no edges or target.
- optimize: split a budget across channels and months on a trained mmm model. Needs model_name and optimize.
- calibrate: not a final check on a probability. A single probability with no resolved history cannot be calibrated, and coefficient 1 with no intercept changes nothing (refused as an error). Use it to correct one forecaster's probabilities for a systematic bias (for example an LLM's hedging toward 0.5), using a fixed coefficient or one fitted on that forecaster's resolved history. It transforms each probability on its own; it does not combine or average probabilities from different sources (market, experts, models), so never pass several sources' estimates for one event expecting a consensus. Needs probabilities and nothing from the DAG actions (no edges, target, model_name or table_name). Give coefficient, optionally with intercept, to apply a fixed log-odds transform, or history_table_name with history_columns to fit the transform to past outcomes and apply it in the same call; never both. Nothing is persisted.

Give a linear and an nn fit different model_names to compare them.

Edges are required only for train. ab and predict read the trained model's own edges and ignore the edges argument if given; node names must be exact column names. In ab_data and predict_data you may omit variables; omitted variables are sampled, so specify the variables you want fixed (for example price) and leave out those whose uncertainty you want modeled (for example demand). An omitted variable with parents in the DAG is sampled from its parents; one without parents around its training mean and spread if numeric, or at its training frequencies if binary or categorical (an older model draws those 50/50 or uniformly; the result's warnings say which). Either way the prediction averages over it, so pass a variable whenever its value matters.

A calibrate call with neither coefficient nor history_table_name finishes as a COMPLETED job whose result has status "refused", an available_presets map such as {"sqrt3": {"value": 1.7320508, "description": "..."}} and a hint. That is not an error: nothing was applied. Read available_presets, judge from each description whether the preset fits the situation (sqrt3 assumes the probability is an average of independent LLM estimates, which leaves it hedged toward 0.5; it does not fit correlated sources such as markets and experts, or a predict result from a fitted model), and resend with an explicit coefficient (the number or the preset name) or with history_table_name to fit one. Successful calibrate results carry calibrated_probabilities and mode ("fixed_coefficient" or "fitted"); fitted results add fit.global, fit.sources[*].pooling_weight, fit.diagnostics and an optional fit.warnings list, which you should relay to the user. Malformed calibrate input fails the job with a readable message.

Arguments:

- `action` (string ("train" | "ab" | "predict" | "calibrate" | "replay" | "optimize"), required): Required. One of "train", "ab", "predict", "calibrate", "replay", "optimize". ab and predict need a trained model; calibrate needs no model; replay and optimize need a trained mmm model.
- `target` (string): Name of the target column; for train, a column some edge points to. Required for train, ab and predict. replay and optimize ignore it: ROI is in the units of the model's own target.
- `edges` (string): The model as a shallow DAG: a list of {"from": "<column>", "to": "<column>"} dicts using exact column names from the table. Required for train, mmm included, where it is the funnel (media -> kpi -> outcome, controls -> kpi or outcome). Ignored for ab, predict, replay and optimize, which use the trained model's own edges.
- `model_name` (string): Required for train, ab, predict, replay and optimize. New unique name when training: letters, digits, _ - . only, at most 100 characters (check list_items; train refuses a name that already exists on any table, ignoring case), or the existing model's name for ab and predict.
- `table_name` (string): Dataset name. Required for train, and for predict without predict_data. Ignored for ab, replay, for predict with predict_data, and for calibrate. For predict on an mmm model (a forecast), a table of future rows shaped like predict_data's. For optimize, future control values, only when the model has controls: one row per series and week from the training end through the horizon (the panel plus future rows works).
- `ab_data` (string): For ab. Dict of column -> {"a": value, "b": value}, for example {"weight": {"a": 40, "b": 40}, "height": {"a": 170, "b": 180}}. Omit variables to model their uncertainty.
- `predict_data` (string): For predict. List of dicts of column -> value, for example [{"price": 50}, {"price": 60}]. All dicts in one call must specify the same variables; make separate calls to mix variable sets. A null value is not sampled: that row's prediction is None, so leave the variable out instead. Include the target to get evaluation metrics. On an mmm model predict is a forecast; pass its rows here directly (a table_name of future rows also works, but needs no create_dataset for a few weeks): one row per series and future week, every week from the training end with no gap, with the time, channel and group columns, planned spend and every control the model uses, and no KPIs (leads and the outcome may be left out). A series left out is forecast at spend 0.
- `model_type` (string ("linear" | "nn" | "mmm")): For train. "linear" (default) fits an additive linear predictor per node with interpretable coefficients. "nn" fits a small neural network per node that captures interactions and non-linearities; it returns no coefficients, only fit metrics, so use it when the question is predictive or when the linear model's fit is poor, and compare the two with WAIC / LOO. "mmm" fits a marketing-mix model on a weekly panel (create_dataset panel) and needs mmm. Ignored for ab, predict and replay, which read the model type from the trained model.
- `nn_options` (string): For train with model_type "nn". Optional architecture, for example {"hiddenUnits": 16, "activation": "relu"}. Keys: hiddenUnits (int 1-64, default 8), hiddenLayers (int 1-3, default 1), activation ("tanh", "relu" or "softplus", default "tanh"). Defaults are fine for most datasets. Passing nn_options with model_type "linear", or any other key or out-of-range value, fails the job.
- `mmm` (string): For train with model_type "mmm", required. The panel's roles as an object: {"time": "week", "channel": "channel", "group": "program", "media": "spend", "kpi": "leads", "active": "cell_active", "outcome": {"column": "applications", "grain": ["program"], "lag_weeks": 26}, "brand": {"column": "program", "level": "Brand"}, "controls": [], "seasonality": {"history_table_name": "<dataset>", "time": "<column>", "value": "<column>"}, "adstock_max_lag": 8, "holdout_weeks": 8, "priors": {"roi": {"<channel value>": {"median": 0.005, "low": 0.001, "high": 0.02}}, "saturation": {"<channel value>": {"median": 4000, "low": 1500, "high": 9000, "source": "lift test"}}}}. time, channel, media and kpi are required; pass a create_dataset panel's cell_active column as active. Copy channel values exactly into priors; a key may also name a media group such as "Google (Brand)" (that channel's brand cells), and the most specific key wins. low and high are the 5th and 95th percentiles; priors.roi is in the trained target's units per dollar (the outcome's when given: 0.005 is 5 per $1,000), as replay reports ROI; priors.saturation is the half-saturation point from a pulse or lift test, in dollars of adstocked spend per series per week. One KPI per week (or per week and group), the shape most marketing-mix tools fit: add "kpi_grain": [] (or [<group>]), with optional "media_prior" ("roi" default, or "spend_share"), "likelihood" ("normal" default, or "negative_binomial" for counts), "saturation" ("logistic" default, or "hill"; priors.saturation needs "hill" here), "trend" (true or {"changepoints": n}, 1-20) and "seasonality": {"order": n}; the KPI and controls repeat on each channel row of their week; not with outcome, brand or a seasonality history table. Spend must have no NULLs. Train reads every row: a panel over 400,000 rows is refused. A fit takes minutes, so poll check_job_status. Not with nn_options, pooled or varying_slopes. The result's channels and cells keys are the names replay's multipliers take.
- `train_method` (string): For train. "vi" (variational, the default for linear and nn), "mcmc" (slower, more faithful intervals) or "auto" (mmm only and its default: mcmc when the fit is small enough, else vi with a warning).
- `pooled` (string): For train. Categorical DAG nodes to partially pool (a multilevel model), as a list such as ["program", "geo"]; a single name is fine. Use it for many groups with few rows per group (programs, schools, regions): the levels' effects on each child become varying intercepts that share a fitted spread, tau, so small groups borrow strength from the rest; with fewer than about 5 groups tau mostly reflects its prior. Each pooled node needs an outgoing edge. Two pooled nodes into one child are crossed effects; for a nested group, pool a combined column (for example school_class) made with create_dataset. The result adds group_effects, keyed "group→child": tau, per-level slopes and correlations. Fits are variational unless train_method is "mcmc", and variational fits tend to understate tau and over-shrink group effects, so read them as approximate. A level not seen in training predicts as a new group, with a wider interval. Ignored for ab, predict and calibrate, which read the trained model.
- `varying_slopes` (string): For train. List of {"from": "<column>", "to": "<column>", "by": "<pooled column>"} objects: the edge from -> to gets a slope per level of by. from -> to must be an edge, by must be in pooled with an edge by -> to, from must be numeric or binary, and to must not be categorical. Not with model_type "nn".
- `correlated_effects` (boolean): For train. true correlates each group's intercept with its varying slopes. Needs at least one varying_slopes entry. Default false.
- `probabilities` (string): For calibrate, required. List of probabilities in [0, 1], for example [0.7, 0.55, 0.9].
- `method` (string ("platt")): For calibrate. Only "platt" (log-odds scaling) for now.
- `coefficient` (string): For calibrate, fixed mode. The slope applied to logit(p): a positive number, or the preset name "sqrt3" (1.7320508). Cannot be combined with history_table_name.
- `intercept` (string): For calibrate, fixed mode. Number added to the scaled logit; default 0.
- `source_ids` (string): For calibrate. Optional list, same length as probabilities, naming the source of each probability so a fitted calibration with a source column can apply per-source terms.
- `history_table_name` (string): For calibrate, fit mode. Dataset holding past probabilities and their observed outcomes to fit the transform from. Needs history_columns.
- `history_columns` (string): For calibrate, fit mode. {"probability": "<column>", "outcome": "<column>", "source": "<column>"}; source is optional and enables per-source terms.
- `scenarios` (string): For replay. Spend scenarios, at most 20: [{"name": "meta_half", "multipliers": {"Meta": 0.5, "Google|MSC": 1.2}}]. A key is a channel value (all its series), a media group such as "Google (Brand)", or a series "channel|group"; the most specific wins. Each scenario's change against current spend comes back paired across draws.
- `curves` (string): For replay. true (default) adds response curves at 0-3x spend per channel; false skips them.
- `period` (string): For replay. {"start": "YYYY-MM-DD", "end": "YYYY-MM-DD"}, inclusive, either optional; default the whole panel.
- `draws` (string): For replay and optimize. Posterior draws, 50-2000, default 1000.
- `optimize` (string): For optimize, required. An object: {"budget": 200000, "horizon": {"start": "2026-07", "months": 6}, "bounds": [0.5, 2], "minimums": {"MBA": 4000}, "fixed": {"Google|Brand": 1900}, "damping": 0.5, "objective": "outcome"}. budget (required) is the total over the whole horizon. horizon (required) is 1-24 months, starting after the training end's month. bounds are multiples of each series' current spend (its mean over the last 8 training weeks): one [low, high] for every series, or keyed by channel, media group or series "channel|group" (most specific wins); default [0.25, 3]. minimums: per month, at least that much across a group's series, keyed by group value. fixed: per-month spend for a series ("channel|group", or a channel with one series), not optimized but counted in the budget. damping, 0-1 (default 0.5), moves the recommendation that share of the way from the current mix to the optimum. objective: "outcome" (the default when the model has one) or "kpi". Relay the result's warnings to the user: one naming prior-dominated channels means the recommendation moves spend the data barely informs, so suggest a pulse test there before acting on it.

### add_search_pages

Add web pages to an AI search collection, the set of pages an AI answer engine picks from for a query. Add the user's own live pages and the competitor or reference pages that rank for the queries they care about; then score drafts against them with score_search_content. Pass public URLs (the service fetches each one) and/or one pasted page. A page added again under the same name is replaced if its content changed. Runs as a job.

Arguments:

- `urls` (string): Public http(s) page URLs to fetch and add, as a list (at most 20 per call).
- `content` (string): One pasted page instead of a URL: HTML, markdown or plain text. Needs name or url.
- `name` (string): The pasted page's name in the collection (default: derived from url).
- `url` (string): The pasted page's real public URL, shown to the reader as its citation link. Leave it out for an unpublished draft or when the address is unknown; never make one up.
- `collection` (string): The collection to add to (lowercase letters, digits, - and _). Default "default". list_items with kind "search" shows the existing collections and their pages.

### score_search_content

Score a page for AI search: how an AI answer engine would retrieve and cite pages for a query: where each page in the collection ranks, whether the answer cites it and how much of the answer it supplies. Pass a draft as content to score it beside the collection without saving it; you rewrite the draft and score again. Check retrieval first with reader false (fast), then run the reader. Add pages with add_search_pages first. Runs as a job.

Arguments:

- `query` (string, required): The question as a user would ask an AI assistant (at most 1,000 characters).
- `content` (string): A draft page to score with the collection: a public URL or pasted HTML, markdown or text. It is never saved. Omit to score the collection as it is. When rewriting, use other pages only to see which questions they answer and how they are organised: never copy their wording, and add no fact, figure or claim the user has not given (ask the user instead).
- `name` (string): The draft's name (shown in the result).
- `url` (string): The draft's public URL, used as its citation link.
- `collection` (string): The collection to score against. Default "default". list_items with kind "search" shows the existing collections and their pages.
- `baseline` (boolean): Also score the collection without the draft and report each page's change. Needs content. Default false.
- `reader` (boolean): Run the answer step and measure citations (slower). false scores retrieval only. Default true.
- `runs` (integer): How many times the reader answers, 1 to 5; metrics are averaged. Default 1.
- `model` (string): Reader model id. Omit for the service default; an id the search service does not allow is refused with the allowed ones.
- `effort` (string ("none" | "low" | "medium" | "high")): Reader reasoning effort: "none", "low", "medium" or "high". Omit to let the service pick (medium when it fits, none otherwise). A set effort with too many runs is refused with the largest runs allowed.

### check_job_status

Check the status of a Daggy job (from query_data_sql, create_dataset, run_statistical_model, add_search_pages or score_search_content) by job_id and return its result once it has finished. The server holds the request while the job runs, so call it right away rather than waiting.

Arguments:

- `job_id` (string, required): The job_id of the status to check.

### update_item

kind="datasets": update the description of a Daggy dataset or view (the text list_items shows for it). It changes no data: new rows or a replacement go through upload_dataset (append or overwrite). kind="search": rename a collection; its pages keep their names.

Arguments:

- `kind` (string ("datasets" | "search"), required): "datasets" to change a description; "search" to rename a collection.
- `name` (string, required): Exact dataset_name, or for "search" the collection name, from list_items.
- `description` (string): For "datasets": the new description.
- `new_name` (string): For "search": the collection's new name (lowercase letters, digits, - and _).

### delete_item

Delete one dataset, view or model from the account by its exact name. kind="datasets": a dataset or view that a view reads or a model was trained on is not deleted unless force is true; the refusal names each view and model with the call that removes it. After a forced delete those views stop working and those models can no longer predict on it. kind="models": deletes the model with that model_name; if older models share the name, the refusal lists each one and force deletes them all. kind="search": removes an AI search page from its collection (name is the page name, or a URL to remove every page from it); with collection and no name, removes the whole collection with its pages and saved queries (force if it still has pages). WARNING: This permanently deletes data. Be cautious with original/source datasets, users may want to preserve these and only delete derived/intermediate datasets created during analysis. Confirm with the user if you are unsure.

Arguments:

- `kind` (string ("datasets" | "models" | "search"), required): "datasets" for a dataset or view; "models" for a trained model; "search" for an AI search page or collection.
- `name` (string): Exact dataset_name, model_name or search page name (or page URL) from list_items. Omit only with kind "search" and collection, to delete the whole collection.
- `force` (boolean): For datasets, delete even if views read it or models were trained on it; for models, delete every model sharing the name; for search, delete a collection that still has pages. Default false.
- `collection` (string): For kind "search" only: the page's collection (default "default"), or with no name the collection to delete.
