# Build a deferred scorer

Sometimes you don't know how well a function performed until later. A user clicks "helpful." A support ticket gets reopened. A conversion happens hours after the initial run.

Deferred scorers handle this. They run as separate functions in the background, with access to step tooling like `step.waitForEvent()`. You define what signals matter, and the scorer collects them over time.

> **Callout:** Deferred scoring uses createScorer from inngest/experimental. The API may change before GA.

***

## Create a scorer

A scorer is a function that receives input data and returns a score. Define it with `createScorer`.

```ts
import { createScorer } from "inngest/experimental";
import { z } from "zod";

export const feedbackScorer = createScorer(
  inngest,
  {
    id: "feedback-scorer",
    schema: z.object({ ticketId: z.string() }),
  },
  async ({ event, step }) => {
    // Wait up to 7 days for the user to give feedback
    const feedback = await step.waitForEvent("wait-for-feedback", {
      event: "support/feedback.received",
      timeout: "7d",
      if: `async.data.ticketId == '${event.data.ticketId}'`,
    });

    if (!feedback) {
      // No feedback received within the window
      return { name: "user-feedback", value: 0.5 };
    }

    return {
      name: "user-feedback",
      value: feedback.data.helpful ? 1 : 0,
    };
  }
);
```

Register the scorer alongside your other functions in the serve handler.

```ts
serve({
  client: inngest,
  functions: [...myFunctions, feedbackScorer],
});
```

***

## Trigger a scorer from a function

Inside any Inngest function, use `defer()` to trigger a scorer with the data it needs.

```ts
export default inngest.createFunction(
  { id: "handle-support-ticket", triggers: { event: "support/ticket.created" } },
  async ({ event, step, defer }) => {
    const response = await step.run("generate-response", async () => {
      return await generateAIResponse(event.data.content);
    });

    // Kick off the deferred scorer in the background
    defer("score-feedback", {
      function: feedbackScorer,
      data: {
        ticketId: event.data.ticketId,
      },
    });

    return { response };
  }
);
```

You don't need to pass the parent run's ID in `data`. The SDK threads it through automatically, so the scorer's return value is attributed to the run that deferred it. If you need to write scores explicitly, the parent run ID is available in the handler on `ctx.parents[0].runId`.

## How deferred scoring works

The scorer is a completely separate function run. It is enqueued when the parent run finalizes, not when the signal arrives. The scorer starts running on its own, and if it includes a `step.waitForEvent()`, it pauses and waits for that signal independently.

If the signal never arrives within the timeout, the scorer still completes and returns a default score. The parent function is never affected by the scorer's lifecycle.

***

## Sending the signal

The scorer above waits for a `support/feedback.received` event. Your app sends this event when the user takes action. For example, when a user clicks "helpful" in your UI:

```ts
// In your API route or event handler
await inngest.send({
  name: "support/feedback.received",
  data: {
    ticketId: "tk_123",
    helpful: true,
  },
});
```

Inngest matches this event to any waiting scorer by the `ticketId` field. The scorer resumes, reads `feedback.data.helpful`, and returns the score.

***

## When to use deferred vs direct scoring

**Direct scoring** (inline `step.score()`) is for outcomes you know before the function finishes. A guardrail passed. JSON parsed correctly. A tool call returned the expected format.

**Deferred scoring** is for outcomes that depend on what happens next. User feedback. Ticket resolution. Conversion events. Anything that requires waiting for a signal from outside the run.

***

## Next steps

- [Agent Evals overview](/docs-markdown/learn/agent-evals?ref=docs-deferred-scoring) to understand how scorers, sessions, traces, and experiments fit together
- [Score a function run](/docs-markdown/features/inngest-functions/steps-workflows/scoring) for inline scoring
- [Run experiments](/docs-markdown/patterns/ai-evals/run-experiments-in-production) to compare variants using scores