A run that takes twenty minutes will meet a queue worker timeout. Rather than being killed part-way through a step, a run can hand the turn back at a safe boundary and be re-queued to continue:
Clutch::agent(ResearchAgent::class)
->for($user)
->sliceAfterSeconds(240) // below your worker's timeout
->create();
Each slice checkpoints, records a run.suspended event, and queues the
continuation. Usage accumulates across slices, so a run cut into ten pieces
still meets its budget as one run.
Workflows slice too, and for them the boundary is a step. The runtime hands
the turn back at the first step (or wave) past the budget, the run suspends
with everything finished already persisted, and the continuation re-enters
handle() with those steps cached — so a workflow whose wall-clock exceeds
any single worker’s timeout completes as a chain of sub-timeout jobs instead
of being killed mid-flight:
ProduceReport::start()
->for($user)
->sliceAfterSeconds(1500) // below a 1800s worker timeout
->dispatch(['report_id' => $id]);
Or set clutch.limits.max_seconds_per_slice once and every bounded driver
inherits it. Two rules keep slicing safe: replayed steps never count toward a
slice, so a resumed workflow cannot suspend without progressing; and a slice
always completes at least one step, so a budget tighter than a single step
still moves forward one step per job.
Slicing needs a driver that can park a turn and pick it up again. The bundled
laravel-ai driver cannot: Laravel AI runs a turn to completion, and abandoning
it part-way discards the work rather than parking it. Asking that driver to slice
fails with CapabilityUnsupported before the run starts, which is the same
promise the package makes everywhere else.