Introducing Durable Workflows

Inferable Workflows is a way to mix and match agent results and programmatic control flow.

Nadeesha Cabral on 07-02-2025
Workflows 1

When we first started using AI agents to define control flow, we were excited by the potential. We could easily attach a few tools contextually and let the agent handle the rest.

However, we quickly discovered that as powerful as these agents are, they sometimes struggle to follow instructions. Specifically, they can fail in several predictable ways:

  1. There are instances where the developer requires 100% accuracy in behavior (e.g., a refund request).
  2. When the context is large, the agent may lose important details.
  3. Repeatedly prompting the agent without deterministic code can be cumbersome to maintain.

Common Issues with AI-Driven Workflows

For example, consider the following simple prompt:

- Given this customer request, see if it's a refund request.
- If it's a refund request, see if the customer is already registered in our system.
- Get the order customer is referring to.
- For each item in the order, refund the customer.

Despite its simplicity, a Re-Act agent could easily fail, even with significant "prompt engineering":

  1. The agent may find the customer request but not continue to the next step.
  2. The agent may correctly find the order but fail to issue refunds.
  3. The agent might only refund some of the items.

A deterministic control flow with LLM calls can avoid these pitfalls by explicitly managing iteration and conditional logic. However, not giving the agent the ability to "cycle through the problem space" means that you take the autonomy away from the agent to reason and act on its own.

Our Insight

We think the perfect abstraction is one where you can mix and match results of probabilistic reasoning with deterministic code, while providing the best developer experience with type safety.

Enter Durable Workflows

Therefore, we built Durable Workflows.

Durable Workflows lets you create versioned "workflow as code" artifacts that chain multiple agents. This approach allows you to combine AI-driven results with deterministic logic, feeding the output of one agent as input to the next.

Durable Workflows is currently in beta, and supports the Typescript SDK. We'll be rolling out support for our other SDKs over the next few weeks.

const refunds = inferable.workflows.create({
  name: "refunds",
  inputSchema: z.object({
    executionId: z.string(),
    customerRequest: z.string()
  }),
})

refunds.version(1).define(async (ctx, input) => {
  const triageAgent = ctx.agent({
    name: "triageAgent",
    systemPrompt: helpers.structuredPrompt({
      facts: [
        "You are given a customer service request",
        "The requester may be a customer in the database"
      ],
      goals: [
        "Determine the type of request",
        "Output the customer ID if the requester is a customer in the database"
      ]
    }),
    resultSchema: z.object({
      requestType: z.enum(["refund", "other"]),
      customerId: z.string().optional()
    })
  });

  const result = await triageAgent.run({
    data: {
      customerRequest: input.customerRequest // From: [email protected]; I need a refund for my order
    }
  });

  if (result.requestType === "refund") {
    const refundAgent = ctx.agent({
      name: "refundAgent",
      systemPrompt: helpers.structuredPrompt({
        facts: [
          "This customer is asking for a refund",
          "Some items may not be refundable. Refer to the refund policy"
        ],
        goals: [
          "Determine the order the customer is referring to",
          "Return the items that the customer is asking to be refunded",
          "Write a message to the customer with the status of their refund request"
        ]
      }),
      resultSchema: z.object({
        refundableItems: z.array(z.object({
          itemId: z.string(),
          refundAmount: z.number()
        })),
        messageToCustomer: z.string()
      })
    });

    ctx.effect("refund", async () => {
      for (const item of refundResult.refundableItems) {
        await refundService.refund(item.itemId, item.refundAmount);
      }

      sendEmail(input.customerEmail, refundResult.messageToCustomer);
    })
  } else {
    // nothing to do
  }
}

workflow.listen();

In this example, the agent does what it does best—reasoning and producing fuzzy results—while the workflow orchestrates these results and triggers any necessary side-effects.

Durability

Workflows are designed to be durable. If your workflow is interrupted, it will resume from where it left off. If the machine stalls, the control plane will fail over automatically.

They will never run the same agent twice for the same input and execution (making agent calls idempotent). It only progresses the control flow if the agent returns a "success" status.

As a caveat, workflows themselves cannot have side-effects. Instead, you can use ctx.effect for actions with at-least-once semantics.

Workflow as Code

All logic is expressed in standard imperative code. Supported constructs include if, for, while, try, catch, throw, and more. Each agent invocation returns a promise, so you can take advantage of parallel processing with Promise.all() or Promise.allSettled().

Versioning

Workflows are versioned. This approach lets you deploy new workflow versions without disrupting in-progress executions.

On-premise Execution

Workflows can run in your own environment. As long as you call .listen(), the workflow can receive requests from the control plane. Interim data remains in your infrastructure and is not exposed externally.

Check out the docs for further details and complete examples.

Subscribe to our newsletter for high signal updates from the cross section of AI agents, LLMs, and distributed systems.

Maximum one email per week.

Subscribe to Newsletter