Authentication is a long-running Workflow

AUTHORS
Mason Williams
PUBLISHED
Sep 15, 2026
CATEGORY
DURATION
7 MIN
  • Code Samples
  • Durable Execution
  • Security
This is a guest post from Mason Williams, Co-Founding Engineer at KERNEL.
tl;dr: In the last 30 days, KERNEL used Temporal to orchestrate nearly 80,000 successful authentication sessions, including more than 5,000 that paused for human input or external approval before resuming to completion. We model each Managed Auth connection as one long-running Workflow, while short-lived child Workflows own individual login and reauthentication attempts.

In this article#

  • A login is not a request
  • Model the connection, not the attempt
  • The human is part of the Workflow
  • Staying logged in is part of the product
  • Go owns durability; TypeScript runs the agent
  • What we learned

1. A login is not a request#

An agent can enter a password, stop at an MFA prompt, wait for a person to approve a push notification, and then resume in the same browser. A deployment or Worker restart can happen anywhere in between. Days later, the authenticated session may expire and require the process to begin again.

That makes authentication a poor fit for an HTTP request or a conventional background job. At KERNEL, our Managed Auth system has to remember what the agent has already done, what it’s waiting for, which browser owns the attempt, and what should happen after that browser disappears. It also has to distinguish a transient website failure from a true need for human input, without exposing credentials to an LLM or leaving abandoned browser sessions running.

We use Temporal across our infrastructure. This post focuses on one bounded system: Managed Auth.

Core decision: We model the Managed Auth connection, not the individual login request, as the durable Workflow.

2. Model the connection, not the attempt#

Each connection has one long-lived parent Workflow. It owns configuration, connection state, health-check scheduling, automatic reauthentication, and cancellation. Each login or reauthentication attempt runs as a child Workflow with its own browser, timeout, and cleanup path. All durable Workflow state remains in Go. When a child needs model-driven work, it sends a plain JSON payload through an Activity to a dedicated TypeScript Worker rather than running the agent inside Workflow code.

This gives the two lifecycles different boundaries. The connection may exist for months; a browser may exist for minutes. A failed or canceled attempt can clean up its browser without destroying the connection’s durable state. Starting another login can supersede an abandoned attempt, while the parent remains the stable address for API updates and operational inspection.

authentication-long-running-workflow-architecture

The API uses Temporal’s UpdateWithStart operation to atomically start the parent or update the existing one. That removes the race between “does this Workflow exist?” and “send this login request to it.”

3. The human is part of the Workflow#

Human intervention is an ordinary Workflow state, not a failure path. When the agent reaches a step it should not or cannot complete on its own, such as entering a password or one-time code, selecting an account, or choosing an SSO option, the child Workflow enters AWAITING_INPUT. It records what is needed and durably races a user submission against a timeout.

// Simplified from the production Go Workflow.
inputReceived := workflow.NewChannel(ctx)
workflow.Go(ctx, func(ctx workflow.Context) {
    _ = workflow.Await(ctx, func() bool { return state.HasPendingInput })
    inputReceived.Send(ctx, true)
})
selector := workflow.NewSelector(ctx)
selector.AddFuture(workflow.NewTimer(timerCtx, awaitDeadline), func(workflow.Future) {
    timedOut = true
})
selector.AddReceive(inputReceived, func(c workflow.ReceiveChannel, _ bool) {
    var received bool
    c.Receive(ctx, &received)
})
selector.Select(ctx)

No HTTP request or Worker thread has to stay open while the person finds a code. If a Worker restarts, Temporal reconstructs the wait from Event History. The submitted values arrive through a validated Workflow Update, so late or stale input can be rejected instead of being silently placed on a queue.

We handle external actions separately. Push approval or confirmation on another device may not produce a value to submit to KERNEL. The Workflow instead sleeps durably, rechecks the browser, and remains bounded by the attempt’s absolute deadline. Waiting does not consume the agent’s step budget or look like an agent stuck in a loop.

CAPTCHAs use a similar pause-and-resume pattern without involving the model during the solve. When the flow detects a blocking CAPTCHA, it stops model execution and listens to the live KERNEL Browser Telemetry stream for the captcha_solve_result event. Our automated solver works independently; once telemetry reports the terminal outcome, the Workflow resumes the agent. The model does not spend steps or tokens watching or polling the page while the CAPTCHA is being solved.

Managed Auth, last 30 days: Temporal orchestrated nearly 80,000 successful authentication sessions, including more than 5,000 that paused for human input or external approval before resuming to completion.

4. Staying logged in is part of the product#

A successful login is not the end of the Workflow. The parent schedules recurring authentication checks and classifies the result as authenticated, unauthenticated, or inconclusive. The third state matters: a website timeout should not incorrectly mark a connection as logged out.

When authentication has expired, the Workflow can start reauthentication using what it learned from earlier attempts. If the required inputs are available, such as stored credentials or TOTP, it proceeds automatically. If the site now requires human participation, reauthentication stops cleanly and marks the connection as needing attention instead of waiting for a person who is not present.

Repeated failures feed a durable circuit breaker. After several consecutive reauthentication failures, the connection enters a cooldown rather than continually retrying against a broken site or risking an account lockout. A later successful health check resets the failure state.

This is the larger product shift Temporal enabled for us: we are not orchestrating a one-time login job. We are orchestrating the continuing obligation to keep a browser profile authenticated.

5. Go owns durability; TypeScript runs the agent#

All Workflow logic remains in Go. It owns the connection lifecycle, timers, retries, health checks, cancellation, and human waits. When the login needs model-driven work, the Go Workflow schedules an Activity on a dedicated TypeScript Task Queue.

A long-running TypeScript Worker polls that queue and imports our managed-auth agent as a versioned package. The agent uses browser tools backed by our Playwright Execute API, which runs its Playwright actions inside the browser VM. The Worker reattaches to the browser by session ID, invokes the requested agent phase, and returns the result to Go.

If the agent needs a person, the Activity returns a structured pause instead of keeping the TypeScript Worker occupied. Go records the pause and waits durably. After the person responds, Go schedules another Activity, which reattaches to the same browser and continues from the page state left behind. Credentials reach the agent Activity through a short-lived lease rather than being written to Event History.

| | | ---------- | | Division of responsibility: Go is the durable source of truth. TypeScript runs the agent only when the Workflow asks it to. | | |

6. What we learned#

  • Choose the durable business lifecycle as the Workflow boundary. For Managed Auth, that is the connection, not a login request or browser session.
  • Model human intervention explicitly. Input, external approval, timeout, and cancellation are separate states with different behavior.
  • Keep nondeterminism at the Activity boundary. Browser and model operations need intentional retry and idempotency policies.
  • Design for the second login. Health checks, learned requirements, automatic reauthentication, and circuit breaking turn authentication into a maintained capability.

Without Temporal, we would have needed to assemble durable timers, queue correlation, database-backed state reconstruction, retry policies, cancellation ownership, deployment recovery, versioning, history compaction, and an execution audit trail. Temporal gave us one programming model for all of them, which let us spend our time on the authentication problem itself.

If you are building long-running browser agents, start by modeling the connection rather than the login request.

Temporal Cloud

Ready to see for yourself?

Sign up for Temporal Cloud today and get $150 in free credits.

Build invincible applications

It sounds like magic, we promise it's not.