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.