> For the complete documentation index, see [llms.txt](/llms.txt).
> A full single-fetch corpus is available at [llms-full.txt](/llms-full.txt).
---
last_verified: 2026-10-09
title: Workflows
description: Register Go workflows, checkpoint work with Step and Task, keep state, sleep durably, and attach triggers
category: Go SDK
hideRightSidebar: true
---

A workflow is a durable program made of checkpointed steps. When a worker restarts, the workflow replays and each completed step returns its saved result instead of running again. For a guided introduction, see [Workflows](/docs/build/workflows.md).

## `agnt5.RegisterWorkflow`

```go
func RegisterWorkflow[In any, Out any](
	w *Worker,
	name string,
	handler func(*Context, In) (Out, error),
	opts ...ComponentOption,
) error
```

```go
type DoubleInput struct {
	N int `json:"n"`
}

func double(ctx *agnt5.Context, in DoubleInput) (int, error) { return in.N * 2, nil }

func doubleTwice(ctx *agnt5.Context, in DoubleInput) (map[string]int, error) {
	once, err := agnt5.Task(ctx, "first", in, double)
	if err != nil {
		return nil, err
	}
	twice, err := agnt5.Task(ctx, "second", DoubleInput{N: once}, double)
	if err != nil {
		return nil, err
	}
	return map[string]int{"once": once, "twice": twice}, nil
}

err := agnt5.RegisterWorkflow(worker, "double_twice", doubleTwice)
```

Workflows take the same `ComponentOption`s as functions, plus:

| Option | Description |
|---|---|
| `agnt5.WithCron(expression)` | Start the workflow on a schedule. |
| `agnt5.WithTriggers(triggers...)` | Start it from events: `agnt5.EventTrigger(name)` or `agnt5.WebhookTrigger(source, event)`. See [Webhooks](/docs/build/webhooks.md). |

```go
err := agnt5.RegisterWorkflow(worker, "on_order", onOrder,
	agnt5.WithTriggers(agnt5.EventTrigger("order.created")),
)
```

## Checkpointed work

| Function | Checkpoints |
|---|---|
| `agnt5.Step(ctx, name, func(context.Context) (T, error))` | A closure's result. |
| `agnt5.StepWithKey(ctx, name, key, func(*agnt5.Context) (T, error))` | A closure's result, matched on replay by `key`. |
| `agnt5.Task(ctx, name, input, handler)` | A registered-style handler `func(*Context, In) (Out, error)` called with `input`. It also shows as its own function in the trace. |
| `agnt5.TaskWithKey(ctx, name, key, input, handler)` | The same, matched on replay by `key`. |

On replay, a completed step returns its saved result without running again. Use the `WithKey` forms when the same step name runs in a loop or in a different order, so each one replays the right result:

```go
results := []int{}
for _, n := range []int{1, 2, 3} {
	v, err := agnt5.TaskWithKey(ctx, "double", fmt.Sprint(n), DoubleInput{N: n}, double)
	if err != nil {
		return nil, err
	}
	results = append(results, v)
}
```

Inside a `Step` closure, `agnt5.ActivationFromContext(stepCtx)` returns the step's activation, including an `IdempotencyKey` to send to downstream systems. See [Workflows](/docs/build/workflows.md).

## Durable sleep

```go
if err := ctx.Sleep(24*time.Hour, agnt5.WithSleepKey("wait_24h")); err != nil {
	return nil, err
}
```

## Workflow state

`ctx.State()` returns run-scoped state that survives replay. Each method takes a `context.Context`; pass `ctx`.

| Method | Description |
|---|---|
| `Set(ctx, key, value)` | Store a JSON-serializable value. |
| `Get(ctx, key)` | Read a value as `any`. |
| `GetString(ctx, key)` | Read a string value. |
| `List(ctx)` | Every key and value, as `map[string]any`. |
| `Delete(ctx, key)` | Remove a key. |

```go
state := ctx.State()
if err := state.Set(ctx, "count", 1); err != nil {
	return nil, err
}
count, err := state.Get(ctx, "count")
```

## Next steps

* [Context API](/sdk/go/context.md): asking people, logging, and model calls.
* [Functions](/sdk/go/functions.md): the handlers that tasks run.
* [Workflows guide](/docs/build/workflows.md): durability, replay, and idempotent side effects in practice.
