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:
agents.go
package main

import (
  "encoding/json"
  "errors"
  "fmt"
  "math"
  "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
  intelligenceReports []string
  threatLevel         float64
  lastContact         time.Time
  currentMission      *MissionPhase
  missionLog          []string
}
types.go
Go Expandable
type MissionPhase struct {
  Name      string
  StartTime time.Time
  Duration  time.Duration
  Complete  bool
}

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

type ThreatDetected struct {
  ThreatLevel string  `json:"threatLevel"`
  Confidence  float64 `json:"confidence"`
  Analysis    string  `json:"analysis"`
}

type MissionStarted struct {
  MissionName  string `json:"missionName"`
  Priority     string `json:"priority"`
  ActivityData int    `json:"activityData"`
}

type SurveillanceProgress struct {
  Phase            string  `json:"phase"`
  Progress         float64 `json:"progress"`
  ReportsProcessed int     `json:"reportsProcessed"`
  TotalReports     int     `json:"totalReports"`
}

type AIAnalysisStarted struct {
  ModelName   string `json:"modelName"`
  ContextSize int    `json:"contextSize"`
  ReportCount int    `json:"reportCount"`
}

type MissionCompleted struct {
  MissionName     string  `json:"missionName"`
  Confidence      float64 `json:"confidence"`
  ReportsAnalyzed int     `json:"reportsAnalyzed"`
  Status          string  `json:"status"`
}
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:
func init() {
  agents.Register(&IntelligenceAgent{})
}

func DeployAgent() (string, error) {
  info, err := agents.Start("IntelligenceAgent")
  if err != nil {
    return "", err
  }
  return info.Id, nil
}
When you call this function through GraphQL, it returns a unique agent ID:
mutation {
  deployAgent
}
Response:
{
  "data": {
    "deployAgent": "d1qn141d6u7qu64mesjg"
  }
}
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, activityData string) (string, error) {
  res, err := agents.SendMessage(agentId, "matrix_surveillance", agents.WithData(activityData))
  if err != nil {
    return "", err
  }
  if res == nil {
    return "", errors.New("no response from agent")
  }
  return *res, nil
}

func GetThreatStatus(agentId string) (string, error) {
  res, err := agents.SendMessage(agentId, "threat_assessment")
  if err != nil {
    return "", err
  }
  if res == nil {
    return "", errors.New("no response from agent")
  }
  return *res, nil
}
These functions become GraphQL operations that you can call with your agentโ€™s ID:
mutation {
  importActivity(
    agentId: "d1qn141d6u7qu64mesjg"
    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 {
  threatStatus(agentId: "d1qn141d6u7qu64mesjg")
}
Response:
{
  "data": {
    "threatStatus": "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()
  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) {
  a.IntelReports = append(a.IntelReports, *data)
  a.LastContact = time.Now()

  model, err := models.GetModel[openai.ChatModel]("text-generator")
  if err != nil {
    return nil, err
  }

  input, err := model.CreateInput(
    openai.NewSystemMessage(
      "You are a resistance operative in the Matrix.
      Analyze patterns from accumulated surveillance reports and provide threat assessment.
      Respond in no more than 3 concise sentences."
      ),
    openai.NewUserMessage(fmt.Sprintf("All Matrix Intelligence:\n%s\n\nProvide threat assessment:", strings.Join(a.IntelReports, "\n"))),
  )
  if err != nil {
    return nil, err
  }

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

  a.ThreatLevel = math.Min(1.0, float64(len(a.IntelReports))/10.0)
  if s := strings.ToLower(analysis); strings.Contains(s, "critical") || strings.Contains(s, "agent smith") {
    a.ThreatLevel = math.Min(a.ThreatLevel+0.2, 1.0)
  }

  result := fmt.Sprintf("Matrix surveillance complete:\n%s\n\n(Threat level: %.2f based on %d intelligence reports)", analysis, a.ThreatLevel, len(a.IntelReports))
  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 {
  state := AgentState{
    ThreatLevel:         a.threatLevel,
    IntelligenceReports: a.intelligenceReports,
    LastContactUnix:     a.lastContact.Unix(),
    CurrentMission:      a.currentMission,
    MissionLog:          a.missionLog,
  }
  bytes, err := json.Marshal(state)
  if err != nil {
    return nil
  }
  result := string(bytes)
  return &result
}

func (a *IntelligenceAgent) SetState(data *string) {
  if data == nil {
    return
  }
  state := AgentState{}
  if err := json.Unmarshal([]byte(*data), &state); err != nil {
    return
  }
  a.threatLevel = state.ThreatLevel
  a.intelligenceReports = state.IntelligenceReports
  a.lastContact = time.Unix(state.LastContactUnix, 0)
  a.currentMission = state.CurrentMission
  a.missionLog = state.MissionLog
}

Agent lifecycle

Agents have built-in lifecycle management protocols:
func (a *IntelligenceAgent) OnInitialize() error {
  a.lastContact = time.Now();
  a.threatLevel = 0;
  fmt.Printf("Agent %s ready\n", a.Id());
  return nil
}

func (a *IntelligenceAgent) OnResume() error {
  fmt.Printf("Agent resumed. Reports: %d, Threat Level: %.2f\n", len(a.intelligenceReports), a.threatLevel);
  return nil
}

func (a *IntelligenceAgent) OnSuspend() error {
  return nil
}

func (a *IntelligenceAgent) OnTerminate() error {
  fmt.Printf("Agent %s terminated. Archive saved.\n", 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, data string) error {
  return agents.SendMessageAsync(
    agentId,
    "background_reconnaissance",
    agents.WithData(data)
  )
}
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 {
  agentEvent(agentId: "d1qn141d6u7qu64mesjg") {
    name
    data
    timestamp
  }
}
Your agent streams various types of operational events:
{
  "data": {
    "agentEvent": {
      "name": "mission_started",
      "data": {
        "missionName": "Deep Matrix Surveillance",
        "priority": "HIGH",
        "estimatedDuration": "180s"
      },
      "timestamp": "2025-06-04T14:30:00Z"
    }
  }
}
{
  "data": {
    "agentEvent": {
      "name": "agent_threat_detected",
      "data": {
        "threatLevel": "CRITICAL",
        "confidence": 0.92,
        "indicators": ["agent_smith_replication", "unusual_code_patterns"],
        "recommendation": "immediate_extraction"
      },
      "timestamp": "2025-06-04T14:31:15Z"
    }
  }
}
{
  "data": {
    "agentEvent": {
      "name": "surveillance_progress",
      "data": {
        "phase": "Processing Matrix surveillance data",
        "progress": 0.65,
        "reportsProcessed": 5,
        "totalReports": 8
      },
      "timestamp": "2025-06-04T14:32:00Z"
    }
  }
}

Publishing events from your agent

Agents can broadcast real-time operational intelligence by publishing events during their operations. Use the PublishEvent method to emit custom events:
// Custom event types implement the AgentEvent interface
type ThreatDetected struct {
    ThreatLevel string `json:"threatLevel"`
    Confidence  float64 `json:"confidence"`
    Analysis    string `json:"analysis"`
}
func (ThreatDetected) EventName() string { return "threat_detected" }

// Other event types can be defined similarly...

func (a *IntelligenceAgent) analyzeMatrixActivity(data *string) (*string, error) {
  a.PublishEvent(MissionStarted{
    MissionName:  "Matrix Surveillance Analysis",
    Priority:     "HIGH",
    ActivityData: len(*data),
  })

  a.intelligenceReports = append(a.intelligenceReports, *data)
  a.lastContact = time.Now()

  a.PublishEvent(SurveillanceProgress{
    Phase:            "Processing Matrix surveillance data",
    Progress:         0.3,
    ReportsProcessed: len(a.intelligenceReports),
    TotalReports:     len(a.intelligenceReports),
  })

  model, err := models.GetModel[openai.ChatModel]("text-generator")
  if err != nil {
    return nil, err
  }

  a.PublishEvent(AIAnalysisStarted{
    ModelName:   "text-generator",
    ContextSize: len(strings.Join(a.intelligenceReports, "\n")),
    ReportCount: len(a.intelligenceReports),
  })

  input, err := model.CreateInput(
    openai.NewSystemMessage("You are a resistance operative in the Matrix. Analyze patterns from accumulated surveillance reports and provide threat assessment."),
    openai.NewUserMessage(fmt.Sprintf("All Matrix Intelligence:\n%s\n\nProvide threat assessment:", strings.Join(a.intelligenceReports, "\n"))),
  )
  if err != nil {
    return nil, err
  }

  output, err := model.Invoke(input)
  if err != nil {
    return nil, err
  }

  analysis := output.Choices[0].Message.Content
  a.threatLevel = math.Min(1.0, float64(len(a.intelligenceReports))/10.0)

  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.PublishEvent(ThreatDetected{
      ThreatLevel: "HIGH",
      Confidence:  a.threatLevel,
      Analysis:    analysis,
    })
  }

  a.PublishEvent(MissionCompleted{
    MissionName:     "Matrix Surveillance Analysis",
    Confidence:      a.threatLevel,
    ReportsAnalyzed: len(a.intelligenceReports),
    Status:          "SUCCESS",
  })

  result := fmt.Sprintf("Matrix surveillance complete:\n%s\n\n(Threat level: %.2f based on %d 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.

Subscription protocol

Modus uses GraphQL subscriptions over Server-Sent Events (SSE) following the GraphQL-SSE specification. To consume these subscriptions:
  1. From a web browser: Use the EventSource API or a GraphQL client that supports SSE subscriptions
  2. From Postman: Set Accept header to text/event-stream and make a POST request
  3. From curl: Use -N flag and appropriate headers for streaming
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: \"d1qn141d6u7qu64mesjg\") { name data timestamp } }"}'

Monitoring ongoing operations

You can also poll agent status directly through dedicated functions:
func CheckMissionProgress(agentId string) (*MissionStatus, error) {
  res, err := agents.SendMessage(agentId, "get_status")
  if err != nil || res == nil {
    return nil, err
  }
  var status MissionStatus
  if err := json.Unmarshal([]byte(*res), &status); 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) {
  status := MissionStatus{
    Phase:       "Standby",
    Progress:    1.0,
    CurrentTask: "Awaiting mission directives in the Matrix",
    IsComplete:  true,
  }

  if a.currentMission != nil && len(a.missionLog) > 0 {
    progress := float64(len(a.missionLog)) / 4.0
    if progress > 1 {
      progress = 1
    }
    status = MissionStatus{
      Phase:       a.currentMission.Name,
      Progress:    progress,
      CurrentTask: a.missionLog[len(a.missionLog)-1],
      IsComplete:  a.currentMission.Complete,
    }
  }

  jsonData, err := json.Marshal(status)
  if err != nil {
    return nil, err
  }
  result := string(jsonData)
  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!) {
  agentEvent(agentId: $agentId) {
    name
    data
    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.