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

Core Concepts

Understanding the fundamental concepts of AgenticGoKit will help you build powerful AI agents. This guide covers the architecture and key components.


🎯 Overview

AgenticGoKit is built around four core concepts:

  1. Agents - The primary interface for AI interactions
  2. Handlers - Custom logic and augmentation functions
  3. Tools - Extensible capabilities for agents
  4. Memory - Context and knowledge retention

🤖 Agents

An Agent is the primary abstraction in v1beta. It encapsulates:

  • LLM provider configuration
  • Execution logic (handlers)
  • Tool access
  • Memory integration
  • Middleware chain

Agent Interface

Every agent implements this interface:

go
type Agent interface {
    // Run executes the agent with the given query
    Run(ctx context.Context, query string) (*Result, error)
    
    // RunWithOptions executes with additional options
    RunWithOptions(ctx context.Context, query string, opts *RunOptions) (*Result, error)
    
    // RunStream executes with streaming responses
    RunStream(ctx context.Context, query string) (Stream, error)
    
    // Config returns the agent's configuration
    Config() *Config
    
    // Capabilities returns available features
    Capabilities() []string
}

Agent Types

AgenticGoKit provides several ways to create agents:

go
// Chat agent - general purpose conversation
chatAgent, err := v1beta.NewBuilder("Assistant").
    WithPreset(v1beta.ChatAgent).
    Build()

// Research agent - optimized for analysis
researchAgent, err := v1beta.NewBuilder("Researcher").
    WithPreset(v1beta.ResearchAgent).
    Build()

// Quick agent - rapid prototyping (single parameter: model)
quickAgent, _ := v1beta.QuickChatAgent("gpt-4")

2. Custom Builder (Full Control)

go
// Create agent with full customization
agent, err := v1beta.NewBuilder("CustomAgent").
    WithLLM("openai", "gpt-4").
    WithConfig(&v1beta.Config{
        SystemPrompt: "You are a helpful assistant",
        Temperature:  0.7,
        MaxTokens:    2000,
    }).
    WithTools(myTools).
    WithMemory(&v1beta.MemoryOptions{
        Type:     "simple",
        Provider: memProvider,
    }).
    WithHandler(myHandler).
    Build()

Agent Lifecycle

Create Agent → Configure → Run/Stream → Process Result
     ↓            ↓           ↓              ↓
  Builder      Options    Middleware      Handler
  1. Create: Use builder to construct agent
  2. Configure: Set LLM, tools, memory, handlers
  3. Run/Stream: Execute with context and query
  4. Process: Handle result or stream chunks

🎨 Handlers

Handlers define how agents process queries. AgenticGoKit provides two handler types:

1. CustomHandlerFunc

Simple handler with LLM fallback capability:

go
type CustomHandlerFunc func(
    ctx context.Context,
    query string,
    llmCall func(systemPrompt, userPrompt string) (string, error),
) (string, error)

Use when:

  • You need simple custom logic
  • You want automatic LLM fallback
  • You don't need tool or memory access

Example:

go
handler := func(ctx context.Context, query string, llmCall func(string, string) (string, error)) (string, error) {
    // Custom logic for specific patterns
    if strings.Contains(strings.ToLower(query), "time") {
        return fmt.Sprintf("Current time: %s", time.Now().Format(time.RFC3339)), nil
    }
    
    // Return empty string to fall back to LLM
    return "", nil
}

agent, _ := v1beta.NewBuilder("TimeAgent").
    WithPreset(v1beta.ChatAgent).
    WithHandler(handler).
    Build()

2. EnhancedHandlerFunc (AgentHandlerFunc)

Advanced handler with full capabilities:

go
type EnhancedHandlerFunc func(
    ctx context.Context,
    query string,
    capabilities *HandlerCapabilities,
) (string, error)

type HandlerCapabilities struct {
    LLMCall  func(systemPrompt, userPrompt string) (string, error)
    ToolCall func(toolName string, args map[string]interface{}) (interface{}, error)
    Memory   MemoryProvider // Access to memory storage
    Config   *Config        // Agent configuration
}

Use when:

  • You need access to tools
  • You need memory integration
  • You need full control over agent logic

Example:

go
handler := func(ctx context.Context, query string, cap *v1beta.HandlerCapabilities) (string, error) {
    // Use tools
    weatherData, err := cap.ToolCall("get_weather", map[string]interface{}{
        "location": "New York",
    })
    if err != nil {
        return "", err
    }
    
    // Use LLM with tool data
    response, err := cap.LLMCall(
        "You are a weather assistant",
        fmt.Sprintf("Weather data: %v\nUser query: %s", weatherData, query),
    )
    
    return response, err
}

agent, _ := v1beta.NewBuilder("WeatherAgent").
    WithPreset(v1beta.ChatAgent).
    WithHandler(handler).
    WithTools(weatherTools).
    Build()

Handler Augmentation

Pre-built augmentation functions automatically enhance handlers:

CreateToolAugmentedHandler

Automatically includes tool information in prompts:

go
handler := v1beta.CreateToolAugmentedHandler(
    func(ctx context.Context, query, toolPrompt string, llmCall func(string, string) (string, error)) (string, error) {
        // toolPrompt contains formatted tool descriptions
        return llmCall("You are an assistant with tools", query)
    },
)

CreateMemoryAugmentedHandler

Automatically includes relevant memory context:

go
handler := v1beta.CreateMemoryAugmentedHandler(
    func(ctx context.Context, query, memoryContext string, llmCall func(string, string) (string, error)) (string, error) {
        // memoryContext contains relevant past interactions
        return llmCall("You are an assistant with memory", query)
    },
)

CreateFullAugmentedHandler

Combines tool and memory augmentation:

go
handler := v1beta.CreateFullAugmentedHandler(
    func(ctx context.Context, query, toolPrompt, memoryContext string, llmCall func(string, string) (string, error)) (string, error) {
        // Both toolPrompt and memoryContext available
        systemPrompt := fmt.Sprintf("Assistant with:\nTools: %s\nContext: %s", toolPrompt, memoryContext)
        return llmCall(systemPrompt, query)
    },
)

🛠️ Tools

Tools extend agent capabilities beyond LLM interactions.

Tool Structure

go
type Tool struct {
    Name        string                 // Unique tool identifier
    Description string                 // What the tool does
    Parameters  map[string]interface{} // JSON Schema for parameters
    Handler     ToolHandler            // Function to execute
}

type ToolHandler func(ctx context.Context, args map[string]interface{}) (interface{}, error)

Creating Tools

go
searchTool := v1beta.Tool{
    Name:        "web_search",
    Description: "Search the web for information",
    Parameters: map[string]interface{}{
        "type": "object",
        "properties": map[string]interface{}{
            "query": map[string]interface{}{
                "type":        "string",
                "description": "Search query",
            },
            "max_results": map[string]interface{}{
                "type":        "integer",
                "description": "Maximum number of results",
                "default":     5,
            },
        },
        "required": []string{"query"},
    },
    Handler: func(ctx context.Context, args map[string]interface{}) (interface{}, error) {
        query := args["query"].(string)
        maxResults := 5
        if mr, ok := args["max_results"].(int); ok {
            maxResults = mr
        }
        
        // Implement search logic
        results := performSearch(query, maxResults)
        return results, nil
    },
}

Adding Tools to Agents

go
agent, err := v1beta.NewBuilder("SearchBot").
    WithPreset(v1beta.ChatAgent).
    WithTools([]v1beta.Tool{searchTool, calculatorTool}).
    Build()

ToolCallHelper

Simplified tool execution in handlers:

go
handler := func(ctx context.Context, query string, cap *v1beta.HandlerCapabilities) (string, error) {
    helper := v1beta.NewToolCallHelper(cap)
    
    // Call with map
    result, err := helper.Call("web_search", map[string]interface{}{
        "query": "Go programming",
        "max_results": 10,
    })
    
    // Call with struct
    type SearchParams struct {
        Query      string `json:"query"`
        MaxResults int    `json:"max_results"`
    }
    result, err = helper.CallWithStruct("web_search", SearchParams{
        Query:      "Go programming",
        MaxResults: 10,
    })
    
    return fmt.Sprintf("Search results: %v", result), nil
}

MCP (Model Context Protocol) Tools

AgenticGoKit supports MCP for tool discovery:

go
import "github.com/agenticgokit/agenticgokit/plugins/mcp"

// Discover tools from MCP servers
mcpTools, err := mcp.DiscoverTools(ctx, mcpServers...)

agent, err := v1beta.NewBuilder("MCPAgent").
    WithPreset(v1beta.ChatAgent).
    WithTools(mcpTools).
    Build()

💾 Memory

Memory provides context retention and knowledge storage.

Memory Interface

go
type MemoryProvider interface {
    // Store saves a memory entry
    Store(ctx context.Context, entry MemoryEntry) error
    
    // Retrieve gets relevant memories
    Retrieve(ctx context.Context, query string, limit int) ([]MemoryEntry, error)
    
    // Clear removes all memories (optional)
    Clear(ctx context.Context) error
}

type MemoryEntry struct {
    ID        string                 // Unique identifier
    Content   string                 // Memory content
    Metadata  map[string]interface{} // Additional data
    Timestamp time.Time              // When stored
    Embedding []float64              // Vector embedding (optional)
}

Memory Backends

In-Memory (Development)

go
import "github.com/agenticgokit/agenticgokit/v1beta/memory"

memProvider := memory.NewInMemory()

agent, err := v1beta.NewBuilder("MemoryAgent").
    WithPreset(v1beta.ChatAgent).
    WithMemory(&v1beta.MemoryOptions{
        Type:     "simple",
        Provider: memProvider,
    }).
    Build()

PostgreSQL with pgvector (Production)

go
import "github.com/agenticgokit/agenticgokit/plugins/memory/postgres"

memProvider, err := postgres.New(
    "postgresql://user:pass@localhost/db",
    postgres.WithTableName("agent_memory"),
    postgres.WithEmbeddingDim(1536),
)

agent, err := v1beta.NewBuilder("PostgresAgent").
    WithPreset(v1beta.ChatAgent).
    WithMemory(&v1beta.MemoryOptions{
        Type:     "postgres",
        Provider: memProvider,
    }).
    Build()

Weaviate (Vector DB)

go
import "github.com/agenticgokit/agenticgokit/plugins/memory/weaviate"

memProvider, err := weaviate.New(
    "http://localhost:8080",
    weaviate.WithClassName("AgentMemory"),
)

agent, err := v1beta.NewBuilder("WeaviateAgent").
    WithPreset(v1beta.ChatAgent).
    WithMemory(&v1beta.MemoryOptions{
        Type:     "weaviate",
        Provider: memProvider,
    }).
    Build()

RAG (Retrieval-Augmented Generation)

Memory automatically enables RAG when configured:

go
memProvider := memory.NewInMemory()

agent, err := v1beta.NewBuilder("RAGAgent").
    WithPreset(v1beta.ChatAgent).
    WithMemory(&v1beta.MemoryOptions{
        Type:     "simple",
        Provider: memProvider,
        RAG: &v1beta.RAGConfig{
            Enabled:    true,
            TopK:       5,    // Retrieve top 5 memories
            Threshold:  0.7,  // Similarity threshold
        },
    }).
    Build()

// Agent automatically retrieves relevant context
result, err := agent.Run(context.Background(), "What did we discuss about Go?")
// Agent searches memory and augments query with relevant context

🔧 Configuration

Builder Configuration

go
agent, err := v1beta.NewBuilder("Agent").
    // LLM Configuration
    WithLLM("openai", "gpt-4").
    WithConfig(&v1beta.Config{
        SystemPrompt: "You are helpful",
        Temperature:  0.7,
        MaxTokens:    2000,
        TopP:         0.9,
        Timeout:      30 * time.Second,
        MaxRetries:   3,
        RetryDelay:   time.Second,
    }).
    
    // Components
    WithTools(tools).
    WithMemory(&v1beta.MemoryOptions{
        Type:     "simple",
        Provider: memProvider,
    }).
    WithHandler(handler).
    WithMiddleware(middleware).
    
    Build()

Runtime Options

Override configuration at runtime:

go
result, err := agent.RunWithOptions(
    ctx,
    "query",
    &v1beta.RunOptions{
        Temperature:  0.5,    // Override temperature
        MaxTokens:    1000,   // Override max tokens
        SystemPrompt: "...",  // Override system prompt
    },
)

Config Struct

Direct configuration access:

go
cfg := &v1beta.Config{
    SystemPrompt: "You are helpful",
    Temperature:  0.7,
    MaxTokens:    2000,
    Timeout:      30 * time.Second,
}

agent, err := v1beta.NewBuilder("Agent").
    WithLLM("openai", "gpt-4").
    WithConfig(cfg).
    WithTools(tools).
    WithMemory(&v1beta.MemoryOptions{
        Type:     "simple",
        Provider: memProvider,
    }).
    WithHandler(handler).
    Build()

🔄 Execution Flow

Standard Run Flow

User Query

Context + Options

Middleware (Before)

Handler Selection

Custom Handler?
    ↓ No
Default LLM Handler
    ↓ Yes
Custom Logic

Tool Calls? ←---→ Tool Execution

Memory Access? ←---→ Memory Retrieval

LLM Call

Response Processing

Middleware (After)

AgentResult

Streaming Flow

User Query

Context + Options + Channel

Middleware (Before)

Handler with Streaming

LLM Stream

Chunk Processing

    ├→ ChunkTypeText ----→ Content chunks
    ├→ ChunkTypeDelta ---→ Token chunks
    ├→ ChunkTypeThought -→ Reasoning
    ├→ ChunkTypeToolCall → Tool execution
    ├→ ChunkTypeMetadata → Extra info
    ├→ ChunkTypeError ---→ Error handling
    └→ ChunkTypeDone ----→ Completion

Middleware (After)

Close Channel

🎭 Middleware

Middleware intercepts agent execution:

go
type Middleware interface {
    BeforeRun(ctx context.Context, input string) (context.Context, string, error)
    AfterRun(ctx context.Context, input string, result *AgentResult, err error) (*AgentResult, error)
}

Example: Logging Middleware

go
type LoggingMiddleware struct{}

func (m *LoggingMiddleware) BeforeRun(ctx context.Context, input string) (context.Context, string, error) {
    log.Printf("Agent executing: %s", input)
    ctx = context.WithValue(ctx, "start_time", time.Now())
    return ctx, input, nil
}

func (m *LoggingMiddleware) AfterRun(ctx context.Context, input string, result *v1beta.Result, err error) (*v1beta.Result, error) {
    startTime := ctx.Value("start_time").(time.Time)
    duration := time.Since(startTime)
    log.Printf("Agent completed in %v: success=%t", duration, result.Success)
    return result, err
}

agent, err := v1beta.NewBuilder("LogAgent").
    WithPreset(v1beta.ChatAgent).
    WithMiddleware(&LoggingMiddleware{}).
    Build()

📊 Result Types

Result

go
type Result struct {
    FinalOutput  string                   // Response content
    Success      bool                     // Execution success
    Error        error                    // Error if failed
    Metadata     map[string]interface{}   // Additional data
    ToolCalls    []ToolCall               // Tools executed
    TokenUsage   *TokenUsage              // Token statistics
    StepResults  map[string]*StepResult   // For workflows
    IterationInfo *IterationInfo          // For loop workflows
}

type TokenUsage struct {
    PromptTokens     int
    CompletionTokens int
    TotalTokens      int
}

StreamChunk

go
type StreamChunk struct {
    Type     ChunkType              // Chunk type
    Delta    string                 // Incremental content
    Content  string                 // Complete content (for non-delta)
    Metadata map[string]interface{} // Additional data
    Error    error                  // Error if any
    Done     bool                   // Is final chunk
}

// Chunk types
const (
    ChunkTypeDelta      ChunkType = "delta"       // Incremental token
    ChunkTypeContent    ChunkType = "content"     // Complete text
    ChunkTypeThought    ChunkType = "thought"     // Agent reasoning
    ChunkTypeToolCall   ChunkType = "tool_call"   // Tool execution
    ChunkTypeToolResult ChunkType = "tool_result" // Tool result
    ChunkTypeMetadata   ChunkType = "metadata"    // Extra information
    ChunkTypeError      ChunkType = "error"       // Error chunk
    ChunkTypeDone       ChunkType = "done"        // Completion marker
)

🎯 Best Practices

1. Choose the Right Handler Type

  • Use CustomHandlerFunc for simple logic
  • Use EnhancedHandlerFunc when you need tools/memory

2. Use Preset Builders

  • Leverage NewBuilder(name).WithPreset(ChatAgent) for common cases
  • Use QuickChatAgent(model) for rapid prototyping
  • Use custom builder with full configuration for complex needs

3. Configure Timeouts

  • Always set appropriate timeout values
  • Use context cancellation for user-initiated stops

4. Handle Errors Properly

  • Check both err and result.Success
  • Use typed errors for specific handling

5. Leverage Middleware

  • Add logging for debugging
  • Add metrics for monitoring
  • Add validation for input checking

6. Memory Management

  • Use in-memory for development
  • Use vector DB for production
  • Configure appropriate retrieval limits

📚 Next Steps


Ready to dive deeper? Continue to Streaming Guide

Released under the Apache 2.0 License.