While each Modus SDK offers similar capabilities, the APIs and usage may vary between languages.

Modus Agents APIs documentation is available on the following pages:

The Modus Agents APIs allow you to create stateful agents that maintain persistent memory across interactions, survive system failures, and coordinate complex multi-step operations.

Import

To begin, import the agents package from the SDK:

import "github.com/hypermodeinc/modus/sdk/go/pkg/agents"

Agent APIs

The APIs in the agents package are below, organized by category.

We’re constantly introducing new APIs through ongoing development with early users. Please open an issue if you have ideas on what would make Modus even more powerful for your next app!

Agent Management Functions

Register

Register an agent struct with the Modus runtime before it can be instantiated.

func Register(agent Agent)
agent
Agent
required

A pointer to an instance of the agent struct that implements the Agent interface.

Agent registration must be done in an init() function to ensure it happens before any agent operations.

Start

Create and start a new agent instance.

func Start(agentName string) (AgentInfo, error)
agentName
string
required

The name of the agent to instantiate. This must match the Name() method returned by the agent implementation.

Stop

Stop an agent instance. Once stopped, the agent can’t be resumed.

func Stop(agentId string) (AgentInfo, error)
agentId
string
required

The unique identifier of the agent instance to stop.

GetInfo

Get information about a specific agent instance.

func GetInfo(agentId string) (AgentInfo, error)
agentId
string
required

The unique identifier of the agent instance.

ListAll

List all active agent instances.

func ListAll() ([]AgentInfo, error)

Communication Functions

SendMessage

Send a synchronous message to an agent and wait for a response.

func SendMessage(agentId, messageName string, opts ...MessageOption) (*string, error)
agentId
string
required

The unique identifier of the target agent instance.

messageName
string
required

The name of the message to send to the agent.

opts
...MessageOption

Optional message options. Use WithData(data string) to include a data payload.

SendMessageAsync

Send an asynchronous message to an agent without waiting for a response.

func SendMessageAsync(agentId, messageName string, opts ...MessageOption) error
agentId
string
required

The unique identifier of the target agent instance.

messageName
string
required

The name of the message to send to the agent.

opts
...MessageOption

Optional message options. Use WithData(data string) to include a data payload.

WithData

Create a message option that includes a data payload.

func WithData(data string) MessageOption
data
string
required

The data payload to include with the message.

Agent Interface

Agent

The interface that all agents must implement.

type Agent interface {
    Name() string
    OnReceiveMessage(messageName string, data string) (*string, error)
    GetState() *string
    SetState(data string)
    OnInitialize() error
    OnSuspend() error
    OnResume() error
    OnTerminate() error
}
Name()
method
required

Must return a unique name for the agent implementation.

OnReceiveMessage(messageName, data)
method
required

Handles incoming messages to the agent. Must be implemented by all agents.

GetState()
method

Returns the agent’s current state as a string for persistence. Called automatically when the agent needs to be suspended or migrated.

SetState(data)
method

Restores the agent’s state from a string. Called automatically when the agent is resumed or migrated.

OnInitialize()
method

Lifecycle method called when the agent is first created.

OnSuspend()
method

Lifecycle method called when the agent is about to be suspended.

OnResume()
method

Lifecycle method called when the agent is resumed from suspension.

OnTerminate()
method

Lifecycle method called when the agent is about to be terminated.

AgentBase

A convenient base struct that provides default implementations for optional methods and includes event publishing capabilities.

type AgentBase struct{}

func (AgentBase) GetState() *string { return nil }
func (AgentBase) SetState(data string) {}
func (AgentBase) OnInitialize() error { return nil }
func (AgentBase) OnSuspend() error { return nil }
func (AgentBase) OnResume() error { return nil }
func (AgentBase) OnTerminate() error { return nil }
func (a *AgentBase) PublishEvent(event AgentEvent) error

You can embed AgentBase in your agent struct to automatically implement the optional methods, then override only the ones you need.

PublishEvent(event)
method

Publishes an event from this agent to any subscribers. The event must implement the AgentEvent interface.

Event Interface

AgentEvent

Interface that custom events must implement to be published from agents.

type AgentEvent interface {
    EventName() string
}
EventName()
method
required

Must return the name/type of the event as a string.

Custom events should implement this interface and include any additional data as struct fields:

type CountUpdated struct {
    Count int `json:"count"`
}

func (e CountUpdated) EventName() string {
    return "countUpdated"
}

Types

AgentInfo

Information about an agent instance.

type AgentInfo struct {
    Id     string
    Name   string
    Status string
}
Id
string

The unique identifier of the agent instance.

Name
string

The name of the agent implementation.

Status
string

The current status of the agent instance.

MessageOption

Options for customizing agent messages.

type MessageOption interface {
    // internal implementation
}

Example Usage

Here’s a complete example of a simple counter agent with event streaming:

Agent Implementation

package main

import (
    "strconv"
    "github.com/hypermodeinc/modus/sdk/go/pkg/agents"
)

type CounterAgent struct {
    agents.AgentBase
    count int
}

func (c *CounterAgent) Name() string {
    return "Counter"
}

func (c *CounterAgent) GetState() *string {
    state := strconv.Itoa(c.count)
    return &state
}

func (c *CounterAgent) SetState(data string) {
    if data == nil {
        return
    }
    if count, err := strconv.Atoi(*data); err == nil {
        c.count = count
    }
}

func (c *CounterAgent) OnInitialize() error {
    fmt.Printf("Counter agent started\n")
    return nil
}

func (c *CounterAgent) OnReceiveMessage(messageName string, data string) (*string, error) {
    switch messageName {
    case "count":
        result := strconv.Itoa(c.count)
        return &result, nil

    case "increment":
        if data != nil {
            if increment, err := strconv.Atoi(*data); err == nil {
                c.count += increment
            }
        } else {
            c.count++
        }

        // Publish an event to subscribers
        if err := c.PublishEvent(CountUpdated{Count: c.count}); err != nil {
            return nil, err
        }

        result := strconv.Itoa(c.count)
        return &result, nil
    }

    return nil, nil
}

// Custom event for count updates
type CountUpdated struct {
    Count int `json:"count"`
}

func (e CountUpdated) EventName() string {
    return "countUpdated"
}

// Register the agent in init function
func init() {
    agents.Register(&CounterAgent{})
}

Function Integration

// Start a counter agent and return its info
func StartCounterAgent() (agents.AgentInfo, error) {
    return agents.Start("Counter")
}

// Get the current count from an agent
func GetCount(agentId string) (int, error) {
    count, err := agents.SendMessage(agentId, "count")
    if err != nil {
        return 0, err
    }
    if count == nil {
        return 0, nil
    }
    return strconv.Atoi(*count)
}

// Increment the count and return the new value
func UpdateCount(agentId string) (int, error) {
    count, err := agents.SendMessage(agentId, "increment")
    if err != nil {
        return 0, err
    }
    if count == nil {
        return 0, nil
    }
    return strconv.Atoi(*count)
}

// Increment the count asynchronously by a specific amount
func UpdateCountAsync(agentId string, qty int) error {
    return agents.SendMessageAsync(agentId, "increment",
        agents.WithData(strconv.Itoa(qty)))
}

// Stop an agent
func StopAgent(agentId string) (agents.AgentInfo, error) {
    return agents.Stop(agentId)
}

// List all active agents
func ListAgents() ([]agents.AgentInfo, error) {
    return agents.ListAll()
}

GraphQL Usage

Once deployed, your agent functions become available via GraphQL:

# Start a new agent
mutation {
  startCounterAgent {
    id
    name
    status
  }
}

# Get the current count
query {
  getCount(agentId: "agent_abc123")
}

# Increment the count
mutation {
  updateCount(agentId: "agent_abc123")
}

# Increment asynchronously
mutation {
  updateCountAsync(agentId: "agent_abc123", qty: 5)
}

# Subscribe to real-time events
subscription {
  agentEvent(agentId: "agent_abc123") {
    name
    data
    timestamp
  }
}

Event Subscription

To receive real-time events from your agent, subscribe using GraphQL subscriptions over Server-Sent Events:

subscription CounterEvents($agentId: String!) {
  agentEvent(agentId: $agentId) {
    name
    data
    timestamp
  }
}

Example events you might receive:

{
  "data": {
    "agentEvent": {
      "name": "countUpdated",
      "data": {
        "count": 5
      },
      "timestamp": "2025-06-08T14:30:00Z"
    }
  }
}
{
  "data": {
    "agentEvent": {
      "name": "status",
      "data": {
        "status": "running"
      },
      "timestamp": "2025-06-08T14:30:05Z"
    }
  }
}

Multiple Event Types

You can define multiple event types for different scenarios:

// Mission events
type MissionStarted struct {
    MissionName string `json:"missionName"`
    Priority    string `json:"priority"`
}

func (e MissionStarted) EventName() string {
    return "missionStarted"
}

type MissionCompleted struct {
    MissionName string `json:"missionName"`
    Success     bool   `json:"success"`
    Duration    int    `json:"duration"`
}

func (e MissionCompleted) EventName() string {
    return "missionCompleted"
}

// Error events
type ErrorOccurred struct {
    Message string `json:"message"`
    Code    string `json:"code"`
}

func (e ErrorOccurred) EventName() string {
    return "errorOccurred"
}

Client Integration

Use appropriate GraphQL Server-Sent Events (SSE) clients such as:

  • graphql-sse for JavaScript/TypeScript
  • gqlgen for Go clients
  • Standard HTTP libraries with Server-Sent Events (SSE) support

Example with curl:

curl -N -H "accept: text/event-stream" \
     -H "content-type: application/json" \
     -X POST http://localhost:8080/graphql \
     -d '{"query":"subscription { agentEvent(agentId: \"agent_abc123\") { name data timestamp } }"}'

Example with Go client:

import (
    "bufio"
    "encoding/json"
    "fmt"
    "net/http"
    "strings"
)

func subscribeToAgentEvents(agentId string) {
    query := fmt.Sprintf(`{"query":"subscription { agentEvent(agentId: \"%s\") { name data timestamp } }"}`, agentId)

    req, _ := http.NewRequest("POST", "http://localhost:8080/graphql", strings.NewReader(query))
    req.Header.Set("Accept", "text/event-stream")
    req.Header.Set("Content-Type", "application/json")

    client := &http.Client{}
    resp, err := client.Do(req)
    if err != nil {
        panic(err)
    }
    defer resp.Body.Close()

    scanner := bufio.NewScanner(resp.Body)
    for scanner.Scan() {
        line := scanner.Text()
        if strings.HasPrefix(line, "data: ") {
            data := strings.TrimPrefix(line, "data: ")
            fmt.Printf("Received event: %s\n", data)
        }
    }
}