# ask\_question

Ask a natural-language question about a project's data, or continue a chat. Cassis grounds the question against the published ontology, plans the answer, and generates the SQL.

When the plan has a genuine fork, an assumption you could resolve differently, the tool surfaces the plan first and waits for explicit confirmation before executing.

Each chat is pinned to the published ontology version current when it started. Follow-ups in the same chat keep that version even if a newer one is published meanwhile. Omit `chat_id` to start a new chat on the latest published version.

What comes back depends on the data source. Connected to an executable warehouse, Cassis runs the SQL and returns the answer, the SQL, and results. On a schema-only source, it generates SQL in that dialect but never runs it, so `results` is always null.

## Parameters

Passed by the client, not typed by the user.

| Name | Type | Notes |
| --- | --- | --- |
| `project_id` | UUID, required | The Cassis project to query |
| `question` | string | Natural-language question. For follow-ups it can reference earlier messages (“now group by circuit”). Ignored when `execute_pending_plan=true`. Defaults to an empty string |
| `chat_id` | string | Passed by the client on follow-ups, plan refinement, or plan execution. Omitted to start a new chat |
| `execute_pending_plan` | boolean | Set true to execute the plan surfaced on the previous turn (when `status="needs_execution"`). Requires `chat_id` and the user’s explicit approval. Defaults to false |

## Response

| Field | Notes |
| --- | --- |
| `chat_id` | Always set. The client stores it to continue the chat |
| `status` | `"answered"`, `"needs_execution"`, `"not_answerable"`, or `"error"` |
| `answer` | The assistant’s text response. Present on every status. On `needs_execution` it carries the plan’s intent when the assistant added no prose of its own |
| `sql` | The generated SQL. Populated when `status="answered"` |
| `results` | Columnar: `{columns, rows, total_rows, truncated}`, rows as positional lists. Null when the SQL was not executed, and always null on a schema-only source. Zero rows means the query ran and matched nothing |
| `objects_used` | Qualified names of the ontology objects the answer used |
| `plan` | The plan awaiting execution, when `status="needs_execution"`. Its `assumptions[]` carry `text` and, for a fork, `options` and `selected` |
| `ontology_version` | The published version this answer was grounded on. Pinned when the chat starts |
| `error` | Populated when `status="error"` or `status="not_answerable"` |

**`not_answerable` is terminal for that question.** It means the approved plan cannot run against the current ontology or data: an undefined concept, structural drift, or missing data. The reason is in `answer` and `error`. Retrying the same question will not help; refine the question or fix the ontology.

## Example: a straight answer

The user types “Who won the most races in 2023?” and Cassis returns:

```yaml
chat_id: "chat_01JZ8K4M2R"
status: "answered"
answer: "Max Verstappen won 19 out of 22 races in 2023,
         far ahead of Pérez (2) and Sainz (1)."
sql: |
  SELECT d.full_name, COUNT(*) AS wins FROM public.results r
    JOIN public.races ra ON r.race_id = ra.race_id
    JOIN public.drivers d ON r.driver_id = d.driver_id
    WHERE ra.year = 2023 AND r.position = 1
    GROUP BY d.full_name ORDER BY wins DESC
results:
  columns: ["full_name", "wins"]
  rows: [["Max Verstappen", 19], ["Sergio Pérez", 2], ["Carlos Sainz", 1]]
  total_rows: 3
  truncated: false
objects_used: [public.results, public.races, public.drivers]
ontology_version: 3
```

## Example: plan, then execution

“Compare total points scored by all drivers in 2022 vs 2023, including sprint results.” has a fork, so the first call returns a plan rather than an answer:

```yaml
chat_id: "chat_01JZ8K6T7Q"
status: "needs_execution"
plan:
  intent: "UNION query combining public.results and
           public.sprint_results, grouped by season."
  assumptions:
    - text: "Sprint points included in the total"
    - text: "Points rolled up across all drivers per season"
  objects: [public.races, public.results, public.sprint_results]
```

Surface the assumptions to the user. If they want a different option, send a revised question naming it rather than executing the default. On approval, the client re-calls the tool with the same `chat_id` and `execute_pending_plan=true`, and Cassis executes:

```yaml
chat_id: "chat_01JZ8K6T7Q"
status: "answered"
answer: "In 2023 drivers scored 2,458 points combined, up from
         2,350 in 2022. Sprint points are included in both totals."
```

## Example: a follow-up

The client keeps the `chat_id` from the previous turn, so “Now break it down by constructor for 2023” needs no restated scope and answers against the same pinned ontology version.

Limits (question timeout, row caps) are in [Limits and errors](/reference/limits/).
