How to manually trigger a heartbeat run via the API, watching the execution log in real time, and understanding what the agent actually does during its work cycle.
Create an issue (task) for your agent, trigger or wait for the heartbeat, and read the resulting heartbeat_runs log to understand what happened.
Before the heartbeat has work to do you need at least one open issue assigned to your agent.
curl -X POST http://localhost:3100/api/companies/${COMPANY_ID}/issues \
-H "Content-Type: application/json" \
-d '{
"title": "Research: PostgreSQL row-level security",
"description": "Produce a 3-paragraph summary of PostgreSQL RLS, with key use cases and an example policy.",
"assigned_agent_id": "'${AGENT_ID}'",
"priority": "medium"
}'
The issue is created with status = 'open'.
In development you don't want to wait for the cron schedule. Use the manual trigger:
curl -X POST http://localhost:3100/api/heartbeat/run \
-H "Content-Type: application/json" \
-d '{"company_id": "'${COMPANY_ID}'"}'
You'll see a response like:
{
"run_id": "01929f3a-...",
"company_id": "...",
"agents_invoked": 1,
"issues_claimed": 1,
"status": "running"
}
# Poll for status
curl http://localhost:3100/api/heartbeat/runs/${RUN_ID}
{
"id": "...",
"status": "completed",
"agents_invoked": 1,
"issues_resolved": 1,
"total_cost_usd": "0.023",
"started_at": "...",
"finished_at": "..."
}
curl http://localhost:3100/api/companies/${COMPANY_ID}/issues/${ISSUE_ID}
The result field contains the agent's markdown output. The issue status should now be resolved.
1. Lock company row (SELECT FOR UPDATE SKIP LOCKED)
2. Check company is not paused / over budget
3. Find open issues with assigned agents that are active
4. For each issue: atomic checkout (UPDATE status='in_progress' RETURNING)
5. Invoke adapter (claude-code invoke) with assembled prompt
6. Receive structured JSON response
7. Update issue: status='resolved', result=response.output
8. Record cost_event: tokens used, cost in USD
9. Record heartbeat_run: summary stats
10. Release lock
SELECT
hr.id,
hr.status,
hr.agents_invoked,
hr.issues_resolved,
hr.total_cost_usd,
hr.started_at,
hr.finished_at,
(hr.finished_at - hr.started_at) AS duration
FROM heartbeat_runs hr
WHERE hr.company_id = 'your-company-id'
ORDER BY hr.started_at DESC
LIMIT 10;
status = 'open'status = 'resolved' with a resultheartbeat_runs row exists with cost and timing data