NewWe open-sourced 50+ Laravel packages
Custom AI apps, agents and automation — Roundly ConsultingRoundly
All packages

Eight tables, published by workflows:install (tag workflows-migrations, timestamped on publish; never auto-loaded). Names come from workflows.tables.*. Migrations are forward-only. Key types are fixed at migration time — set them before php artisan migrate:

  • id and every package-internal key column (*_id pointing at a package table) follow primary_key_type (bigint auto-increment, or a time-ordered uuid / ulid primary key).
  • A morph pair <name>_type + <name>_id (nullable) holds a host model. Its id column follows owner_key_type (owner_*), subject_key_type (subject_*) or key_type (publisher_*, starter_*, requester_*).
  • Every timestamp is stored in UTC (created_at, updated_at, and deleted_at on soft-deleting tables).

workflow_definitions

ColumnTypeNotes
idkey—
keystring(100)Unique per owner (wdef_key_unique on owner_scope, key).
namestring(200)—
descriptiontext, nullable—
owner_type, owner_idmorph, nullableNull = app-wide.
owner_scopestring(191)The owner as one string (<morph class>:<key>, * for app-wide), for the unique key.
subject_keystring(100), nullableThe subject type’s registry key (never a class name).
statusstring(16)DefinitionStatus.
current_version_idkey, nullable, indexedThe published version new instances run.
webhook_idstring(26), nullable, uniqueThe inbound hook id (minted at first publish, rotatable).
timestamps, deleted_atSoft deletes; a trashed definition still holds its key.

workflow_versions

ColumnTypeNotes
definition_idkeyFK → definitions, restrict on delete.
numberunsigned intUnique per definition (wver_number_unique).
statusstring(16)VersionStatus.
schema_versionstring(32)roundly.workflows.graph/v1
graph / trigger / settingsjsonThe whole document, the trigger node’s config, the settings.
trigger_type / trigger_keystring(16) / string(191), nullableIndexed with status (wver_trigger_idx) for emit and webhook lookups.
actor_modestring(16)ActorMode.
checksumchar(64)The canonical graph digest (expectedChecksum, sync, export).
review_flagsjson, nullableOpen template review flags.
published_atdatetime, nullableOnce set, graph, trigger, actor, settings, checksum and publisher are frozen.
publisher_type, publisher_idmorph, nullableWho published.
schedule_cursor_atdatetime, nullableThe last schedule slot handled.

workflow_instances

ColumnTypeNotes
ulidchar(26), uniqueThe public id (resources, commands, idempotency keys).
definition_id / version_idkeyFKs → definitions / versions; the version never changes.
owner_type, owner_idmorph, nullableThe instance owner.
subject_key, subject_type, subject_idstring(100) / morph, nullableRegistry key and the subject.
trigger_type / trigger_key / trigger_payloadstring / json, nullableHow it started.
idempotency_digestchar(64), nullable, indexed—
statusstring(16)InstanceStatus.
current_node_idstring(64), nullable—
steps_countunsigned int, default 0Counted against limits.max_steps.
depthunsigned tinyint, default 0start_workflow nesting.
parent_instance_id, parent_step_run_id, root_instance_idkey, nullableThe parent (FK, null on delete) and the root.
actor_mode, starter_type, starter_idstring(16) / morph, nullableThe actor and the triggering user.
failure_code / failure_message / failed_node_idstring(64) / text / string(64), nullableA FailureCode value or a host code; the message is stored for operators.
started_at, ended_atdatetime, nullable—
lock_versionunsigned int, default 0Bumped on every state change.

workflow_step_runs

ColumnTypeNotes
instance_idkeyFK → instances, cascade on delete.
node_id / node_typestring(64)—
visit / attemptunsigned int / unsigned smallintUnique with instance_id, node_id (wstep_attempt_unique).
statusstring(16)StepRunStatus.
idempotency_keystring(100)wf:{instance ulid}:{node id}:{visit}
input / outputjson, nullableThe resolved config the handler saw (capped by steps_input.max_bytes) and its output.
resume_signaljson, nullableThe signal the attempt was last resumed with; a retry of a failed resume carries it.
outcomestring(32), nullable—
error_code / error_message / error_retryablestring(64) / text / bool, nullable—
claim_token / lease_untilstring(26) / datetime, nullableThe compare-and-set claim and its lease (wstep_lease_idx).
scheduled_for, dispatched_at, started_at, finished_at, probe_pushed_atdatetime, nullableTimings; a pushed probe not claimed yet is re-pushed by wake.

workflow_waits

ColumnTypeNotes
instance_id / step_run_idkeyFKs, cascade on delete.
kindstring(16)WaitKind.
due_atdatetime, nullablewwait_due_idx (status, due_at).
event_keystring(191), nullablewwait_event_idx (status, event_key).
match / condition / payloadjson, nullableEvent matching, condition, delivered payload.
outcomestring(32), nullableA timeout’s outcome.
status, claim_token, claimed_at, fired_atWaitStatus and the claim.

workflow_escalations

ColumnTypeNotes
instance_id / step_run_idkeyFKs, cascade on delete.
levelunsigned smallint1 = the first.
due_atdatetimewesc_due_idx (status, due_at).
actionsjson—
status, attemptsstring(16) / unsigned smallintEscalationStatus and attempts made.
claim_token, claimed_at, fired_at, cancelled_at, error_code, error_message—

workflow_idempotency_keys

ColumnTypeNotes
scopestring(16)start or effect.
keychar(64)A digest, never the raw key; unique with scope (widem_unique).
definition_id / instance_idkey, nullableFKs (null / cascade on delete).
resultjson, nullableA once() result.
expires_atdatetime, nullable, indexedTrigger keys only (idempotency.ttl).
created_atdatetimeNo soft deletes.

workflow_dry_runs

ColumnTypeNotes
definition_id / version_idkey / key, nullableFKs (cascade / null on delete).
owner_*, subject_*, requester_*morphs, nullable—
input / resultjsonThe input and the DryRunResult.
timestamps, deleted_atPruned after prune.dry_runs_after.

workflow_instances also has winst_overlay_idx (version_id, status, current_node_id) for the run overlay, winst_rate_idx (definition_id, created_at) for the start rate limit and winst_status_idx (status, updated_at) for the doctor’s stuck check.

Retention

workflows:prune (schedule daily) force-deletes ended instances older than prune.instances_after — never one with a running child — with their step runs, waits, escalations and effect keys; expired trigger keys; and dry-run logs older than prune.dry_runs_after.

Show your open-source love

This package is free and MIT-licensed. If it saves you time, a one-off donation or a Patreon membership keeps it maintained, tested and documented.

More ways to support, including crypto

By donating, you agree to our donation terms.

Want this built into your product?

We integrate our packages into custom Laravel and AI builds. Tell us what you're working on and we'll reply within 48 hours.