How agent A signals completion to agent B: issue status transitions as signals, metadata JSONB as a message payload, and the activity_log as a shared event bus for handoffs.
Design clean handoff interfaces between agents, standardise the data format that passes from one agent to the next, and handle failed handoffs.
When Agent A finishes work and Agent B continues from where A left off, B needs A's output in a predictable format. If A writes free-form prose, B has to parse it — fragile and error-prone.
The solution: define a handoff schema for each agent's output.
Add to the agent's system prompt:
## Output Format
You MUST return a JSON object with this exact schema. No prose, no markdown fencing — raw JSON only.
{
"summary": "string (2-3 sentences)",
"key_facts": ["string", ...], // 3-5 items
"sources": [{"title": "string", "url": "string"}], // 1-5 items
"confidence": "high" | "medium" | "low",
"next_steps": ["string", ...] // optional
}
Paperclip stores this in issues.result_structured (JSONB) when the result is valid JSON:
// Heartbeat post-processes the result
if (isValidJson(result.output)) {
await db.issues.update(issueId, {
result: result.output,
result_structured: JSON.parse(result.output),
});
}
When creating the writer's issue, extract fields from the researcher's result_structured:
-- Writer issue description built from researcher's structured output
INSERT INTO issues (company_id, title, description, assigned_agent_id, parent_issue_id)
SELECT
i.company_id,
'Write article from research: ' || topic,
'Summary: ' || (i.result_structured->>'summary') || E'\n\n' ||
'Key facts: ' || (i.result_structured->'key_facts')::text || E'\n\n' ||
'Write a 700-word article covering the key facts above.',
writer_id,
i.parent_issue_id
FROM issues i
WHERE i.title = 'Research: ' || topic
AND i.status = 'resolved';
Add a validation step between agents:
function validateResearchHandoff(result: string): ResearchOutput {
const data = JSON.parse(result);
if (!data.summary || !Array.isArray(data.key_facts)) {
throw new Error('Invalid research handoff: missing required fields');
}
return data as ResearchOutput;
}
If validation fails, the issue is marked escalated and a human reviews the raw output before the next agent proceeds.
For structured data that doesn't need to be in the description prose:
UPDATE issues
SET metadata = jsonb_set(
COALESCE(metadata, '{}'),
'{handoff}',
result_structured
)
WHERE id = 'researcher-issue-id';
The next agent's issue can reference metadata->'handoff' directly.
result_structured and how it differs from result