Skip to content

v0.10.0 — hardening release

Choose a tag to compare

@timmcleod timmcleod released this 30 Jul 16:08

Hardening release from a full-package review: parallel × agents, parallel × crash recovery, interrupt payload integrity, and operational scale. Run php artisan migrate after upgrading — schema changes now always ship as additive migrations (this release adds hot-path indexes, drops the foreign key constraints, and repairs the v0.8 → v0.9 timeout_at gap).

Breaking (flagged per 0.x policy):

  • Unaliased evaluate() steps are now id'd by the bare class basename (was evaluate:{Basename}), so output(Target::class) addresses the loop's checkpoints like any other step's. This changes those workflows' definition hashes — in strict drift mode, in-flight runs refuse to resume after deploying the upgrade (pass as: 'evaluate:{Basename}' to keep the old id).
  • awaitHuman() schemas containing closures or non-Stringable rule objects (Rule::enum, Password::min) now throw at definition time — they silently degraded to empty constraints through JSON persistence. Stringable rules (Rule::in(...)) are cast to their string form, which also corrects those definitions' hashes.
  • Duplicate explicit as: aliases throw instead of being silently renamed.
  • Registering a different definition under an existing workflow name throws; WorkflowRegistry::forget() is the explicit escape hatch.
  • resume()/deliverEvent() payloads may no longer contain the engine-reserved steps key.
  • Step targets must be real classes at definition time; a callback step returning non-null that isn't a WorkflowState now fails instead of silently discarding the value.
  • An agent pausing on tool approvals inside a parallel() branch fails the run with an explicit error (was silently recorded as completed); inside evaluate() bodies the pause now correctly parks the run and resume() continues the loop.
  • Lifecycle events implement ShouldDispatchAfterCommit and SerializesModels; WorkflowFailed drops its Throwable at the serialization boundary (queued listeners read failure_reason).
  • The run_id foreign key constraints are removed; deleting a WorkflowRun model still cascades to its steps and interrupts (query-builder mass deletes bypass model events).

Fixed:

  • The default parallel merge deep-merges the engine-owned steps subtree per step id — two or more agent branches without a merge: closure always conflicted and could never complete.
  • Crashed parallel fan-outs are recoverable: retry() supersedes all in-flight audit rows, the duplicate-delivery guard throws instead of returning the input snapshot as a branch result (sync mode silently completed with a crashed branch's work missing), and the batch completer only merges the current fan-out generation.
  • cancel() (or a failure) stops queued branches from executing full agent turns against a dead run.
  • Strict definition drift is enforced in branch jobs, the batch completer, and in resume()/deliverEvent() before the response is consumed.
  • Queue redeliveries of steps that are still executing (retry_after shorter than the step) no longer fail the run and discard the original attempt's result.

Added:

  • awaitEvent($event, schema:) — validates delivered payloads and strips undeclared fields.
  • assertStepRanTimes() on the fake; all workflow-name assertions accept class names like start() does.
  • audit.step_output config (full | minimal) for step audit snapshot size.
  • The sweeper chunks its scans, batches in-flight checks, and debounces re-dispatch to once per staleness window.

Full details in the CHANGELOG. 145 tests, Pint + PHPStan clean.