Run a Research Investigation
End-to-end recipe for producing a research_report artifact using the researcher agent profile. The agent decomposes the topic into sub-questions, queries your business substrate and knowledge base, synthesizes findings with citations, and saves a single artifact with a structured report body.
Prerequisites
- An API key with scopes
workflows:write(start the agent) andassets:read(fetch the resulting report). See authentication.md and scopes. - A business with data loaded. The researcher leans heavily on
data.*andknowledge_base.search; a cold tenant produces a shallow report.
Export for the examples:
export API_KEY="sk_live_your_key_here"Steps
1. Start the agent
Unlike content_writer, the researcher does not pause for approvals. You get a terminal status on the first run.
curl -sS -X POST "https://app.amdahl.co/api/platform/v1/agents/run" \
-H "Authorization: Bearer $API_KEY" \
-H "Content-Type: application/json" \
-d '{
"profile_id": "researcher",
"task": "What drives churn among our mid-market customers in the last 90 days?",
"input_params": {
"audience": "internal-revops",
"depth": "deep",
"must_cover": ["top 3 root causes", "segment cuts", "leading indicators"]
},
"async": true
}'Expected response:
{
"success": true,
"session_id": "as_01J0...",
"status": "queued",
"profile_id": "researcher",
"created_at": "2026-04-14T19:02:44.001Z",
"async": true
}What this tells you: the investigation is enqueued. Keep the session_id.
2. (Optional) Subscribe to the live SSE stream
If you want UI-grade progress updates instead of poll-based status, open the Server-Sent Events stream. Each event is a JSON payload pushed as the agent thinks and calls tools. Useful for showing a progress panel.
curl -sN "https://app.amdahl.co/api/platform/v1/agents/$SESSION_ID/stream" \
-H "Authorization: Bearer $API_KEY"TypeScript client using the fetch streaming API:
async function streamAgent(sessionId: string) {
const res = await fetch(`${process.env.AMDAHL_URL}/api/platform/v1/agents/${sessionId}/stream`, {
headers: { Authorization: `Bearer ${process.env.API_KEY}` },
})
if (!res.ok || !res.body) throw new Error(`stream failed: ${res.status}`)
const reader = res.body.getReader()
const decoder = new TextDecoder()
let buffer = ''
for (;;) {
const { value, done } = await reader.read()
if (done) break
buffer += decoder.decode(value, { stream: true })
const frames = buffer.split('\n\n')
buffer = frames.pop() ?? ''
for (const frame of frames) {
const line = frame.split('\n').find(l => l.startsWith('data: '))
if (!line) continue
const event = JSON.parse(line.slice(6))
console.log(event.type, event.data)
if (event.type === 'session.completed' || event.type === 'session.failed') {
reader.cancel()
return event
}
}
}
}Event types you will see: tool.started, tool.completed, turn.completed, session.completed, session.failed. Payloads follow the shape documented in api-reference/tools/workflows.md.
3. Or poll the status endpoint
curl -sS "https://app.amdahl.co/api/platform/v1/agents/$SESSION_ID/status" \
-H "Authorization: Bearer $API_KEY"Mid-run response:
{
"session_id": "as_01J0...",
"status": "running",
"turn_count": 9,
"current_tool": "data.cluster_search",
"updated_at": "2026-04-14T19:03:57.445Z"
}What this tells you: the agent is mid-investigation. The researcher profile has a maxTurns cap of 20. If turn_count reaches 20 without completion, the run ends with status=failed and error.code='max_turns_exceeded'.
4. Wait for completion
Terminal states: complete, failed, canceled. A successful run:
{
"session_id": "as_01J0...",
"status": "complete",
"result_summary": "6 sub-questions answered across 14 citations. Primary root cause: onboarding drop-off at week 2.",
"result_artifact_id": "art_01J0...",
"turn_count": 17,
"updated_at": "2026-04-14T19:07:12.998Z"
}5. Fetch the research report
curl -sS "https://app.amdahl.co/api/platform/v1/artifacts/$ARTIFACT_ID" \
-H "Authorization: Bearer $API_KEY"Expected response (shortened):
{
"id": "art_01J0...",
"artifact_type": "research_report",
"status": "draft",
"title": "Mid-market churn drivers, last 90 days",
"content_markdown": "# Mid-market churn drivers...\n\n## Summary\n...\n",
"content_json": {
"summary": "Churn concentrated in the week-2 onboarding gap...",
"sub_questions": [
{
"question": "What are the top 3 root causes?",
"answer": "...",
"confidence": "high",
"citations": ["c_01", "c_05", "c_07"]
}
],
"key_findings": [{ "point": "Week-2 activation drop of 31%.", "citations": ["c_01"] }],
"gaps": ["No qualitative signal from churned SMB customers; survey response rate < 5%."],
"citations": [
{
"id": "c_01",
"source_type": "data_query",
"title": "Cohort activation, last 90d",
"snippet": "..."
}
]
},
"version": 1,
"created_at": "2026-04-14T19:07:12.810Z"
}What this tells you: content_json is the structured report. content_markdown is a rendered version for humans. citations ties every finding back to a specific data query or document.
Common issues
status=failedwitherror.code='max_turns_exceeded': scope was too broad. Split into narrower tasks or setinput_params.depth='shallow'.- Empty
gapsand shallow findings: your substrate has no matching evidence. Add data viadata.*ingestion or upload documents to the knowledge base first. - Stream closes early with no
session.completedevent: network timeout at the proxy layer. Fall back to pollingGET /agents/:id/status. result_artifact_idis null onstatus=complete: the agent finished without callingartifacts.create. This is a profile bug; inspectresult_summaryfor reasoning and file an issue. Retry the task.- Citations reference ids that are not in
content_json.citations: the profile caught a synthesis bug. Treat the finding as unsupported and re-run with a narrower task.
See also
- agents.md for agent lifecycle and SSE event details.
- api-reference/tools/workflows.md for
agents.*schemas. - api-reference/tools/data.md for the
data.*tools the researcher calls internally. - api-reference/tools/assets.md for fetching and versioning the report.
- recipes/draft-blog-post.md for a content-creation companion flow.