TurfAITurfAI Developers
Guides

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's slug.
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 id
const 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

SymptomCauseWhat happens / fix
Create returns a validation errorCircular 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/activeThe 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 tasksA (skipped — iteration limit reached) marker is written. Raise max_total_iterations or tighten each agent's max_iterations.
A task ignores the kickoff inputsOnly root tasks (no depends_on) receive the raw inputs; downstream tasks see only their dependencies' outputsMake the task a root, or add the producing task to its depends_on.
Downstream task missing upstream dataIt doesn't list the producer in depends_onThe blackboard exposes a task only the keys its depends_on produced — add the dependency.

Reference

On this page