Build a squad
A sequential research → write → review team that collaborates on a blackboard.
What you'll build
A three-agent squad that researches a topic, writes a report from the research, then edits
the report — each step building on the previous one's output via a shared
blackboard. You'll define the full task pipeline,
kick it off, and poll the job for task_results, the audit_trail, the blackboard, and the
combined final_output. Read Squads first for the model.
Prerequisites
- A TurfAI JWT for every call:
Authorization: Bearer $TURFAI_JWT. - Base URL
https://apisandbox.turfai.in/api. - The three member agents already created —
researcher,writer,editor. A squad orchestrates existing agents; note each one'sslug.
export TURFAI_JWT="eyJhbGci…"
export BASE="https://apisandbox.turfai.in/api"1. Create the squad with a task pipeline
Send agents[] (the members + their roles) and tasks[] (the pipeline) in one POST /squads.
Each task names its agent_slug, its depends_on upstream tasks, and an output_key that
downstream tasks read from. The platform validates the pipeline — circular dependencies,
duplicate IDs, dependencies on unknown tasks, and assignments to non-member agents are all
rejected before any run.
curl -X POST "$BASE/squads" \
-H "Authorization: Bearer $TURFAI_JWT" \
-H "Content-Type: application/json" \
-d '{
"data": {
"name": "Research & Writing Team",
"description": "Research a topic and produce a polished report.",
"process": "sequential",
"max_total_iterations": 30,
"agents": [
{ "agent_slug": "researcher", "role": "Research topics thoroughly" },
{ "agent_slug": "writer", "role": "Write clear, structured content" },
{ "agent_slug": "editor", "role": "Review and improve writing" }
],
"tasks": [
{
"id": "research",
"description": "Research {{context}} comprehensively.",
"agent_slug": "researcher",
"output_key": "research"
},
{
"id": "write",
"description": "Write a detailed report from the research.",
"agent_slug": "writer",
"depends_on": ["research"],
"output_key": "draft",
"expected_output": "A structured report with sections and a summary."
},
{
"id": "edit",
"description": "Review and polish the report for clarity and accuracy.",
"agent_slug": "editor",
"depends_on": ["write"],
"output_key": "final"
}
]
}
}'import os, requests
base = os.environ["BASE"]
r = requests.post(
f"{base}/squads",
headers={"Authorization": f"Bearer {os.environ['TURFAI_JWT']}"},
json={
"data": {
"name": "Research & Writing Team",
"description": "Research a topic and produce a polished report.",
"process": "sequential",
"max_total_iterations": 30,
"agents": [
{"agent_slug": "researcher", "role": "Research topics thoroughly"},
{"agent_slug": "writer", "role": "Write clear, structured content"},
{"agent_slug": "editor", "role": "Review and improve writing"},
],
"tasks": [
{
"id": "research",
"description": "Research {{context}} comprehensively.",
"agent_slug": "researcher",
"output_key": "research",
},
{
"id": "write",
"description": "Write a detailed report from the research.",
"agent_slug": "writer",
"depends_on": ["research"],
"output_key": "draft",
"expected_output": "A structured report with sections and a summary.",
},
{
"id": "edit",
"description": "Review and polish the report for clarity and accuracy.",
"agent_slug": "editor",
"depends_on": ["write"],
"output_key": "final",
},
],
}
},
)
squad = r.json()["data"]
print(squad["id"], squad["slug"]) # kick off by numeric idconst base = process.env.BASE!;
const res = await fetch(`${base}/squads`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TURFAI_JWT}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
data: {
name: "Research & Writing Team",
description: "Research a topic and produce a polished report.",
process: "sequential",
max_total_iterations: 30,
agents: [
{ agent_slug: "researcher", role: "Research topics thoroughly" },
{ agent_slug: "writer", role: "Write clear, structured content" },
{ agent_slug: "editor", role: "Review and improve writing" },
],
tasks: [
{
id: "research",
description: "Research {{context}} comprehensively.",
agent_slug: "researcher",
output_key: "research",
},
{
id: "write",
description: "Write a detailed report from the research.",
agent_slug: "writer",
depends_on: ["research"],
output_key: "draft",
expected_output: "A structured report with sections and a summary.",
},
{
id: "edit",
description: "Review and polish the report for clarity and accuracy.",
agent_slug: "editor",
depends_on: ["write"],
output_key: "final",
},
],
},
}),
});
const { data: squad } = await res.json();
console.log(squad.id, squad.slug); // kick off by numeric id{ "data": { "id": 7, "slug": "research-writing-team", "process": "sequential", "…": "…" } }Save the numeric id — kickoff and polling use it (export SQUAD_ID=7).
2. How collaboration flows
research is a root task, so it receives the raw kickoff inputs. write reads the
research key, edit reads draft, and the final answer lands under final.
3. Kick it off
Kickoff is asynchronous: POST /squads/:id/kickoff returns a jobId immediately.
curl -X POST "$BASE/squads/$SQUAD_ID/kickoff" \
-H "Authorization: Bearer $TURFAI_JWT" \
-H "Content-Type: application/json" \
-d '{ "inputs": { "topic": "Benefits of AI in healthcare", "depth": "comprehensive" } }'import os, requests
base = os.environ["BASE"]
sid = os.environ["SQUAD_ID"]
r = requests.post(
f"{base}/squads/{sid}/kickoff",
headers={"Authorization": f"Bearer {os.environ['TURFAI_JWT']}"},
json={"inputs": {"topic": "Benefits of AI in healthcare", "depth": "comprehensive"}},
)
job_id = r.json()["jobId"]
print(job_id)const base = process.env.BASE!;
const sid = process.env.SQUAD_ID!;
const res = await fetch(`${base}/squads/${sid}/kickoff`, {
method: "POST",
headers: {
Authorization: `Bearer ${process.env.TURFAI_JWT}`,
"Content-Type": "application/json",
},
body: JSON.stringify({
inputs: { topic: "Benefits of AI in healthcare", depth: "comprehensive" },
}),
});
const { jobId } = await res.json();
console.log(jobId);{ "jobId": "squad_1710859200_a1b2c3", "status": "processing", "startedAt": "…" }4. Poll the job
Poll GET /squads/jobs/:jobId until status is completed (or failed).
curl "$BASE/squads/jobs/squad_1710859200_a1b2c3" \
-H "Authorization: Bearer $TURFAI_JWT"import os, time, requests
base = os.environ["BASE"]
h = {"Authorization": f"Bearer {os.environ['TURFAI_JWT']}"}
job_id = "squad_1710859200_a1b2c3"
while True:
job = requests.get(f"{base}/squads/jobs/{job_id}", headers=h).json()
if job["status"] in ("completed", "failed"):
break
time.sleep(3)
print(job["result"]["final_output"])const base = process.env.BASE!;
const headers = { Authorization: `Bearer ${process.env.TURFAI_JWT}` };
const jobId = "squad_1710859200_a1b2c3";
let job;
do {
await new Promise((r) => setTimeout(r, 3000));
job = await (await fetch(`${base}/squads/jobs/${jobId}`, { headers })).json();
} while (job.status !== "completed" && job.status !== "failed");
console.log(job.result.final_output);On completion the result carries the per-task breakdown, the audit trail, the blackboard
snapshot, the combined final_output, and processing_time (seconds):
{
"jobId": "squad_1710859200_a1b2c3",
"status": "completed",
"result": {
"squad": "Research & Writing Team",
"process": "sequential",
"final_output": "## research (by researcher)\n…\n\n## write (by writer)\n…\n\n## edit (by editor)\n…",
"task_results": {
"research": { "status": "completed", "agent": "researcher", "answer": "Key findings: …", "tools_used": ["search_documents", "fetch_url"], "iterations": 3 },
"write": { "status": "completed", "agent": "writer", "answer": "A first-draft report …", "tools_used": ["generate_text"], "iterations": 2 },
"edit": { "status": "completed", "agent": "editor", "answer": "A polished report …", "tools_used": ["generate_text"], "iterations": 1 }
},
"audit_trail": [
{ "task_id": "_input", "agent_slug": "_system", "key": "topic", "timestamp": 1710859200.12 },
{ "task_id": "research", "agent_slug": "researcher", "key": "research", "timestamp": 1710859231.74 },
{ "task_id": "write", "agent_slug": "writer", "key": "draft", "timestamp": 1710859258.30 },
{ "task_id": "edit", "agent_slug": "editor", "key": "final", "timestamp": 1710859275.91 }
],
"blackboard": {
"topic": "Benefits of AI in healthcare",
"research": "Key findings: …",
"draft": "A first-draft report …",
"final": "A polished report …"
},
"processing_time": 45.2
}
}final_output is the per-task answers concatenated under ## <task> (by <agent>) headings;
the blackboard holds each output_key's final value; the audit_trail logs every write in
order.
Sequential vs hierarchical
This squad is sequential — the order is fixed by depends_on. Switch process to
hierarchical (and set a manager_agent) to have a manager receive the member list and
task descriptions and delegate work in an order it decides at runtime. task_results then
reports a single manager entry. Use it when the order can't be drawn up front. See the
decision aid.
Troubleshooting
| Symptom | Cause | What happens / fix |
|---|---|---|
| Create returns a validation error | Circular dependency (e.g. A depends_on B and B depends_on A), duplicate task id, a depends_on referencing an unknown task, or an agent_slug not in agents[] | The pipeline is rejected before any run — fix the offending task and re-create. A cycle is also re-checked at runtime and fails the job. |
One task shows "status": "error" | The member agent failed or its slug isn't found/active | The error string is written to that task's output_key, and downstream tasks still run — they just read an error in their upstream context, so results degrade rather than halt. |
Later tasks show "status": "skipped" | max_total_iterations (default 30) was exhausted by earlier tasks | A (skipped — iteration limit reached) marker is written. Raise max_total_iterations or tighten each agent's max_iterations. |
A task ignores the kickoff inputs | Only root tasks (no depends_on) receive the raw inputs; downstream tasks see only their dependencies' outputs | Make the task a root, or add the producing task to its depends_on. |
| Downstream task missing upstream data | It doesn't list the producer in depends_on | The blackboard exposes a task only the keys its depends_on produced — add the dependency. |
Reference
- Concept + full config/shape: Squads
- Member agents: Agents · Build an agent
- Endpoints + contracts: Squad API