Skip to content

Commit 2e05764

Browse files
committed
sync to support sub hypermodeinc/modus#875
1 parent 3838fcd commit 2e05764

File tree

4 files changed

+407
-46
lines changed

4 files changed

+407
-46
lines changed

modus/agents.mdx

Lines changed: 76 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -440,9 +440,9 @@ Monitor your agent's real-time activities using the unified event subscription:
440440

441441
```graphql
442442
subscription {
443-
agentEvents(agentId: "agent_neo_001") {
444-
type
445-
payload
443+
agentEvent(agentId: "agent_neo_001") {
444+
name
445+
data
446446
timestamp
447447
}
448448
}
@@ -453,9 +453,9 @@ Your agent streams various types of operational events:
453453
```json
454454
{
455455
"data": {
456-
"agentEvents": {
457-
"type": "mission_started",
458-
"payload": {
456+
"agentEvent": {
457+
"name": "mission_started",
458+
"data": {
459459
"missionName": "Deep Matrix Surveillance",
460460
"priority": "HIGH",
461461
"estimatedDuration": "180s"
@@ -469,9 +469,9 @@ Your agent streams various types of operational events:
469469
```json
470470
{
471471
"data": {
472-
"agentEvents": {
473-
"type": "agent_threat_detected",
474-
"payload": {
472+
"agentEvent": {
473+
"name": "agent_threat_detected",
474+
"data": {
475475
"threatLevel": "CRITICAL",
476476
"confidence": 0.92,
477477
"indicators": ["agent_smith_replication", "unusual_code_patterns"],
@@ -486,9 +486,9 @@ Your agent streams various types of operational events:
486486
```json
487487
{
488488
"data": {
489-
"agentEvents": {
490-
"type": "surveillance_progress",
491-
"payload": {
489+
"agentEvent": {
490+
"name": "surveillance_progress",
491+
"data": {
492492
"phase": "Processing Matrix surveillance data",
493493
"progress": 0.65,
494494
"reportsProcessed": 5,
@@ -500,31 +500,47 @@ Your agent streams various types of operational events:
500500
}
501501
```
502502

503-
### Emitting events from your agent
503+
### Publishing events from your agent
504504

505-
Agents can broadcast real-time operational intelligence by emitting events
506-
during their operations. Let's enhance our Matrix surveillance example:
505+
Agents can broadcast real-time operational intelligence by publishing events
506+
during their operations. Use the `PublishEvent` method to emit custom events:
507507

508508
```go
509+
// Custom event types implement the AgentEvent interface
510+
type ThreatDetected struct {
511+
ThreatLevel string `json:"threatLevel"`
512+
Confidence float64 `json:"confidence"`
513+
Analysis string `json:"analysis"`
514+
}
515+
516+
func (e ThreatDetected) EventName() string {
517+
return "threat_detected"
518+
}
519+
520+
// Other event types can be defined similarly...
521+
509522
func (a *IntelligenceAgent) analyzeMatrixActivity(
510523
data string,
511524
) (*string, error) {
512525
// Emit mission start event
513-
a.EmitEvent("mission_started", any{
514-
"missionName": "Matrix Surveillance Analysis",
515-
"priority": "HIGH",
516-
"activityData": len(*data),
526+
err := a.PublishEvent(MissionStarted{
527+
MissionName: "Matrix Surveillance Analysis",
528+
Priority: "HIGH",
529+
ActivityData: len(*data),
517530
})
531+
if err != nil {
532+
return nil, err
533+
}
518534

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

523539
// Emit progress update
524-
a.EmitEvent("surveillance_progress", any{
525-
"reportsProcessed": len(a.intelligenceReports),
526-
"phase": "Processing Matrix surveillance data",
527-
"progress": 0.3,
540+
a.PublishEvent(SurveillanceProgress{
541+
ReportsProcessed: len(a.intelligenceReports),
542+
Phase: "Processing Matrix surveillance data",
543+
Progress: 0.3,
528544
})
529545

530546
// Build context from all accumulated intelligence
@@ -555,10 +571,10 @@ func (a *IntelligenceAgent) analyzeMatrixActivity(
555571
}
556572

557573
// Emit AI processing event
558-
a.EmitEvent("ai_analysis_started", any{
559-
"modelName": "analyst-model",
560-
"contextSize": len(accumulatedReports),
561-
"reportCount": len(a.intelligenceReports),
574+
a.PublishEvent(AIAnalysisStarted{
575+
ModelName: "analyst-model",
576+
ContextSize: len(accumulatedReports),
577+
ReportCount: len(a.intelligenceReports),
562578
})
563579

564580
output, err := model.Invoke(input)
@@ -577,20 +593,19 @@ func (a *IntelligenceAgent) analyzeMatrixActivity(
577593
if strings.Contains(strings.ToLower(analysis), "critical") ||
578594
strings.Contains(strings.ToLower(analysis), "agent smith") {
579595
a.threatLevel = math.Min(a.threatLevel + 0.2, 1.0)
580-
a.EmitEvent("agent_threat_detected", any{
581-
"threatLevel": "HIGH",
582-
"confidence": a.threatLevel,
583-
"analysis": analysis,
584-
"recommendation": "immediate_extraction",
596+
a.PublishEvent(ThreatDetected{
597+
ThreatLevel: "HIGH",
598+
Confidence: a.threatLevel,
599+
Analysis: analysis,
585600
})
586601
}
587602

588603
// Emit mission completion
589-
a.EmitEvent("mission_completed", any{
590-
"missionName": "Matrix Surveillance Analysis",
591-
"confidence": a.threatLevel,
592-
"reportsAnalyzed": len(a.intelligenceReports),
593-
"status": "SUCCESS",
604+
a.PublishEvent(MissionCompleted{
605+
MissionName: "Matrix Surveillance Analysis",
606+
Confidence: a.threatLevel,
607+
ReportsAnalyzed: len(a.intelligenceReports),
608+
Status: "SUCCESS",
594609
})
595610

596611
result := fmt.Sprintf(`Matrix surveillance complete:
@@ -622,6 +637,27 @@ analysis.
622637
**Progressive Enhancement**: update user interfaces progressively as agents work
623638
through complex, multi-phase operations without polling or manual refresh.
624639

640+
### Subscription protocol
641+
642+
Modus uses GraphQL subscriptions over Server-Sent Events (SSE) following the
643+
[GraphQL-SSE specification](https://the-guild.dev/graphql/sse). To consume these
644+
subscriptions:
645+
646+
1. **From a web browser**: Use the EventSource API or a GraphQL client that
647+
supports SSE subscriptions
648+
2. **From Postman**: Set Accept header to `text/event-stream` and make a POST
649+
request
650+
3. **From curl**: Use `-N` flag and appropriate headers for streaming
651+
652+
Example with curl:
653+
654+
```bash
655+
curl -N -H "accept: text/event-stream" \
656+
-H "content-type: application/json" \
657+
-X POST http://localhost:8080/graphql \
658+
-d '{"query":"subscription { agentEvent(agentId: \"agent_neo_001\") { name data timestamp } }"}'
659+
```
660+
625661
## Monitoring ongoing operations
626662

627663
You can also poll agent status directly through dedicated functions:
@@ -706,9 +742,9 @@ query MonitorMission($agentId: String!) {
706742

707743
# Real-time streaming approach (recommended)
708744
subscription LiveAgentMonitoring($agentId: String!) {
709-
agentEvents(agentId: $agentId) {
710-
type
711-
payload
745+
agentEvent(agentId: $agentId) {
746+
name
747+
data
712748
timestamp
713749
}
714750
}

modus/sdk/assemblyscript/agents.mdx

Lines changed: 133 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ complex multi-step operations.
2121
To begin, import the `agents` namespace and `Agent` base class from the SDK:
2222

2323
```ts
24-
import { agents, Agent, AgentInfo } from "@hypermode/modus-sdk-as"
24+
import { agents, Agent, AgentInfo, AgentEvent } from "@hypermode/modus-sdk-as"
2525
```
2626

2727
## Agent APIs
@@ -165,6 +165,7 @@ abstract class Agent {
165165
onSuspend(): void
166166
onResume(): void
167167
onTerminate(): void
168+
publishEvent(event: AgentEvent): void
168169
}
169170
```
170171
@@ -208,6 +209,42 @@ abstract class Agent {
208209
Optional lifecycle method called when the agent is about to be terminated.
209210
</ResponseField>
210211
212+
<ResponseField name="publishEvent(event)" type="method">
213+
Publishes an event from this agent to any subscribers. The event must extend
214+
the `AgentEvent` base class.
215+
</ResponseField>
216+
217+
### Event Classes
218+
219+
#### AgentEvent
220+
221+
Base class for agent events that can be published to subscribers.
222+
223+
```ts
224+
abstract class AgentEvent {
225+
readonly eventName: string
226+
227+
constructor(eventName: string)
228+
}
229+
```
230+
231+
<ResponseField name="eventName" type="string" required>
232+
The name of the event type. Must be provided in the constructor and can't be
233+
empty.
234+
</ResponseField>
235+
236+
Custom events should extend this class and include any additional data as
237+
properties:
238+
239+
```ts
240+
@json
241+
class CountUpdated extends AgentEvent {
242+
constructor(public count: i32) {
243+
super("countUpdated")
244+
}
245+
}
246+
```
247+
211248
### Types
212249
213250
#### AgentInfo
@@ -236,12 +273,12 @@ class AgentInfo {
236273
237274
## Example Usage
238275
239-
Here's a complete example of a simple counter agent:
276+
Here's a complete example of a simple counter agent with event streaming:
240277
241278
### Agent Implementation
242279
243280
```ts
244-
import { Agent } from "@hypermode/modus-sdk-as"
281+
import { Agent, AgentEvent } from "@hypermode/modus-sdk-as"
245282

246283
export class CounterAgent extends Agent {
247284
get name(): string {
@@ -276,12 +313,24 @@ export class CounterAgent extends Agent {
276313
} else {
277314
this.count++
278315
}
316+
317+
// Publish an event to subscribers
318+
this.publishEvent(new CountUpdated(this.count))
319+
279320
return this.count.toString()
280321
}
281322

282323
return null
283324
}
284325
}
326+
327+
// Custom event for count updates
328+
@json
329+
class CountUpdated extends AgentEvent {
330+
constructor(public count: i32) {
331+
super("countUpdated")
332+
}
333+
}
285334
```
286335

287336
### Function Integration
@@ -341,4 +390,85 @@ query {
341390
mutation {
342391
updateCount(agentId: "agent_abc123")
343392
}
393+
394+
# Subscribe to real-time events
395+
subscription {
396+
agentEvent(agentId: "agent_abc123") {
397+
name
398+
data
399+
timestamp
400+
}
401+
}
402+
```
403+
404+
### Event Subscription
405+
406+
To receive real-time events from your agent, subscribe using GraphQL
407+
subscriptions over Server-Sent Events:
408+
409+
```graphql
410+
subscription CounterEvents($agentId: String!) {
411+
agentEvent(agentId: $agentId) {
412+
name
413+
data
414+
timestamp
415+
}
416+
}
417+
```
418+
419+
Example events you might receive:
420+
421+
```json
422+
{
423+
"data": {
424+
"agentEvent": {
425+
"name": "countUpdated",
426+
"data": {
427+
"count": 5
428+
},
429+
"timestamp": "2025-06-08T14:30:00Z"
430+
}
431+
}
432+
}
433+
```
434+
435+
```json
436+
{
437+
"data": {
438+
"agentEvent": {
439+
"name": "status",
440+
"data": {
441+
"status": "running"
442+
},
443+
"timestamp": "2025-06-08T14:30:05Z"
444+
}
445+
}
446+
}
447+
```
448+
449+
### Client Integration
450+
451+
Use appropriate GraphQL Server-Sent Events (SSE) clients such as:
452+
453+
- [graphql-sse](https://the-guild.dev/graphql/sse) for vanilla JavaScript
454+
- [urql](https://formidable.com/open-source/urql/docs/basics/subscriptions/)
455+
with Server-Sent Events (SSE) exchange
456+
- EventSource API directly for simple use cases
457+
458+
Example with EventSource:
459+
460+
```javascript
461+
const eventSource = new EventSource(
462+
'/graphql?query=subscription{agentEvent(agentId:"agent_abc123"){name,data,timestamp}}',
463+
{
464+
headers: {
465+
Accept: "text/event-stream",
466+
},
467+
},
468+
)
469+
470+
eventSource.addEventListener("next", (event) => {
471+
const data = JSON.parse(event.data)
472+
console.log("Agent event:", data)
473+
})
344474
```

0 commit comments

Comments
 (0)