Build Your First Agent
Deep dive into agent development with Agentfield
Build Your First Agent
Understand the architecture and build production-ready agents
This guide takes you beyond the quick start. You'll understand the generated code, add AI-powered reasoners, configure different languages, and learn production patterns.
Prerequisites
- Completed the Quick Start
- Agentfield CLI installed
- Agentfield server running (
af server)
Understanding Agent Creation
When you run af init my-agent, the CLI prompts you interactively (unless you use --defaults):
🎯 Creating Agentfield Agent
? Select language:
❯ Python
Go
? Author name: Jane Smith
? Author email: jane@company.com
✨ Creating project structure...
✓ main.py (or main.go)
✓ reasoners.py (or reasoners.go)
✓ requirements.txt (or go.mod)
✓ agentfield.yaml
✓ README.md
🚀 Agent 'my-agent' created successfully!Python vs Go: Choose Your Path
Choose Python if:
- You need rich AI/ML ecosystem (transformers, langchain, etc.)
- Rapid prototyping is priority
- You're comfortable with virtual environments
Trade-off: Deployment requires Python runtime and dependencies
Choose Go if:
- You want single binary deployment
- No runtime dependencies in production
- You need maximum performance
Trade-off: Smaller AI/ML ecosystem compared to Python
Both languages get the same production features: REST APIs, async execution, identity, and observability.
Generated Project Structure
my-agent/
├── agentfield.yaml # Agent configuration
├── main.py # Agent entry point
├── reasoners.py # AI-powered functions
├── requirements.txt # Dependencies
└── README.md # Project documentationExploring the Generated Code
The generated agent works immediately with no API keys required.
Enabling AI Features
Want to use AI-powered reasoners? Follow these steps:
Uncomment AI Configuration
In main.py (or main.go), uncomment the ai_config section:
app = Agent(
node_id="my-agent",
agentfield_server="http://localhost:8080",
version="1.0.0",
dev_mode=True,
ai_config=AIConfig(
model="gpt-4o",
temperature=0.2,
),
)app := agent.New(agent.Config{
NodeID: "my-agent",
AgentFieldURL: "http://localhost:8080",
Version: "1.0.0",
ListenAddress: ":0",
AIConfig: &ai.Config{
Model: "gpt-4o",
APIKey: os.Getenv("OPENAI_API_KEY"),
BaseURL: "https://api.openai.com/v1",
Temperature: 0.2,
},
})Set Your API Key
Either set it in the code or use an environment variable:
export OPENAI_API_KEY=sk-...
# or
export OPENROUTER_API_KEY=sk-or-...Environment Variables Reference:
| Variable | Required | Example | When Needed |
|---|---|---|---|
OPENAI_API_KEY | For AI features | sk-proj-... | When calling OpenAI-hosted models |
OPENROUTER_API_KEY | For AI features | sk-or-v1-... | When using openrouter/... model IDs |
AGENT_CALLBACK_URL | For Docker | http://host.docker.internal:8001 | When control plane runs in Docker, agent on host |
AGENTFIELD_SERVER | Optional | http://localhost:8080 | Default works for local development |
Local development with default settings? You only need one AI key to enable AI features. Everything else works out-of-the-box.
For production deployments and advanced configuration, see:
Uncomment AI Reasoners
In reasoners.py (or reasoners.go), uncomment the analyze_sentiment reasoner.
Restart Your Agent
python main.py # or: go run .Test AI Reasoner
curl -X POST http://localhost:8080/api/v1/execute/my-agent.demo_analyze_sentiment \
-H "Content-Type: application/json" \
-d '{
"input": {
"text": "I love building with Agentfield!"
}
}'Response:
{
"execution_id": "exec_20251117_161355_dd2rdzzb",
"status": "succeeded",
"result": {
"confidence": 0.95,
"key_phrases": ["love building", "Agentfield"],
"reasoning": "The text expresses strong positive emotion...",
"sentiment": "positive"
},
"duration_ms": 2943
}Building Custom Reasoners
Basic AI-Powered Reasoner
from pydantic import BaseModel, Field
class CategoryResult(BaseModel):
category: str = Field(description="tech, business, health, or sports")
confidence: float = Field(ge=0.0, le=1.0)
reasoning: str
@reasoners_router.reasoner()
async def categorize_text(text: str) -> dict:
"""Categorize text into predefined categories"""
result = await reasoners_router.ai(
system="You are a text categorization expert. Categories: tech, business, health, sports.",
user=f"Categorize this text: {text}",
schema=CategoryResult
)
return result.model_dump()type CategoryResult struct {
Category string `json:"category"`
Confidence float64 `json:"confidence"`
Reasoning string `json:"reasoning"`
}
app.RegisterReasoner("categorize_text", func(ctx context.Context, input map[string]any) (any, error) {
text, _ := input["text"].(string)
var result CategoryResult
err := app.AI(ctx, agent.AIRequest{
System: "You are a text categorization expert. Categories: tech, business, health, sports.",
User: "Categorize this text: " + text,
Schema: &result,
})
return result, err
})Multi-Step Reasoning
@reasoners_router.reasoner()
async def research_topic(topic: str) -> dict:
"""Multi-step research workflow"""
# Step 1: Generate search queries
queries = await reasoners_router.ai(
system="Generate 3 search queries for research.",
user=f"Topic: {topic}",
schema=list[str]
)
# Step 2: Call search skill (cross-agent communication)
results = []
for query in queries:
result = await app.call(
"search-agent.web_search",
query=query
)
results.append(result)
# Step 3: Synthesize findings
summary = await reasoners_router.ai(
system="Synthesize research findings into a coherent summary.",
user=f"Findings: {results}",
schema=str
)
return {
"topic": topic,
"queries": queries,
"summary": summary
}app.RegisterReasoner("research_topic", func(ctx context.Context, input map[string]any) (any, error) {
topic, _ := input["topic"].(string)
// Step 1: Generate search queries
var queries []string
app.AI(ctx, agent.AIRequest{
System: "Generate 3 search queries for research.",
User: "Topic: " + topic,
Schema: &queries,
})
// Step 2: Call search skill
var results []interface{}
for _, query := range queries {
result, _ := app.Call(ctx, "search-agent.web_search", map[string]string{
"query": query,
})
results = append(results, result)
}
// Step 3: Synthesize findings
var summary string
app.AI(ctx, agent.AIRequest{
System: "Synthesize research findings.",
User: fmt.Sprintf("Findings: %v", results),
Schema: &summary,
})
return map[string]interface{}{
"topic": topic,
"queries": queries,
"summary": summary,
}, nil
})Cross-Agent Communication
Call other agents' reasoners and skills using app.call():
@reasoners_router.reasoner()
async def analyze_with_context(text: str) -> dict:
"""Use multiple agents for comprehensive analysis"""
# Call sentiment agent
sentiment = await app.call(
"sentiment-agent.analyze",
text=text
)
# Call entity extraction agent
entities = await app.call(
"nlp-agent.extract_entities",
text=text
)
# Synthesize results
analysis = await reasoners_router.ai(
system="Combine sentiment and entity analysis.",
user=f"Sentiment: {sentiment}, Entities: {entities}",
schema=dict
)
return analysisapp.RegisterReasoner("analyze_with_context", func(ctx context.Context, input map[string]any) (any, error) {
text, _ := input["text"].(string)
// Call sentiment agent
sentiment, _ := app.Call(ctx, "sentiment-agent.analyze", map[string]string{
"text": text,
})
// Call entity extraction agent
entities, _ := app.Call(ctx, "nlp-agent.extract_entities", map[string]string{
"text": text,
})
// Synthesize results
var analysis map[string]interface{}
app.AI(ctx, agent.AIRequest{
System: "Combine sentiment and entity analysis.",
User: fmt.Sprintf("Sentiment: %v, Entities: %v", sentiment, entities),
Schema: &analysis,
})
return analysis, nil
})The control plane handles:
- Service discovery
- Load balancing
- Workflow tracking
- Error handling
Memory: Shared State Across Agents
Store and retrieve data across agent executions:
@reasoners_router.reasoner()
async def remember_preference(user_id: str, preference: str) -> dict:
"""Store user preference in shared memory"""
# Store in memory
await app.memory.set(
key=f"user:{user_id}:preference",
value=preference,
ttl=86400 # 24 hours
)
return {"status": "saved", "user_id": user_id}
@reasoners_router.reasoner()
async def get_preference(user_id: str) -> dict:
"""Retrieve user preference"""
preference = await app.memory.get(
key=f"user:{user_id}:preference"
)
return {"user_id": user_id, "preference": preference}app.RegisterReasoner("remember_preference", func(ctx context.Context, input map[string]any) (any, error) {
userID, _ := input["user_id"].(string)
preference, _ := input["preference"].(string)
app.Memory.Set(ctx, agent.MemoryKey{
Key: "user:" + userID + ":preference",
Value: preference,
TTL: 86400,
})
return map[string]string{
"status": "saved",
"user_id": userID,
}, nil
})Docker Deployment Considerations
Running Control Plane in Docker?
If you're running the Agentfield control plane in Docker but your agent on the host machine, you'll need to set the callback URL:
export AGENT_CALLBACK_URL=http://host.docker.internal:8001This is required because Docker containers cannot reach localhost on the host. See the Docker Deployment Guide for complete details.