Priority Queues & Scheduling

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.

Priority Queues and Scheduling

What you'll learn

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.

The Four Priority Levels

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).

Setting Priority

# 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');

Time-Based Scheduling with due_at

Issues 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.

Recurring Tasks

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;

Fair-Use Queuing Across Agents

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.

Monitoring Queue Depth

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;

Checkpoint ✓

Sign in for progress tracking & Q&A →