Agent Package

Core Agent type for building distributed agent systems in Go

Agent Package

Core Agent type for lifecycle management, reasoner registration, and Agentfield integration

The agent package provides the core Agent type that manages HTTP servers, registers reasoners, integrates AI capabilities, and communicates with the Agentfield control plane.

Creating an Agent

import (
    "github.com/Agent-Field/agentfield/sdk/go/agent"
    "github.com/Agent-Field/agentfield/sdk/go/ai"
)

app, err := agent.New(agent.Config{
    NodeID:        "my-agent",
    Version:       "1.0.0",
    TeamID:        "my-team",
    AgentFieldURL:      "http://localhost:8080",
    ListenAddress: ":8001",
    PublicURL:     "http://localhost:8001",
    Token:         os.Getenv("BRAIN_TOKEN"),
    AIConfig:      aiConfig,  // Optional: nil disables AI
})

Configuration

Prop

Type

CLIConfig Fields

Enable your agent binary to run as a CLI tool with CLIConfig:

Prop

Type

Example:

app, err := agent.New(agent.Config{
    NodeID: "my-tool",
    AgentFieldURL: os.Getenv("AGENTFIELD_URL"), // Optional for CLI mode
    CLIConfig: &agent.CLIConfig{
        AppName:        "my-tool",
        AppDescription: "Production-ready CLI with control plane integration",
        HelpPreamble:   "⚠️  Set API_KEY environment variable before running",
        EnvironmentVars: []string{
            "API_KEY (required) - Your API key",
            "AGENTFIELD_URL (optional) - Control plane URL for server mode",
        },
    },
})

See CLI Configuration for complete documentation.

Agent Methods

Lifecycle Methods

Initialize(ctx context.Context) error

Registers the agent with the Agentfield control plane and discovers reasoners.

if err := app.Initialize(ctx); err != nil {
    log.Fatalf("Failed to initialize: %v", err)
}

When to call: Call Initialize() before Run() to register with Agentfield. For serverless agents, this can be called on each invocation or once during cold start.

Run(ctx context.Context) error

Starts the HTTP server and begins serving reasoner requests. Blocks until context is cancelled.

// Blocking - runs until ctx is cancelled
if err := app.Run(ctx); err != nil {
    log.Fatal(err)
}

Reasoner Methods

RegisterReasoner(name string, handler HandlerFunc, opts ...ReasonerOption)

Registers a reasoner that Agentfield can execute.

app.RegisterReasoner("process_data", func(ctx context.Context, input map[string]any) (any, error) {
    // Process input
    data := input["data"].(string)

    // Return structured result
    return map[string]any{
        "status": "completed",
        "result": processedData,
    }, nil
})

Handler Requirements:

  • Must accept context.Context as first parameter
  • Must accept map[string]any as input
  • Must return (any, error)
  • Should return JSON-serializable types

Call(ctx context.Context, target string, input map[string]any) (map[string]any, error)

Calls another reasoner via the Agentfield control plane. Automatically creates parent-child workflow relationships.

// Calls another agent's reasoner
result, err := app.Call(ctx, "other-agent.process", map[string]any{
    "data": "hello",
})

// Calls own reasoner (creates parent-child link)
result, err := app.Call(ctx, "my-agent.analyze", input)

Automatic Workflow Tracking: The SDK automatically:

  • Extracts current execution context from ctx
  • Sets X-Parent-Execution-ID header
  • Agentfield creates parent-child relationship
  • Builds workflow DAG for visualization

AI Methods

AI(ctx context.Context, prompt string, opts ...ai.Option) (*ai.Response, error)

Makes an AI/LLM call with structured output support.

response, err := app.AI(ctx, "What is 2+2?",
    ai.WithSystem("You are a math tutor"),
    ai.WithTemperature(0.3))

fmt.Println(response.Text())  // "4"

Returns error if AIConfig was not set during agent creation.

AIStream(ctx context.Context, prompt string, opts ...ai.Option) (<-chan ai.StreamChunk, <-chan error)

Makes a streaming AI call. Returns channels for chunks and errors.

chunks, errs := app.AIStream(ctx, "Tell me a story")

for chunk := range chunks {
    if len(chunk.Choices) > 0 {
        fmt.Print(chunk.Choices[0].Delta.Content)
    }
}

if err := <-errs; err != nil {
    log.Printf("Stream error: %v", err)
}

Common Patterns

Long-Running Agent

Standard deployment for persistent agents:

func main() {
    ctx := context.Background()

    app, err := agent.New(config)
    if err != nil {
        log.Fatal(err)
    }

    // Register reasoners
    app.RegisterReasoner("task1", handler1)
    app.RegisterReasoner("task2", handler2)

    // Initialize with Agentfield
    if err := app.Initialize(ctx); err != nil {
        log.Fatal(err)
    }

    // Run forever (blocks)
    log.Fatal(app.Run(ctx))
}

Serverless Agent

Optimized for Lambda/Cloud Functions/Cloud Run with adapters:

var app *agent.Agent

func init() {
    // Create agent once during cold start
    app, _ = agent.New(agent.Config{
        NodeID:           "serverless-agent",
        AgentFieldURL:    os.Getenv("AGENTFIELD_URL"),
        AIConfig:         aiConfig,
        DeploymentType:   "serverless",
        DisableLeaseLoop: true, // No heartbeats in serverless
    })

    app.RegisterReasoner("process", handler)

    // Initialize once
    ctx := context.Background()
    _ = app.Initialize(ctx)
}

// HTTP handler (Cloud Run / Functions)
// http.ListenAndServe(":8080", app.Handler())

// Raw event handler (Lambda-style)
func LambdaHandler(ctx context.Context, event map[string]any) (map[string]any, error) {
    normalize := func(e map[string]any) map[string]any {
        return map[string]any{
            "path":   stringFrom(e, "rawPath", "path"),
            "target": stringFrom(e, "target", "reasoner", "skill"),
            "input":  e["input"],
        }
    }
    result, status, err := app.HandleServerlessEvent(ctx, event, normalize)
    if err != nil {
        return map[string]any{"statusCode": 500, "body": map[string]any{"error": err.Error()}}, nil
    }
    return map[string]any{"statusCode": status, "body": result}, nil
}

AI-Powered Reasoner

Use AI within reasoners for intelligent processing:

type Analysis struct {
    Summary    string   `json:"summary"`
    Confidence float64  `json:"confidence"`
    Tags       []string `json:"tags"`
}

app.RegisterReasoner("analyze", func(ctx context.Context, input map[string]any) (any, error) {
    text := input["text"].(string)

    // AI call with structured output
    response, err := app.AI(ctx,
        fmt.Sprintf("Analyze: %s", text),
        ai.WithSchema(Analysis{}))
    if err != nil {
        return nil, err
    }

    var analysis Analysis
    if err := response.Into(&analysis); err != nil {
        return nil, err
    }

    return analysis, nil
})

Parent-Child Workflow

Orchestrate multiple reasoners automatically:

app.RegisterReasoner("orchestrate", func(ctx context.Context, input map[string]any) (any, error) {
    // These calls automatically create parent-child relationships
    result1, err := app.Call(ctx, "my-agent.step1", input)
    if err != nil {
        return nil, err
    }

    result2, err := app.Call(ctx, "my-agent.step2", result1)
    if err != nil {
        return nil, err
    }

    result3, err := app.Call(ctx, "my-agent.step3", result2)
    if err != nil {
        return nil, err
    }

    // Agentfield automatically tracks: orchestrate -> step1 -> step2 -> step3
    return result3, nil
})

Parallel Execution

Use goroutines for parallel reasoner calls:

app.RegisterReasoner("parallel_process", func(ctx context.Context, input map[string]any) (any, error) {
    var wg sync.WaitGroup
    results := make([]map[string]any, 4)

    for i := 0; i < 4; i++ {
        wg.Add(1)
        go func(index int) {
            defer wg.Done()

            // Each goroutine creates its own child execution
            result, err := app.Call(ctx, "my-agent.worker", map[string]any{
                "index": index,
                "data":  input,
            })
            if err == nil {
                results[index] = result
            }
        }(i)
    }

    wg.Wait()

    // Agentfield tracks all 4 parallel executions as children
    return map[string]any{"results": results}, nil
})

Dual-Mode Agent (CLI + Server)

Build agents that work as CLI tools or control plane agents:

package main

import (
    "context"
    "fmt"
    "log"
    "os"

    "github.com/Agent-Field/agentfield/sdk/go/agent"
)

func main() {
    app, err := agent.New(agent.Config{
        NodeID:        "my-tool",
        AgentFieldURL: os.Getenv("AGENTFIELD_URL"), // Optional for CLI mode

        CLIConfig: &agent.CLIConfig{
            AppName:        "my-tool",
            AppDescription: "Production tool with control plane integration",
            HelpEpilog: `Examples:
  # CLI mode (standalone)
  $ ./my-tool --set input="data"

  # Server mode (with control plane)
  $ AGENTFIELD_URL=http://localhost:8080 ./my-tool serve`,
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    app.RegisterReasoner("process", func(ctx context.Context, input map[string]any) (any, error) {
        data := input["input"].(string)
        return fmt.Sprintf("Processed: %s", data), nil
    },
        agent.WithCLI(),        // Enable CLI access
        agent.WithDefaultCLI(), // Make this the default command
    )

    // Automatically detects CLI or server mode
    if err := app.Run(context.Background()); err != nil {
        log.Fatal(err)
    }
}

Usage:

# Build once
go build -o my-tool

# CLI mode (no control plane)
./my-tool --set input="hello"

# Server mode (with control plane)
AGENTFIELD_URL=http://localhost:8080 ./my-tool serve

See CLI Mode Overview for complete documentation.

Best Practices

Always Use Context

Pass context.Context through all calls for proper cancellation and timeout handling:

// ✅ Good - respects timeouts
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()

result, err := app.Call(ctx, "my-agent.task", input)

Error Handling

Always handle errors from reasoner calls:

// ✅ Good - handles errors
result, err := app.Call(ctx, "my-agent.risky_task", input)
if err != nil {
    // Log and return structured error
    return map[string]any{
        "status": "failed",
        "error":  err.Error(),
    }, nil
}

Structured Returns

Use maps or Go structs for JSON-serializable returns:

// ✅ Good - structured response
return map[string]any{
    "status":  "success",
    "result":  data,
    "elapsed": elapsed.Milliseconds(),
}, nil

// ❌ Bad - not JSON-serializable
return myComplexStruct, nil  // Unless it has json tags