> ## Documentation Index
> Fetch the complete documentation index at: https://docs.firstflow.app/llms.txt
> Use this file to discover all available pages before exploring further.

# Conversations & signals

> Attach intent, outcome, tags, and notes to a conversation with ff.signal() and ff.tagConversation() set by your own logic, not an LLM.

Beyond automatic token and cost capture, you can attach your own signals to a conversation: a resolved intent, an outcome, tags, and notes. These are set by **your** logic (a rules engine, a human, a downstream step) distinct from the [LLM classifier](/server/analytics).

## Set intent and outcome

```ts theme={null}
await ff.signal({
  conversationId,
  intent: "billing",       // optional
  outcome: "resolved",     // optional
});
```

Writes the `intent` and/or `outcome` columns on `firstflow_conversations`. Use it when your application already knows the answer for example, the user clicked "this resolved my issue."

## Tags and notes

```ts theme={null}
await ff.tagConversation({
  conversationId,
  tags: ["vip", "escalated"],
  notes: "Asked for a refund; promised follow-up.",
});
```

Writes the `tags` (text array) and `notes` columns. Useful for triage and later filtering.

## Requirements

* These columns ship in migration `0007_conversation_signals.sql` apply it first. See [Set up Supabase](/self-hosting/supabase-cloud).
* Both methods are no-ops (with a warning) if your persistence adapter doesn't implement `signal` / `tagConversation`. The shipped Supabase adapter implements both.

## Signals vs. the classifier

|                   | `signal` / `tagConversation` | [`classifyIntent`](/server/analytics)        |
| ----------------- | ---------------------------- | -------------------------------------------- |
| Decides the value | Your code                    | An LLM                                       |
| Cost              | Free (a DB write)            | One LLM call                                 |
| Use when          | You already know the outcome | You want to infer intent from the transcript |

You can use both: classify intent automatically, then `signal` a final outcome when the conversation closes.

## Querying

```sql theme={null}
select intent, outcome, count(*)
from firstflow_conversations
where intent is not null
group by intent, outcome
order by count(*) desc;
```

## Next

Infer intent and sentiment automatically in [Intent & sentiment](/server/analytics).
