TurfAITurfAI Developers
Concepts

Composer

A system agent that builds workflows, agents, squads, and prompts from natural language.

Composer is a built-in system agent (slug turfai-composer) that turns plain-English requests into TurfAI building blocks — workflows, agents, squads, and prompts. Instead of hand-authoring node graphs, task types, and input mappings, you describe the outcome ("build a workflow that fetches invoices from Drive, extracts the total, and emails a summary") and Composer designs it, shows a preview, and creates it on confirmation.

It is the same agent that powers the product's conversational builder, and because it is a public system agent you can drive it directly over the API.

Composer is Phase 1 (foundation, backend agent, and tools). It runs the standard agent ReAct loop, so a request with tool calls can take 15–40 seconds to return. Enhanced previews, context awareness, and streaming are on the roadmap (see What's next).

How it works

Composer is not a frontend LLM call — the intelligence lives in a backend agent so it reuses the existing ReAct loop, tool execution, session management, and model routing. A request flows like this:

The agent reasons over your request, silently calls read tools to inspect what already exists and which task types are available, designs the component, and returns an answer string. Structured results are embedded in that text as :::composer_block markers (see Structured response protocol), which the calling client parses into UI — or which you parse yourself when calling over the API.

The nine composer tools

The Composer agent has exactly nine tools, registered in the processor's tool registry. Five are read-only (to ground the design in existing resources) and four are writes (to create the building block).

ToolTypeActionUnderlying call
composer_list_task_typesreadList available workflow node typesGET /api/workflows/task-types
composer_list_agentsreadSearch existing agentsGET /api/agents
composer_list_workflowsreadSearch existing workflowsGET /api/activities
composer_list_squadsreadSearch existing squadsGET /api/squads
composer_list_promptsreadSearch existing promptsGET /api/prompts
composer_create_workflowwriteCreate a workflow (as a draft)POST /api/activities
composer_create_agentwriteCreate an agentPOST /api/agents
composer_create_squadwriteCreate a squadPOST /api/squads
composer_create_promptwriteCreate a promptPOST /api/prompts

Workflows are created with status: 'draft', so strict workflow validation is skipped at creation time — you can refine a draft before activating it. The list tools are used silently to inform the design; Composer does not dump full inventories back to you.

The exact JSON argument schema for each tool (field names and shapes for composer_create_workflow, composer_create_agent, etc.) and the tool internals are not yet documented here — treat the table above as the stable surface.

Structured response protocol

Composer embeds structured output as fenced :::composer_block markers inside the answer text. Each marker wraps a single JSON object with a type field. A typical reply looks like:

Here's the workflow I designed:

:::composer_block
{"type": "workflow_preview", "data": { ... }, "draft_id": "draft_001"}
:::

Want me to make changes, or shall I create it?

Your client splits the answer on these markers, parses the JSON, and renders the corresponding component (everything outside the markers is normal prose to display as-is).

Block types:

Block typePurpose
workflow_previewA proposed workflow — render as a step list with task-type badges.
agent_previewA proposed agent — render as a config card (goal, model, tools).
squad_previewA proposed squad — render the team plus task pipeline.
prompt_previewA proposed prompt — render its structure (roles, type).
navigationA clickable link — route the user to a page when clicked.
creation_resultA success card after a write, with an "Open" action for the new entity.

The per-block data JSON schema is not yet documented — field names inside each block may change while Composer is in Phase 1. Parse on the type field, render the prose around the markers, and treat the inner data shape as best-effort until it is finalized.

Calling Composer over the API

Composer is a public system agent, so you talk to it through the standard POST /api/agents/:slug/public-chat endpoint with slug = turfai-composer. Send a query and reuse session_id across turns to keep the conversation going. The response is the normal public-chat shape — { answer, trace, tools_used, session_id } — where answer carries the prose plus any embedded :::composer_block markers.

BASE="https://apisandbox.turfai.in/api"

curl -X POST "$BASE/agents/turfai-composer/public-chat" \
  -H "Authorization: Bearer $TURFAI_JWT" \
  -H "Content-Type: application/json" \
  -d '{
    "query": "Build a workflow that extracts the total from an uploaded invoice PDF and emails a summary",
    "session_id": "composer-demo-1"
  }'

# Response: { "answer": "...:::composer_block...:::...", "trace": "...",
#             "tools_used": [...], "session_id": "composer-demo-1" }
import os, re, json, requests

BASE = "https://apisandbox.turfai.in/api"
HEAD = {"Authorization": f"Bearer {os.environ['TURFAI_JWT']}", "Content-Type": "application/json"}

resp = requests.post(
    f"{BASE}/agents/turfai-composer/public-chat",
    headers=HEAD,
    json={
        "query": "Create an agent that answers questions about company policies",
        "session_id": "composer-demo-1",
    },
).json()

# `answer` carries prose plus embedded :::composer_block markers.
answer = resp["answer"]
blocks = [
    json.loads(b) for b in re.findall(r":::composer_block\s*(\{.*?\})\s*:::", answer, re.S)
]
for block in blocks:
    print(block["type"])  # e.g. agent_preview, creation_result, navigation
const BASE = "https://apisandbox.turfai.in/api";

const resp = await fetch(`${BASE}/agents/turfai-composer/public-chat`, {
  method: "POST",
  headers: {
    Authorization: `Bearer ${process.env.TURFAI_JWT}`,
    "Content-Type": "application/json",
  },
  body: JSON.stringify({
    query: "Build a squad that researches a topic then writes a report",
    session_id: "composer-demo-1",
  }),
}).then((r) => r.json());

// `answer` carries prose plus embedded :::composer_block markers.
const blocks = [...resp.answer.matchAll(/:::composer_block\s*(\{[\s\S]*?\})\s*:::/g)].map(
  (m) => JSON.parse(m[1]),
);
blocks.forEach((b) => console.log(b.type)); // squad_preview, creation_result, ...

The write tools create real resources in your tenant (drafts for workflows). Treat a creation_result block as a side effect, not just text — confirm with the user before prompting Composer to create.

What's next

Phase 1 is the foundation. The following are coming soon and not yet available:

  • Phase 2 — Enhanced previews: mini canvas rendering for workflow previews, auto-layout, and smarter input_mapping wiring between nodes.
  • Phase 3 — Context awareness: Composer learns the page you're on, can explain and suggest improvements to existing components, and supports "fork and customize" through conversation.
  • Phase 4 — Polish: session history, mobile-responsive UI, streaming responses (today the call waits for the full agent response), and actionable error recovery.

Reference

On this page