# Recipe: report insights with a routing action

Build one action that asks the user which KPI to optimize, then routes the analysis to a specialized task agent and returns structured insights.

## What you'll build

An **Insights** capability: the user points the agent at a reporting or analytics report and gets
back *decisions* — the drivers, the anomalies, and what to do next — not just a summary.

The trick is a single **routing action**. Instead of one giant "analyze everything" prompt, the
action first has the agent ask a **qualifying question** — *what goal or KPI do you want to
optimize?* — and then its handler **routes** that intent to the right specialized
[task agent](/user-guides/task-agents/): a revenue analyst, a funnel analyst, a retention analyst,
and so on. One action, many specialized analysts.

Why route instead of writing one mega-prompt:

- **Each analyst is focused.** A funnel expert and a churn expert need different reasoning; keeping
  them separate makes each sharper.
- **Each analyst is independently versioned, evaluated, cached, and tuned.** You can put a cheap
  model behind a simple summary and a stronger one behind cohort analysis.
- **The capability grows without a rewrite.** Adding a KPI means adding one task agent and one line
  in a routing map — the action and its handler don't change.

:::note[Prerequisites]
You should be comfortable [authoring an action](/user-guides/authoring-actions/), wiring its
[`execute()` handler](/guides/executing-actions/), and calling a
[task agent with `runTask()`](/reference/task-agent-api/). This recipe assembles those three
into one capability.
:::

## Architecture

<Mermaid chart={`sequenceDiagram
    participant U as User
    participant A as Main Agent
    participant Act as Routing Action (execute)
    participant T as Specialist Task Agent
    U->>A: "Analyze last quarter's report"
    A->>U: Which goal or KPI do you want to optimize?
    U->>A: Retention
    A->>Act: analyze_report_insights({ report, goal, kpi })
    Act->>Act: Map kpi → taskKey
    Act->>T: runTask(retention_churn_analyzer, { report, question })
    T-->>Act: Structured insights (JSON)
    Act-->>A: { kpi, summary, findings, recommendations }
    A->>U: Reply grounded in the analyst's insights
`} />

The main agent handles the conversation and the qualifying question. The **routing action** is a
thin dispatcher: it maps the chosen KPI to a task-agent key and calls it. The **task agent** does
the heavy reasoning and returns a predictable JSON object the agent can relay.

## Step 1 — Build the specialist task agents

In **Agent Studio → Task Agents**, create one analyst per KPI family. Configure each with a **JSON**
response so the handler gets a predictable object back. A single reusable output schema keeps every
analyst interchangeable from the handler's point of view:

```json
{
  "type": "object",
  "properties": {
    "summary": { "type": "string", "description": "One-paragraph headline of what the data shows" },
    "findings": {
      "type": "array",
      "description": "The most important observations, most significant first",
      "items": {
        "type": "object",
        "properties": {
          "metric": { "type": "string" },
          "observation": { "type": "string" },
          "impact": { "type": "string", "enum": ["high", "medium", "low"] }
        },
        "required": ["metric", "observation", "impact"],
        "additionalProperties": false
      }
    },
    "anomalies": {
      "type": "array",
      "description": "Values that look off and are worth a human check",
      "items": {
        "type": "object",
        "properties": {
          "metric": { "type": "string" },
          "note": { "type": "string" }
        },
        "required": ["metric", "note"],
        "additionalProperties": false
      }
    },
    "recommendations": {
      "type": "array",
      "description": "Concrete next actions to move the chosen KPI",
      "items": { "type": "string" }
    },
    "confidence": { "type": "string", "enum": ["high", "medium", "low"] }
  },
  "required": ["summary", "findings", "anomalies", "recommendations", "confidence"],
  "additionalProperties": false
}
```

Give each analyst instructions scoped to its KPI. A starting set:

| Task agent (`api_name`) | Optimizes for | Instructions focus |
| :--- | :--- | :--- |
| `revenue_growth_analyzer` | Revenue | Revenue drivers by segment, expansion vs. new, ARPU, refund/discount drag |
| `conversion_funnel_analyzer` | Conversion | Stage-by-stage drop-off, activation, signup→paid, where to intervene |
| `retention_churn_analyzer` | Retention | Cohort retention, churn concentration, refunds, at-risk segments |
| `engagement_analyzer` | Engagement | Feature usage, active-user trends, engagement anomalies |

Example instructions for one analyst — keep them tight and tell it the input to expect and the goal
to serve:

> You are a retention and churn analyst. You receive `report` (rows of metrics, often by cohort or
> segment) and a `question` describing the user's goal. Identify where retention is strongest and
> weakest, concentrate on churn drivers and at-risk segments, flag anomalies, and return concrete
> actions to improve retention. Be specific and quantitative; never invent numbers not in the data.

:::tip[Let the agent draft it]
The Task Agent editor's **Generate** button (✦) drafts the description and instructions from a
plain-English brief. The Knowledge Base also feeds these analysts automatically — if you keep an
article on *what good retention analysis looks like for our product*, the analyst applies it.
:::

See the [Task Agents guide](/user-guides/task-agents/) for the editor, and
[Example: analyze report data](/guides/task-agent-analysis/) for a worked single-analyst call.

## Step 2 — Author the routing action

In **Agent Studio → Actions**, create `analyze_report_insights`. This is the front door; its job is
to collect the goal/KPI and hand off.

**Description** (the agent reads this to decide *when* to call it — say what it returns):

> Analyzes a reporting or analytics report and returns structured insights — summary, key findings,
> anomalies, and recommended actions — tuned to the KPI the user wants to optimize.

**Instructions** (the qualifying-question rule — this is what makes it a *router*, not a guesser):

> Use when the user wants to analyze a report, dashboard, or analytics export and get insights or
> recommendations. **Before calling, make sure you know which goal or KPI to optimize for.** If the
> user hasn't said, ask: *"What goal or KPI do you want to optimize — for example revenue,
> conversion, retention, or engagement?"* When the goal is already clear from context, skip the
> question and proceed. Pass the user's objective in `goal` and the matching category in `kpi`.

**User Input** parameters (see [Defining inputs](/user-guides/defining-inputs/)):

| Parameter | Type | Required | Purpose |
| :--- | :--- | :---: | :--- |
| `report` | Object / Array | Yes | The report data (or a reference the handler can resolve) |
| `goal` | String | No | The user's objective in their own words, e.g. "reduce churn in EU" |
| `kpi` | String (enum) | Yes | One of `revenue`, `conversion`, `retention`, `engagement`, `other` |

The `kpi` enum is what the handler routes on; `goal` is the free-text nuance the analyst reads.

## Step 3 — Write the `execute()` router handler

The handler is small on purpose: map `kpi` to a task-agent key, call it, and return the structured
result. All the reasoning lives in the analysts.

```javascript
// KPI → the specialist task agent that handles it.
// Add a KPI by adding a task agent and one entry here — nothing else changes.
const ROUTES = {
  revenue:    'revenue_growth_analyzer',
  conversion: 'conversion_funnel_analyzer',
  retention:  'retention_churn_analyzer',
  engagement: 'engagement_analyzer',
};

foldspace('when', 'ready', () => {
  foldspace.agent({ /* …common setup… */ })
    .addActionHandlers({
      analyze_report_insights: {
        // Analysis can take longer than the 5s default — give it room.
        timeout: 60000,
        execute: async ({ report, goal, kpi }) => {
          const taskKey = ROUTES[kpi];

          // Routing couldn't decide — hand control back so the agent asks the
          // qualifying question instead of guessing an analyst.
          if (!taskKey) {
            return {
              needsGoal: true,
              message: 'Which KPI should I optimize for?',
              options: Object.keys(ROUTES),
            };
          }

          try {
            const insights = await foldspace
              .agent('YOUR-AGENT-API-NAME')
              .runTask({
                taskKey,
                data: {
                  report,
                  goal,
                  question: `Optimize for ${goal || kpi}. Surface the drivers, flag anomalies, and recommend concrete next actions.`,
                },
                // Dashboards refresh often; reuse an identical analysis for an hour.
                cacheOptions: { ttlSeconds: 3600 },
              });

            // Return the KPI alongside the analyst's output so the agent can
            // frame its reply ("Here's what matters for retention…").
            return { kpi, goal, ...insights };
          } catch (error) {
            console.error('Report analysis failed:', error);
            return { error: true, kpi, message: 'The analysis could not be completed. Try again shortly.' };
          }
        },
      },
    });
});
```

A few things this handler gets right:

- **It routes, it doesn't analyze.** Swapping a model or sharpening a prompt happens in the task
  agent, never here.
- **It closes the loop when routing fails.** The `needsGoal` return nudges the agent back to the
  qualifying question rather than picking an analyst at random.
- **It caches.** Identical `data` returns from cache, so a re-opened dashboard doesn't pay twice —
  see [cache options](/reference/task-agent-api/#cache-options).

## Step 4 — The qualifying-question pattern, in depth

There are two ways the agent gathers the goal/KPI. Pick based on how much you want to steer:

1. **Conversationally (default, no code).** The action **Instructions** from Step 2 tell the agent
   to ask *"what goal or KPI do you want to optimize?"* whenever it's unclear, and to skip the
   question when the goal is obvious from context. This is enough for most cases and keeps the flow
   natural.
2. **With an in-chat UI picker.** For a guided experience, render a KPI chooser in the chat from the
   action instead of returning plain data — see [UI components in chat](/guides/in-chat-ui/). Set
   `awaitUserInput: true` on the handler so the agent pauses for the user's selection before the
   analysis runs.

The `needsGoal` fallback in the handler is the safety net for both: if the KPI still isn't set when
the action fires, control returns to the agent to ask, rather than to a wrong analyst.

## Step 5 — Test, evaluate, publish

- **Route correctly.** Use the action's **Evals** tab to confirm real phrasings ("why are we losing
  customers?", "where's revenue coming from?") extract the right `kpi` and route to the right
  analyst.
- **Analyze correctly.** Use each task agent's **Evaluation** tab to assert the JSON output meets
  your criteria on representative reports before it ever reaches the main agent.
- **Watch cost and latency.** The task agent **Logs** tab records inputs, output, model, latency,
  and cost per run — use it to move simple analysts to a cheaper model.
- **Publish in order.** Publish the **task agents first** (the handler calls them by `taskKey`),
  then publish the **routing action**. A task agent must be Published before the agent can delegate
  to it.

## Best practices & extend it

- **One clear job per analyst.** If an analyst's instructions need "and" several times, it's
  probably two analysts.
- **Add a KPI in two edits.** Create the task agent, add one `ROUTES` entry. The action, its
  schema, and the handler are untouched.
- **Cache for dashboards, bypass on fresh data.** Set `cacheOptions.bypass: true` when the
  underlying report changed and you need a fresh run.
- **Observe adoption.** Track execution and routing accuracy with Action Insights and
  [Conversational Analytics](/user-guides/analytics/dashboard/) — they tell you which KPIs users
  actually ask about, so you know which analyst to build next.

## Related

- [Task agents](/user-guides/task-agents/) — configure the specialist sub-agents.
- [Example: analyze report data](/guides/task-agent-analysis/) — a single-analyst `runTask()` call.
- [Authoring actions](/user-guides/authoring-actions/) · [Execute actions](/guides/executing-actions/) — the two halves of the routing action.
- [UI components in chat](/guides/in-chat-ui/) — render a KPI picker for the qualifying question.
