assign_to= on await_human() tells the server “who should do this.” The router resolves it to a specific user record, stamps that user as the assignee, and notifications go out per-channel.
The four shapes
The user directory
Users live in theusers table, manageable from the dashboard’s Settings → Users page. Each user has:
email(or null for Slack-only users)slack_team_id+slack_user_id(optional)role— free-form string, e.g.kyc-reviewer,support-tier-1access_level— free-form string, e.g.junior,seniorpool— free-form string, e.g.ops,complianceis_operator— bool, dashboard adminactive— bool, default true
Fairness model
Within a resolved set (pool, role, etc.), the router picks the least-recently-assigned user. The user record carrieslast_assigned_at; on every routing decision it gets bumped on the picked user.
This spreads load — fresh tasks rotate through a pool rather than piling up on whoever’s listed first. It’s Option C from the routing pillar: not perfectly even (a slow reviewer ends up with fewer recent tasks), but trivially understandable and good-enough for v0.1.
Operators who need a different fairness model (round-robin, weighted, on-call schedules) override the router. See Custom routers.
Resolution rules
Slack-only users
Users without an email but with a Slack identity work fine for routing:assigned_to_user_id (stable across email changes) so attribution stays consistent.
Marketplace (reserved for Phase 3)
MarketplaceNotAvailableError. Reserved for the post-Phase-2 workforce marketplace where tasks can be sourced from external reviewers. See the roadmap.
Custom routers
The router lives inserver/services/task_router.py. The function:
task_router.
This is one of the four buckets — extension points by design. New routing strategies are a Phase-2 community contribution surface.