AgenticGoKit is currently in Beta. APIs may change before the stable v1.0 release.
Skip to content

Agent Basics

Understanding the AgenticGoKit Agent System

This guide covers the fundamental concepts of building agents in AgenticGoKit, from the basic interfaces to advanced multi-agent orchestration patterns.

Core Concepts

AgentHandler Interface

The AgentHandler is the primary interface for implementing agent logic in AgenticGoKit:

go
type AgentHandler interface {
    Run(ctx context.Context, event Event, state State) (AgentResult, error)
}

Key Components:

  • Event: Contains the user input and metadata
  • State: Thread-safe storage for agent data
  • AgentResult: The agent's response and updated state

Multi-Agent Orchestration

AgenticGoKit supports multiple orchestration patterns for coordinating agents:

Collaborative Orchestration

All agents process the same event in parallel (config-driven):

go
runner, _ := core.NewRunnerFromConfig("agentflow.toml")
_ = runner.RegisterAgent("researcher", NewResearchAgent())
_ = runner.RegisterAgent("analyzer", NewAnalysisAgent())
_ = runner.RegisterAgent("validator", NewValidationAgent())

Sequential Orchestration

Agents process events in pipeline order:

go
runner, _ := core.NewRunnerFromConfig("agentflow.toml")
_ = runner.RegisterAgent("collector", NewCollectorAgent())
_ = runner.RegisterAgent("processor", NewProcessorAgent())
_ = runner.RegisterAgent("formatter", NewFormatterAgent())

Loop Orchestration

Single agent repeats until conditions are met:

go
runner, _ := core.NewRunnerFromConfig("agentflow.toml")
_ = runner.RegisterAgent("quality-checker", NewQualityCheckerAgent())

Basic Agent Structure

Every agent follows this pattern:

go
package main

import (
    "context"
    "fmt"
    agentflow "github.com/kunalkushwaha/agenticgokit/core"
)

type MyAgentHandler struct {
    llm        agentflow.ModelProvider
    mcpManager agentflow.MCPManager
    name       string
}

func NewMyAgent(name string, llm agentflow.ModelProvider, mcp agentflow.MCPManager) *MyAgentHandler {
    return &MyAgentHandler{
        name:       name,
        llm:        llm,
        mcpManager: mcp,
    }
}

func (a *MyAgentHandler) Run(ctx context.Context, event agentflow.Event, state agentflow.State) (agentflow.AgentResult, error) {
    logger := agentflow.Logger()
    logger.Info().Str("agent", a.name).Msg("Processing request")
    
    // 1. Extract input from event
    eventData := event.GetData()
    message, ok := eventData["message"]
    if !ok {
        return agentflow.AgentResult{}, fmt.Errorf("no message in event data")
    }
    
    // 2. Build system prompt
    systemPrompt := "You are a helpful assistant."
    
    // 3. Add available tools to prompt
    toolPrompt := ""
    if a.mcpManager != nil {
        toolPrompt = agentflow.FormatToolsForPrompt(ctx, a.mcpManager)
    }
    
    fullPrompt := fmt.Sprintf("%s\n%s\nUser: %s", systemPrompt, toolPrompt, message)
    
    // 4. Call LLM
    response, err := a.llm.Generate(ctx, fullPrompt)
    if err != nil {
        return agentflow.AgentResult{}, fmt.Errorf("LLM call failed: %w", err)
    }
    
    // 5. Execute any tool calls
    var finalResponse string
    if a.mcpManager != nil {
        toolResults := agentflow.ParseAndExecuteToolCalls(ctx, a.mcpManager, response)
        if len(toolResults) > 0 {
            // Synthesize tool results
            synthesisPrompt := fmt.Sprintf("Original response: %s\nTool results: %v\nProvide a comprehensive answer:", response, toolResults)
            finalResponse, _ = a.llm.Generate(ctx, synthesisPrompt)
        } else {
            finalResponse = response
        }
    } else {
        finalResponse = response
    }
    
    // 6. Update state and return
    state.Set("response", finalResponse)
    state.Set("processed_by", a.name)
    
    return agentflow.AgentResult{
        Result: finalResponse,
        State:  state,
    }, nil
}

Agent Patterns

1. Information Gathering Agent

Specializes in research and data collection:

go
type ResearchAgent struct {
    llm        agentflow.ModelProvider
    mcpManager agentflow.MCPManager
}

func (a *ResearchAgent) Run(ctx context.Context, event agentflow.Event, state agentflow.State) (agentflow.AgentResult, error) {
    message := event.GetData()["message"]
    
    systemPrompt := `You are a research agent. Your job is to gather comprehensive information using available tools.
    
Key behaviors:
- Use search tools for current information
- Use fetch_content for specific URLs
- Gather multiple perspectives
- Organize findings clearly`
    
    // Include tools and generate research-focused response
    toolPrompt := agentflow.FormatToolsForPrompt(ctx, a.mcpManager)
    prompt := fmt.Sprintf("%s\n%s\nResearch query: %s", systemPrompt, toolPrompt, message)
    
    response, err := a.llm.Generate(ctx, prompt)
    if err != nil {
        return agentflow.AgentResult{}, err
    }
    
    // Execute research tools
    toolResults := agentflow.ParseAndExecuteToolCalls(ctx, a.mcpManager, response)
    
    // Compile research findings
    if len(toolResults) > 0 {
        compilationPrompt := fmt.Sprintf(`Research findings: %v
        
Please compile these findings into a structured research report with:
1. Key findings
2. Sources
3. Important details
4. Areas for further investigation`, toolResults)
        
        response, _ = a.llm.Generate(ctx, compilationPrompt)
    }
    
    state.Set("research_findings", response)
    return agentflow.AgentResult{Result: response, State: state}, nil
}

2. Analysis Agent

Processes information and draws insights:

go
type AnalysisAgent struct {
    llm agentflow.ModelProvider
}

func (a *AnalysisAgent) Run(ctx context.Context, event agentflow.Event, state agentflow.State) (agentflow.AgentResult, error) {
    // Get previous research findings
    findings, exists := state.Get("research_findings")
    if !exists {
        return agentflow.AgentResult{}, fmt.Errorf("no research findings to analyze")
    }
    
    message := event.GetData()["message"]
    
    systemPrompt := `You are an analysis agent. Your job is to analyze information and provide insights.
    
Key behaviors:
- Identify patterns and trends
- Draw meaningful conclusions  
- Highlight important implications
- Provide actionable insights`
    
    prompt := fmt.Sprintf(`%s

Original query: %s
Research findings: %s

Please provide a thorough analysis with insights and implications.`, systemPrompt, message, findings)
    
    analysis, err := a.llm.Generate(ctx, prompt)
    if err != nil {
        return agentflow.AgentResult{}, err
    }
    
    state.Set("analysis", analysis)
    return agentflow.AgentResult{Result: analysis, State: state}, nil
}

3. Synthesis Agent

Combines multiple inputs into final output:

go
type SynthesisAgent struct {
    llm agentflow.ModelProvider
}

func (a *SynthesisAgent) Run(ctx context.Context, event agentflow.Event, state agentflow.State) (agentflow.AgentResult, error) {
    // Gather all previous work
    research, _ := state.Get("research_findings")
    analysis, _ := state.Get("analysis")
    message := event.GetData()["message"]
    
    systemPrompt := `You are a synthesis agent. Your job is to create comprehensive, well-structured final responses.
    
Key behaviors:
- Integrate multiple information sources
- Create coherent, flowing narrative
- Ensure completeness and accuracy
- Provide clear, actionable conclusions`
    
    prompt := fmt.Sprintf(`%s

Original query: %s
Research findings: %s
Analysis: %s

Please synthesize this into a comprehensive, well-structured response that fully addresses the original query.`, 
        systemPrompt, message, research, analysis)
    
    synthesis, err := a.llm.Generate(ctx, prompt)
    if err != nil {
        return agentflow.AgentResult{}, err
    }
    
    state.Set("final_response", synthesis)
    return agentflow.AgentResult{Result: synthesis, State: state}, nil
}

State Management

Using State for Data Flow

State allows agents to share data across the workflow:

go
// Agent 1: Store research data
state.Set("research_data", researchResults)
state.Set("sources", sourceList)
state.SetMeta("research_agent", "agent1")

// Agent 2: Access research data
researchData, exists := state.Get("research_data")
if exists {
    // Process the research data
}

// Access metadata
researchAgent, _ := state.GetMeta("research_agent")

State Best Practices

  1. Use descriptive keys: "user_preferences" not "prefs"
  2. Store structured data: Use structs or maps for complex data
  3. Set metadata: Track which agent processed what
  4. Handle missing data: Always check if data exists before using
go
// Good: Structured data storage
type UserProfile struct {
    Name        string
    Preferences []string
    Context     map[string]interface{}
}

profile := UserProfile{
    Name:        "John",
    Preferences: []string{"technical", "detailed"},
    Context:     map[string]interface{}{"industry": "software"},
}
state.Set("user_profile", profile)

// Good: Metadata tracking
state.SetMeta("processed_by", "agent1")
state.SetMeta("processing_time", time.Now().Format(time.RFC3339))
state.SetMeta("data_sources", "research,analysis")

Error Handling

Graceful Error Management

go
func (a *MyAgent) Run(ctx context.Context, event agentflow.Event, state agentflow.State) (agentflow.AgentResult, error) {
    // Validate inputs
    message, ok := event.GetData()["message"]
    if !ok {
        return agentflow.AgentResult{}, fmt.Errorf("missing required field: message")
    }
    
    // Handle LLM errors
    response, err := a.llm.Generate(ctx, prompt)
    if err != nil {
        // Log error for debugging
        agentflow.Logger().Error().Err(err).Msg("LLM generation failed")
        
        // Return graceful fallback
        fallbackResponse := "I apologize, but I'm having trouble processing your request right now. Please try again."
        state.Set("error", err.Error())
        state.Set("fallback_used", true)
        
        return agentflow.AgentResult{
            Result: fallbackResponse,
            State:  state,
        }, nil // Don't propagate error, handle gracefully
    }
    
    // Handle tool execution errors
    toolResults := agentflow.ParseAndExecuteToolCalls(ctx, a.mcpManager, response)
    if len(toolResults) == 0 && strings.Contains(response, "tool_call") {
        // Tool call was attempted but failed
        agentflow.Logger().Warn().Msg("Tool calls failed, proceeding without tools")
        // Continue with original response
    }
    
    return agentflow.AgentResult{Result: response, State: state}, nil
}

Testing Agents

Unit Testing Agent Logic

go
package main

import (
    "context"
    "testing"
    agentflow "github.com/kunalkushwaha/agenticgokit/core"
)

func TestMyAgent(t *testing.T) {
    // Setup
    mockLLM := &MockModelProvider{}
    mockMCP := &MockMCPManager{}
    agent := NewMyAgent("test-agent", mockLLM, mockMCP)
    
    // Create test event
    eventData := agentflow.EventData{"message": "Hello, world!"}
    event := agentflow.NewEvent("test", eventData, nil)
    state := agentflow.NewState()
    
    // Execute
    result, err := agent.Run(context.Background(), event, state)
    
    // Assert
    if err != nil {
        t.Fatalf("Expected no error, got %v", err)
    }
    
    if result.Result == "" {
        t.Error("Expected non-empty result")
    }
    
    // Check state was updated
    response, exists := result.State.Get("response")
    if !exists {
        t.Error("Expected response to be set in state")
    }
    
    if response != result.Result {
        t.Error("State response should match result")
    }
}

// Mock implementations for testing
type MockModelProvider struct{}
func (m *MockModelProvider) Generate(ctx context.Context, prompt string) (string, error) {
    return "Mock response", nil
}
func (m *MockModelProvider) Name() string { return "mock" }

type MockMCPManager struct{}
func (m *MockMCPManager) ListTools(ctx context.Context) ([]agentflow.ToolSchema, error) { return nil, nil }
func (m *MockMCPManager) CallTool(ctx context.Context, name string, args map[string]interface{}) (interface{}, error) { return nil, nil }
// ... implement other required methods

Next Steps

Released under the Apache 2.0 License.