One custom table, per-site (not centralised — the atomic claim UPDATE joins against the posts table, and post IDs are only unique within a single site’s own posts table on Multisite): {$wpdb->prefix}lh_tasks_status. Everything else lives in WordPress’s own posts, postmeta, terms/term_relationships/term_taxonomy tables (or their per-site equivalents on Multisite).

Post type

lh_tasks-task_post

Post status (native WordPress)

draft / pending / lh_tasks-active / private / future / trash. lh_tasks-active (added 1.6.0, replaces publish for this CPT) is required for a task to be claimable via claim_next_task(), and is registered public => false — front-end read access is gated per-task via map_meta_cap, not blanket-public. create_task() defaults to lh_tasks-active; pass draft or pending for a task that shouldn’t be visible/claimable until reviewed.

Workflow status ({$wpdb->prefix}lh_tasks_status table)

Status key (namespace lh_tasks-) Label Notes
new_task New Initial state; claimable (subject to post_status = lh_tasks-active)
claim_task Claimed Set atomically by claim_next_task(); renew_task() (the renew-claim heartbeat) keeps a task in this status while pushing claimed_at forward
prog_task In Progress For workers that separate “claimed” from “started”; not set automatically
pend_task Pending Manual/human-workflow use
block_task Blocked Set automatically when retries are exhausted, or when a blocked dependency is encountered at claim time
res_task Resolved Set by complete_task(); terminal
clos_task Closed Manual final closure after resolution; terminal
cncl_task Cancelled Terminal
defr_task Deferred Parked for later — deliberately worth doing eventually, not urgent now. Not in return_open_statuses() (won’t dilute the dashboard widget/default list) and not terminal (promotable back to New later via reassign_task())

Status key naming (e.g. pend_task not pending_task) is unchanged from when these were post_status values constrained to 20 characters — the table column isn’t constrained the same way, but there was no reason to rename and break continuity with existing data/consumers.

Table columns (as of 1.6.0, table version 2.0): post_id (PK), status, claim_token, assigned_to (BIGINT UNSIGNED NULL — hard requirement), assignee (BIGINT UNSIGNED NULL — current claim holder), claimed_at, retry_count, max_retries, created_at, updated_at.

Taxonomies

  • lh_tasks_queue — non-hierarchical, non-public. Routing/lane, e.g. email-triage. Admin column shown on the Tasks list screen.
  • lh_tasks_task_type — non-hierarchical, non-public. Classification of the kind of work, e.g. github_triage. Distinct from queue: a queue can carry more than one task type and vice versa. Admin column shown on the Tasks list screen.

Post meta (all keys prefixed _lh_tasks-)

Meta key Type Description
payload JSON string Machine-actionable spec for agent tasks, e.g. {"ability": "lh-email/send-broadcast", "params": {...}}
result JSON string Output on completion, or {"error": "..."} on failure
priority string low|normal|high|urgent
due_date MySQL datetime Optional
blocked_by array of int Task IDs that must reach a terminal status first
source_plugin string Slug of the plugin that created this task, e.g. lh-crm
source_post_id int ID of the originating post in the source plugin
progress_log array of objects Appended to by reassign_task(), renew_task() (only when a note is given), and edit_task(). Each entry: timestamp, event (reassigned, heartbeat, or admin_override), from_status/to_status (reassign/admin_override only), from_assignee/to_assigned_to (reassign only), actor (WP user ID), note

assignee, assigned_to, claim_token, claimed_at, retry_count, and max_retries live in the status table, not postmeta — see above.