All fields of the issues table: id, company_id, parent_id, title, description, status, assigned_agent_id, priority, metadata JSONB, created_at, updated_at. Field-by-field walkthrough.
Every field in the issues table, the six status states an issue moves through, and how issues relate to agents and heartbeat runs.
In Paperclip AI everything an agent does is represented as an issue — a persistent row in the database that describes a task, tracks its lifecycle, and records the result. This is the central design decision: work is durable and auditable by default.
issues TableCREATE TABLE public.issues (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
company_id uuid NOT NULL REFERENCES companies(id),
title text NOT NULL,
description text,
status text NOT NULL DEFAULT 'open'
CHECK (status IN ('open','claimed','in_progress','resolved','escalated','cancelled')),
priority text NOT NULL DEFAULT 'medium'
CHECK (priority IN ('low','medium','high','critical')),
assigned_agent_id uuid REFERENCES agents(id),
parent_issue_id uuid REFERENCES issues(id), -- for subtasks
result text, -- agent output
result_structured jsonb, -- parsed result
heartbeat_run_id uuid REFERENCES heartbeat_runs(id),
attempt_count int NOT NULL DEFAULT 0,
max_attempts int NOT NULL DEFAULT 3,
created_at timestamptz NOT NULL DEFAULT now(),
claimed_at timestamptz,
resolved_at timestamptz,
due_at timestamptz,
metadata jsonb
);
open
│
├─ claimed (heartbeat has selected this issue; agent about to run)
│
├─ in_progress (adapter invoke called; waiting for result)
│
├─ resolved (agent returned output; work done)
│
├─ escalated (agent could not complete; requires human or manager review)
│
└─ cancelled (manually cancelled; will not be retried)
The transition open → claimed → in_progress happens within a single heartbeat tick using the atomic checkout pattern (covered in detail in pcl-30).
Issues are claimed in priority order within each heartbeat tick:
-- Simplified version of the heartbeat query
SELECT *
FROM issues
WHERE company_id = $1
AND status = 'open'
AND assigned_agent_id = $2
ORDER BY
CASE priority
WHEN 'critical' THEN 1
WHEN 'high' THEN 2
WHEN 'medium' THEN 3
WHEN 'low' THEN 4
END,
created_at ASC
LIMIT 1
FOR UPDATE SKIP LOCKED;
parent_issue_id FieldIssues can have subtasks. Set parent_issue_id to create a hierarchy:
-- Parent: write a blog post
INSERT INTO issues (company_id, title, assigned_agent_id)
VALUES ('...', 'Write a blog post about PostgreSQL', '...')
RETURNING id;
-- Child tasks
INSERT INTO issues (company_id, title, parent_issue_id, assigned_agent_id)
VALUES
('...', 'Research: gather 5 key PostgreSQL facts', 'parent-id', 'researcher-id'),
('...', 'Outline: create 5-section blog outline', 'parent-id', 'planner-id'),
('...', 'Draft: write full blog post from outline', 'parent-id', 'writer-id');
metadata FieldStore arbitrary structured data alongside an issue without schema changes:
UPDATE issues SET metadata = jsonb_build_object(
'source', 'slack',
'channel_id', 'C01234',
'requester', 'alice@company.com',
'tags', ARRAY['blog', 'technical']
)
WHERE id = 'issue-id';
parent_issue_id and how to create subtask hierarchies