# Conversation workflows

These are browser SDK conversation flows. They are not durable background jobs
or server automation delays. Developer SDK access is beta; check [availability](https://docs.masheev.com/capabilities.md).

Guide AI conversations through structured, multi-step flows with full analytics.

## Quick Start

### Level 1: Workflow ID Only (3 lines)

```js
Masheev.init({
  inboxId: "your-inbox-id",
  workflow: { id: "onboarding" },
});
```

This is enough to start tracking workflow analytics. The AI agent runs freely; you get started/completed/abandoned events in the dashboard.

### Level 2: Add Steps

```js
Masheev.init({
  inboxId: "your-inbox-id",
  workflow: {
    id: "onboarding",
    name: "New User Onboarding",
    steps: [
      { id: "welcome", name: "Welcome", instructions: "Greet the user by name." },
      {
        id: "collect_info",
        name: "Collect Info",
        instructions: "Ask for their business name and use case.",
      },
      {
        id: "setup",
        name: "Setup",
        instructions: "Create their workspace using the available tools.",
      },
    ],
  },
});
```

Steps give you a funnel visualization in the analytics dashboard, per-step instructions for the AI, and turn-count tracking.

### Level 3: Full Configuration

```tsx
import { defineWorkflow, useMasheev, useWorkflow } from "@masheev/embed-sdk/react";

const ONBOARDING = defineWorkflow({
  id: "onboarding",
  name: "New User Onboarding",
  variant: "v2-short",
  steps: [
    {
      id: "welcome",
      name: "Welcome",
      instructions: "Greet {{context.userName}}. Ask about their business.",
    },
    {
      id: "collect_info",
      name: "Collect Business Info",
      instructions: "Collect business name and use case (support, sales, or both).",
    },
    {
      id: "create_org",
      name: "Create Organization",
      instructions: "Use create_organization tool with collected info.",
      tools: ["create_organization"],
      requiredTools: ["create_organization"],
    },
  ],
});

function OnboardingChat() {
  const masheev = useMasheev({
    inboxId: "your-inbox-id",
    workflow: {
      ...ONBOARDING,
      context: { userName: user.name },
    },
  });

  useWorkflow(masheev, {
    onStepComplete: ({ stepId, data }) => {
      analytics.track("onboarding_step", { stepId });
    },
    onComplete: ({ outcome }) => {
      if (outcome === "success") navigate("/dashboard");
    },
  });

  return <div ref={masheev.containerRef} />;
}
```

## API Reference

### `WorkflowConfig`

| Field          | Type                                          | Required | Description                                                                                             |
| -------------- | --------------------------------------------- | -------- | ------------------------------------------------------------------------------------------------------- |
| `id`           | `string`                                      | Yes      | Unique ID for analytics grouping. Alphanumeric + underscore/hyphen, max 64 chars.                       |
| `name`         | `string`                                      | No       | Human-readable name (max 100 chars).                                                                    |
| `steps`        | `WorkflowStepConfig[]`                        | No       | Ordered step definitions (max 20).                                                                      |
| `resumeAtStep` | `string`                                      | No       | Step ID to resume at (for returning users). Defaults to first step.                                     |
| `context`      | `Record<string, string \| number \| boolean>` | No       | Key-value pairs available via `{{context.KEY}}` in step instructions. Max 20 keys, 500 chars per value. |
| `variant`      | `string`                                      | No       | A/B testing tag (e.g., `"control"`, `"variant-a"`).                                                     |

### `WorkflowStepConfig`

| Field           | Type       | Required | Description                                                                                            |
| --------------- | ---------- | -------- | ------------------------------------------------------------------------------------------------------ |
| `id`            | `string`   | Yes      | Unique step ID. Alphanumeric + underscore/hyphen, max 64 chars.                                        |
| `name`          | `string`   | Yes      | Human-readable step name (max 100 chars).                                                              |
| `instructions`  | `string`   | No       | Step-specific AI instructions (max 1,000 chars). Supports `{{context.KEY}}`.                           |
| `allowSkip`     | `boolean`  | No       | Whether the AI can skip this step (default: `false`).                                                  |
| `tools`         | `string[]` | No       | Restrict visible tools to this list during the step. Workflow tools are always available.              |
| `requiredTools` | `string[]` | No       | Tools the AI **must** call before completing this step. Injected as a hard constraint into the prompt. |
| `suggestions`   | `string[]` | No       | Quick reply buttons auto-shown when this step activates. Max 10, 200 chars each.                       |

### `defineWorkflow(config)`

Type-inference helper for defining workflows as module-level constants. Returns the same config object (identity function for TypeScript inference).

```ts
import { defineWorkflow } from "@masheev/embed-sdk/react";

const MY_WORKFLOW = defineWorkflow({
  id: "my_workflow",
  steps: [{ id: "step1", name: "Step 1" }],
});
```

### `useWorkflow(masheev, handlers)`

React hook that wires workflow event listeners with automatic cleanup.

```ts
useWorkflow(masheev, {
  onStepComplete: (payload) => {
    /* { workflowId, stepId, data?, nextStepId?, suggestions? } */
  },
  onComplete: (payload) => {
    /* { workflowId, outcome, data? } */
  },
});
```

### `updateWorkflow(updates)`

Update workflow context or name mid-session. Available on the `useMasheev` return object.

```ts
const { updateWorkflow } = useMasheev({ ... });

// Add/update context values
updateWorkflow({ context: { cartTotal: 99.99 } });
```

### `workflowState` (reactive)

Reactive workflow state returned by `useMasheev`. Populated from server state on init (survives page refresh), updates automatically on step/workflow completion.

```ts
const { workflowState } = useMasheev({ ... });
```

| Field              | Type                                             | Description                               |
| ------------------ | ------------------------------------------------ | ----------------------------------------- |
| `status`           | `"idle" \| "running" \| "completed" \| "failed"` | Current workflow status                   |
| `workflowId`       | `string`                                         | The workflow ID                           |
| `currentStepId`    | `string \| null`                                 | Active step ID (null when completed)      |
| `completedStepIds` | `string[]`                                       | IDs of completed steps                    |
| `context`          | `Record<string, unknown>`                        | Accumulated context from step completions |

Returns `null` when no workflow is configured.

```tsx
// Progress bar
const progress = workflowState ? workflowState.completedStepIds.length / steps.length : 0;

// Step tracker
steps.map((step) => ({
  ...step,
  status: workflowState?.completedStepIds.includes(step.id)
    ? "done"
    : workflowState?.currentStepId === step.id
      ? "active"
      : "pending",
}));

// Redirect on completion
if (workflowState?.status === "completed") {
  navigate("/dashboard");
}
```

**Vanilla JS** — use `getWorkflowState()` for current state and `on("workflowState", callback)` for updates:

```js
import { on, getWorkflowState } from "@masheev/embed-sdk/js";

on("workflowState", (state) => {
  document.getElementById("progress").style.width =
    `${(state.completedStepIds.length / totalSteps) * 100}%`;
});
```

## Session Mode

When using workflows, set `sessionMode: "workflow"` on the SDK config. This ensures each workflow gets its own isolated session (keyed by `inboxId` + `workflowId`) and enables workflow-specific behavior like AI-first turns and automatic session reset on workflow completion.

```ts
const masheev = useMasheev({
  inboxId: "your-inbox-id",
  sessionMode: "workflow",
  workflow: { ... },
});
```

Without `sessionMode: "workflow"`, the widget uses a shared visitor session across all conversations on the same inbox.

## Lifecycle

Workflows automatically handle session persistence, resumption, and restart.

### New visitor

1. Widget fetches config — no existing conversation found
2. Session is created on first interaction
3. AI triggers its first turn using step 1 instructions (no generic greeting)
4. Workflow progresses through steps as the AI calls `workflow_step_complete`

### Returning visitor (in-progress workflow)

1. Widget fetches config — existing conversation found
2. Session is resumed, chat history is loaded
3. AI continues from the last completed step (`resumeAtStep` is set automatically)

### Returning visitor (completed workflow)

1. Widget fetches config — server reports `workflowCompleted: true`
2. Client clears the stored visitor ID from localStorage
3. Widget re-fetches config as a new visitor — fresh workflow starts automatically
4. The previous conversation and workflow run are preserved in the database for analytics

This means completed workflows always restart cleanly. Users never see a stale "already done" state.

### Returning visitor (failed workflow run)

If the workflow run has a `"completed"` status on the resumed session, the client calls `resetConversation()` which clears state and starts fresh (same as the completed flow above).

### Manual restart

The host app can trigger a restart by calling `resetConversation()` from the widget context. This clears the visitor ID, resets all client state, and re-fetches config to start a new workflow session.

```ts
// From useWorkflow onComplete handler
useWorkflow(masheev, {
  onComplete: ({ outcome }) => {
    if (outcome === "success") {
      // Widget auto-resets on next mount; navigate away
      navigate("/dashboard");
    }
  },
});
```

## Context Interpolation

Step instructions support `{{context.KEY}}` placeholders that resolve server-side using the workflow's `context` object.

```js
workflow: {
  id: "support",
  context: { plan: "pro", region: "EU" },
  steps: [{
    id: "greet",
    name: "Greeting",
    instructions: "The user is on the {{context.plan}} plan in {{context.region}}.",
  }],
}
```

Context values are XML-escaped on the server to prevent prompt injection.

### Cross-Step Data Propagation

When a step completes with `data` (via the `workflow_step_complete` tool), that data is automatically merged into the workflow context. Subsequent steps can access it via `{{context.KEY}}`:

```js
// Step "create_business" completes with data: { name: "Talia's", country: "US" }
// Next step can use:
{
  id: "setup_menu",
  instructions: "Set up the menu for {{context.name}} in {{context.country}}.",
}
// Also available namespaced: {{context.create_business_name}}
```

No client-side `updateWorkflow()` call needed — tool result data flows into context automatically.

## Per-Step Tool Filtering

When a step defines `tools`, only those tools (plus the built-in workflow management tools) are visible to the AI during that step.

```js
steps: [
  {
    id: "browse",
    name: "Browse Products",
    instructions: "Help the user find products.",
    tools: ["search_products", "get_product_details"],
  },
  {
    id: "checkout",
    name: "Checkout",
    instructions: "Process the order.",
    tools: ["create_order", "apply_coupon"],
  },
];
```

## Events

Workflows emit lifecycle events that you can listen to client-side:

| Event                   | Payload                                                    | When                                   |
| ----------------------- | ---------------------------------------------------------- | -------------------------------------- |
| `workflow:stepComplete` | `{ workflowId, stepId, data?, nextStepId?, suggestions? }` | AI calls `workflow_step_complete` tool |
| `workflow:complete`     | `{ workflowId, outcome, data? }`                           | AI calls `workflow_complete` tool      |

The `suggestions` field in the step complete event contains the next step's configured suggestions (if any). The widget auto-renders them — no client-side `setQuestions()` wiring needed.

### Third-Party Relay

Forward events to your analytics provider:

```ts
// Segment
useWorkflow(masheev, {
  onStepComplete: ({ stepId, data }) =>
    analytics.track("Workflow Step Completed", { stepId, ...data }),
  onComplete: ({ outcome }) => analytics.track("Workflow Completed", { outcome }),
});

// GA4
useWorkflow(masheev, {
  onStepComplete: ({ stepId }) => gtag("event", "workflow_step", { step_id: stepId }),
  onComplete: ({ outcome }) => gtag("event", "workflow_complete", { outcome }),
});
```

## Analytics Dashboard

Workflow analytics are available at **Analytics > Workflows** in the Masheev dashboard.

- **Overview**: Total started, completed, abandoned counts per workflow with completion rates
- **Funnel**: Per-step completion counts and average turn counts (color-coded: green >85%, amber 50-85%, red <50%)
- **Sentiment**: Average CSAT and sentiment scores for completed workflows
- **Conversation drilldown**: Step-by-step timeline for individual conversations

## Security

### Prompt Injection Defense

- Step instructions are placed within XML structural boundaries (`<workflow-context>`, `<current-step-instructions>`)
- Context values are XML-escaped via `escapeXml()` before interpolation
- Workflow data is PII-scrubbed before storage (keys containing email, phone, ssn, password, etc. are stripped)
- Instructions follow the prompt hierarchy: guardrails > identity > workflow instructions > knowledge

### Rate Limits

| Operation                     | Limit                                                                                           |
| ----------------------------- | ----------------------------------------------------------------------------------------------- |
| `workflow_step_complete` tool | 4 calls per AI turn (idempotent — duplicate step completions are ignored via unique constraint) |
| `workflow_complete` tool      | 1 per conversation (idempotent)                                                                 |
| Trigger (`{ trigger: true }`) | 3 per minute per session                                                                        |
| Step order validation         | Steps must follow the defined order (no skipping unless `allowSkip: true`)                      |

### Cardinality Guards

- Max 500 distinct workflow IDs per organization
- Max 50 variants per workflow
- Events exceeding these limits are silently dropped

## Limitations

- Max 20 steps per workflow
- Max 20 context keys, 500 chars per value
- Total step instructions capped at 5,000 chars across all steps
- Workflow and step IDs must match `/^[a-zA-Z][a-zA-Z0-9_-]{0,63}$/`
- Invisible Unicode characters in IDs and instructions are stripped server-side
