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).
| Tool | Type | Action | Underlying call |
|---|---|---|---|
composer_list_task_types | read | List available workflow node types | GET /api/workflows/task-types |
composer_list_agents | read | Search existing agents | GET /api/agents |
composer_list_workflows | read | Search existing workflows | GET /api/activities |
composer_list_squads | read | Search existing squads | GET /api/squads |
composer_list_prompts | read | Search existing prompts | GET /api/prompts |
composer_create_workflow | write | Create a workflow (as a draft) | POST /api/activities |
composer_create_agent | write | Create an agent | POST /api/agents |
composer_create_squad | write | Create a squad | POST /api/squads |
composer_create_prompt | write | Create a prompt | POST /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 type | Purpose |
|---|---|
workflow_preview | A proposed workflow — render as a step list with task-type badges. |
agent_preview | A proposed agent — render as a config card (goal, model, tools). |
squad_preview | A proposed squad — render the team plus task pipeline. |
prompt_preview | A proposed prompt — render its structure (roles, type). |
navigation | A clickable link — route the user to a page when clicked. |
creation_result | A 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, navigationconst 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_mappingwiring 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.