Skip to main content

What are Agents?

Agents are the core building blocks of Agate applications. They are modular, reusable components that encapsulate specific functionality and can interact with users, manage state, and communicate with other agents.

Agent Types

Interactive Agents

Interactive agents provide user interfaces and handle user input:
type InteractiveAgent struct {
    name string
    ui   *UI
}

func (a *InteractiveAgent) Render() error {
    // Render UI components
    return a.ui.Render()
}

func (a *InteractiveAgent) HandleInput(input Input) error {
    // Process user input
    return nil
}

Background Agents

Background agents run tasks without direct user interaction:
type BackgroundAgent struct {
    name     string
    interval time.Duration
}

func (a *BackgroundAgent) Run(ctx context.Context) error {
    ticker := time.NewTicker(a.interval)
    defer ticker.Stop()

    for {
        select {
        case <-ctx.Done():
            return ctx.Err()
        case <-ticker.C:
            // Perform background task
            a.doWork()
        }
    }
}

Creating Custom Agents

Basic Agent Structure

All agents must implement the Agent interface:
type Agent interface {
    Name() string
    Start(ctx context.Context) error
    Stop() error
    Status() AgentStatus
}

Example: File Watcher Agent

type FileWatcherAgent struct {
    name      string
    watchPath string
    onChange  func(string)
}

func NewFileWatcherAgent(path string, onChange func(string)) *FileWatcherAgent {
    return &FileWatcherAgent{
        name:      "file-watcher",
        watchPath: path,
        onChange:  onChange,
    }
}

func (a *FileWatcherAgent) Name() string {
    return a.name
}

func (a *FileWatcherAgent) Start(ctx context.Context) error {
    // Initialize file watcher
    watcher, err := fsnotify.NewWatcher()
    if err != nil {
        return err
    }
    defer watcher.Close()

    err = watcher.Add(a.watchPath)
    if err != nil {
        return err
    }

    for {
        select {
        case <-ctx.Done():
            return ctx.Err()
        case event := <-watcher.Events:
            if a.onChange != nil {
                a.onChange(event.Name)
            }
        }
    }
}

Agent Communication

Agents can communicate through the message bus:
// Send message to another agent
app.MessageBus().Send("target-agent", Message{
    Type: "data-update",
    Data: map[string]interface{}{
        "value": 42,
    },
})

// Subscribe to messages
app.MessageBus().Subscribe("data-update", func(msg Message) {
    // Handle incoming message
})

Agent Lifecycle

Registration

app := agate.New()

// Register agents
app.RegisterAgent(NewFileWatcherAgent("/tmp", nil))
app.RegisterAgent(NewInteractiveAgent())

// Start application
app.Run()

Lifecycle Hooks

type LifecycleAgent struct {
    // ... fields
}

func (a *LifecycleAgent) OnStart() error {
    // Called when agent starts
    return nil
}

func (a *LifecycleAgent) OnStop() error {
    // Called when agent stops
    return nil
}

func (a *LifecycleAgent) OnPause() error {
    // Called when agent is paused
    return nil
}

func (a *LifecycleAgent) OnResume() error {
    // Called when agent resumes
    return nil
}

Best Practices

Each agent should have a single, well-defined responsibility. This makes them easier to test, maintain, and reuse.
Implement robust error handling. Agents should gracefully handle errors and communicate issues through the message bus or logging.
Always clean up resources in the Stop() method. This includes closing files, network connections, and stopping goroutines.
Make agents configurable through constructor parameters or configuration objects rather than hardcoding values.

Next Steps