xcomponent-ai 0.2.2 β†’ 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. package/EVENT-ACCUMULATION-GUIDE.md +505 -0
  2. package/EXTERNAL-API.md +719 -0
  3. package/LLM-GUIDE.md +575 -0
  4. package/QUICKSTART.md +1 -1
  5. package/SCALABILITY.md +455 -0
  6. package/dist/cli.js +170 -63
  7. package/dist/cli.js.map +1 -1
  8. package/dist/component-registry.d.ts +35 -4
  9. package/dist/component-registry.d.ts.map +1 -1
  10. package/dist/component-registry.js +194 -9
  11. package/dist/component-registry.js.map +1 -1
  12. package/dist/external-broker-api.d.ts +205 -0
  13. package/dist/external-broker-api.d.ts.map +1 -0
  14. package/dist/external-broker-api.js +222 -0
  15. package/dist/external-broker-api.js.map +1 -0
  16. package/dist/fsm-runtime.d.ts +47 -17
  17. package/dist/fsm-runtime.d.ts.map +1 -1
  18. package/dist/fsm-runtime.js +333 -136
  19. package/dist/fsm-runtime.js.map +1 -1
  20. package/dist/index.d.ts +4 -0
  21. package/dist/index.d.ts.map +1 -1
  22. package/dist/index.js +11 -1
  23. package/dist/index.js.map +1 -1
  24. package/dist/mermaid-generator.d.ts.map +1 -1
  25. package/dist/mermaid-generator.js +1 -17
  26. package/dist/mermaid-generator.js.map +1 -1
  27. package/dist/message-broker.d.ts +116 -0
  28. package/dist/message-broker.d.ts.map +1 -0
  29. package/dist/message-broker.js +225 -0
  30. package/dist/message-broker.js.map +1 -0
  31. package/dist/types.d.ts +92 -19
  32. package/dist/types.d.ts.map +1 -1
  33. package/examples/advanced-patterns-demo.yaml +339 -0
  34. package/examples/cross-component-demo.yaml +68 -0
  35. package/examples/distributed-demo/README.md +234 -0
  36. package/examples/distributed-demo/order.yaml +71 -0
  37. package/examples/distributed-demo/payment.yaml +60 -0
  38. package/examples/event-accumulation-demo.yaml +172 -0
  39. package/examples/explicit-transitions-demo.yaml +236 -0
  40. package/examples/payment-receiver.yaml +56 -0
  41. package/package.json +8 -1
  42. package/public/dashboard.html +647 -110
@@ -0,0 +1,719 @@
1
+ # 🌐 External Broker API Guide
2
+
3
+ This guide explains how to interact with xcomponent-ai from **any programming language** using the message broker (Redis), without needing HTTP API access.
4
+
5
+ ## πŸ“‹ Table of Contents
6
+
7
+ - [Overview](#overview)
8
+ - [Sending Events to FSMs](#sending-events-to-fsms)
9
+ - [Subscribing to FSM Events](#subscribing-to-fsm-events)
10
+ - [Channel Reference](#channel-reference)
11
+ - [Examples by Language](#examples-by-language)
12
+
13
+ ---
14
+
15
+ ## Overview
16
+
17
+ The External Broker API allows you to:
18
+ 1. **Send events to FSM instances** from any system via message broker
19
+ 2. **Subscribe to FSM state changes** in real-time from external applications
20
+ 3. **Language-agnostic integration** (Python, Go, Java, Ruby, Rust, etc.)
21
+
22
+ ### Architecture
23
+
24
+ ```
25
+ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” Redis Pub/Sub β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
26
+ β”‚ Your App β”‚ β”‚ xcomponent-ai β”‚
27
+ β”‚ (Python/Go/ │──────────────────────────────▢│ Runtime β”‚
28
+ β”‚ Java/etc.) β”‚ Send events via broker β”‚ β”‚
29
+ β”‚ β”‚ β”‚ β”‚
30
+ β”‚ │◀──────────────────────────────│ Publish state β”‚
31
+ β”‚ β”‚ Subscribe to FSM events β”‚ changes β”‚
32
+ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
33
+ ```
34
+
35
+ **No HTTP needed!** Just publish/subscribe to Redis channels.
36
+
37
+ ---
38
+
39
+ ## Sending Events to FSMs
40
+
41
+ ### Enable External Commands
42
+
43
+ Start xcomponent-ai with the `--external-api` flag:
44
+
45
+ ```bash
46
+ xcomponent-ai serve order.yaml \
47
+ --port 3000 \
48
+ --broker redis://localhost:6379 \
49
+ --external-api
50
+ ```
51
+
52
+ ### Send Event to Specific Instance
53
+
54
+ **Channel:** `xcomponent:external:commands`
55
+
56
+ **Message format:**
57
+ ```json
58
+ {
59
+ "componentName": "OrderComponent",
60
+ "instanceId": "abc-123-def-456",
61
+ "event": {
62
+ "type": "VALIDATE",
63
+ "payload": {
64
+ "approvedBy": "user@example.com"
65
+ },
66
+ "timestamp": 1706280000000
67
+ }
68
+ }
69
+ ```
70
+
71
+ ### Broadcast Event to Instances in a State
72
+
73
+ **Channel:** `xcomponent:external:broadcasts`
74
+
75
+ #### Broadcast to ALL instances (no filters)
76
+
77
+ ```json
78
+ {
79
+ "componentName": "OrderComponent",
80
+ "machineName": "Order",
81
+ "currentState": "Pending",
82
+ "event": {
83
+ "type": "TIMEOUT",
84
+ "payload": {},
85
+ "timestamp": 1706280000000
86
+ }
87
+ }
88
+ ```
89
+
90
+ This sends `TIMEOUT` to **ALL** Order instances in `Pending` state.
91
+
92
+ #### Broadcast to specific instances (with property filters)
93
+
94
+ **Target a single customer:**
95
+ ```json
96
+ {
97
+ "componentName": "OrderComponent",
98
+ "machineName": "Order",
99
+ "currentState": "Pending",
100
+ "filters": [
101
+ {
102
+ "property": "customerId",
103
+ "operator": "===",
104
+ "value": "CUST-001"
105
+ }
106
+ ],
107
+ "event": {
108
+ "type": "TIMEOUT"
109
+ }
110
+ }
111
+ ```
112
+
113
+ Sends `TIMEOUT` only to Orders with `customerId === "CUST-001"`.
114
+
115
+ **Multiple filters (AND logic):**
116
+ ```json
117
+ {
118
+ "componentName": "OrderComponent",
119
+ "machineName": "Order",
120
+ "currentState": "Pending",
121
+ "filters": [
122
+ {
123
+ "property": "customerId",
124
+ "value": "CUST-001"
125
+ },
126
+ {
127
+ "property": "amount",
128
+ "operator": ">",
129
+ "value": 1000
130
+ }
131
+ ],
132
+ "event": {
133
+ "type": "URGENT_REVIEW"
134
+ }
135
+ }
136
+ ```
137
+
138
+ Sends `URGENT_REVIEW` to Orders with `customerId === "CUST-001"` **AND** `amount > 1000`.
139
+
140
+ **Nested properties:**
141
+ ```json
142
+ {
143
+ "filters": [
144
+ {
145
+ "property": "customer.tier",
146
+ "value": "premium"
147
+ }
148
+ ]
149
+ }
150
+ ```
151
+
152
+ **βœ… Can target a single instance** if filters are specific enough!
153
+
154
+ **Supported operators:**
155
+ - `===` (equal, default)
156
+ - `!==` (not equal)
157
+ - `>` (greater than)
158
+ - `<` (less than)
159
+ - `>=` (greater or equal)
160
+ - `<=` (less or equal)
161
+
162
+ ---
163
+
164
+ ## Subscribing to FSM Events
165
+
166
+ ### Enable Event Publishing
167
+
168
+ Start xcomponent-ai with event publishing enabled:
169
+
170
+ ```bash
171
+ xcomponent-ai serve order.yaml \
172
+ --port 3000 \
173
+ --broker redis://localhost:6379 \
174
+ --publish-events
175
+ ```
176
+
177
+ ### Available Event Channels
178
+
179
+ | Channel | Description |
180
+ |---------|-------------|
181
+ | `xcomponent:events:state_change` | State transitions (e.g., Pending β†’ Validated) |
182
+ | `xcomponent:events:instance_created` | New FSM instances created |
183
+ | `xcomponent:events:instance_disposed` | FSM instances disposed |
184
+ | `xcomponent:events:instance_error` | Errors in FSM instances |
185
+ | `xcomponent:events:cross_component_cascade` | Cross-component cascades triggered |
186
+
187
+ ### Event Message Format
188
+
189
+ #### state_change Event
190
+
191
+ Emitted when an instance transitions between states.
192
+
193
+ ```json
194
+ {
195
+ "type": "state_change",
196
+ "componentName": "OrderComponent",
197
+ "data": {
198
+ "instanceId": "abc-123",
199
+ "machineName": "Order",
200
+ "previousState": "Pending",
201
+ "newState": "Validated",
202
+ "event": {
203
+ "type": "VALIDATE",
204
+ "payload": {
205
+ "approvedBy": "user@example.com"
206
+ }
207
+ },
208
+ "eventId": "evt-456",
209
+ "timestamp": 1706280000000,
210
+ "instance": {
211
+ "id": "abc-123",
212
+ "machineName": "Order",
213
+ "currentState": "Validated",
214
+ "context": {
215
+ "orderId": "ORD-001",
216
+ "amount": 1000,
217
+ "customerId": "CUST-001",
218
+ "approvedBy": "user@example.com"
219
+ },
220
+ "publicMember": {
221
+ "status": "validated",
222
+ "totalAmount": 1000
223
+ },
224
+ "status": "active",
225
+ "createdAt": 1706279000000,
226
+ "updatedAt": 1706280000000
227
+ }
228
+ },
229
+ "timestamp": 1706280000000
230
+ }
231
+ ```
232
+
233
+ **Key fields:**
234
+ - βœ… `componentName` - Component name (e.g., "OrderComponent")
235
+ - βœ… `data.machineName` - State machine name (e.g., "Order")
236
+ - βœ… `data.instanceId` - Instance ID
237
+ - βœ… `data.instance` - **Complete instance object** with:
238
+ - `context` - Full instance context (business data)
239
+ - `publicMember` - Public member data (if defined in YAML)
240
+ - `currentState` - Current state after transition
241
+ - `status` - Instance status (active, completed, error)
242
+ - `createdAt`, `updatedAt` - Timestamps
243
+
244
+ #### instance_created Event
245
+
246
+ ```json
247
+ {
248
+ "type": "instance_created",
249
+ "componentName": "OrderComponent",
250
+ "data": {
251
+ "id": "abc-123",
252
+ "machineName": "Order",
253
+ "currentState": "Created",
254
+ "context": {
255
+ "orderId": "ORD-001",
256
+ "amount": 1000
257
+ },
258
+ "publicMember": {},
259
+ "status": "active",
260
+ "createdAt": 1706279000000,
261
+ "updatedAt": 1706279000000
262
+ },
263
+ "timestamp": 1706279000000
264
+ }
265
+ ```
266
+
267
+ #### instance_disposed Event
268
+
269
+ ```json
270
+ {
271
+ "type": "instance_disposed",
272
+ "componentName": "OrderComponent",
273
+ "data": {
274
+ "id": "abc-123",
275
+ "machineName": "Order",
276
+ "currentState": "Completed",
277
+ "context": {
278
+ "orderId": "ORD-001",
279
+ "amount": 1000
280
+ },
281
+ "status": "completed",
282
+ "createdAt": 1706279000000,
283
+ "updatedAt": 1706280000000
284
+ },
285
+ "timestamp": 1706280000000
286
+ }
287
+ ```
288
+
289
+ #### instance_error Event
290
+
291
+ ```json
292
+ {
293
+ "type": "instance_error",
294
+ "componentName": "OrderComponent",
295
+ "data": {
296
+ "instanceId": "abc-123",
297
+ "machineName": "Order",
298
+ "error": "Guard validation failed",
299
+ "instance": {
300
+ "id": "abc-123",
301
+ "machineName": "Order",
302
+ "currentState": "Pending",
303
+ "context": {
304
+ "orderId": "ORD-001"
305
+ },
306
+ "status": "error"
307
+ }
308
+ },
309
+ "timestamp": 1706280000000
310
+ }
311
+ ```
312
+
313
+ ---
314
+
315
+ ## Channel Reference
316
+
317
+ ### Commands (Publish to these)
318
+
319
+ | Channel | Purpose | Message Type |
320
+ |---------|---------|--------------|
321
+ | `xcomponent:external:commands` | Send event to specific instance | `ExternalCommand` |
322
+ | `xcomponent:external:broadcasts` | Broadcast event to instances in state | `ExternalBroadcastCommand` |
323
+
324
+ ### Events (Subscribe to these)
325
+
326
+ | Channel | Purpose | Message Type |
327
+ |---------|---------|--------------|
328
+ | `xcomponent:events:state_change` | State transitions | `PublishedFSMEvent` |
329
+ | `xcomponent:events:instance_created` | Instance creations | `PublishedFSMEvent` |
330
+ | `xcomponent:events:instance_disposed` | Instance disposals | `PublishedFSMEvent` |
331
+ | `xcomponent:events:instance_error` | Instance errors | `PublishedFSMEvent` |
332
+ | `xcomponent:events:cross_component_cascade` | Cross-component cascades | `PublishedFSMEvent` |
333
+
334
+ ---
335
+
336
+ ## Examples by Language
337
+
338
+ ### Node.js / TypeScript
339
+
340
+ ```typescript
341
+ import { createClient } from 'redis';
342
+
343
+ // Connect to Redis
344
+ const redis = createClient({ url: 'redis://localhost:6379' });
345
+ await redis.connect();
346
+
347
+ // Send event to instance
348
+ await redis.publish('xcomponent:external:commands', JSON.stringify({
349
+ componentName: 'OrderComponent',
350
+ instanceId: 'order-123',
351
+ event: {
352
+ type: 'VALIDATE',
353
+ payload: { approvedBy: 'alice@example.com' },
354
+ timestamp: Date.now()
355
+ }
356
+ }));
357
+
358
+ // Broadcast to all instances in a state
359
+ await redis.publish('xcomponent:external:broadcasts', JSON.stringify({
360
+ componentName: 'OrderComponent',
361
+ machineName: 'Order',
362
+ currentState: 'Pending',
363
+ event: { type: 'TIMEOUT', timestamp: Date.now() }
364
+ }));
365
+
366
+ // Broadcast with property filters (target specific customer)
367
+ await redis.publish('xcomponent:external:broadcasts', JSON.stringify({
368
+ componentName: 'OrderComponent',
369
+ machineName: 'Order',
370
+ currentState: 'Pending',
371
+ filters: [
372
+ { property: 'customerId', value: 'CUST-001' },
373
+ { property: 'amount', operator: '>', value: 1000 }
374
+ ],
375
+ event: { type: 'URGENT_REVIEW', timestamp: Date.now() }
376
+ }));
377
+
378
+ // Subscribe to state changes
379
+ const subscriber = redis.duplicate();
380
+ await subscriber.connect();
381
+
382
+ await subscriber.subscribe('xcomponent:events:state_change', (message) => {
383
+ const event = JSON.parse(message);
384
+ console.log(`State changed: ${event.data.previousState} β†’ ${event.data.newState}`);
385
+ });
386
+ ```
387
+
388
+ ### Python
389
+
390
+ ```python
391
+ import redis
392
+ import json
393
+ import time
394
+
395
+ # Connect to Redis
396
+ r = redis.Redis(host='localhost', port=6379, decode_responses=True)
397
+
398
+ # Send event to instance
399
+ command = {
400
+ 'componentName': 'OrderComponent',
401
+ 'instanceId': 'order-123',
402
+ 'event': {
403
+ 'type': 'VALIDATE',
404
+ 'payload': {'approvedBy': 'alice@example.com'},
405
+ 'timestamp': int(time.time() * 1000)
406
+ }
407
+ }
408
+ r.publish('xcomponent:external:commands', json.dumps(command))
409
+
410
+ # Broadcast with property filters
411
+ broadcast = {
412
+ 'componentName': 'OrderComponent',
413
+ 'machineName': 'Order',
414
+ 'currentState': 'Pending',
415
+ 'filters': [
416
+ {'property': 'customerId', 'value': 'CUST-001'},
417
+ {'property': 'amount', 'operator': '>', 'value': 1000}
418
+ ],
419
+ 'event': {'type': 'URGENT_REVIEW', 'timestamp': int(time.time() * 1000)}
420
+ }
421
+ r.publish('xcomponent:external:broadcasts', json.dumps(broadcast))
422
+
423
+ # Subscribe to state changes
424
+ pubsub = r.pubsub()
425
+ pubsub.subscribe('xcomponent:events:state_change')
426
+
427
+ for message in pubsub.listen():
428
+ if message['type'] == 'message':
429
+ event = json.loads(message['data'])
430
+ prev = event['data']['previousState']
431
+ new = event['data']['newState']
432
+ print(f"State changed: {prev} β†’ {new}")
433
+ ```
434
+
435
+ ### Go
436
+
437
+ ```go
438
+ package main
439
+
440
+ import (
441
+ "context"
442
+ "encoding/json"
443
+ "fmt"
444
+ "time"
445
+
446
+ "github.com/redis/go-redis/v9"
447
+ )
448
+
449
+ func main() {
450
+ ctx := context.Background()
451
+ client := redis.NewClient(&redis.Options{
452
+ Addr: "localhost:6379",
453
+ })
454
+
455
+ // Send event to instance
456
+ command := map[string]interface{}{
457
+ "componentName": "OrderComponent",
458
+ "instanceId": "order-123",
459
+ "event": map[string]interface{}{
460
+ "type": "VALIDATE",
461
+ "payload": map[string]string{"approvedBy": "alice@example.com"},
462
+ "timestamp": time.Now().UnixMilli(),
463
+ },
464
+ }
465
+ commandJSON, _ := json.Marshal(command)
466
+ client.Publish(ctx, "xcomponent:external:commands", commandJSON)
467
+
468
+ // Subscribe to state changes
469
+ pubsub := client.Subscribe(ctx, "xcomponent:events:state_change")
470
+ ch := pubsub.Channel()
471
+
472
+ for msg := range ch {
473
+ var event map[string]interface{}
474
+ json.Unmarshal([]byte(msg.Payload), &event)
475
+
476
+ data := event["data"].(map[string]interface{})
477
+ prev := data["previousState"].(string)
478
+ new := data["newState"].(string)
479
+ fmt.Printf("State changed: %s β†’ %s\n", prev, new)
480
+ }
481
+ }
482
+ ```
483
+
484
+ ### Java
485
+
486
+ ```java
487
+ import redis.clients.jedis.Jedis;
488
+ import redis.clients.jedis.JedisPubSub;
489
+ import com.google.gson.Gson;
490
+ import java.util.HashMap;
491
+ import java.util.Map;
492
+
493
+ public class XComponentClient {
494
+ public static void main(String[] args) {
495
+ Jedis jedis = new Jedis("localhost", 6379);
496
+ Gson gson = new Gson();
497
+
498
+ // Send event to instance
499
+ Map<String, Object> command = new HashMap<>();
500
+ command.put("componentName", "OrderComponent");
501
+ command.put("instanceId", "order-123");
502
+
503
+ Map<String, Object> event = new HashMap<>();
504
+ event.put("type", "VALIDATE");
505
+ event.put("payload", Map.of("approvedBy", "alice@example.com"));
506
+ event.put("timestamp", System.currentTimeMillis());
507
+ command.put("event", event);
508
+
509
+ jedis.publish("xcomponent:external:commands", gson.toJson(command));
510
+
511
+ // Subscribe to state changes
512
+ Jedis subscriber = new Jedis("localhost", 6379);
513
+ subscriber.subscribe(new JedisPubSub() {
514
+ @Override
515
+ public void onMessage(String channel, String message) {
516
+ Map<String, Object> event = gson.fromJson(message, Map.class);
517
+ Map<String, Object> data = (Map<String, Object>) event.get("data");
518
+ String prev = (String) data.get("previousState");
519
+ String newState = (String) data.get("newState");
520
+ System.out.printf("State changed: %s β†’ %s\n", prev, newState);
521
+ }
522
+ }, "xcomponent:events:state_change");
523
+ }
524
+ }
525
+ ```
526
+
527
+ ### Ruby
528
+
529
+ ```ruby
530
+ require 'redis'
531
+ require 'json'
532
+
533
+ # Connect to Redis
534
+ redis = Redis.new(host: 'localhost', port: 6379)
535
+
536
+ # Send event to instance
537
+ command = {
538
+ componentName: 'OrderComponent',
539
+ instanceId: 'order-123',
540
+ event: {
541
+ type: 'VALIDATE',
542
+ payload: { approvedBy: 'alice@example.com' },
543
+ timestamp: (Time.now.to_f * 1000).to_i
544
+ }
545
+ }
546
+ redis.publish('xcomponent:external:commands', command.to_json)
547
+
548
+ # Subscribe to state changes
549
+ redis.subscribe('xcomponent:events:state_change') do |on|
550
+ on.message do |channel, message|
551
+ event = JSON.parse(message)
552
+ prev = event['data']['previousState']
553
+ new_state = event['data']['newState']
554
+ puts "State changed: #{prev} β†’ #{new_state}"
555
+ end
556
+ end
557
+ ```
558
+
559
+ ### Rust
560
+
561
+ ```rust
562
+ use redis::{Client, Commands, PubSubCommands};
563
+ use serde_json::json;
564
+
565
+ fn main() -> redis::RedisResult<()> {
566
+ let client = Client::open("redis://127.0.0.1:6379")?;
567
+ let mut con = client.get_connection()?;
568
+
569
+ // Send event to instance
570
+ let command = json!({
571
+ "componentName": "OrderComponent",
572
+ "instanceId": "order-123",
573
+ "event": {
574
+ "type": "VALIDATE",
575
+ "payload": { "approvedBy": "alice@example.com" },
576
+ "timestamp": chrono::Utc::now().timestamp_millis()
577
+ }
578
+ });
579
+ con.publish("xcomponent:external:commands", command.to_string())?;
580
+
581
+ // Subscribe to state changes
582
+ let mut pubsub = con.as_pubsub();
583
+ pubsub.subscribe("xcomponent:events:state_change")?;
584
+
585
+ loop {
586
+ let msg = pubsub.get_message()?;
587
+ let payload: String = msg.get_payload()?;
588
+ let event: serde_json::Value = serde_json::from_str(&payload)?;
589
+
590
+ let prev = event["data"]["previousState"].as_str().unwrap();
591
+ let new = event["data"]["newState"].as_str().unwrap();
592
+ println!("State changed: {} β†’ {}", prev, new);
593
+ }
594
+ }
595
+ ```
596
+
597
+ ---
598
+
599
+ ## Authentication & Security
600
+
601
+ ### Redis with Password
602
+
603
+ All examples support Redis URLs with authentication:
604
+
605
+ ```
606
+ redis://:password@localhost:6379
607
+ redis://username:password@localhost:6379
608
+ ```
609
+
610
+ **Example (Python):**
611
+ ```python
612
+ r = redis.Redis.from_url('redis://:mypassword@prod-redis:6379')
613
+ ```
614
+
615
+ ### Redis with TLS/SSL
616
+
617
+ Use `rediss://` protocol for encrypted connections:
618
+
619
+ ```
620
+ rediss://username:password@prod-redis:6380
621
+ ```
622
+
623
+ ### Access Control
624
+
625
+ Use Redis ACLs to restrict access:
626
+
627
+ ```bash
628
+ # Create user that can only publish commands and subscribe to events
629
+ redis-cli ACL SETUSER external-app on \
630
+ >password \
631
+ +publish|xcomponent:external:* \
632
+ +subscribe|xcomponent:events:* \
633
+ -@all
634
+ ```
635
+
636
+ ---
637
+
638
+ ## Use Cases
639
+
640
+ ### 1. Python Data Pipeline β†’ FSM
641
+
642
+ ```python
643
+ # data_pipeline.py
644
+ import redis, json
645
+
646
+ r = redis.Redis(host='localhost', port=6379)
647
+
648
+ # When data processing completes, trigger FSM
649
+ def on_data_processed(order_id, result):
650
+ r.publish('xcomponent:external:commands', json.dumps({
651
+ 'componentName': 'OrderComponent',
652
+ 'instanceId': order_id,
653
+ 'event': {'type': 'DATA_PROCESSED', 'payload': result}
654
+ }))
655
+ ```
656
+
657
+ ### 2. Monitoring Dashboard (React + Go backend)
658
+
659
+ ```go
660
+ // Go backend streams FSM events to frontend via WebSocket
661
+ func streamFSMEvents(w http.ResponseWriter, r *http.Request) {
662
+ upgrader.Upgrade(w, r, nil) // WebSocket upgrade
663
+
664
+ redisClient := redis.NewClient(&redis.Options{Addr: "localhost:6379"})
665
+ pubsub := redisClient.Subscribe(ctx, "xcomponent:events:state_change")
666
+
667
+ for msg := range pubsub.Channel() {
668
+ // Forward to frontend WebSocket
669
+ wsConn.WriteMessage(websocket.TextMessage, []byte(msg.Payload))
670
+ }
671
+ }
672
+ ```
673
+
674
+ ### 3. Java Microservice Integration
675
+
676
+ ```java
677
+ // OrderService.java - triggers FSM when order validated
678
+ public void onOrderValidated(String orderId) {
679
+ jedis.publish("xcomponent:external:commands", gson.toJson(
680
+ Map.of(
681
+ "componentName", "PaymentComponent",
682
+ "instanceId", "payment-" + orderId,
683
+ "event", Map.of("type", "START_PAYMENT")
684
+ )
685
+ ));
686
+ }
687
+ ```
688
+
689
+ ---
690
+
691
+ ## Troubleshooting
692
+
693
+ ### Commands not received
694
+
695
+ 1. Check xcomponent-ai started with `--external-api` flag
696
+ 2. Verify Redis connection: `redis-cli ping`
697
+ 3. Check channel name exactly matches `xcomponent:external:commands`
698
+
699
+ ### Events not published
700
+
701
+ 1. Check xcomponent-ai started with `--publish-events` flag
702
+ 2. Verify subscription to correct channel (e.g., `xcomponent:events:state_change`)
703
+ 3. Check Redis authentication if using password
704
+
705
+ ### Performance considerations
706
+
707
+ - Use connection pooling for high-throughput systems
708
+ - Consider Redis Cluster for horizontal scaling
709
+ - Monitor Redis memory usage (`INFO memory`)
710
+
711
+ ---
712
+
713
+ ## Next Steps
714
+
715
+ - See [SCALABILITY.md](./SCALABILITY.md) for production deployment
716
+ - See [LLM-GUIDE.md](./LLM-GUIDE.md) for YAML FSM design patterns
717
+ - See `examples/distributed-demo/` for working examples
718
+
719
+ **Built for interoperability.** Any language, any platform. 🌍