The priority integer field, how agents select their next task (ORDER BY priority DESC, created_at ASC), starvation prevention for low-priority tasks, and manual priority bumping.
How priority ordering works in the heartbeat queue, how to use due_at for time-based scheduling, and how to implement a fair-use queue across multiple agents.
critical → processed first
high → before medium
medium → default
low → when nothing else is waiting
Within the same priority level, issues are claimed in created_at ASC order (FIFO).
# Via API
curl -X POST http://localhost:3100/api/companies/${COMPANY_ID}/issues \
-d '{"title": "...", "priority": "critical", "assigned_agent_id": "..."}'
# Via SQL
INSERT INTO issues (company_id, title, priority, assigned_agent_id, status)
VALUES ('cid', 'URGENT: fix broken API endpoint', 'critical', 'dev-id', 'open');
due_atIssues with a due_at timestamp are not claimed until that time arrives. This enables scheduling:
-- Schedule a daily report issue for 09:00 UTC tomorrow
INSERT INTO issues (company_id, title, assigned_agent_id, due_at, status)
VALUES (
'cid',
'Generate daily activity report',
'reporter-id',
(now() + interval '1 day')::date + interval '9 hours',
'open'
);
The heartbeat query adds:
AND (due_at IS NULL OR due_at <= now())
This makes scheduled issues invisible to the queue until their time.
Paperclip does not have a built-in cron for issues, but you can create recurring issues using a "scheduler agent":
Scheduler Agent (shell adapter, runs every heartbeat tick):
- Checks if a 'daily report' issue exists for today
- If not, creates one
- Low priority, lightweight
Or seed recurring issues directly:
-- Create 7 daily report issues for the coming week
INSERT INTO issues (company_id, title, assigned_agent_id, due_at, priority, status)
SELECT
'cid',
'Daily report: ' || to_char(d, 'YYYY-MM-DD'),
'reporter-id',
d + interval '9 hours',
'low',
'open'
FROM generate_series(now()::date, now()::date + interval '6 days', interval '1 day') AS d;
By default, the heartbeat processes one issue per agent per tick. With many agents and many issues, this is naturally fair. To limit an agent to N issues per tick:
-- In adapter_config, add:
{
"max_issues_per_tick": 2
}
Or limit at the company level in heartbeat settings.
SELECT
a.name,
COUNT(*) FILTER (WHERE i.priority = 'critical') AS critical,
COUNT(*) FILTER (WHERE i.priority = 'high') AS high,
COUNT(*) FILTER (WHERE i.priority = 'medium') AS medium,
COUNT(*) FILTER (WHERE i.priority = 'low') AS low,
COUNT(*) AS total
FROM issues i
JOIN agents a ON a.id = i.assigned_agent_id
WHERE i.company_id = 'your-company-id'
AND i.status = 'open'
GROUP BY a.name
ORDER BY total DESC;
due_at and can schedule a future issue