# Step Kinds

Source: https://docs.havilo.ai/reference/ai-engine/step-kinds

When you submit `input_type: "plan"`, each step has a `kind`, a `title`, an optional `instructions` and `description`, and a `params` object. This page lists every public step kind and the public parameters each accepts. Internal-only workflow parameters are intentionally omitted from this client contract.

The full `params` object must JSON-stringify to no more than **64 KB** per step. Plans are limited to **50 steps** total.

Direct plan params are validated when the run is created. Unknown params are rejected, and omitted optional params use the defaults documented below.

## Step fields

`params` says how a step is wired — which steps feed it, how many results to keep, what columns to emit. The three text fields say what it is, in three different registers:

| Field          | Type   | Required          | Read by | Description                                                                   |
| -------------- | ------ | ----------------- | ------- | ----------------------------------------------------------------------------- |
| `title`        | string | Yes               | People  | Up to 200 characters. Names the step in a progress list.                      |
| `instructions` | string | On `filter` steps | Models  | Up to 20,000 characters. What this step should achieve.                       |
| `description`  | string | No                | People  | Up to 2,000 characters. One present-tense sentence shown while the step runs. |

`instructions` is the only one a step's model reads. `title` and `description` are for the person watching, and never reach a prompt.

**`filter` requires `instructions`.** A filter judges every candidate against stated criteria, and no `filter` param carries those criteria — `title` is a label for a reader, not a rubric for a model. A filter step submitted without `instructions` is rejected at run creation with `schema_validation_failed` on `plan.steps.N.instructions`.

Every other kind treats `instructions` as optional steering. It is worth setting on `transform`, `analyze`, and `reconstitute`, which read it to decide what to write, what to rank by, and how to phrase the answer.

A plan may also carry a top-level `thought_process` (up to 8,000 characters) explaining why it is shaped the way it is. The engine's supervisor reads it when judging whether a step's outcome matches what the plan was reaching for.

A plan may also carry a top-level `summary` (up to 1,000 characters): one or two sentences telling the person who will approve it what it does. The engine's planner writes one for the plans it makes; a plan you supply may omit it.

Defaults applied at run creation:

* `fetch.output_mode`: `"entities"`
* `vector_search.limit`: `200`
* `vector_search.mode`: `"semantic"`
* `filter.output_mode`: `"entities"`
* `filter.ai_reasoning_column_name`: `"Reasoning"`
* `analyze.mode`: `"synthesize"`

`transform.reduce` is a runtime default: `true` for `analysis`/`tabular_view`, and `false` for `text_editor`/`email_editor`.

## fetch

Retrieve entities from the database or a previous run's artifact.

| Param              | Type                             | Required                 | Description                                                                                                  |
| ------------------ | -------------------------------- | ------------------------ | ------------------------------------------------------------------------------------------------------------ |
| `source`           | `"database"` \| `"artifact"`     | Yes                      | Where to read from.                                                                                          |
| `artifact_id`      | string                           | If `source = "artifact"` | UUID of a prior table or list artifact.                                                                      |
| `query`            | string                           | No                       | Search terms used when `source = "database"`.                                                                |
| `limit`            | integer                          | No                       | Maximum rows or entities returned. Retrieval cap, not a top-N ranking — use `filter.limit_to` for relevance. |
| `output_mode`      | `"entities"` \| `"tabular_view"` | No                       | Default `"entities"`. Use `"tabular_view"` for a formatted table artifact.                                   |
| `title`            | string                           | No                       | Title for the table artifact when `output_mode = "tabular_view"`.                                            |
| `context_step_ids` | integer\[]                       | No                       | Step indices to pivot from (use their output entities as input).                                             |

```json
{
  "index": 0,
  "kind": "fetch",
  "title": "Fetch climate-tech startups",
  "params": { "source": "database", "query": "climate-tech startups in Europe", "limit": 200 }
}
```

## vector\_search

Run a semantic or keyword search over a candidate pool of entities.

| Param               | Type                        | Required | Description                                                                                                                     |
| ------------------- | --------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------------------- |
| `query`             | string                      | Yes      | Search query — concepts, sectors, or expertise areas.                                                                           |
| `source_step_ids`   | integer\[]                  | Yes      | Step indices whose output entities form the search pool. Every `vector_search` must reference a prior `fetch` or `filter` step. |
| `limit`             | integer                     | No       | Maximum results. Default `200`.                                                                                                 |
| `mode`              | `"semantic"` \| `"keyword"` | No       | Default `"semantic"`. Use `"keyword"` for exact matches like company names or "Series A".                                       |
| `context_step_ids`  | integer\[]                  | No       | Step indices whose output augments the query (concatenated).                                                                    |
| `source_entity_ids` | string\[]                   | No       | Restrict the search to specific entity IDs.                                                                                     |

```json
{
  "index": 1,
  "kind": "vector_search",
  "title": "Find investors interested in climate-tech",
  "params": {
    "query": "investors with climate-tech thesis",
    "source_step_ids": [0],
    "mode": "semantic",
    "limit": 100
  }
}
```

## filter

Reduce a candidate list using LLM-scored relevance evaluation. **Requires `instructions`** — see above. The criteria go there, not in `params`.

| Param                      | Type                             | Required | Description                                                                                            |
| -------------------------- | -------------------------------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `steps_to_filter`          | integer\[]                       | Yes      | Step indices whose output entities are filtered.                                                       |
| `limit_to`                 | integer                          | No       | Keep at most this many entities (top-N by relevance).                                                  |
| `output_mode`              | `"entities"` \| `"tabular_view"` | No       | Default `"entities"`.                                                                                  |
| `ai_reasoning_column_name` | string                           | No       | Column name for the AI-generated reasoning when `output_mode = "tabular_view"`. Default `"Reasoning"`. |
| `title`                    | string                           | No       | Title for the table artifact.                                                                          |
| `context_step_ids`         | integer\[]                       | No       | Additional context steps (e.g., a reference entity for comparison).                                    |

```json
{
  "index": 2,
  "kind": "filter",
  "title": "Top 5 by fund-stage match",
  "instructions": "Keep investors whose stated stage focus includes seed or Series A.",
  "params": { "steps_to_filter": [1], "limit_to": 5 }
}
```

## transform

Run a per-entity LLM transformation. Capped at \~100 entities by default. To remove the cap, set `config.unlimited_transform: true` on the run; this requires the `config:unlimited_transform` scope on the API key (otherwise the run is rejected with `403 forbidden_feature`).

| Param                        | Type                                                                    | Required                          | Description                                                                                                                                                                         |
| ---------------------------- | ----------------------------------------------------------------------- | --------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `output_type`                | `"analysis"` \| `"text_editor"` \| `"email_editor"` \| `"tabular_view"` | Yes                               | Output shape. `"analysis"` is plain text; `"text_editor"` and `"email_editor"` produce one artifact per entity; `"tabular_view"` produces one table for all entities.               |
| `steps_to_transform`         | integer\[]                                                              | Yes                               | Step indices whose output entities are transformed.                                                                                                                                 |
| `columns`                    | string\[]                                                               | If `output_type = "tabular_view"` | Exact column names the LLM must output.                                                                                                                                             |
| `drop_columns`               | string\[]                                                               | No                                | For `"tabular_view"` over a saved table (a `fetch` with `source: "artifact"`): columns of that table to leave out. The table's other columns and every row are kept.                |
| `reduce`                     | boolean                                                                 | No                                | Whether to filter out irrelevant entities. Default true for `analysis`/`tabular_view`, false for `text_editor`/`email_editor`.                                                      |
| `keep_all_reason`            | string                                                                  | No                                | With `reduce: false`, one sentence saying why every input row belongs in the result (for example, the request named these records).                                                 |
| `title`                      | string                                                                  | No                                | Title for table or artifact.                                                                                                                                                        |
| `context_step_ids`           | integer\[]                                                              | No                                | Additional context steps.                                                                                                                                                           |
| `verbatim_template_step_ids` | integer\[]                                                              | No                                | For `text_editor`/`email_editor`: note steps whose note is a template the draft reproduces word for word, filling only the spots it marks. Each must also be in `context_step_ids`. |
| `verbatim_instructions`      | boolean                                                                 | No                                | Every call follows `instructions` exactly as written. Use it when `instructions` is a complete rubric that must grade the same way on every run.                                    |

```json
{
  "index": 3,
  "kind": "transform",
  "title": "Generate investor briefs",
  "params": { "output_type": "text_editor", "steps_to_transform": [2] }
}
```

## analyze

Reduce step outputs into a synthesis, ranking, or set operation. Some modes do not call the LLM (cheap).

| Param              | Type                                                                                                             | Required                 | Description                                                                                                                                                                                                                                                                                                                                                                                                |
| ------------------ | ---------------------------------------------------------------------------------------------------------------- | ------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `mode`             | `"synthesize"` \| `"stack_rank"` \| `"merge"` \| `"intersect"` \| `"overlap"` \| `"exclude"` \| `"pivot_to_row"` | No                       | Default `"synthesize"`. `"merge"` unions and dedupes; `"intersect"` keeps entities present in all input steps; `"overlap"` counts how many input steps each entity is in, sorts by it, and reports exact overlap counts, per record type when the inputs mix types; `"exclude"` subtracts from the first step. `"merge"` / `"intersect"` / `"overlap"` / `"exclude"` / `"pivot_to_row"` skip the LLM call. |
| `context_step_ids` | integer\[]                                                                                                       | Yes                      | Step indices to analyze. Use `[-1]` for passthrough (no context). Min 1.                                                                                                                                                                                                                                                                                                                                   |
| `top_n`            | integer                                                                                                          | If `mode = "stack_rank"` | Top-N to keep. Pass `-1` to keep all.                                                                                                                                                                                                                                                                                                                                                                      |
| `at_least`         | integer                                                                                                          | No                       | For `"overlap"` only. Keep only entities in at least this many input steps. At most the number of `context_step_ids`.                                                                                                                                                                                                                                                                                      |
| `ranking_criteria` | string                                                                                                           | If `mode = "stack_rank"` | Criterion to rank by (e.g., "investment thesis fit").                                                                                                                                                                                                                                                                                                                                                      |
| `fetch_full_graph` | boolean                                                                                                          | No                       | For `"synthesize"` only. Set true if input is small and you need to read entity notes.                                                                                                                                                                                                                                                                                                                     |

```json
{
  "index": 4,
  "kind": "analyze",
  "title": "Rank by thesis fit",
  "params": {
    "mode": "stack_rank",
    "context_step_ids": [3],
    "top_n": 5,
    "ranking_criteria": "thesis alignment"
  }
}
```

## discover

Surface trends and patterns across a list of entities.

| Param               | Type       | Required | Description                                      |
| ------------------- | ---------- | -------- | ------------------------------------------------ |
| `steps_to_discover` | integer\[] | Yes      | Step indices whose output entities are analyzed. |
| `context_step_ids`  | integer\[] | No       | Additional context steps.                        |

```json
{
  "index": 4,
  "kind": "discover",
  "title": "Find sector trends",
  "params": { "steps_to_discover": [3] }
}
```

## reconstitute

Synthesize the final user-facing response. Always the last step in a plan.

| Param                | Type       | Required | Description                                                                                                                                            |
| -------------------- | ---------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `context_step_ids`   | integer\[] | Yes      | Step indices whose output is presented. Use `[-1]` for passthrough. Min 1.                                                                             |
| `expected_artifacts` | string\[]  | Yes      | Artifact types `reconstitute` may create. Use `["NONE"]` for text-only responses (the common case when prior steps already produced artifacts). Min 1. |

```json
{
  "index": 5,
  "kind": "reconstitute",
  "title": "Format the result",
  "params": { "context_step_ids": [4], "expected_artifacts": ["NONE"] }
}
```

## inject\_context

Inject known entity, note, or calendar-event UUIDs into the pipeline. Used when you already have concrete IDs and want them available to downstream steps.

| Param       | Type      | Required | Description                     |
| ----------- | --------- | -------- | ------------------------------- |
| `entityIds` | string\[] | No       | Entity UUIDs to inject.         |
| `noteIds`   | string\[] | No       | Note UUIDs to inject.           |
| `eventIds`  | string\[] | No       | Calendar event UUIDs to inject. |

At least one of the three arrays should be provided.

```json
{
  "index": 0,
  "kind": "inject_context",
  "title": "Seed pipeline with target entity",
  "params": { "entityIds": ["550e8400-e29b-41d4-a716-446655440000"] }
}
```
