docs/devguide/ai/first-ai-agent.md
Build a durable AI agent in 5 minutes. Your agent will discover tools, plan actions, execute them, and summarize results — with full crash recovery, observability, and human approval built in.
Prerequisites:
conductor server start)Your agent needs tools to call. MCP (Model Context Protocol) is the open standard for connecting AI agents to tools. Start a simple MCP server — or use any MCP server you already have running.
# Example: start a weather MCP server (Node.js)
npx -y @anthropic-ai/create-mcp --template weather
Note the URL where your MCP server is running (e.g., http://localhost:3001/mcp). You'll use this in the workflow definition.
!!! tip "Any MCP server works" Conductor connects to any MCP-compatible server. Use community MCP servers for GitHub, Slack, databases, or any API — or build your own. See the MCP integration guide for details.
Add your API key to Conductor's configuration. If you started with the CLI, edit ~/.conductor/config.properties:
conductor.integrations.ai.enabled=true
# Choose one (or both):
conductor.ai.openai.apiKey=sk-your-openai-key
conductor.ai.anthropic.apiKey=sk-ant-your-anthropic-key
Restart the server after updating the configuration.
Save this as my_first_agent.json. This is a complete AI agent in four tasks — no custom code, no workers, no framework:
{
"name": "my_first_agent",
"description": "AI agent that discovers tools, plans, executes, and summarizes",
"version": 1,
"schemaVersion": 2,
"inputParameters": ["task", "mcpServerUrl"],
"tasks": [
{
"name": "discover_tools",
"taskReferenceName": "discover",
"type": "LIST_MCP_TOOLS",
"inputParameters": {
"mcpServer": "${workflow.input.mcpServerUrl}"
}
},
{
"name": "plan_action",
"taskReferenceName": "plan",
"type": "LLM_CHAT_COMPLETE",
"inputParameters": {
"llmProvider": "openai",
"model": "gpt-4o-mini",
"messages": [
{
"role": "system",
"message": "You are an AI agent. Available tools: ${discover.output.tools}. The user wants to: ${workflow.input.task}. Decide which tool to use. Respond with JSON: {\"method\": \"tool_name\", \"arguments\": {}}"
},
{
"role": "user",
"message": "${workflow.input.task}"
}
],
"temperature": 0.1,
"maxTokens": 500
}
},
{
"name": "execute_tool",
"taskReferenceName": "execute",
"type": "CALL_MCP_TOOL",
"inputParameters": {
"mcpServer": "${workflow.input.mcpServerUrl}",
"method": "${plan.output.result.method}",
"arguments": "${plan.output.result.arguments}"
}
},
{
"name": "summarize_result",
"taskReferenceName": "summarize",
"type": "LLM_CHAT_COMPLETE",
"inputParameters": {
"llmProvider": "openai",
"model": "gpt-4o-mini",
"messages": [
{
"role": "user",
"message": "The user asked: ${workflow.input.task}\n\nTool result: ${execute.output.content}\n\nSummarize this clearly for the user."
}
],
"maxTokens": 500
}
}
],
"outputParameters": {
"plan": "${plan.output.result}",
"toolResult": "${execute.output.content}",
"summary": "${summarize.output.result}"
}
}
What each task does:
| Task | Type | Purpose |
|---|---|---|
discover | LIST_MCP_TOOLS | Queries the MCP server to discover available tools |
plan | LLM_CHAT_COMPLETE | Sends the tool list + user task to the LLM, which picks a tool and arguments |
execute | CALL_MCP_TOOL | Calls the selected tool on the MCP server |
summarize | LLM_CHAT_COMPLETE | Summarizes the raw tool output for the user |
Every task is a native Conductor system task. No workers to write, no code to deploy.
# Register the workflow
conductor workflow create my_first_agent.json
# Run the agent
curl -X POST 'http://localhost:8080/api/workflow/my_first_agent' \
-H 'Content-Type: application/json' \
-d '{
"task": "What is the weather in San Francisco?",
"mcpServerUrl": "http://localhost:3001/mcp"
}'
Open http://localhost:8080 to see the execution. Click into the workflow to see each task's input, output, and timing.
!!! success "What just happened" Your agent discovered tools from an MCP server, asked an LLM to pick the right one, executed it, and summarized the result. Every step was persisted — if the server had crashed at any point, execution would have resumed from the last completed task. No tokens wasted, no progress lost.
Real agents need guardrails. Add a HUMAN task between planning and execution so a person reviews the agent's plan before it acts.
Update my_first_agent.json — insert this task between plan_action and execute_tool:
{
"name": "human_review",
"taskReferenceName": "approval",
"type": "HUMAN",
"inputParameters": {
"plannedAction": "${plan.output.result}",
"userTask": "${workflow.input.task}"
}
}
Now when you run the agent, it pauses after planning and waits for human approval. Approve it via the UI or API:
# Approve the plan (replace TASK_ID with the actual task ID from the execution)
curl -X POST 'http://localhost:8080/api/tasks' \
-H 'Content-Type: application/json' \
-d '{
"workflowInstanceId": "WORKFLOW_ID",
"taskId": "TASK_ID",
"status": "COMPLETED",
"outputData": {"approved": true, "reviewer": "you"}
}'
The approval is durable — the workflow stays paused indefinitely, even across server restarts and deploys, until someone approves it.
Turn your agent into an autonomous loop that keeps working until the task is done. Replace the linear workflow with a DO_WHILE loop:
{
"name": "autonomous_agent",
"description": "Agent that loops until the task is complete",
"version": 1,
"schemaVersion": 2,
"inputParameters": ["task", "mcpServerUrl"],
"tasks": [
{
"name": "discover_tools",
"taskReferenceName": "discover",
"type": "LIST_MCP_TOOLS",
"inputParameters": {
"mcpServer": "${workflow.input.mcpServerUrl}"
}
},
{
"name": "agent_loop",
"taskReferenceName": "loop",
"type": "DO_WHILE",
"loopCondition": "if ($.loop['think'].output.result.done == true) { false; } else { true; }",
"loopOver": [
{
"name": "think",
"taskReferenceName": "think",
"type": "LLM_CHAT_COMPLETE",
"inputParameters": {
"llmProvider": "openai",
"model": "gpt-4o-mini",
"messages": [
{
"role": "system",
"message": "You are an autonomous agent. Available tools: ${discover.output.tools}. Previous results: ${loop.output.results}. Respond with JSON: {\"action\": \"tool_name\", \"arguments\": {}, \"done\": false} when you need to use a tool, or {\"answer\": \"final answer\", \"done\": true} when the task is complete."
},
{
"role": "user",
"message": "${workflow.input.task}"
}
],
"temperature": 0.1
}
},
{
"name": "act",
"taskReferenceName": "act",
"type": "SWITCH",
"evaluatorType": "javascript",
"expression": "$.think.output.result.done ? 'done' : 'call_tool'",
"decisionCases": {
"call_tool": [
{
"name": "execute_tool",
"taskReferenceName": "tool_call",
"type": "CALL_MCP_TOOL",
"inputParameters": {
"mcpServer": "${workflow.input.mcpServerUrl}",
"method": "${think.output.result.action}",
"arguments": "${think.output.result.arguments}"
}
}
]
},
"defaultCase": []
}
]
}
],
"outputParameters": {
"answer": "${loop.output.think.output.result.answer}",
"iterations": "${loop.output.iteration}"
}
}
Each iteration of the loop is a durable checkpoint. If the agent crashes at iteration 12, it resumes from iteration 12 — not from the beginning. Every LLM call and tool call is persisted and observable.
In 5 minutes, you built an AI agent that:
All of this with zero custom code. The entire agent is a JSON workflow definition that Conductor executes with durable execution guarantees.