Agents in Modus

Agents in Modus are persistent background processes that maintain memory across interactions. Unlike stateless functions that lose everything when operations end, agents remember every detail, survive system failures, and never lose their operational context.

Key characteristics

  • Stateful: Maintains memory and context across interactions
  • Persistent: Automatically saves and restores state
  • Resilient: Graceful recovery from failures
  • Autonomous: Can operate independently over extended periods
  • Actor-based: Each agent instance runs in isolation
  • Event-driven: Streams real-time updates and operational intelligence

When to use agents

Agents are perfect for:

  • Multi-turn workflows spanning multiple interactions
  • Long-running processes that maintain context over time
  • Stateful operations that need to remember previous actions
  • Complex coordination between different system components
  • Persistent monitoring that tracks changes over time
  • Real-time operations requiring live status updates and event streaming

Agent structure

Every agent starts with the essential framework:

package main

import (
    "fmt"
    "strings"
    "time"
    "github.com/hypermodeinc/modus/sdk/go/pkg/agents"
    "github.com/hypermodeinc/modus/sdk/go/pkg/models"
    "github.com/hypermodeinc/modus/sdk/go/pkg/models/openai"
)

type IntelligenceAgent struct {
    agents.AgentBase

    // The rest of the fields make up the agent's state and can be customized per agent
    intelligenceReports []string     // Matrix surveillance data
    threatLevel         float64      // Current threat assessment
    lastContact         time.Time
    currentMission      *MissionPhase // Track long-running operations
    missionLog          []string     // Operational progress log
}

type MissionPhase struct {
    Name        string
    StartTime   time.Time
    Duration    time.Duration
    Complete    bool
}

func (a *IntelligenceAgent) Name() string {
    return "IntelligenceAgent"
}

The agent embeds agents.AgentBase, which provides all the infrastructure for state management, secure communications, and persistence. Your app data—intelligence reports, threat assessments, contact logs—lives as fields in the struct, automatically preserved across all interactions.

Creating agents through functions

Agents are created and managed through regular Modus functions that become part of your GraphQL API. These functions handle agent lifecycle operations:

// Register your agent type during initialization
func init() {
    agents.Register(&IntelligenceAgent{})
}

// Create a new agent instance - this becomes a GraphQL mutation
func DeployAgent() (string, error) {
    agentInfo, err := agents.Start("IntelligenceAgent")
    if err != nil {
        return "", err
    }

    // Return the agent ID - clients must store this to communicate with the agent
    return agentInfo.Id, nil
}

When you call this function through GraphQL, it returns a unique agent ID:

mutation {
  deployAgent
}

Response:

{
  "data": {
    "deployAgent": "agent_neo_001"
  }
}

You can think of an Agent as a persistent server process with durable memory. Once created, you can reference your agent by its ID across sessions, page reloads, and even system restarts. The agent maintains its complete state and continues operating exactly where it left off.

Agent builders and visual workflows: We’re actively developing Agent Builder tools and “eject to code” features that generate complete agent deployments from visual workflows. These tools automatically create the deployment functions and agent management code for complex multi-agent systems.

Communicating with agents

Once created, you communicate with agents using their unique ID. Create functions that send messages to specific agent instances:

func ImportActivity(agentId string, activityData string) (string, error) {
    result, err := agents.SendMessage(
        agentId,
        "matrix_surveillance",
        agents.WithData(activityData),
    )
    if err != nil {
        return "", err
    }
    if result == nil {
        return "", fmt.Errorf("no response from agent")
    }
    return *result, nil
}

func GetThreatStatus(agentId string) (string, error) {
    result, err := agents.SendMessage(agentId, "threat_assessment")
    if err != nil {
        return "", err
    }
    if result == nil {
        return "", fmt.Errorf("no response from agent")
    }
    return *result, nil
}

These functions become GraphQL operations that you can call with your agent’s ID:

mutation {
  importActivity(
    agentId: "agent_neo_001"
    activityData: "Anomalous Agent Smith replication detected in Sector 7"
  )
}

Response:

{
  "data": {
    "importActivity": "Matrix surveillance complete.
      Agent Smith pattern matches previous incident in the Loop.
      Threat level: 0.89 based on 3 intelligence reports.
      Recommend immediate evasive protocols."
  }
}
query {
  getThreatStatus(agentId: "agent_neo_001")
}

Response:

{
  "data": {
    "getThreatStatus": "Current threat assessment:
      3 intelligence reports analyzed.
      Threat level: 0.89.
      Agent operational in the Matrix."
  }
}

The agent receives the message, processes it using its internal state and AI reasoning, updates its intelligence database, and returns a response—all while maintaining persistent memory of every interaction.

Agent message handling

Agents process requests through their message handling system:

func (a *IntelligenceAgent) OnReceiveMessage(
    msgName string,
    data string,
) (*string, error) {
    switch msgName {
    case "matrix_surveillance":
        return a.analyzeMatrixActivity(data)
    case "background_reconnaissance":
        return a.performBackgroundRecon(data)
    case "threat_assessment":
        return a.getThreatAssessment()
    case "get_status":
        return a.getOperationalStatus()
    case "intelligence_history":
        return a.getIntelligenceHistory()
    default:
        return nil, fmt.Errorf("unrecognized directive: %s", msgName)
    }
}

Each message type triggers specific operations, with all data automatically maintained in the agent’s persistent memory.

Processing operations with AI intelligence

Here’s how agents handle operations while maintaining persistent state and using AI models for analysis:

func (a *IntelligenceAgent) analyzeMatrixActivity(data string) (*string, error) {
    // Store new intelligence in persistent memory
    a.intelligenceReports = append(a.intelligenceReports, *data)
    a.lastContact = time.Now()

    // Build context from all accumulated intelligence
    accumulatedReports := strings.Join(a.intelligenceReports, "\n")

    // AI analysis using complete operational history
    model, err := models.GetModel[openai.ChatModel]("analyst-model")
    if err != nil {
        return nil, err
    }

    systemPrompt := `You are a resistance operative in the Matrix.
        Analyze patterns from accumulated surveillance reports
        and provide threat assessment for anomalous Agent behavior.`

    userPrompt := fmt.Sprintf(`All Matrix Intelligence:
        %s

        Provide threat assessment:`,
        accumulatedReports)

    input, err := model.CreateInput(
        openai.NewSystemMessage(systemPrompt),
        openai.NewUserMessage(userPrompt),
    )
    if err != nil {
        return nil, err
    }

    output, err := model.Invoke(input)
    if err != nil {
        return nil, err
    }
    analysis := output.Choices[0].Message.Content

    // Update threat level based on data volume and AI analysis
    a.threatLevel = float64(len(a.intelligenceReports)) / 10.0
    if a.threatLevel > 1.0 {
        a.threatLevel = 1.0
    }

    // Boost threat level for critical AI analysis
    if strings.Contains(strings.ToLower(analysis), "critical") ||
       strings.Contains(strings.ToLower(analysis), "agent smith") {
        a.threatLevel = math.Min(a.threatLevel + 0.2, 1.0)
    }

    result := fmt.Sprintf(`Matrix surveillance complete:
        %s

        (Threat level: %.2f based on %d intelligence reports)`,
        analysis,
        a.threatLevel,
        len(a.intelligenceReports))
    return &result, nil
}

This demonstrates how agents maintain state across complex operations while using AI models with the full context of accumulated intelligence.

The power of intelligent persistence

This combination creates agents that:

First Analysis: “Anomalous activity detected. Limited context available. (Threat level: 0.10 based on 1 intelligence report)”

After Multiple Reports: “Pattern confirmed across 5 previous incidents. Agent Smith replication rate exceeding normal parameters. Immediate extraction recommended. (Threat level: 0.89 based on 8 intelligence reports)”

The agent doesn’t just remember—it learns and becomes more intelligent with every interaction. AI models see the complete operational picture, enabling sophisticated pattern recognition impossible with stateless functions.

State persistence

Agents automatically preserve their state through Modus’s built-in persistence system:

func (a *IntelligenceAgent) GetState() *string {
    reportsData := strings.Join(a.intelligenceReports, "|")
    state := fmt.Sprintf("%.2f|%s|%d",
        a.threatLevel,
        reportsData,
        a.lastContact.Unix())
    return &state
}

func (a *IntelligenceAgent) SetState(data string) {
    if data == nil {
        return
    }

    parts := strings.Split(*data, "|")
    if len(parts) >= 3 {
        a.threatLevel, _ = strconv.ParseFloat(parts[0], 64)
        if parts[1] != "" {
            a.intelligenceReports = strings.Split(parts[1], "|")
        }
        timestamp, _ := strconv.ParseInt(parts[2], 10, 64)
        a.lastContact = time.Unix(timestamp, 0)
    }
}

Agent lifecycle

Agents have built-in lifecycle management protocols:

func (a *IntelligenceAgent) OnInitialize() error {
    // Called when agent is first created
    a.lastContact = time.Now()
    a.threatLevel = 0.0

    fmt.Printf(`Resistance Agent %s awakened
        and ready for Matrix surveillance`, a.Id())
    return nil
}

func (a *IntelligenceAgent) OnResume() error {
    // Called when agent reconnects with complete state intact
    fmt.Printf(`Agent back online in the Matrix.
        %d intelligence reports processed.
        Threat level: %.2f`,
        len(a.intelligenceReports),
        a.threatLevel)
    return nil
}

func (a *IntelligenceAgent) OnSuspend() error {
    // Called before agent goes offline
    return nil
}

func (a *IntelligenceAgent) OnTerminate() error {
    // Called before final shutdown
    fmt.Printf(`Agent %s extracted from Matrix.
        Intelligence archive preserved.`, a.Id())
    return nil
}

Asynchronous operations

For fire-and-forget operations where you don’t need to wait for a response, agents support asynchronous messaging:

func InitiateBackgroundRecon(agentId string, data string) error {
    // Send message asynchronously - agent processes in background
    err := agents.SendMessageAsync(
        agentId,
        "background_reconnaissance",
        agents.WithData(data),
    )
    if err != nil {
        return err
    }

    // Operation initiated - agent continues processing independently
    return nil
}

This enables agents to handle long-running operations like:

  • Background Matrix monitoring with status updates
  • Scheduled intelligence gathering
  • Multi-phase operations that continue independently
  • Autonomous surveillance with alert notifications

Real-time agent event streaming

For monitoring live operations and receiving real-time intelligence updates, agents support event streaming through GraphQL subscriptions. This enables your clients to receive instant notifications about operational changes, mission progress, and critical alerts.

Subscribing to agent events

Monitor your agent’s real-time activities using the unified event subscription:

subscription {
  agentEvents(agentId: "agent_neo_001") {
    type
    payload
    timestamp
  }
}

Your agent streams various types of operational events:

{
  "data": {
    "agentEvents": {
      "type": "mission_started",
      "payload": {
        "missionName": "Deep Matrix Surveillance",
        "priority": "HIGH",
        "estimatedDuration": "180s"
      },
      "timestamp": "2025-06-04T14:30:00Z"
    }
  }
}
{
  "data": {
    "agentEvents": {
      "type": "agent_threat_detected",
      "payload": {
        "threatLevel": "CRITICAL",
        "confidence": 0.92,
        "indicators": ["agent_smith_replication", "unusual_code_patterns"],
        "recommendation": "immediate_extraction"
      },
      "timestamp": "2025-06-04T14:31:15Z"
    }
  }
}
{
  "data": {
    "agentEvents": {
      "type": "surveillance_progress",
      "payload": {
        "phase": "Processing Matrix surveillance data",
        "progress": 0.65,
        "reportsProcessed": 5,
        "totalReports": 8
      },
      "timestamp": "2025-06-04T14:32:00Z"
    }
  }
}

Emitting events from your agent

Agents can broadcast real-time operational intelligence by emitting events during their operations. Let’s enhance our Matrix surveillance example:

func (a *IntelligenceAgent) analyzeMatrixActivity(
    data string,
) (*string, error) {
    // Emit mission start event
    a.EmitEvent("mission_started", any{
        "missionName": "Matrix Surveillance Analysis",
        "priority": "HIGH",
        "activityData": len(*data),
    })

    // Store new intelligence in persistent memory
    a.intelligenceReports = append(a.intelligenceReports, *data)
    a.lastContact = time.Now()

    // Emit progress update
    a.EmitEvent("surveillance_progress", any{
        "reportsProcessed": len(a.intelligenceReports),
        "phase": "Processing Matrix surveillance data",
        "progress": 0.3,
    })

    // Build context from all accumulated intelligence
    accumulatedReports := strings.Join(a.intelligenceReports, "\n")

    // AI analysis using complete operational history
    model, err := models.GetModel[openai.ChatModel]("analyst-model")
    if err != nil {
        return nil, err
    }

    systemPrompt := `You are a resistance operative in the Matrix.
        Analyze patterns from accumulated surveillance reports
        and provide threat assessment for anomalous Agent behavior.`

    userPrompt := fmt.Sprintf(`All Matrix Intelligence:
        %s

        Provide threat assessment:`,
        accumulatedReports)

    input, err := model.CreateInput(
        openai.NewSystemMessage(systemPrompt),
        openai.NewUserMessage(userPrompt),
    )
    if err != nil {
        return nil, err
    }

    // Emit AI processing event
    a.EmitEvent("ai_analysis_started", any{
        "modelName": "analyst-model",
        "contextSize": len(accumulatedReports),
        "reportCount": len(a.intelligenceReports),
    })

    output, err := model.Invoke(input)
    if err != nil {
        return nil, err
    }
    analysis := output.Choices[0].Message.Content

    // Update threat level based on data volume and AI analysis
    a.threatLevel = float64(len(a.intelligenceReports)) / 10.0
    if a.threatLevel > 1.0 {
        a.threatLevel = 1.0
    }

    // Check for Agent threats and emit alerts
    if strings.Contains(strings.ToLower(analysis), "critical") ||
       strings.Contains(strings.ToLower(analysis), "agent smith") {
        a.threatLevel = math.Min(a.threatLevel + 0.2, 1.0)
        a.EmitEvent("agent_threat_detected", any{
            "threatLevel": "HIGH",
            "confidence": a.threatLevel,
            "analysis": analysis,
            "recommendation": "immediate_extraction",
        })
    }

    // Emit mission completion
    a.EmitEvent("mission_completed", any{
        "missionName": "Matrix Surveillance Analysis",
        "confidence": a.threatLevel,
        "reportsAnalyzed": len(a.intelligenceReports),
        "status": "SUCCESS",
    })

    result := fmt.Sprintf(`Matrix surveillance complete:
        %s

        (Threat level: %.2f based on %d intelligence reports)`,
        analysis,
        a.threatLevel,
        len(a.intelligenceReports))
    return &result, nil
}

Event-driven operational patterns

This streaming capability enables sophisticated real-time operational patterns:

Live Mission Dashboards: build real-time command centers that show agent activities, mission progress, and threat alerts as they happen.

Reactive Coordination: other agents or systems can subscribe to events and automatically respond to operational changes—enabling true multi-agent coordination.

Operational intelligence: stream events to monitoring systems, alerting platforms, or data lakes for real-time operational awareness and historical analysis.

Progressive Enhancement: update user interfaces progressively as agents work through complex, multi-phase operations without polling or manual refresh.

Monitoring ongoing operations

You can also poll agent status directly through dedicated functions:

func CheckMissionProgress(agentId string) (*MissionStatus, error) {
    result, err := agents.SendMessage(agentId, "get_status")
    if err != nil {
        return nil, err
    }
    if result == nil {
        return nil, fmt.Errorf("no response from agent")
    }

    var status MissionStatus
    err = json.Unmarshal([]byte(*result), &status)
    if err != nil {
        return nil, err
    }
    return &status, nil
}

type MissionStatus struct {
    Phase          string    `json:"phase"`
    Progress       float64   `json:"progress"`
    CurrentTask    string    `json:"current_task"`
    EstimatedTime  int       `json:"estimated_time_remaining"`
    IsComplete     bool      `json:"is_complete"`
}

The agent tracks its operational status using the mission state we defined earlier:

func (a *IntelligenceAgent) getOperationalStatus() (*string, error) {
    var status MissionStatus

    if a.currentMission == nil {
        status = MissionStatus{
            Phase:       "Standby",
            Progress:    1.0,
            CurrentTask: "Awaiting mission directives in the Matrix",
            IsComplete:  true,
        }
    } else {
        // Calculate progress based on mission log entries
        progress := float64(len(a.missionLog)) / 4.0 // 4 phases expected
        if progress > 1.0 { progress = 1.0 }

        status = MissionStatus{
            Phase:       a.currentMission.Name,
            Progress:    progress,
            CurrentTask: a.missionLog[len(a.missionLog)-1], // Latest entry
            IsComplete:  a.currentMission.Complete,
        }
    }

    statusJson, err := json.Marshal(status)
    if err != nil {
        return nil, err
    }
    result := string(statusJson)
    return &result, nil
}

Your client can either poll this status endpoint via GraphQL or subscribe to real-time events for instant updates:

# Polling approach
query MonitorMission($agentId: String!) {
  checkMissionProgress(agentId: $agentId) {
    phase
    progress
    currentTask
    estimatedTimeRemaining
    isComplete
  }
}

# Real-time streaming approach (recommended)
subscription LiveAgentMonitoring($agentId: String!) {
  agentEvents(agentId: $agentId) {
    type
    payload
    timestamp
  }
}

The streaming approach provides superior operational intelligence:

  • Instant Updates: Receive events the moment they occur, not on polling intervals
  • Rich Context: Events include detailed payload data about operational state
  • Event Filtering: Subscribe to specific agent IDs and filter event types client-side
  • Operational History: Complete timeline of agent activities for audit and debugging
  • Scalable Monitoring: Monitor multiple agents simultaneously with individual subscriptions

Beyond simple operations

Agents enable sophisticated patterns impossible with stateless functions:

  • Operational continuity: Maintain state across system failures and re-deployments
  • Intelligence building: Accumulate understanding across multiple assignments through AI-powered analysis
  • Recovery protocols: Resume operations from last secure checkpoint instead of starting over
  • Network coordination: Manage complex multi-agent operations with shared intelligence and real-time event coordination
  • Adaptive learning: AI models become more effective as agents accumulate operational data
  • Real-time streaming: Broadcast operational intelligence instantly to monitoring systems and coordinating agents
  • Event-driven coordination: React to operational changes and mission updates through real-time event streams
  • Progressive operations: Update user interfaces and trigger downstream processes as agents work through complex workflows

Agents represent the evolution from stateless functions to persistent background processes that maintain complete operational continuity, build intelligence over time, and provide real-time operational awareness. They’re the foundation for building systems that never lose track of their work, become smarter with every interaction, and keep teams informed through live event streaming—no matter what happens in the infrastructure.