5 min readCJ Brewer

Introducing decisions in Swirls

Declare typed decisions in .swirls files, inspect answers and stats in the dashboard, and use the SDK to integrate it into your application

swirlsdecisionsworkflowssdkobservability

Swirls now supports typed decisions in .swirls files. You can use them to classify a request, score it against a rubric, or estimate whether a statement is true. The dashboard lets you inspect the answers, and the SDK lets you read them in your application and record feedback.

The first provider is TypeSafe's Jev. You define the questions and possible answers, then pass in the data you want it to assess.

#Why we built it

Take a support workflow that sends incoming tickets to billing or technical support. Once it's running, you'll want to check the tickets it sent to the wrong team. That means finding the message it saw, the options it had, and the answer it returned.

You'll also want to know whether a different routing rule would have helped. The model might assign billing a high probability and still be wrong. To check that, you need examples where someone has reviewed the ticket and recorded which team should have received it.

With this release, each decision has a record you can come back to. You can attach the correct department after a review, or record how long the ticket took to resolve. The dashboard uses that feedback to show how your decisions are performing.

#Define the questions in your .swirls file

A decision block contains the questions you want to ask. For support triage, we can ask which department should handle a message, how severe the problem is, and whether it's urgent:

secret decision_vendor {
  vars: [TYPESAFE_API_KEY]
}

decision support_triage {
  provider: typesafe
  model: "jev-1.13.0"
  secrets: decision_vendor
  inputSchema: { type: "string" }
  questions: {
    department: {
      type: choice,
      instructions: "Which team should handle the request?",
      criteria: {
        billing: "Billing and payments",
        technical: "Product faults or technical help",
        other: "No listed team fits"
      }
    },
    severity: {
      type: score,
      instructions: "How severely is work impaired?",
      criteria: ["No impairment", "Partial impairment", "Work is blocked"]
    },
    urgent: {
      type: noul,
      instructions: "Does the request describe a time-sensitive issue?"
    }
  }
}

Jev evaluates all three questions together. The question type determines what you get back:

Question type What comes back
choice The selected department and a probability for each department.
score A score on the severity rubric, which can be fractional, and a probability for each level.
noul The probability that the request is urgent, from zero to one.

Set TYPESAFE_API_KEY in your project's decision_vendor secret block. The key stays in the project vault.

To use the decision, add a workflow that passes the incoming message to it:

workflow classify_support {
  label: "Classify a support request"

  root {
    type: code
    inputSchema: { type: "string" }
    outputSchema: { type: "string" }
    code: @ts { return context.nodes.root.input }
  }

  node assess {
    type: decision
    decision: support_triage
    state: @ts { return context.nodes.root.output }
  }

  flow {
    root -> assess
  }
}

Later nodes can read context.nodes.assess.output.answers.department.choice to get the selected department. The editor knows the question names and allowed options, so a reference to an option you haven't declared is a type error.

A switch can then route the ticket to that team or send it to a human review gate. You write the rule that chooses between those paths. You can also use an answer to select the model for a later AI step, such as choosing a different model to draft replies for severe issues. The complete triage example shows both.

#See what happened in the dashboard

In Cloud, open your project's Observe → Decisions page and choose a record. You'll see the message sent to the model, the questions it was asked, and its answers. For the department question, that includes the probabilities for billing, technical, and other, so you can see whether one option was well ahead of the others.

The record links back to the workflow run. If a routing rule used the answer, you can inspect the rule and the branch it returned. The model name, duration, token usage, and provider attempts are there when you need to debug the call.

As you review tickets, use Record evidence to save the correct answer or a business outcome, such as the time it took to resolve a ticket.

#Use decisions in your application

SDK 2.1.0 adds list, get, addFeedback, and calibration under swirls.client.decisions. You can use these to build your own review screen or connect feedback from a support system you already use.

For example, when a reviewer decides that a ticket belonged with the technical team, your application can record that answer:

import { Swirls } from '@swirls/sdk/client'

const swirls = new Swirls({ apiKey: process.env.SWIRLS_API_KEY! })

await swirls.client.decisions.addFeedback({
  projectId,
  traceId,
  feedback: {
    idempotencyKey: `ticket:${ticketId}:department-label:v1`,
    kind: 'label',
    questionId: 'department',
    value: 'technical',
    source: 'Support quality review',
    observedAt: new Date(),
  },
})

Here, ticketId comes from your support system and projectId identifies your Swirls project. The API calls the decision ID traceId; get it from the dashboard or decisions.list. It isn't the OpenTelemetry trace ID. Keep the idempotency key the same if you retry a submission.

You can also attach the resolution time after the ticket closes and see the average in the dashboard. Recording feedback doesn't retrain the model or change your routing rules automatically.

#Check whether a routing change would help

Once you've recorded the reviewed answers, the dashboard's Calibration view compares them with the model's predictions. For support triage, you can see how often it picked the correct department and how many tickets have been checked. It also reports error metrics for scoring and probability questions.

For a choice question, you can try a different probability threshold against the saved decisions. The view shows how many tickets would qualify for automatic routing, and how often the model was right on those with a recorded label. Trying a threshold here leaves the deployed workflow unchanged; you decide whether to update its routing rule.

Those numbers depend on the tickets you've checked. If your team only labels the difficult cases, the result won't tell you how the workflow performs across all incoming tickets. The view uses up to 500 matching invocations and tells you when that limit cuts off results. The calibration guide covers the metrics and sampling limits.

#Get started

Add the decision and workflow above to a .swirls file, set your TypeSafe key, and run swirls doctor before deploying. Run it with a support message, then open Observe → Decisions to inspect the answer.

To connect your application, install the SDK:

bun add @swirls/[email protected]

The Decisions guide has the full workflow with routing and human review. The SDK reference covers reading decisions and recording feedback from your application.

Use it in the business software you are designing.

Start from a real process, working build, or set of discovery notes.

Join private beta