Workflows
Workflows sort your organization’s chats by the job they do, so you can filter evaluations, traces and analytics to one kind of work.
A workflow is a kind of work your people do with agents, such as refund requests or quarterly reporting. Context files each chat into the workflows it matches, up to five per chat, and you can then narrow evaluations, monitors, datasets, traces and analytics to one workflow.
Workflows are off until your administrator turns them on for your organization. Only organization owners and admins can open the Workflows pages in the app or list workflows from the CLI. Other people still see workflow names on their tasks, evaluations and datasets.
Set up workflows in the app
Open Evals > Workflows. The curation pages have four tabs:
| Tab | What you do there |
|---|---|
| Suggestions | Approve or reject workflows that Context proposes as it reads your chats. |
| Workflow Taxonomy | Track a workflow by hand, or rename, merge or archive one. |
| Unclassified | Chats that matched no workflow. Move them into a workflow, or make a new workflow from them. |
| Review | Check a sample of matches and say whether each chat belongs to its workflow. |
Open one workflow to see its usage, evaluations, datasets, targets and traces. On the Targets tab, add an agent, skill or applet, and every chat that uses it is filed into the workflow. A workflow’s cost and usage are on its Usage tab; if your release shows that tab greyed out, use Settings > Usage.
Find a workflow’s ID
context evals targets workflow --search refunds --pick 'items[].id'This command needs monitoring turned on as well as workflows, an organization owner or admin, and a key with evals:write (a read-only key is refused). evals monitors list --workflow needs the same. Without monitoring, take the ID from the workflow’s address in the app instead: it is the last part of /client/evals/workflows/t/<workflow-id>.
Filter by workflow in the CLI
--workflow <id> narrows these commands to one workflow:
context evals traces list --from 30d --workflow "$WORKFLOW_ID" --where "success=false"context evals benchmark list --workflow "$WORKFLOW_ID"context evals datasets list --workflow "$WORKFLOW_ID"context evals monitors list --workflow "$WORKFLOW_ID"traces list returns the sessions whose chat is in the workflow now. While workflows are off, it lists nothing and exits 0, so an empty list there doesn’t mean there were no sessions. datasets list and benchmark list are refused instead.
Filter by workflow over HTTP
GET /analytics/records, GET /analytics/overview and GET /analytics/records/export take workflowIds: a record matches when its chat is in any of the workflows you name, up to 50. Send the IDs as a JSON list in one value, or repeat the key once per ID. A single plain value is refused with 400.
curl -sS --fail-with-body --get "$CONTEXT_APP_ORIGIN/api/v1/evals/analytics/overview" \ -H "Authorization: Bearer $CONTEXT_API_KEY" \ --data-urlencode 'workflowIds=["'"$WORKFLOW_ID"'"]' \ --data-urlencode "from=2026-10-01T00:00:00Z" | jq '.summary | {totalTasks, totalUsers, avgRating}'import jsonimport os
import requests
origin = os.environ["CONTEXT_APP_ORIGIN"]headers = {"Authorization": f"Bearer {os.environ['CONTEXT_API_KEY']}"}params = {"workflowIds": json.dumps([os.environ["WORKFLOW_ID"]]), "from": "2026-10-01T00:00:00Z"}
resp = requests.get(f"{origin}/api/v1/evals/analytics/overview", headers=headers, params=params, timeout=60)resp.raise_for_status()summary = resp.json()["summary"]print(summary["totalTasks"], summary["totalUsers"], summary["avgRating"])function env(name: string): string { const value = process.env[name] if (!value) throw new Error(`Set ${name} first`) return value}
const url = new URL("/api/v1/evals/analytics/overview", env("CONTEXT_APP_ORIGIN"))url.searchParams.set("workflowIds", JSON.stringify([env("WORKFLOW_ID")]))url.searchParams.set("from", "2026-10-01T00:00:00Z")const res = await fetch(url, { headers: { Authorization: `Bearer ${env("CONTEXT_API_KEY")}` } })if (!res.ok) throw new Error(`${res.status} ${await res.text()}`)const { summary } = (await res.json()) as { summary: { totalTasks: number; totalUsers: number; avgRating: number | null } }console.log(summary.totalTasks, summary.totalUsers, summary.avgRating)If the workflow filter can’t be applied, the server answers 503 rather than return records from outside the workflow. Try again, or drop the filter.
Responses name workflows too:
| Field | Where | What it holds |
|---|---|---|
workflows | Each analytics record | [{id, label}]. [] means the chat is in no workflow; null means its workflows couldn’t be read. |
workflows | Each trace session | state is loaded (with workflows: [{topicId, name}], most confident first), unavailable, or withheld when workflows are off or your key can’t see them. |
topicId | Each run | The workflow the run was started from, or null. |