diff --git a/docs.json b/docs.json index 0c6f771..281bd74 100644 --- a/docs.json +++ b/docs.json @@ -80,13 +80,16 @@ "group": "Getting Started", "pages": [ "modus/overview", - "modus/project-structure", - "modus/quickstart" + "modus/quickstart", + "modus/functions", + "modus/agents", + "modus/knowledge-graphs" ] }, { "group": "Building Your App", "pages": [ + "modus/project-structure", "modus/app-manifest", "modus/data-fetching", "modus/model-invoking", @@ -107,6 +110,7 @@ "group": "AssemblyScript SDK", "pages": [ "modus/sdk/assemblyscript/overview", + "modus/sdk/assemblyscript/agents", "modus/sdk/assemblyscript/console", "modus/sdk/assemblyscript/dgraph", "modus/sdk/assemblyscript/graphql", @@ -122,6 +126,7 @@ "group": "Go SDK", "pages": [ "modus/sdk/go/overview", + "modus/sdk/go/agents", "modus/sdk/go/console", "modus/sdk/go/dgraph", "modus/sdk/go/graphql", diff --git a/modus/agents.mdx b/modus/agents.mdx new file mode 100644 index 0000000..3d59bec --- /dev/null +++ b/modus/agents.mdx @@ -0,0 +1,791 @@ +--- +title: "What is an Agent?" +description: "Learn about stateful agents in Modus" +"og:title": "What is an Agent? - Modus" +--- + +## 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: + +```go +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: + +```go +// 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: + +```graphql +mutation { + deployAgent +} +``` + +Response: + +```json +{ + "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: + +```go +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: + +```graphql +mutation { + importActivity( + agentId: "agent_neo_001" + activityData: "Anomalous Agent Smith replication detected in Sector 7" + ) +} +``` + +Response: + +```json +{ + "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." + } +} +``` + +```graphql +query { + getThreatStatus(agentId: "agent_neo_001") +} +``` + +Response: + +```json +{ + "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: + +```go +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: + +```go +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: + +```go +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: + +```go +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: + +```go +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: + +```graphql +subscription { + agentEvent(agentId: "agent_neo_001") { + name + data + timestamp + } +} +``` + +Your agent streams various types of operational events: + +```json +{ + "data": { + "agentEvent": { + "name": "mission_started", + "data": { + "missionName": "Deep Matrix Surveillance", + "priority": "HIGH", + "estimatedDuration": "180s" + }, + "timestamp": "2025-06-04T14:30:00Z" + } + } +} +``` + +```json +{ + "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" + } + } +} +``` + +```json +{ + "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: + +```go +// Custom event types implement the AgentEvent interface +type ThreatDetected struct { + ThreatLevel string `json:"threatLevel"` + Confidence float64 `json:"confidence"` + Analysis string `json:"analysis"` +} + +func (e ThreatDetected) EventName() string { + return "threat_detected" +} + +// Other event types can be defined similarly... + +func (a *IntelligenceAgent) analyzeMatrixActivity( + data string, +) (*string, error) { + // Emit mission start event + err := a.PublishEvent(MissionStarted{ + MissionName: "Matrix Surveillance Analysis", + Priority: "HIGH", + ActivityData: len(*data), + }) + if err != nil { + return nil, err + } + + // Store new intelligence in persistent memory + a.intelligenceReports = append(a.intelligenceReports, *data) + a.lastContact = time.Now() + + // Emit progress update + a.PublishEvent(SurveillanceProgress{ + 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.PublishEvent(AIAnalysisStarted{ + 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.PublishEvent(ThreatDetected{ + ThreatLevel: "HIGH", + Confidence: a.threatLevel, + Analysis: analysis, + }) + } + + // Emit mission completion + a.PublishEvent(MissionCompleted{ + 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. + +### Subscription protocol + +Modus uses GraphQL subscriptions over Server-Sent Events (SSE) following the +[GraphQL-SSE specification](https://the-guild.dev/graphql/sse). 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: + +```bash +curl -N -H "accept: text/event-stream" \ + -H "content-type: application/json" \ + -X POST http://localhost:8080/graphql \ + -d '{"query":"subscription { agentEvent(agentId: \"agent_neo_001\") { name data timestamp } }"}' +``` + +## Monitoring ongoing operations + +You can also poll agent status directly through dedicated functions: + +```go +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: + +```go +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: + +```graphql +# 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. diff --git a/modus/api-generation.mdx b/modus/api-generation.mdx index 041ed08..b2ebf1a 100644 --- a/modus/api-generation.mdx +++ b/modus/api-generation.mdx @@ -152,6 +152,7 @@ these prefixes: - `post`, `patch`, `put`, `delete` - `add`, `update`, `insert`, `upsert` - `create`, `edit`, `save`, `remove`, `alter`, `modify` +- `start`, `stop` For example: diff --git a/modus/functions.mdx b/modus/functions.mdx new file mode 100644 index 0000000..6ecf481 --- /dev/null +++ b/modus/functions.mdx @@ -0,0 +1,328 @@ +--- +title: "What is a Function?" +description: "Learn about functions in Modus" +"og:title": "What is a Function? - Modus" +--- + +## Functions in Modus + +Functions in Modus are stateless, request-response operations that handle +specific tasks with precision and speed. They get in, complete their objective, +and report back—no unnecessary complications, no lingering presence, just pure +operational efficiency. + +You can think of a function as an endpoint—each function you write automatically +becomes a callable API endpoint that external systems can access. + +Functions are the backbone of your app, handling everything from data gathering +to AI-powered analysis, all while maintaining clean, predictable behavior. + +## Core characteristics + +Functions are stateless, request-response operations designed for fast, +predictable execution. They're your go-to choice when you need quick data +processing, external API integration, or computational tasks without maintaining +state between requests. + +### Key capabilities + +- **Stateless**: Each operation is independent with no memory of previous + requests +- **Lightning fast**: Optimized for sub-second response times +- **Infinitely scalable**: Deploy as many instances as needed +- **Clean operations**: Straightforward request-in, response-out pattern +- **Auto-deployed**: Exposed automatically as GraphQL queries or mutations + +## How functions become endpoints + +When you deploy your functions, Modus automatically exposes them through a +GraphQL API. Your functions become either **queries** (for data retrieval) or +**mutations** (for data modifications) based on their operation type. + +### Data retrieval queries + +Most functions become GraphQL queries—perfect for fetching and processing data: + +```go +// This function becomes a GraphQL query +func GatherThreatIntelligence(source string) (*ThreatReport, error) { + // Data gathering and processing operation + return fetchThreatData(source) +} +``` + +Your functions are now accessible via GraphQL: + +```graphql +query { + gatherThreatIntelligence(source: "network_logs") { + threatLevel + indicators + recommendations + } +} +``` + +**Response:** + +```json +{ + "data": { + "gatherThreatIntelligence": { + "threatLevel": "HIGH", + "indicators": ["unusual_traffic", "failed_auth_attempts"], + "recommendations": ["immediate_investigation", "block_suspicious_ips"] + } + } +} +``` + +### Data modification mutations + +Functions that modify data automatically become GraphQL mutations. Modus detects +these by their operation prefixes: + +```go +// This becomes a GraphQL mutation +func CreateSecurityAlert(data AlertInput) (*SecurityAlert, error) { + // Create new security alert + return deploySecurityAlert(data) +} +``` + +Now you can execute data modifications: + +```graphql +mutation { + createSecurityAlert( + data: { + type: "INTRUSION_ATTEMPT" + severity: "CRITICAL" + source: "firewall_logs" + } + ) { + alertId + status + timestamp + } +} +``` + +**Response:** + +```json +{ + "data": { + "createSecurityAlert": { + "alertId": "alert_20250115_001", + "status": "ACTIVE", + "timestamp": "2025-01-15T14:30:00Z" + } + } +} +``` + +Functions starting with `create`, `update`, `delete`, and similar action words +automatically become mutations. + +## Example: Weather intelligence analysis + +Here's a complete example that demonstrates how functions integrate external +APIs with AI models for intelligent data processing: + +```go +package main + +import ( + "fmt" + "strings" + "github.com/hypermodeinc/modus/sdk/go/pkg/http" + "github.com/hypermodeinc/modus/sdk/go/pkg/models" + "github.com/hypermodeinc/modus/sdk/go/pkg/models/openai" +) + +type WeatherIntel struct { + City string `json:"city"` + Temperature float64 `json:"temperature"` + Conditions string `json:"conditions"` + Analysis string `json:"analysis"` +} + +const modelName = "text-generator" + +// Function: Gather weather data and provide tactical analysis +func GatherWeatherIntelligence(city string) (*WeatherIntel, error) { + // Fetch weather data from OpenWeatherMap API + url := fmt.Sprintf( + "https://api.openweathermap.org/data/2.5/weather?q=%s&appid={{API_KEY}}&units=metric", + city, + ) + + response, err := http.Fetch(url) + if err != nil { + return nil, err + } + if !response.Ok() { + return nil, fmt.Errorf( + "weather data retrieval failed: %d %s", + response.Status, + response.StatusText, + ) + } + + // Parse weather data + var weatherData struct { + Name string `json:"name"` + Main struct { + Temp float64 `json:"temp"` + } `json:"main"` + Weather []struct { + Description string `json:"description"` + } `json:"weather"` + } + + response.JSON(&weatherData) + + conditions := "unknown" + if len(weatherData.Weather) > 0 { + conditions = weatherData.Weather[0].Description + } + + // Generate tactical analysis + analysis, err := analyzeTacticalConditions( + weatherData.Name, + weatherData.Main.Temp, + conditions, + ) + if err != nil { + fmt.Printf("Analysis failed for %s: %v\n", weatherData.Name, err) + analysis = "Analysis unavailable - proceed with standard protocols" + } + + return &WeatherIntel{ + City: weatherData.Name, + Temperature: weatherData.Main.Temp, + Conditions: conditions, + Analysis: analysis, + }, nil +} + +// Analyze weather conditions for tactical implications +func analyzeTacticalConditions(city string, temp float64, conditions string) (string, error) { + model, err := models.GetModel[openai.ChatModel](modelName) + if err != nil { + return "", err + } + + prompt := `You are a tactical analyst evaluating weather conditions for field operations. + Provide a brief tactical assessment of how these weather conditions might impact + outdoor activities, visibility, and operational considerations in 1-2 sentences.` + + content := fmt.Sprintf( + "Location: %s, Temperature: %.1f°C, Conditions: %s", + city, + temp, + conditions, + ) + + input, err := model.CreateInput( + openai.NewSystemMessage(prompt), + openai.NewUserMessage(content), + ) + if err != nil { + return "", err + } + + input.Temperature = 0.7 + + output, err := model.Invoke(input) + if err != nil { + return "", err + } + + return strings.TrimSpace(output.Choices[0].Message.Content), nil +} +``` + +This function automatically becomes available as a GraphQL query: + +```graphql +query { + gatherWeatherIntelligence(city: "London") { + city + temperature + conditions + analysis + } +} +``` + +You'll receive an intelligence report like: + +```json +{ + "data": { + "gatherWeatherIntelligence": { + "city": "London", + "temperature": 12.3, + "conditions": "light rain", + "analysis": " + Light rain conditions will reduce visibility for surveillance operations and may impact equipment performance. + Recommend waterproof gear and consider delayed outdoor activities requiring clear sight lines." + } + } +} +``` + +### Setting up the function + +To use this function, you'll need to: + +Sign up for a free API key at [OpenWeatherMap](https://openweathermap.org/api) + +Add the connections and models to your `modus.json`: + +```json +{ + "models": { + "text-generator": { + "sourceModel": "meta-llama/Llama-3.2-3B-Instruct", + "provider": "hugging-face", + "connection": "hypermode" + } + }, + "connections": { + "weather-api": { + "type": "http", + "baseUrl": "https://api.openweathermap.org/", + "queryParameters": { + "appid": "{{API_KEY}}" + } + } + } +} +``` + +Set your API key in `.env.dev.local`: + +```sh +MODUS_WEATHER_API_API_KEY=your_openweathermap_api_key_here +``` + +## When to use agents instead + +Functions are perfect for most computational tasks, but when you need persistent +state, complex multi-step workflows, or processes that remember details across +multiple interactions, it's time to consider [agents](/modus/agents). + +Consider upgrading to agents when your use case requires: + +- **Persistent memory** across multiple requests +- **Conversation context** that builds over time +- **Multi-step workflows** spanning extended periods +- **Recovery from failures** with state preservation +- **Continuous monitoring** with context retention + +Your functions form the API backbone of your app—fast, reliable, and always +ready for the next request. They handle the immediate computational needs while +your agents manage the long-term stateful processes. diff --git a/modus/knowledge-graphs.mdx b/modus/knowledge-graphs.mdx new file mode 100644 index 0000000..6f6613a --- /dev/null +++ b/modus/knowledge-graphs.mdx @@ -0,0 +1,596 @@ +--- +title: "What about knowledge graphs?" +description: + "Build persistent intelligence networks using agents, functions, and knowledge + graphs" +"og:title": "What about knowledge graphs? - Modus" +--- + +## Knowledge graphs in Modus + +Your applications need more than just individual memory—they need shared +organizational knowledge. While agents maintain their own operational state and +memory across interactions, knowledge graphs provide something fundamentally +different: shared institutional knowledge that captures relationships between +entities, events, and data across your entire app. + +At Hypermode, we recognize that knowledge graphs aren't just storage—they're +becoming critical infrastructure for next-generation AI systems. That's why +we've invested deeply in Dgraph, bringing enterprise-grade graph capabilities to +Modus applications. + +This is where knowledge graphs transform your Modus deployment from isolated +processes into a coordinated system with shared institutional memory. + +## What are knowledge networks? + +Knowledge networks in Modus combine: + +- **Agent state**: personal memory that each agent maintains across interactions +- **Knowledge graphs**: shared organizational knowledge that captures + relationships between entities, events, and data across your entire app +- **Functions**: rapid operations for data processing and analysis +- **AI models**: advanced pattern recognition and decision-making capabilities + +Think of it as the difference between what an individual process remembers +versus what your organization knows. Agent state is personal memory—what +happened to this specific agent, what conversations they've had, what tasks +they're tracking. Knowledge graphs are organizational intelligence—how entities +relate to each other, which patterns connect to which outcomes, what +relationships emerge across all operations. + +## Setting up your knowledge infrastructure + +First, connect to your knowledge graph by adding this to your `modus.json`: + +```json +{ + "connections": { + "dgraph": { + "type": "dgraph", + "connString": "dgraph://your-graph.hypermode.host:443?sslmode=verify-ca&bearertoken={{API_KEY}}" + } + }, + "models": { + "text-generator": { + "sourceModel": "meta-llama/Llama-3.2-3B-Instruct", + "provider": "hugging-face", + "connection": "hypermode" + } + } +} +``` + +Set your credentials in `.env.dev.local`: + +```sh +MODUS_DGRAPH_API_KEY=your_graph_api_key_here +``` + +## Building a comprehensive system + +Let's walk through a realistic scenario that demonstrates how all these +components work together. You're building a system to track anomalous Agent +behavior in the simulated reality. The system needs to: + +1. Rapidly import new Agent sightings and behavioral data +2. Find patterns across historical Agent encounters +3. Coordinate ongoing surveillance operations +4. Provide strategic analysis to the resistance + +### Step 1: Rapid data import + +When new Agent activity is detected in the Matrix, you need to process it +quickly. This is perfect for a stateless function: + +```go +type AgentSighting struct { + SightingID string `json:"sighting_id"` + AgentName string `json:"agent_name"` + Location string `json:"location"` + Behavior string `json:"behavior"` + ThreatLevel int `json:"threat_level"` + Timestamp string `json:"timestamp"` +} + +func ImportAgentSighting(sighting AgentSighting) (*string, error) { + // AI-powered analysis of the Agent behavior + analysis, err := analyzeAgentWithAI(sighting.Behavior) + if err != nil { + return nil, err + } + + // Store in knowledge graph - builds organizational knowledge + mutation := dgraph.NewMutation().WithSetJson(fmt.Sprintf(`{ + "dgraph.type": "AgentSighting", + "sighting_id": "%s", + "agent_name": "%s", + "location": "%s", + "behavior": "%s", + "threat_level": %d, + "timestamp": "%s", + "ai_analysis": "%s" + }`, sighting.SightingID, sighting.AgentName, sighting.Location, + sighting.Behavior, sighting.ThreatLevel, sighting.Timestamp, analysis)) + + err = dgraph.ExecuteMutations("dgraph", mutation) + if err != nil { + return nil, err + } + + result := fmt.Sprintf("Agent sighting processed: %s", sighting.SightingID) + return &result, nil +} + +func analyzeAgentWithAI(behavior string) (string, error) { + model, err := models.GetModel[openai.ChatModel]("text-generator") + if err != nil { + return "", err + } + + prompt := `Analyze this Agent behavior pattern and assess threat level, + behavioral changes, and tactical implications for resistance operations.` + + input, err := model.CreateInput( + openai.NewSystemMessage(prompt), + openai.NewUserMessage(behavior), + ) + if err != nil { + return "", err + } + input.Temperature = 0.3 + + output, err := model.Invoke(input) + if err != nil { + return "", err + } + return strings.TrimSpace(output.Choices[0].Message.Content), nil +} +``` + +Now let's deploy this data import function and test it: + +```graphql +mutation { + importAgentSighting( + sighting: { + sightingId: "SIGHT-2025-001" + agentName: "Smith" + location: "Downtown Loop - Financial District" + behavior: "Unusual pattern recognition algorithm detected. + Agent displaying enhanced replication capabilities + beyond normal parameters." + threatLevel: 9 + timestamp: "2025-01-15T14:30:00Z" + } + ) +} +``` + +**Response:** + +```json +{ + "data": { + "importAgentSighting": "Agent sighting processed: SIGHT-2025-001" + } +} +``` + +### Step 2: Strategic analysis using organizational knowledge + +Now that we've got the Agent sighting data in our knowledge graph, let's analyze +the broader threat landscape: + +```go +type ThreatAnalysisResponse struct { + SightingCount int `json:"sighting_count"` + ActiveAgents []string `json:"active_agents"` + ThreatAssessment string `json:"threat_assessment"` + Recommendations []string `json:"recommendations"` +} + +func AnalyzeAgentPatterns(timeRange string) (*ThreatAnalysisResponse, error) { + // Query organizational knowledge - traverses relationships + query := dgraph.NewQuery(` + query analyzeAgents($since: string) { + sightings(func: ge(timestamp, $since)) { + agent_name + behavior + threat_level + ai_analysis + } + } + `).WithVariable("$since", timeRange) + + response, err := dgraph.ExecuteQuery("dgraph", query) + if err != nil { + return nil, err + } + + // Parse and extract threat data + var data SightingsData + err = json.Unmarshal([]byte(response.Json), &data) + if err != nil { + return nil, err + } + + // Generate strategic assessment using AI with graph context + assessment, err := generateThreatAssessment(data.Sightings) + if err != nil { + return nil, err + } + + return &ThreatAnalysisResponse{ + SightingCount: len(data.Sightings), + ActiveAgents: extractActiveAgents(data.Sightings), + ThreatAssessment: assessment, + Recommendations: generateRecommendations(len(data.Sightings)), + }, nil +} + +func generateThreatAssessment(sightings interface{}) (string, error) { + model, err := models.GetModel[openai.ChatModel]("text-generator") + if err != nil { + return "", err + } + + prompt := `Based on these Agent sightings, provide a strategic threat + assessment focusing on behavioral patterns and risks to + resistance operations.` + + sightingsJson, err := json.Marshal(sightings) + if err != nil { + return "", err + } + + input, err := model.CreateInput( + openai.NewSystemMessage(prompt), + openai.NewUserMessage(fmt.Sprintf("Agent surveillance data: %s", + string(sightingsJson))), + ) + if err != nil { + return "", err + } + input.Temperature = 0.4 + + output, err := model.Invoke(input) + if err != nil { + return "", err + } + return strings.TrimSpace(output.Choices[0].Message.Content), nil +} + +// Helper functions for data extraction and recommendations +func extractActiveAgents(sightings []AgentSightingData) []string { /* ... */ } +func generateRecommendations(count int) []string { /* ... */ } +``` + +Let's query our surveillance data: + +```graphql +query { + analyzeAgentPatterns(timeRange: "2025-01-01T00:00:00Z") { + sightingCount + activeAgents + threatAssessment + recommendations + } +} +``` + +**Response:** + +```json +{ + "data": { + "analyzeAgentPatterns": { + "sightingCount": 1, + "activeAgents": ["Smith"], + "threatAssessment": "Initial Agent Smith sighting shows enhanced + replication capabilities beyond standard parameters. + Requires additional surveillance data for pattern + analysis and threat escalation assessment.", + "recommendations": [ + "Continue surveillance monitoring", + "Increase Agent activity detection" + ] + } + } +} +``` + +### Step 3: Automated processing with asynchronous coordination + +Now let's enhance our system to automatically coordinate surveillance when new +data arrives. We'll deploy persistent surveillance agents and upgrade our import +function to trigger them: + +```go +type SurveillanceAgent struct { + agents.AgentBase + MonitoredSectors []string `json:"monitored_sectors"` + SightingsTracked int `json:"sightings_tracked"` + RecentActivities []string `json:"recent_activities"` + LastSweepTime time.Time `json:"last_sweep_time"` +} + +func (s *SurveillanceAgent) Name() string { + return "SurveillanceAgent" +} + +func (s *SurveillanceAgent) OnInitialize() error { + s.MonitoredSectors = []string{ + "Downtown Loop", "Megacity Financial", "Industrial District"} + s.SightingsTracked = 0 + s.RecentActivities = []string{} + s.LastSweepTime = time.Now() + return nil +} + +func (s *SurveillanceAgent) OnReceiveMessage( + msgName string, data string) (*string, error) { + switch msgName { + case "continuous_surveillance": + return s.processNewIntelligence() + case "get_status": + return s.getOperationalStatus() + } + return nil, fmt.Errorf("unrecognized directive: %s", msgName) +} + +func (s *SurveillanceAgent) processNewIntelligence() (*string, error) { + // Query knowledge graph for latest data since last sweep + query := dgraph.NewQuery(` + query getRecentSightings($since: string) { + sightings(func: ge(timestamp, $since)) { + agent_name + threat_level + location + } + } + `).WithVariable("$since", s.LastSweepTime.Format(time.RFC3339)) + + _, err := dgraph.ExecuteQuery("dgraph", query) + if err != nil { + return nil, err + } + + // Update agent's surveillance state + s.LastSweepTime = time.Now() + s.SightingsTracked += 1 + + activity := fmt.Sprintf("Auto surveillance at %s", + s.LastSweepTime.Format("15:04:05")) + s.RecentActivities = append(s.RecentActivities, activity) + + // Keep only last 3 activities + if len(s.RecentActivities) > 3 { + s.RecentActivities = s.RecentActivities[1:] + } + + result := fmt.Sprintf(`Data processed automatically. + Tracking %d sightings. Matrix integrity: COMPROMISED`, + s.SightingsTracked) + return &result, nil +} + +func (s *SurveillanceAgent) getOperationalStatus() (*string, error) { + status := fmt.Sprintf(`Surveillance Agent Status: +- Operational: Active +- Monitoring %d sectors: %s +- Last sweep: %s +- Tracking %d ongoing sightings +- Recent activities: %s`, + len(s.MonitoredSectors), + strings.Join(s.MonitoredSectors, ", "), + s.LastSweepTime.Format("2006-01-02 15:04:05"), + s.SightingsTracked, + strings.Join(s.RecentActivities, ", ")) + return &status, nil +} + +func init() { agents.Register(&SurveillanceAgent{}) } + +func DeploySurveillanceAgent() (string, error) { + agentInfo, err := agents.Start("SurveillanceAgent") + if err != nil { + return "", err + } + return agentInfo.Id, nil +} + +func GetSurveillanceStatus(agentId string) (string, error) { + result, err := agents.SendMessage(agentId, "get_status") + if err != nil { + return "", err + } + if result == nil { + return "", fmt.Errorf("no response from agent") + } + return *result, nil +} +``` + +Now let's enhance our original import function to automatically trigger +surveillance: + +```go +func ImportAgentSighting(sighting AgentSighting) (*string, error) { + // AI-powered analysis of the Agent behavior + analysis, err := analyzeAgentWithAI(sighting.Behavior) + if err != nil { + return nil, err + } + + // Store in knowledge graph - builds organizational knowledge + mutation := dgraph.NewMutation().WithSetJson(fmt.Sprintf(`{ + "dgraph.type": "AgentSighting", + "sighting_id": "%s", + "agent_name": "%s", + "location": "%s", + "behavior": "%s", + "threat_level": %d, + "timestamp": "%s", + "ai_analysis": "%s" + }`, sighting.SightingID, sighting.AgentName, sighting.Location, + sighting.Behavior, sighting.ThreatLevel, sighting.Timestamp, analysis)) + + err = dgraph.ExecuteMutations("dgraph", mutation) + if err != nil { + return nil, err + } + + // Automatically trigger surveillance via async message + err = agents.SendMessageAsync("agent_neo_001", "continuous_surveillance") + if err != nil { + return nil, err + } + + result := fmt.Sprintf("Agent sighting processed: %s", sighting.SightingID) + return &result, nil +} +``` + +Deploy your surveillance agent: + +```graphql +mutation { + deploySurveillanceAgent +} +``` + +**Response:** + +```json +{ + "data": { + "deploySurveillanceAgent": "agent_neo_001" + } +} +``` + +### Step 4: Coordinated processing + +Now when you import new Agent sightings, surveillance automatically triggers: + +```graphql +mutation { + importAgentSighting( + sighting: { + sightingId: "SIGHT-2025-002" + agentName: "Brown" + location: "Megacity Financial - Server Room B12" + behavior: "Agent Brown detected implementing advanced + countermeasures against known resistance + encryption protocols. Adaptive learning + subroutines active." + threatLevel: 8 + timestamp: "2025-01-15T15:45:00Z" + } + ) +} +``` + +**Response:** + +```json +{ + "data": { + "importAgentSighting": "Agent sighting processed: SIGHT-2025-002" + } +} +``` + +Each import automatically triggers the surveillance agent through asynchronous +messaging. Check the surveillance status: + +```graphql +query { + getSurveillanceStatus(agentId: "agent_neo_001") +} +``` + +**Response:** + +```json +{ + "data": { + "getSurveillanceStatus": "Surveillance Agent Status: + - Operational: Active + - Monitoring 3 sectors: Downtown Loop, + Megacity Financial, Industrial District + - Last sweep: 2025-01-15 15:45:22 + - Tracking 2 ongoing sightings + - Recent activities: Auto surveillance at 14:30:05, + Auto surveillance at 15:45:22" + } +} +``` + +### Step 5: Enhanced threat analysis + +Query the strategic analysis to see patterns across automatically processed +data: + +```graphql +query { + analyzeAgentPatterns(timeRange: "2025-01-15T00:00:00Z") { + sightingCount + activeAgents + threatAssessment + recommendations + } +} +``` + +**Response:** + +```json +{ + "data": { + "analyzeAgentPatterns": { + "sightingCount": 2, + "activeAgents": ["Smith", "Brown"], + "threatAssessment": "Critical escalation detected. Agent Smith's + enhanced replication capabilities combined with + Agent Brown's encryption countermeasures indicates + coordinated Matrix defense upgrade. Systematic + pattern suggests machines adapting to resistance + operations.", + "recommendations": [ + "Emergency extraction protocols", + "Activate deep cover cells", + "Increase surveillance sweeps" + ] + } + } +} +``` + +## Conclusion + +You've just built a complete automated surveillance network that demonstrates +the power of coordinated systems. By combining functions for rapid data +processing, knowledge graphs for organizational memory, AI models for enhanced +analysis, and agents for persistent processing—all coordinated through +asynchronous messaging—you've created something far more powerful than any +single component could achieve. + +Your system now automatically processes Agent sightings, triggers surveillance +operations, builds organizational knowledge over time, and provides AI-enhanced +threat analysis across all accumulated data. The surveillance agent maintains +persistent memory across system failures while the knowledge graph captures +relationships that no single sighting could reveal. + +This isn't just a database with some AI on top—it's a coordinated system where +each component enhances the others, creating emergent capabilities that scale +with your operations. Welcome to the real world. + +## Next steps + +Ready to deploy your surveillance network against the machines? Check out: + +- [Dgraph integration guide](/modus/modus-dgraph) for advanced graph operations +- [Agent coordination patterns](/modus/agents) for multi-agent workflows +- [Production deployment](/modus/deploying) for scaling your knowledge network diff --git a/modus/overview.mdx b/modus/overview.mdx index ae5f9b4..d7c47e6 100644 --- a/modus/overview.mdx +++ b/modus/overview.mdx @@ -8,49 +8,76 @@ sidebarTitle: "Overview" ## What is Modus? {/* vale Google.Contractions = NO */} -Modus is an open source, serverless framework for building functions and APIs, -powered by WebAssembly. +Modus is an open source, serverless framework for building intelligent agents +and APIs in Go or AssemblyScript (a TypeScript-like language). Modus is a +runtime purpose-built for orchestrating autonomous AI agents that operate as +first-class citizens in your stack. -You write your app logic in Go or AssemblyScript, and Modus provides additional -features to easily integrate models, data, and external services. +You write your app logic in Go or AssemblyScript—whichever you prefer—and Modus +compiles everything to WebAssembly for fast performance at scale and at the +edge. Each agent instance gets a dedicated execution environment, sand-boxed for +resiliency, scalability, and security. -To **build apps that are thoughtful, fun, and effective**, we often need to -integrate models in different forms, whether generative large language models or -classical machine/deep learning models. +Modus enables both stateless functions for quick API responses and stateful +agents that maintain persistent memory across interactions. This eliminates the +need to parse conversation histories, rebuild context from scratch, or lose +state when errors occur. -Your app might be a simple create, read, update, and delete (CRUD) function that -has a single model to identify similar entries. Or, it could be a complex -agentic reasoning system that chains dozens of models and data sources together. -**Modus creates a way of working with models that scales with your needs.** +Modus provides a complete toolkit for building intelligent applications: -Modus exists to make it easier for you to build the apps of your dreams. +- **Functions**: Stateless operations that automatically become shareable skills + via auto-generated APIs +- **Agents**: Stateful entities with short-term and long-term memory that + coordinate models, tools, and data +- **Models**: Domain-specific AI models with the ability to easily swap in + different providers or versions +- **Knowledge Graphs**: Organizational memory that scales context across + multi-agent architectures - - Modus is a multi-language framework. It currently includes support for Go and - AssemblyScript, a WebAssembly compatible TypeScript-like language. Additional - language support is in development. - +Built-in observability provides automatic inference logging and tracing, while +secure-by-default authorization ensures risk-appropriate access to tools and +data. Following modern best practices for AI-native architectures, Modus +implements principles from the 12-factor agentic app methodology for +maintainable, scalable intelligent systems. + +You can run Modus locally for development or deploy it in seconds to Hypermode +for production. ## What is Modus good for? {/* vale Google.Contractions = NO */} -We designed Modus primarily as a general-purpose app framework, it just happens -to treat models as a first-class component. With Modus you can use models, as -appropriate, without additional complexity. +Modus is designed for building applications where AI is at its core capability. +It supports sub-second response times for stateless operations and long-running, +autonomous workflows that maintain state over time. + +**Beyond demo prompt apps**: we've all built apps with prompts and AI tool +integrations. When you're ready to take your project to the next level, Modus +serves as your AI component in the stack. Modus can sit right alongside your +existing app, handling the intelligent workflows while your main app focuses on +what it does best. -However, Modus is best for apps that require sub-second response times. We've -made trade-offs to optimize for speed and simplicity. +Both functions and agents can be deployed in the same app, allowing you to +choose the right abstraction for each use case. For more inspiration, check out the [Modus recipes](https://github.com/hypermodeinc/modus-recipes). -## Main features - -A few of the core Modus features include: +## Where to next? -| Feature | Description | -| ------------------------------------------- | -------------------------------------------------------------------------------------------- | -| [Multi-Language](/modus/project-structure) | Write functions in Go and AssemblyScript, with additional language support in development | -| [Auto-Generated API](/modus/api-generation) | A secure API is automatically generated from your function signatures | -| [Model Integration](/modus/model-invoking) | Connect and invoke AI models from different providers, without learning a new SDK | -| [Authentication](/modus/authentication) | Secure your API endpoints with minimal configuration | -| **WebAssembly Runtime** | Small and portable execution engine for deployment across server, edge, and desktop computes | + + + Build your first Modus app in minutes with our quickstart guide + + + Learn about stateless functions for APIs and data processing + + + Discover stateful agents that maintain memory across interactions + + + Build intelligence networks with persistent institutional memory + + diff --git a/modus/quickstart.mdx b/modus/quickstart.mdx index 2756e5a..8b117bd 100644 --- a/modus/quickstart.mdx +++ b/modus/quickstart.mdx @@ -5,9 +5,16 @@ mode: "wide" "og:title": "Quickstart - Modus" --- -In this quickstart we'll show you how to get set up with Modus and its CLI and -build a simple app that fetches a random quote from an external API. You'll -learn how to use the basic components of a Modus app and how to run it locally. +## Your first Modus app + +In this quickstart, you'll learn to build your first Modus app with a single +function that demonstrates how to integrate external APIs with AI models. We'll +start with this stateless function, and later you can learn about building +stateful agents that maintain memory and coordinate complex operations. + +Our examples use Go, which we recommend for new projects. You can also choose +AssemblyScript if you prefer. For AssemblyScript usage, refer to the +[AssemblyScript SDK overview](/modus/sdk/assemblyscript/overview). ## Prerequisites @@ -19,8 +26,7 @@ learn how to use the basic components of a Modus app and how to run it locally. - The Modus CLI provides a set of commands to help you create, build, and run your Modus apps. - Install the CLI using npm. + Install the Modus CLI to manage your Modus applications. ```sh npm install -g @hypermode/modus-cli @@ -28,49 +34,28 @@ learn how to use the basic components of a Modus app and how to run it locally. - - To create a new Modus app, run the following command in your terminal: + + Create your first Modus app. ```sh modus new ``` - This command prompts you to choose between Go and AssemblyScript as the language for your app. It - then creates a new directory with the necessary files and folders for your app. You will also be asked if you would like to initialize a Git repository. + Choose between Go and AssemblyScript. Both compile to WebAssembly for fast performance. - - To build and run your app, navigate to the app directory and run the following command: + + Start your local development server: ```sh modus dev ``` - This command runs your app locally in development mode and provides you with a URL to access your - app's generated API. - - - - Once your app is running, you can access the graphical interface for your API at the URL located in your terminal. - - ```sh - View endpoint: http://localhost:8686/explorer - ``` - - The API Explorer interface allows you to interact with your app's API and test your functions. + This starts your app and provides a URL to access the API. + - API Graphical Interface. - - - - Modus is a secure-by-default framework. To connect to external services, you need to add a connection - in your app manifest. - - Add the following code into your `modus.json` manifest file: + + To connect to external services, add this to your `modus.json`: ```json modus.json { @@ -84,41 +69,45 @@ learn how to use the basic components of a Modus app and how to run it locally. ``` - - Modus also supports AI models. You can define new models in your `modus.json` file. Let's add a new meta-llama model: + + Add an AI model to your manifest: ```json + { "models": { "text-generator": { "sourceModel": "meta-llama/Llama-3.2-3B-Instruct", "provider": "hugging-face", "connection": "hypermode" } - }, + } + } ``` - - Next, install the Hyp CLI. This allows you to access hosted models on the Hypermode platform. + + Install and authenticate with the Hyp CLI to access Hypermode-hosted models: ```sh npm install -g @hypermode/hyp-cli ``` - You can now use the `hyp login` command to log in to the Hyp CLI. - This links your project to the Hypermode platform, allowing you to leverage the model in your modus app. + Authenticate with your Hypermode account: - + ```sh + hyp login + ``` - - Functions are the building blocks of your app. Let's add a function that fetches a random quote from - the ZenQuotes connection and uses AI to generate a summary for the quote. + This connects your local development environment to Hypermode's model infrastructure. - - - Create a new file in the root directory with the following code: + + + Create a function that fetches data from an external API and uses AI for analysis: + + + Create `intelligence.go`: - ```go quotes.go + ```go intelligence.go package main import ( @@ -131,17 +120,16 @@ learn how to use the basic components of a Modus app and how to run it locally. "github.com/hypermodeinc/modus/sdk/go/pkg/models/openai" ) - type Quote struct { - Quote string `json:"q"` - Author string `json:"a"` - Summary string `json:"summary,omitempty"` + type IntelReport struct { + Quote string `json:"quote"` + Author string `json:"author"` + Analysis string `json:"analysis,omitempty"` } const modelName = "text-generator" - // this function makes a request to an API that returns data in JSON format, - // and returns a single quote with AI-generated summary - func GetRandomQuote() (*Quote, error) { + // Fetch a random quote and provide AI analysis + func GatherIntelligence() (*IntelReport, error) { request := http.NewRequest("https://zenquotes.io/api/random") response, err := http.Fetch(request) @@ -149,50 +137,51 @@ learn how to use the basic components of a Modus app and how to run it locally. return nil, err } if !response.Ok() { - return nil, fmt.Errorf("failed to fetch quote. Received: %d %s", response.Status, response.StatusText) + return nil, fmt.Errorf("request failed: %d %s", response.Status, response.StatusText) } - // the API returns an array of quotes, but we only need the first one - var quotes []Quote + // Parse the API response + var quotes []IntelReport response.JSON("es) if len(quotes) == 0 { - return nil, errors.New("expected at least one quote in the response, but none were found") + return nil, errors.New("no data received") } - // Get the first (and only) quote - quote := quotes[0] + // Get the quote + intel := quotes[0] - // Generate AI summary for the quote - summary, err := summarizeQuote(quote.Quote, quote.Author) + // Generate AI analysis + analysis, err := analyzeIntelligence(intel.Quote, intel.Author) if err != nil { - fmt.Printf("Warning: failed to summarize quote by %s: %v\n", quote.Author, err) - quote.Summary = "Summary unavailable" + fmt.Printf("AI analysis failed for %s: %v\n", intel.Author, err) + intel.Analysis = "Analysis unavailable" } else { - quote.Summary = summary + intel.Analysis = analysis } - return "e, nil + return &intel, nil } - // summarizeQuote uses the AI model to generate a concise summary of the quote - func summarizeQuote(quote, author string) (string, error) { + // Use AI to analyze the quote + func analyzeIntelligence(quote, author string) (string, error) { model, err := models.GetModel[openai.ChatModel](modelName) if err != nil { return "", err } - instruction := "Provide a brief, insightful summary that captures the essence and meaning of the quote in 1-2 sentences." - prompt := fmt.Sprintf("Quote: \"%s\" - %s", quote, author) + prompt := `You are an analyst. + Provide a brief insight that captures the core meaning + and practical application of this wisdom in 1-2 sentences.` + content := fmt.Sprintf("Quote: \"%s\" - %s", quote, author) input, err := model.CreateInput( - openai.NewSystemMessage(instruction), - openai.NewUserMessage(prompt), + openai.NewSystemMessage(prompt), + openai.NewUserMessage(content), ) if err != nil { return "", err } - // Set temperature for consistent but creative responses input.Temperature = 0.7 output, err := model.Invoke(input) @@ -203,138 +192,86 @@ learn how to use the basic components of a Modus app and how to run it locally. return strings.TrimSpace(output.Choices[0].Message.Content), nil } ``` - - - - Create a new file in the `assembly` directory with the following code: - - ```ts quotes.ts - import { http, models } from "@hypermode/modus-sdk-as"; - import { - OpenAIChatModel, - SystemMessage, - UserMessage, - } from "@hypermode/modus-sdk-as/models/openai/chat"; - - @json - class Quote { - @alias("q") - quote!: string; - - @alias("a") - author!: string; - - summary?: string; - } - - const modelName: string = "text-generator"; - - // this function makes a request to an API that returns data in JSON format, - // and returns a single quote with AI-generated summary - export function getRandomQuote(): Quote { - const request = new http.Request("https://zenquotes.io/api/random"); - - const response = http.fetch(request); - if (!response.ok) { - throw new Error( - `Failed to fetch quote. Received: ${response.status} ${response.statusText}`, - ); - } - - // the API returns an array of quotes, but we only need the first one - const quotes = response.json(); - if (quotes.length === 0) { - throw new Error("Expected at least one quote in the response, but none were found"); - } - - // Get the first (and only) quote - const quote = quotes[0]; - - // Generate AI summary for the quote - try { - quote.summary = summarizeQuote(quote.quote, quote.author); - } catch (error) { - console.log(`Warning: failed to summarize quote by ${quote.author}: ${error}`); - quote.summary = "Summary unavailable"; - } - - return quote; - } - - // summarizeQuote uses the AI model to generate a concise summary of the quote - function summarizeQuote(quote: string, author: string): string { - const model = models.getModel(modelName); - - const instruction = "Provide a brief, insightful summary that captures the essence and meaning of the quote in 1-2 sentences."; - const prompt = `Quote: "${quote}" - ${author}`; - - const input = model.createInput([ - new SystemMessage(instruction), - new UserMessage(prompt), - ]); - - // Set temperature for consistent but creative responses - input.temperature = 0.7; - - const output = model.invoke(input); - return output.choices[0].message.content.trim(); - } - ``` - - Then add the following to `index.ts`. This includes the `getRandomQuote` function on - your generated API. - - ```ts index.ts - export * from "./quotes"; - ``` - - - + - - - Now that you've integrated the AI model, let's test it! After adding your function, restart your development server: + + Restart your development server: ```sh modus dev ``` - Navigate to the API Explorer at `http://localhost:8686/explorer` and you'll see your `randomQuote` function available to test. + Modus automatically generates a GraphQL API from your functions. + Since your function is named `GatherIntelligence()`, it becomes a GraphQL query field called `gatherIntelligence`. - When you call the function, you'll notice that the quote includes three fields: - - `quote`: The original quote text - - `author`: The author's name - - `summary`: An AI-generated summary that captures the essence of the quote + Open the Modus API Explorer at `http://localhost:8686/explorer` to test your function. + The explorer is fully GraphQL-compatible, so you can issue this query: - The AI model analyzes the quote and provides insightful context about its meaning, making your app more engaging and informative for users. + ```graphql + query { + gatherIntelligence { + quote + author + analysis + } + } + ``` - Try calling the function multiple times to see how the AI generates different summaries for various quotes! + You'll receive a response like: - + ```json + { + "data": { + "gatherIntelligence": { + "quote": "The only way to do great work is to love what you do.", + "author": "Steve Jobs", + "analysis": " + This emphasizes that passion and genuine interest in your work are fundamental drivers of excellence. + When you love what you do, the effort required for mastery feels less burdensome and innovation flows more naturally. + " + } + } + } + ``` - + Your function now: + - Fetches data from external APIs + - Uses AI models for analysis + - Provides intelligent responses + - Handles errors gracefully - When testing an AI app locally, Modus records the inference and related metadata - in the `View Inferences` tab of the APIs explorer. + + + When running locally, Modus records every AI model call for monitoring and debugging. + You can see this in the Modus explorer. - Local model tracing is only supported on Linux and macOS. Windows support is - coming soon. + Local inference tracking is supported on Linux and macOS. Windows + support is incoming. - ![local model tracing](../images/observe-functions/local-inference-history.png) - - You can now see detailed information about each AI model call, including: - - Input prompts sent to the model - - Generated responses - - Performance metrics like response time - - Token usage and costs + You can monitor: + - Requests sent to AI models + - Response times and token usage + - Model performance metrics + - Error rates and debugging info +## What's next? + +You've successfully built your first Modus function. But this is just the +beginning. Real agents maintain memory, coordinate complex operations, and never +lose their context. + +Ready to upgrade from simple functions to stateful agents? Check out +[What's an Agent?](/modus/agents) to learn how to build persistent, +memory-enabled agents that remember every interaction and can coordinate +sophisticated multi-step operations. + - For more inspiration, check out the [Modus + For more mission templates and advanced operations, explore the [Modus recipes](https://github.com/hypermodeinc/modus-recipes). diff --git a/modus/sdk/assemblyscript/agents.mdx b/modus/sdk/assemblyscript/agents.mdx new file mode 100644 index 0000000..8f5d7a5 --- /dev/null +++ b/modus/sdk/assemblyscript/agents.mdx @@ -0,0 +1,474 @@ +--- +title: "Agents APIs" +sidebarTitle: "Agents" +description: + "Build stateful agents that maintain persistent memory across interactions" +icon: "circle-small" +iconType: "solid" +--- + +import { SdkHeader } from "/snippets/sdk-header.mdx" +import SdkTip from "/snippets/sdk-tip.mdx" + + + +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` namespace and `Agent` base class from the SDK: + +```ts +import { agents, Agent, AgentInfo, AgentEvent } from "@hypermode/modus-sdk-as" +``` + +## Agent APIs + +{/* vale Google.Headings = NO */} + +The APIs in the `agents` namespace are below, organized by category. + + + +### Agent Management Functions + +#### register + +Register an agent class with the Modus runtime before it can be instantiated. + +```ts +function register(): void +``` + + + The agent class type that extends the `Agent` base class. + + + + Agent registration must be done at the module level, outside of any function. + + +#### start + +Create and start a new agent instance. + +```ts +function start(agentName: string): AgentInfo +``` + + + The name of the agent class to instantiate. This must match the `name` + property returned by the agent class. + + +#### stop + +Stop an agent instance. Once stopped, the agent can't be resumed. + +```ts +function stop(agentId: string): AgentInfo +``` + + + The unique identifier of the agent instance to stop. + + +#### get info + +Get information about a specific agent instance. + +```ts +function getInfo(agentId: string): AgentInfo +``` + + + The unique identifier of the agent instance. + + +#### list all + +List all active agent instances. + +```ts +function listAll(): AgentInfo[] +``` + +### Communication Functions + +#### send message + +Send a synchronous message to an agent and wait for a response. + +```ts +function sendMessage( + agentId: string, + messageName: string, + data: string | null = null, +): string | null +``` + + + The unique identifier of the target agent instance. + + + + The name of the message to send to the agent. + + + + Optional data payload to send with the message. + + +#### sendMessageAsync + +Send an asynchronous message to an agent without waiting for a response. + +```ts +function sendMessageAsync( + agentId: string, + messageName: string, + data: string | null = null, +): void +``` + + + The unique identifier of the target agent instance. + + + + The name of the message to send to the agent. + + + + Optional data payload to send with the message. + + +### Agent Base Class + +#### Agent + +The base class that all agents must extend. + +```ts +abstract class Agent { + abstract get name(): string + abstract onReceiveMessage( + messageName: string, + data: string | null, + ): string | null + + getState(): string | null + setState(data: string | null): void + onInitialize(): void + onSuspend(): void + onResume(): void + onTerminate(): void + publishEvent(event: AgentEvent): void +} +``` + + + Abstract property that must return a unique name for the agent class. + + + + Abstract method that handles incoming messages to the agent. Must be + implemented by all agent classes. + + + + Optional method that returns the agent's current state as a string for + persistence. Called automatically when the agent needs to be suspended or + migrated. + + + + Optional method that restores the agent's state from a string. Called + automatically when the agent is resumed or migrated. + + + + Optional lifecycle method called when the agent is first created. + + + + Optional lifecycle method called when the agent is about to be suspended. + + + + Optional lifecycle method called when the agent is resumed from suspension. + + + + Optional lifecycle method called when the agent is about to be terminated. + + + + Publishes an event from this agent to any subscribers. The event must extend + the `AgentEvent` base class. + + +### Event Classes + +#### AgentEvent + +Base class for agent events that can be published to subscribers. + +```ts +abstract class AgentEvent { + readonly eventName: string + + constructor(eventName: string) +} +``` + + + The name of the event type. Must be provided in the constructor and can't be + empty. + + +Custom events should extend this class and include any additional data as +properties: + +```ts +@json +class CountUpdated extends AgentEvent { + constructor(public count: i32) { + super("countUpdated") + } +} +``` + +### Types + +#### AgentInfo + +Information about an agent instance. + +```ts +class AgentInfo { + id: string + name: string + status: string +} +``` + + + The unique identifier of the agent instance. + + + + The name of the agent class. + + + + The current status of the agent instance. + + +## Example Usage + +Here's a complete example of a simple counter agent with event streaming: + +### Agent Implementation + +```ts +import { Agent, AgentEvent } from "@hypermode/modus-sdk-as" + +export class CounterAgent extends Agent { + get name(): string { + return "Counter" + } + + private count: i32 = 0 + + getState(): string | null { + return this.count.toString() + } + + setState(data: string | null): void { + if (data == null) { + return + } + this.count = i32.parse(data) + } + + onInitialize(): void { + console.info("Counter agent started") + } + + onReceiveMessage(name: string, data: string | null): string | null { + if (name == "count") { + return this.count.toString() + } + + if (name == "increment") { + if (data != null) { + this.count += i32.parse(data) + } else { + this.count++ + } + + // Publish an event to subscribers + this.publishEvent(new CountUpdated(this.count)) + + return this.count.toString() + } + + return null + } +} + +// Custom event for count updates +@json +class CountUpdated extends AgentEvent { + constructor(public count: i32) { + super("countUpdated") + } +} +``` + +### Function Integration + +```ts +import { agents, AgentInfo } from "@hypermode/modus-sdk-as" +import { CounterAgent } from "./counterAgent" + +// Register the agent +agents.register() + +export function startCounterAgent(): AgentInfo { + return agents.start("Counter") +} + +export function getCount(agentId: string): i32 { + const count = agents.sendMessage(agentId, "count") + if (count == null) { + return 0 + } + return i32.parse(count) +} + +export function updateCount(agentId: string): i32 { + const count = agents.sendMessage(agentId, "increment") + if (count == null) { + return 0 + } + return i32.parse(count) +} + +export function updateCountAsync(agentId: string, qty: i32): void { + agents.sendMessageAsync(agentId, "increment", qty.toString()) +} +``` + +### GraphQL Usage + +Once deployed, your agent functions become available via GraphQL: + +```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") +} + +# 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: + +```graphql +subscription CounterEvents($agentId: String!) { + agentEvent(agentId: $agentId) { + name + data + timestamp + } +} +``` + +Example events you might receive: + +```json +{ + "data": { + "agentEvent": { + "name": "countUpdated", + "data": { + "count": 5 + }, + "timestamp": "2025-06-08T14:30:00Z" + } + } +} +``` + +```json +{ + "data": { + "agentEvent": { + "name": "status", + "data": { + "status": "running" + }, + "timestamp": "2025-06-08T14:30:05Z" + } + } +} +``` + +### Client Integration + +Use appropriate GraphQL Server-Sent Events (SSE) clients such as: + +- [graphql-sse](https://the-guild.dev/graphql/sse) for vanilla JavaScript +- [urql](https://nearform.com/open-source/urql/docs/advanced/subscriptions/) + with Server-Sent Events (SSE) exchange +- EventSource API directly for simple use cases + +Example with EventSource: + +```javascript +const eventSource = new EventSource( + '/graphql?query=subscription{agentEvent(agentId:"agent_abc123"){name,data,timestamp}}', + { + headers: { + Accept: "text/event-stream", + }, + }, +) + +eventSource.addEventListener("next", (event) => { + const data = JSON.parse(event.data) + console.log("Agent event:", data) +}) +``` diff --git a/modus/sdk/assemblyscript/overview.mdx b/modus/sdk/assemblyscript/overview.mdx index 832a1ff..c46abff 100644 --- a/modus/sdk/assemblyscript/overview.mdx +++ b/modus/sdk/assemblyscript/overview.mdx @@ -36,6 +36,12 @@ APIs. | [`http`](./http) | Provides access to external HTTP endpoints, including REST APIs and other services | | [`graphql`](./graphql) | Allows you to securely call and fetch data from any GraphQL endpoint | +### Agent APIs + +| Namespace | Purpose | +| :------------------- | :--------------------------------------------------------------------------------------- | +| [`agents`](./agents) | Create stateful agents that maintain persistent memory and coordinate complex operations | + ### Database APIs | Namespace | Purpose | diff --git a/modus/sdk/go/agents.mdx b/modus/sdk/go/agents.mdx new file mode 100644 index 0000000..ff5783d --- /dev/null +++ b/modus/sdk/go/agents.mdx @@ -0,0 +1,609 @@ +--- +title: "Agents APIs" +sidebarTitle: "Agents" +description: + "Build stateful agents that maintain persistent memory across interactions" +icon: "circle-small" +iconType: "solid" +--- + +import { SdkHeader } from "/snippets/sdk-header.mdx" +import SdkTip from "/snippets/sdk-tip.mdx" + + + +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: + +```go +import "github.com/hypermodeinc/modus/sdk/go/pkg/agents" +``` + +## Agent APIs + +{/* vale Google.Headings = NO */} + +The APIs in the `agents` package are below, organized by category. + + + +### Agent Management Functions + +#### Register + +Register an agent struct with the Modus runtime before it can be instantiated. + +```go +func Register(agent Agent) +``` + + + 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. + +```go +func Start(agentName string) (AgentInfo, error) +``` + + + 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. + +```go +func Stop(agentId string) (AgentInfo, error) +``` + + + The unique identifier of the agent instance to stop. + + +#### GetInfo + +Get information about a specific agent instance. + +```go +func GetInfo(agentId string) (AgentInfo, error) +``` + + + The unique identifier of the agent instance. + + +#### ListAll + +List all active agent instances. + +```go +func ListAll() ([]AgentInfo, error) +``` + +### Communication Functions + +#### SendMessage + +Send a synchronous message to an agent and wait for a response. + +```go +func SendMessage(agentId, messageName string, opts ...MessageOption) (*string, error) +``` + + + The unique identifier of the target agent instance. + + + + The name of the message to send to the agent. + + + + 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. + +```go +func SendMessageAsync(agentId, messageName string, opts ...MessageOption) error +``` + + + The unique identifier of the target agent instance. + + + + The name of the message to send to the agent. + + + + Optional message options. Use `WithData(data string)` to include a data + payload. + + +#### WithData + +Create a message option that includes a data payload. + +```go +func WithData(data string) MessageOption +``` + + + The data payload to include with the message. + + +### Agent Interface + +#### Agent + +The interface that all agents must implement. + +```go +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 +} +``` + + + Must return a unique name for the agent implementation. + + + + Handles incoming messages to the agent. Must be implemented by all agents. + + + + Returns the agent's current state as a string for persistence. Called + automatically when the agent needs to be suspended or migrated. + + + + Restores the agent's state from a string. Called automatically when the agent + is resumed or migrated. + + + + Lifecycle method called when the agent is first created. + + + + Lifecycle method called when the agent is about to be suspended. + + + + Lifecycle method called when the agent is resumed from suspension. + + + + 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. + +```go +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. + + + + 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. + +```go +type AgentEvent interface { + EventName() string +} +``` + + + 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: + +```go +type CountUpdated struct { + Count int `json:"count"` +} + +func (e CountUpdated) EventName() string { + return "countUpdated" +} +``` + +### Types + +#### AgentInfo + +Information about an agent instance. + +```go +type AgentInfo struct { + Id string + Name string + Status string +} +``` + + + The unique identifier of the agent instance. + + + + The name of the agent implementation. + + + + The current status of the agent instance. + + +#### MessageOption + +Options for customizing agent messages. + +```go +type MessageOption interface { + // internal implementation +} +``` + +## Example Usage + +Here's a complete example of a simple counter agent with event streaming: + +### Agent Implementation + +```go +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 + +```go +// 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: + +```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: + +```graphql +subscription CounterEvents($agentId: String!) { + agentEvent(agentId: $agentId) { + name + data + timestamp + } +} +``` + +Example events you might receive: + +```json +{ + "data": { + "agentEvent": { + "name": "countUpdated", + "data": { + "count": 5 + }, + "timestamp": "2025-06-08T14:30:00Z" + } + } +} +``` + +```json +{ + "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: + +```go +// 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](https://the-guild.dev/graphql/sse) for JavaScript/TypeScript +- [gqlgen](https://gqlgen.com/recipes/subscriptions/) for Go clients +- Standard HTTP libraries with Server-Sent Events (SSE) support + +Example with curl: + +```bash +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: + +```go +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) + } + } +} +``` diff --git a/modus/sdk/go/overview.mdx b/modus/sdk/go/overview.mdx index 1701ac6..a6540ca 100644 --- a/modus/sdk/go/overview.mdx +++ b/modus/sdk/go/overview.mdx @@ -34,6 +34,12 @@ your convenience. Click on a package to view more information about its APIs. | [`http`](./http) | Provides access to external HTTP endpoints, including REST APIs and other services | | [`graphql`](./graphql) | Allows you to securely call and fetch data from any GraphQL endpoint | +### Agent APIs + +| Namespace | Purpose | +| :------------------- | :--------------------------------------------------------------------------------------- | +| [`agents`](./agents) | Create stateful agents that maintain persistent memory and coordinate complex operations | + ### Database APIs | Package | Purpose | diff --git a/styles/config/vocabularies/general/accept.txt b/styles/config/vocabularies/general/accept.txt index 0c53ac6..9d16876 100644 --- a/styles/config/vocabularies/general/accept.txt +++ b/styles/config/vocabularies/general/accept.txt @@ -134,4 +134,6 @@ vCPU|vCPUs WebSocket USA -[Gg]raphs \ No newline at end of file +[Gg]raphs + +gqlgen \ No newline at end of file