PromptABCD
FeaturesLearnHow it worksUse casesFAQGuideBlogContext Blocks
Sign inGet started free
Sign inSign up
PromptABCD

A calm home for your best AI prompts. Save them once, find them in seconds, reuse them forever.

Product

  • Features
  • Chrome Extension
  • Free Courses
  • How it works
  • Use cases
  • Blog
  • Context Blocks
  • Export Anywhere
  • FAQ

Resources

  • User guide
  • Learn prompting
  • Sign in
  • Get started free

© 2026 PromptABCD. All rights reserved.

Privacy PolicyTerms and Conditions
Home/Blog/AI Harness/Building a Harness for Long-Running Agents
AI Harness

Building a Harness for Long-Running Agents

A four-hour run can't live in a thirty-second request. A long running agent harness separates the durable run from the disposable worker so it survives crashes and deploys.

September 9, 2026·8 min read
ShareShare
⚡Featured Prompt— copy and use right now
def worker_loop(queue, store):
    while True:
        run_id = queue.claim(lease_seconds=60)     # lease, don't just pop
        if run_id is None:
            time.sleep(1); continue
        state = store.load(run_id)
        try:
            state = advance_one_step(state)        # do a little work
            store.save(run_id, state)              # checkpoint immediately
            if state.done:
                queue.complete(run_id)
            else:
                queue.release(run_id)              # back to the queue
        except Exception as e:
            queue.release(run_id, delay=backoff(state))

Picture this: you're building an agent that migrates a codebase, and a single run legitimately takes four hours — hundreds of model calls, thousands of tool invocations, the occasional pause for a human to approve a risky change. Your first version ran it inside a web request, the request timed out at 30 seconds, and everything past that point evaporated. A long running agent harness is a different animal from the request-response agents most guides teach, and the differences aren't cosmetic — they change the fundamental architecture. This guide walks through building one that survives hours, restarts, and dead workers.

The core realization is that a long-running run cannot live inside the thing that started it. A web request, a Lambda invocation, a single process — all of these have lifetimes measured in seconds to minutes, and your run needs to outlive all of them. Once you internalize that, the architecture follows, and most of the "hard" parts of long-running agents turn out to be consequences of that one decision rather than separate problems to solve.

Quick-Start (Copy This Right Now)

Here's the shape that unlocks everything else: separate the run (a durable record) from the worker (an ephemeral process that advances it one step at a time).

hljs python
[object Object], ,[object Object],(,[object Object],):
    ,[object Object], ,[object Object],:
        run_id = queue.claim(lease_seconds=,[object Object],)     ,[object Object],
        ,[object Object], run_id ,[object Object], ,[object Object],:
            time.sleep(,[object Object],); ,[object Object],
        state = store.load(run_id)
        ,[object Object],:
            state = advance_one_step(state)        ,[object Object],
            store.save(run_id, state)              ,[object Object],
            ,[object Object], state.done:
                queue.complete(run_id)
            ,[object Object],:
                queue.release(run_id)              ,[object Object],
        ,[object Object], Exception ,[object Object], e:
            queue.release(run_id, delay=backoff(state))

What this does: A worker claims a run with a lease (not a permanent pop), advances it by one step, saves the new state, and releases the run back to the queue for the next step. The run is a durable record that any worker can pick up; the worker holds it only briefly. Kill the worker mid-step and the lease expires, another worker claims the run, and it continues from the last checkpoint. Nothing is tied to a single process's lifetime.

⚡ Pro tip: Make each "step" small — one model call plus its tool calls, then checkpoint. The smaller the step, the less work you lose when a worker dies, and the more evenly work spreads across your worker pool. A long-running agent harness that does an hour of work between checkpoints loses an hour on every crash; one that checkpoints every step loses seconds.

What a Long Running Agent Harness Needs

Three decisions define a long-running harness, and they interact. A long running agent harness lives or dies on how these three settings relate to each other, so it's worth reasoning about them together rather than tuning each in isolation.

Step granularity. How much work happens between checkpoints. Finer steps mean less lost work and better load-balancing but more storage writes. One model turn per step is a good default — it's a natural boundary, and it's usually where the interesting state changes anyway.

Lease duration. How long a worker holds a run before the system assumes the worker is dead and reclaims it. Too short and slow-but-healthy steps get reclaimed mid-work; too long and a genuinely dead worker leaves a run stuck for that whole duration. Set it to a comfortable multiple of your typical step time.

Concurrency and fairness. With many long runs and finite workers, you need a policy for which run advances next. Naive FIFO lets one giant run monopolize a worker for hours; interleaving steps across runs keeps every run progressing. This matters more the longer your runs get.

Step-by-Step: Surviving a Dead Worker

The scenario that breaks naive designs is a worker that dies holding a run. Here's how the lease pattern handles it end to end.

Step one: Workers claim by lease, which records an expiry, not a permanent assignment.

hljs python
[object Object], ,[object Object],(,[object Object],):
    ,[object Object], ,[object Object],.db.execute(,[object Object],, (lease_seconds, ,[object Object],.worker_id))

What this does: Atomically grabs one run whose lease has expired (or was never leased), sets a new lease, and returns it — using

SKIP LOCKED
so multiple workers never grab the same run. The key phrase is
leased_until < now()
: a run held by a dead worker becomes claimable the instant its lease lapses, with no separate cleanup process needed.

Step two: A live worker renews its lease periodically during a long step.

hljs python
[object Object], ,[object Object],(,[object Object],):
    ,[object Object],.db.execute(,[object Object],
                    ,[object Object],, (run_id, ,[object Object],.worker_id))

What this does: Extends the lease while the worker is genuinely still working, so a legitimately slow step doesn't get reclaimed out from under a healthy worker. The heartbeat is how the system tells "slow but alive" apart from "dead" — a worker that stops heartbeating loses its lease; one that keeps beating keeps its run.

⚡ Pro tip: Cap how many times a run can be reclaimed. A run that dies, gets reclaimed, and dies again at the same step isn't unlucky — it's hitting a deterministic bug, and infinite reclaiming just burns workers on a step that will never succeed. After a few reclaims at the same step, fail the run to a dead-letter state a human can inspect.

Pro-Level Variations

For runs that pause on human approval, don't hold a lease while waiting — that's a worker frozen for hours. Move the run to a

waiting
status that no worker claims, and transition it back to
pending
when the approval arrives. The run costs nothing while it waits, which is the whole point of decoupling run from worker.

For very long runs, add progress checkpoints the user can watch and a way to cancel cleanly. Cancellation on a long-running agent harness means setting a flag the worker checks between steps, not killing a process — a killed process might die mid-tool-call and leave state half-written.

For runs that must survive a full deploy, make sure a

SIGTERM
to a worker releases its current lease immediately rather than waiting for expiry. A graceful worker shutdown that releases leases means a deploy costs you nothing; an abrupt one costs you one lease-duration of stall per in-flight run.

Troubleshooting Common Issues

Runs get stuck in

running
forever. A worker died without releasing and something's wrong with lease expiry — usually the reclaim query isn't checking
leased_until
, so dead runs never become claimable. Verify a manually-killed worker's run gets picked up after the lease lapses.

The same step runs twice. A worker completed a step but died before marking it done, so another worker redid it. This is expected in an at-least-once system — the fix isn't to prevent it but to make each step idempotent, so redoing it is harmless.

Throughput collapses under load. Usually lease contention — every worker fighting for the same head-of-queue run.

SKIP LOCKED
(above) is the fix; without it, workers serialize on a lock instead of spreading across available runs.

⚠️ Common mistake: Running a long agent inside the request that triggered it, then "fixing" timeouts by raising the request timeout. A ten-minute request timeout is still a request, still tied to one process, still lost on a deploy or a crash. The fix isn't a longer leash — it's cutting the leash entirely by making the run durable and the worker disposable.

Watching Runs You Can't Watch in Real Time

A run that lasts hours creates a monitoring problem that short runs don't: nobody is sitting there watching it, so it can go wrong for an hour before anyone notices. A long running agent harness needs monitoring built around the run record, not around a live connection, because there's no live connection to hang a dashboard off.

The run record itself is the monitoring surface. Because every step updates a durable row — step count, last-updated time, tokens spent, status — you can query for trouble across your whole fleet without touching a single running process. A run whose

updated_at
hasn't moved in ten minutes but whose status is still
running
is stalled. A run whose step count is climbing past any reasonable bound is looping. A run whose cumulative spend crossed a threshold needs attention regardless of what it's doing.

hljs python
[object Object], ,[object Object],(,[object Object],):
    ,[object Object], store.query(,[object Object],, (quiet_minutes,))

What this does: Finds runs that claim to be running but haven't checkpointed recently — the signature of a stall. Because the state is durable and queryable, this is a simple database query you can run on a schedule and alert on, rather than something you'd need to instrument inside every worker. The durability you added for resumability doubles as your monitoring backbone.

⚡ Pro tip: Cap total cost per run, not just steps and time. A run can stay under your step limit and still spend a fortune if each step processes huge contexts. Track cumulative tokens on the run record and fail runs that blow a spend ceiling — for a job that might run for hours, an unbounded cost is a much scarier failure than an unbounded duration.

Your Turn

Take your longest-running agent and make one change today: move its state into durable storage and have a worker advance it one step at a time instead of running it start-to-finish in one process. Even before you add leases and heartbeats, that single split makes the run survivable. It's the change that unlocks all the others — once the run is a durable record instead of a live process, leases, resumption, and monitoring all become natural extensions rather than rewrites.

Keep your run-state schema, lease conventions, and worker loop versioned alongside your prompts in a library like PromptABCD, so the durable-run architecture you built once becomes the default every long-running agent inherits, instead of each new one rediscovering why a four-hour job can't live in a thirty-second request.

ai-harnesslong-runningdurabilityleasesworkerscheckpointing

Continue Reading

Managing Prompt Templates Across a Harness Codebase
AI Harness

Managing Prompt Templates Across a Harness Codebase

Four divergent copies of one prompt caused a two-day bug. Harness prompt templates management makes prompts versioned, tested, single-source artifacts instead of scattered strings.

September 10, 2026·8 min read
How to Open-Source Your Agent Harness
AI Harness

How to Open-Source Your Agent Harness

An agent harness isn't an ordinary library — it's security-sensitive infra tangled with your secrets. Release an open source agent harness without leaking a key or shipping unusable code.

September 10, 2026·8 min read
Error Taxonomy: Classifying Harness Failures
AI Harness

Error Taxonomy: Classifying Harness Failures

When every failure looks the same, you can't retry, route, or alert correctly. Agent harness error classification gives failures types that drive real behavior.

September 10, 2026·8 min read

Save the prompts from this post

PromptABCD is a free prompt manager. Paste, organize, and reuse your best AI prompts — no more hunting through chat history.

Start free →
← PreviousHow to Stream Harness Output to a UINext →Persisting Agent State in the Harness
Share this post:
ShareShare