Dispatch priority bias ordering audit¶
Historical audit, amended 2026-07-25. The original priority/FIFO analysis below remains useful, but dispatch now also applies a work-conserving class lane and bounded due-time aging.
urgentandrecoveryoutranknormal, whilebackgroundyields; no worker is held idle when the higher lanes are empty. The candidate scan includes an explicit urgent/recovery window, andtask showexposes the resolved class, age bonus, and due bonus.
Ground-truth finding for the parent repair
(task_repair_6cd69ae5474dfb0f601ad5cd). It traces how task-dispatch selection
orders by priority, whether a distinct "priority bias" concept exists, and where
the existing tests do and do not pin the behavior. No source ordering logic was
changed by this audit; the one code change is a gap-closing test (see
"Recommendation").
Scope¶
Which open task an idle agent claims next, from the SQLite tasks table through the Python dispatch ordering to the thin dispatch/api/worker surfaces:
src/mac/store.py—tasks.priority INTEGER NOT NULL DEFAULT 0and the covering indexesidx_tasks_state_priority ON tasks(state, priority DESC, created_at)andidx_tasks_review_queue ON tasks(priority DESC, created_at, id).src/mac/services.py—_dispatch_candidate_tasks,_dispatch_ordered_tasks,_interleave_tasks_by_project,_rotate_by_page_prefix,_dispatch_task_sort_key,_dispatch_priority_age_bonus, and the callersdispatch_once,claim_next_for_agent,ready_tasks,list_tasks.src/mac/dispatch.py,src/mac/api.py,src/mac/worker.py— priority passthrough only.
(1) Exact ordering / tie-break rule and where it lives¶
The store defines the priority column and the priority DESC, created_at
indexes but issues no dispatch-time ORDER BY priority query itself. The
authoritative selection order is computed in src/mac/services.py:
_dispatch_candidate_tasksreads a bounded candidate set as the union of windows over open, unowned, unleased tasks:ORDER BY priority DESC, created_at, id LIMIT 500(the hot high-priority window) unioned withORDER BY created_at, id LIMIT 500(the oldest window, so an ancient low-priority task can still enter the scoring pass when the priority window is saturated), plus an expliciturgent/recoverymetadata window. De-duplicated by id._dispatch_task_sort_keyis the real comparator. It returns the tuple(-effective_priority, 0.0, created_at, id)whereeffective_priority = class_stride + priority + age_bonus + due_bonus. Because the sort is ascending, negating the first field surfaces the higher priority first;created_atthenidare ascending fallbacks (FIFO, then stable id). The second slot held a work-package critical-path order signal and is retained as a constant so the tuple arity is unchanged._dispatch_priority_age_bonusaddsfloor(age_seconds / step)priority points,step = MAC_DISPATCH_PRIORITY_AGING_SECONDS(default 24h, floor 60s)._dispatch_due_bonusadds a bounded bonus after an optionaldue_atordeadline_atpasses (default one point per four hours, capped at 24)._dispatch_ordered_tasksgroups candidates by tenant, and_interleave_tasks_by_projectround-robins each tenant's projects; within a project_rotate_by_page_prefixbuckets by the leadingMAC_DISPATCH_PAGE_PREFIX_WIDTH(default 2) characters of the page cursor/id and round-robins across buckets. Every group/bucket keeps its internal_dispatch_task_sort_keyorder, and single-bucket / single-project groups are no-ops, so the head still leads on(priority, age).
Effective tie-break precedence: dispatch class → effective priority (priority + age bonus + due bonus) → created_at (oldest first) → id, wrapped in tenant/project/page-prefix fairness round-robins that only reorder across groups, never within a same-key group.
(2) Is "priority bias" a distinct concept, or IS priority ordering the bias?¶
Both, at different layers:
- Base ordering is the raw
priority DESC, created_atrule (the index and the candidate query). - A distinct, additive bias layer sits on top: the age-based bonus in
_dispatch_priority_age_bonus(anti-starvation aging) plus the tenant/project interleave and page-prefix rotation fairness. These bias which eligible task wins beyond raw stored priority, so "priority bias" is not merely a synonym for theORDER BY; it is the aging + fairness adjustment applied in Python.
(3) Coverage gaps¶
Existing coverage (all currently passing) is substantial and lives mostly in
tests/test_control_plane.py and tests/test_dispatch_advisor.py:
- Priority order within a project, cross-project interleave fairness, and
page-prefix rotation:
test_dispatch_preserves_priority_order_within_project,test_dispatch_ordered_tasks_*,test_rotate_by_page_prefix_*,test_ready_tasks_consume_page_prefix_rotation. - Sort-key precedence, priority>signal ties, aging bonus math, corrupt-timestamp
tolerance, env overrides, and the candidate-window union:
test_dispatch_task_sort_key_*,test_dispatch_priority_age_bonus_*,test_dispatch_candidate_tasks_*. - Claim-path priority:
test_claim_next_prefers_high_priority_over_older_default_ready_task,test_dispatch_priority_aging_prevents_low_priority_starvation. - Work-package order-signal tie ordering:
test_current_compiled_critical_path_rank_orders_equal_priority_tasks,test_ordinary_task_order_falls_back_to_priority_aging_and_age.
Gaps identified:
- created_at FIFO tie-break was not pinned end-to-end. The sort-key tests
only asserted
created_atis present at tuple index [2]; every end-to-end_dispatch_ordered_tasksordering test used distinct priorities or an order signal, so nothing proved that two same-priority, no-order-signal peers actually dispatch oldest-first. This is the primary gap and is closed below. - Index vs query direction is intentional, not a bug.
created_atis stored as ISO-8601 UTC text; the aging bonus and candidate query both treat older (lexically smaller) as higher effective priority, so ascendingcreated_atafterpriority DESCis the correct FIFO tie-break.list_tasksusespriority DESC, created_at(ascending) and matches the dispatch semantics; the keyword-search helper (ORDER BY priority DESC, created_at DESC) is a presentation-only listing, not a dispatch path, so its DESC secondary is not a dispatch mismatch. No coverage gap or defect here. - Minor (not fixed here): no explicit assertion that
MAC_DISPATCH_TASK_WINDOWis honored as the candidate cap, and no test that tenant-level interleave fairness composes with page-prefix rotation in the same pass. These are low-risk secondary paths.
(4) Recommendation: add-tests¶
The ordering logic is correct and does not need a code fix; the meaningful exposure is the untested created_at FIFO tie-break. Recommendation is add-tests, scoped to a single regression that pins current behavior without touching source ordering logic:
tests/test_control_plane.py::test_dispatch_ordered_tasks_breaks_priority_ties_by_created_at— two equal-priority, no-order-signal tasks must dispatch oldest-first through_dispatch_ordered_tasks.
The remaining "minor" items are optional follow-ups and are not required to close the audit.
Consequential assumptions¶
- The keyword-search
created_at DESCsecondary sort is presentation-only and intentionally not a dispatch ordering; it was therefore excluded from the dispatch tie-break audit rather than flagged as a mismatch. - "Priority bias" in the parent title maps to the additive aging bonus plus the
tenant/project/page-prefix fairness rotation, not to a separately named
configuration knob (none exists beyond the
MAC_DISPATCH_*env overrides). - The added regression pins current behavior (oldest-first FIFO); it is a characterization test, so it must be updated deliberately if the tie-break rule is ever intentionally changed.