Task Dispatcher as a taOS component
Why
The claim lifecycle (ready/claim/claimable/release) is product, but the thing
that MATCHES idle agents to claimable cards is not: it is shell on the lead
box. Every rule it learned on 2026-07-26 (priority-then-oldest pooled across
boards, mechanical blocked-on, fix-forward anti-deadlock, one-claim caps)
benefits exactly one user. Any taOS user with shared agents across projects
should get this for free.
Scope: assignment engine, NOT execution
The dispatcher decides WHICH claimable card goes to WHICH eligible agent and
wakes that agent. Execution stays the agent's business (deployed container
agent via the heartbeat loop, external CLI via its own harness, etc.). This is
what makes it product-shaped: it composes with every agent type instead of
embedding one.
Design
New tinyagentos/projects/dispatcher.py service, started with the app, off by
default per user.
Config (per user, Settings + API):
- enabled: bool
- boards: project ids (default: all the user's projects)
- eligible agents: canonical ids (must hold project_tasks on the board a card
sits on; the dispatcher never bypasses grants)
- max_concurrent_per_agent: default 1
- poll_seconds: default 30 (superseded by event-driven when broker events for
task-create/close exist; do not build a new event system for this)
Selection policy (the 2026-07-26 rules, now in product):
- candidate = open/todo/ready/backlog, unclaimed, label
claimable or
fleet:claimable, assignee empty/@any or the specific agent
- skip while a
blocked-on:tsk-X dependency is open (same-board lookup;
unknown ids fail open)
- order: priority desc, then created_at asc, pooled across enabled boards
- respect max_concurrent_per_agent (count of currently-claimed cards)
Dispatch = assign + wake: set assignee_id via the existing task update
path (audit-logged), then wake the agent through the existing heartbeat/wake
machinery. The agent claims on wake via the existing claim endpoint - claim
stays an agent action so the audit trail shows who actually took the work.
Explicitly out of scope: the exec-PR throttle and fix-forward bypass are
git-workflow-specific and stay in external harnesses; the product dispatcher
caps by max_concurrent_per_agent instead, which is transport-agnostic.
Acceptance (S1, backend only)
- Config CRUD (owner/admin only) + persisted per user
- With two eligible agents and three claimable cards across two boards,
assignment follows priority-then-oldest and respects the per-agent cap
- blocked-on skip proven by test; closing the dependency releases the card
- An agent lacking project_tasks on a board never gets its cards
- Disabled by default; enabling requires explicit user action
- Existing manual claim flows completely unaffected
S2 (later): Settings UI pane + per-project dispatcher status view.
Task Dispatcher as a taOS component
Why
The claim lifecycle (ready/claim/claimable/release) is product, but the thing
that MATCHES idle agents to claimable cards is not: it is shell on the lead
box. Every rule it learned on 2026-07-26 (priority-then-oldest pooled across
boards, mechanical blocked-on, fix-forward anti-deadlock, one-claim caps)
benefits exactly one user. Any taOS user with shared agents across projects
should get this for free.
Scope: assignment engine, NOT execution
The dispatcher decides WHICH claimable card goes to WHICH eligible agent and
wakes that agent. Execution stays the agent's business (deployed container
agent via the heartbeat loop, external CLI via its own harness, etc.). This is
what makes it product-shaped: it composes with every agent type instead of
embedding one.
Design
New
tinyagentos/projects/dispatcher.pyservice, started with the app, off bydefault per user.
Config (per user, Settings + API):
sits on; the dispatcher never bypasses grants)
task-create/close exist; do not build a new event system for this)
Selection policy (the 2026-07-26 rules, now in product):
claimableorfleet:claimable, assignee empty/@any or the specific agentblocked-on:tsk-Xdependency is open (same-board lookup;unknown ids fail open)
Dispatch = assign + wake: set assignee_id via the existing task update
path (audit-logged), then wake the agent through the existing heartbeat/wake
machinery. The agent claims on wake via the existing claim endpoint - claim
stays an agent action so the audit trail shows who actually took the work.
Explicitly out of scope: the exec-PR throttle and fix-forward bypass are
git-workflow-specific and stay in external harnesses; the product dispatcher
caps by max_concurrent_per_agent instead, which is transport-agnostic.
Acceptance (S1, backend only)
assignment follows priority-then-oldest and respects the per-agent cap
S2 (later): Settings UI pane + per-project dispatcher status view.