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 documentation

Exploring 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:

VariableRequiredExampleWhen Needed
OPENAI_API_KEYFor AI featuressk-proj-...When calling OpenAI-hosted models
OPENROUTER_API_KEYFor AI featuressk-or-v1-...When using openrouter/... model IDs
AGENT_CALLBACK_URLFor Dockerhttp://host.docker.internal:8001When control plane runs in Docker, agent on host
AGENTFIELD_SERVEROptionalhttp://localhost:8080Default 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 analysis
app.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:8001

This is required because Docker containers cannot reach localhost on the host. See the Docker Deployment Guide for complete details.


Troubleshooting


Production Deployment


Next Steps