@kadi.build/core 0.0.1-alpha.2 → 0.0.1-alpha.3

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 (39) hide show
  1. package/README.md +1145 -216
  2. package/examples/example-abilities/echo-js/README.md +131 -0
  3. package/examples/example-abilities/echo-js/agent.json +63 -0
  4. package/examples/example-abilities/echo-js/package.json +24 -0
  5. package/examples/example-abilities/echo-js/service.js +43 -0
  6. package/examples/example-abilities/hash-go/agent.json +53 -0
  7. package/examples/example-abilities/hash-go/cmd/hash_ability/main.go +340 -0
  8. package/examples/example-abilities/hash-go/go.mod +3 -0
  9. package/examples/example-agent/abilities/echo-js/0.0.1/README.md +131 -0
  10. package/examples/example-agent/abilities/echo-js/0.0.1/agent.json +63 -0
  11. package/examples/example-agent/abilities/echo-js/0.0.1/package-lock.json +93 -0
  12. package/examples/example-agent/abilities/echo-js/0.0.1/package.json +24 -0
  13. package/examples/example-agent/abilities/echo-js/0.0.1/service.js +41 -0
  14. package/examples/example-agent/abilities/hash-go/0.0.1/agent.json +53 -0
  15. package/examples/example-agent/abilities/hash-go/0.0.1/bin/hash_ability +0 -0
  16. package/examples/example-agent/abilities/hash-go/0.0.1/cmd/hash_ability/main.go +340 -0
  17. package/examples/example-agent/abilities/hash-go/0.0.1/go.mod +3 -0
  18. package/examples/example-agent/agent.json +39 -0
  19. package/examples/example-agent/index.js +102 -0
  20. package/examples/example-agent/package-lock.json +93 -0
  21. package/examples/example-agent/package.json +17 -0
  22. package/package.json +4 -2
  23. package/src/KadiAbility.js +478 -0
  24. package/src/index.js +65 -0
  25. package/src/loadAbility.js +1086 -0
  26. package/src/servers/BaseRpcServer.js +404 -0
  27. package/src/servers/BrokerRpcServer.js +776 -0
  28. package/src/servers/StdioRpcServer.js +360 -0
  29. package/src/transport/BrokerMessageBuilder.js +377 -0
  30. package/src/transport/IpcMessageBuilder.js +1229 -0
  31. package/src/utils/agentUtils.js +137 -0
  32. package/src/utils/commandUtils.js +64 -0
  33. package/src/utils/configUtils.js +72 -0
  34. package/src/utils/logger.js +161 -0
  35. package/src/utils/pathUtils.js +86 -0
  36. package/broker.js +0 -214
  37. package/index.js +0 -382
  38. package/ipc.js +0 -220
  39. package/ipcInterfaces/pythonAbilityIPC.py +0 -177
package/README.md CHANGED
@@ -1,306 +1,1235 @@
1
1
  # @kadi.build/core
2
2
 
3
- ---
3
+ > A comprehensive toolkit for building and managing KADI abilities with multiple transport protocols
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@kadi.build/core.svg)](https://www.npmjs.com/package/@kadi.build/core)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
7
+
8
+ ## 🎯 Overview
9
+
10
+ `@kadi.build/core` is the foundational library for creating KADI abilities - modular, protocol-agnostic services that can communicate via multiple transport layers. Whether you're building local tools, distributed microservices, or high-performance native modules, this toolkit provides a unified, developer-friendly API that abstracts away transport complexity.
11
+
12
+ ## 📚 Table of Contents
13
+
14
+ - [🎯 Overview](#-overview)
15
+ - [📦 Installation](#-installation)
16
+ - [🚀 Quick Start](#-quick-start)
17
+ - [🔌 Transport Protocols](#-transport-protocols)
18
+ - [🛠️ Creating Abilities](#-creating-abilities)
19
+ - [🔄 Architecture Deep Dive](#-architecture-deep-dive)
20
+ - [📥 Loading Abilities](#-loading-abilities)
21
+ - [⚙️ Configuration](#-configuration)
22
+ - [🚀 Advanced Usage](#-advanced-usage)
23
+ - [🔧 Development Workflow](#-development-workflow)
24
+ - [📖 API Reference](#-api-reference)
25
+ - [🎮 Running the Examples](#-running-the-examples)
26
+ - [💡 Additional Examples](#-additional-examples)
27
+ - [🐛 Troubleshooting](#-troubleshooting)
28
+ - [🤝 Contributing](#-contributing)
29
+ - [📄 License](#-license)
30
+ - [🔗 Related Projects](#-related-projects)
31
+ - [📚 Resources](#-resources)
32
+
33
+ ## 📦 Installation
34
+
35
+ ```bash
36
+ npm install @kadi.build/core
37
+ ```
38
+
39
+ For global CLI tools:
40
+
41
+ ```bash
42
+ npm install -g @kadi.build/cli
43
+ ```
44
+
45
+ ## 🚀 Quick Start
4
46
 
5
- The `@kadi.build/core` module is a comprehensive toolkit for developers integrating with the KADI infrastructure. This module simplifies tasks such as managing `agent.json` files, spawning processes, interacting with brokers, and handling interprocess communication (IPC).
47
+ ### Creating Your First Ability
6
48
 
7
- ## Features
49
+ ```javascript
50
+ #!/usr/bin/env node
51
+ import { KadiAbility } from '@kadi.build/core';
52
+
53
+ // Create an ability instance
54
+ const mathAbility = new KadiAbility({
55
+ name: 'math-ability',
56
+ version: '1.0.0',
57
+ description: 'Simple math operations'
58
+ });
59
+
60
+ // Register a method
61
+ mathAbility.method('add', async ({ a, b }) => {
62
+ return { result: a + b };
63
+ });
64
+
65
+ // Start serving requests
66
+ mathAbility.serve().catch(console.error);
67
+ ```
68
+
69
+ ### Loading and Using Abilities
8
70
 
9
- - **Agent JSON Management**: Manage configurations for tools, agents, or systems using KADI.
10
- - **Process Management**: Support launching and managing subprocesses.
11
- - **Multi-Broker Support**: Dynamic broker selection and management with environment/runtime configuration.
12
- - **Broker Interaction**: Facilitate communications with WebSocket brokers.
13
- - **IPC Support**: Tools for interprocess communication across various programming languages.
71
+ ```javascript
72
+ import { loadAbility } from '@kadi.build/core';
14
73
 
15
- ## Installation
74
+ async function main() {
75
+ // Load an ability (uses first interface defined in agent.json)
76
+ const math = await loadAbility('math-ability');
16
77
 
17
- Install `@kadi.build/core` using npm:
78
+ // Call methods like regular functions
79
+ const result = await math.add({ a: 5, b: 3 });
80
+ console.log(result); // { result: 8 }
81
+
82
+ // List available methods
83
+ console.log(math.__list()); // ['add']
84
+ }
85
+
86
+ main().catch(console.error);
87
+ ```
88
+
89
+ ## 🔌 Transport Protocols
90
+
91
+ KADI abilities support three transport protocols, each optimized for different use cases:
92
+
93
+ ### 1. Native Protocol (Fastest)
94
+
95
+ - **Use Case**: High-performance, in-process execution
96
+ - **How it Works**: Loads abilities as ES modules, enabling direct function calls
97
+ - **Performance**: Zero IPC overhead - functions run in the same process
98
+ - **Best For**: Utility libraries, data processing, performance-critical operations
99
+
100
+ ```javascript
101
+ // Loads as native module by default if available
102
+ const ability = await loadAbility('my-ability', 'native');
103
+ ```
104
+
105
+ ### 2. Stdio Protocol (Balanced)
106
+
107
+ - **Use Case**: Language-agnostic local execution
108
+ - **How it Works**: Spawns abilities as child processes, communicates via LSP-style JSON-RPC over stdin/stdout
109
+ - **Performance**: Minimal overhead, reliable local IPC
110
+ - **Best For**: Python scripts, Go binaries, Node.js services, development/testing
111
+
112
+ ```javascript
113
+ // Force stdio protocol
114
+ const ability = await loadAbility('my-ability', 'stdio');
115
+ ```
116
+
117
+ **Environment Variables Passed to Child Process:**
18
118
 
19
119
  ```bash
20
- npm install @kadi.build/core
120
+ KADI_PROTOCOL=stdio # Identifies the protocol being used
121
+ # Plus all parent process.env variables
21
122
  ```
22
123
 
23
- ## Documentation
124
+ ### 3. Broker Protocol (Distributed)
24
125
 
25
- ### Agent JSON Functions
126
+ - **Use Case**: Distributed services, containerized deployments
127
+ - **How it Works**: Abilities connect to a WebSocket broker for network communication
128
+ - **Performance**: Network overhead, but enables scaling and distribution
129
+ - **Best For**: Microservices, cloud deployments, load-balanced services
26
130
 
27
- - **`getAbilityJSON(abilityName, abilityVersion)`**: Retrieves the `agent.json` for a specified ability.
28
- - **`getAbilityJSONPath(abilityName, abilityVersion)`**: Provides the file path to the `agent.json` for a specific ability.
29
- - **`getAbilityVersionFromArray(abilities, name)`**: Searches ability array provided, and returns the version number for name provided.
30
- - **`getAbilitiesDir()`**: Returns the directory path where abilities are stored.
31
- - **`getProjectJSON()`**: Fetches the `agent.json` for the current project.
32
- - **`getProjectJSONPath()`**: Gets the file path for the project's `agent.json`.
33
- - **`getKadiCoreJSON()`**: Retrieves the `agent.json` for the Kadi core.
34
- - **`getKadiCoreJSONPath()`**: Provides the file path to the Kadi core's `agent.json`.
35
- - **`getKadiJSON()`**: Fetches the `agent.json` for the Kadi system.
36
- - **`getKadiJSONPath()`**: Returns the file path for the Kadi system's `agent.json`.
131
+ ```javascript
132
+ // Use broker for distributed execution
133
+ const ability = await loadAbility('my-ability', 'broker');
134
+ ```
37
135
 
38
- ### Broker Management Functions
136
+ **Environment Variables Passed to Child Process:**
39
137
 
40
- The broker management system allows dynamic selection from multiple configured brokers defined in `agent.json`:
138
+ ```bash
139
+ KADI_PROTOCOL=broker # Identifies the protocol
140
+ KADI_BROKER_URL=ws://localhost:8080 # Broker connection URL
141
+ KADI_SERVICE_NAME=ability.echo.1_0_0 # Service identifier
142
+ KADI_AGENT_SCOPE=uuid-here # Agent's scope/namespace
143
+ # Plus all parent process.env variables
144
+ ```
145
+
146
+ **Scope/Namespace Visibility:**
147
+
148
+ The `KADI_AGENT_SCOPE` environment variable represents the agent's namespace. For the ability to be visible to its parent agent via the broker, it must register with the same scope:
149
+
150
+ ```javascript
151
+ // In your ability code:
152
+ const echoAbility = new KadiAbility({
153
+ name: 'echo-js',
154
+ version: '0.0.1',
155
+ description: 'Echo ability with broker support',
156
+ scope: process.env.KADI_AGENT_SCOPE // Use agent's scope for visibility
157
+ });
158
+
159
+ // Without this, the ability would only be visible in the 'global' scope
160
+ // and the parent agent wouldn't be able to communicate with it
161
+ ```
162
+
163
+ ### Protocol Selection Strategy
164
+
165
+ When no protocol is specified, `loadAbility` uses the **first interface** defined in the ability's `agent.json`. The KADI team recommends ordering interfaces from fastest to slowest (native → stdio → broker) for optimal default performance.
166
+
167
+ ```mermaid
168
+ graph TD
169
+ A[Load Ability] --> B{Protocol Specified?}
170
+ B -->|Yes| C[Use Specified Protocol]
171
+ B -->|No| D{Check agent.json interfaces}
172
+ D --> E[Use First Interface Listed]
173
+ E --> F[native/stdio/broker]
174
+ ```
175
+
176
+ **Recommended Interface Order in agent.json:**
41
177
 
42
178
  ```json
43
179
  {
44
- "brokers": {
45
- "local": "ws://127.0.0.1:8080",
46
- "remote": "ws://production.example.com:8080",
47
- "staging": "ws://staging.example.com:8080"
180
+ "interfaces": {
181
+ "native": { "entry": "service.js" }, // 1st choice: Fastest
182
+ "stdio": { "discover": true }, // 2nd choice: Balanced
183
+ "broker": { "discover": true } // 3rd choice: Distributed
48
184
  }
49
185
  }
50
186
  ```
51
187
 
52
- Available functions:
188
+ ## 🛠️ Creating Abilities
189
+
190
+ ### Basic Ability Structure
191
+
192
+ ```javascript
193
+ import { KadiAbility } from '@kadi.build/core';
194
+
195
+ const ability = new KadiAbility({
196
+ name: 'echo-ability',
197
+ version: '1.0.0',
198
+ description: 'Echo service with metadata',
199
+ scope: process.env.KADI_AGENT_SCOPE || 'global'
200
+ });
201
+
202
+ // Method 1: Simple handler
203
+ ability.method('echo', async ({ message }) => ({
204
+ echo: message,
205
+ timestamp: new Date().toISOString()
206
+ }));
207
+
208
+ // Method 2: Handler with inline schema
209
+ ability.method(
210
+ 'format',
211
+ async ({ text, style }) => ({
212
+ formatted: style === 'upper' ? text.toUpperCase() : text.toLowerCase()
213
+ }),
214
+ {
215
+ description: 'Format text with specified style',
216
+ inputSchema: {
217
+ type: 'object',
218
+ properties: {
219
+ text: { type: 'string', description: 'Text to format' },
220
+ style: {
221
+ type: 'string',
222
+ enum: ['upper', 'lower'],
223
+ description: 'Formatting style'
224
+ }
225
+ },
226
+ required: ['text', 'style']
227
+ },
228
+ outputSchema: {
229
+ type: 'object',
230
+ properties: {
231
+ formatted: { type: 'string', description: 'Formatted text' }
232
+ }
233
+ }
234
+ }
235
+ );
53
236
 
54
- - **`KADI_BROKERS`**: Object containing all configured brokers with their parsed URLs.
55
- - **`KADI_BROKER_URL`**: Default broker URL (first one defined, for backward compatibility).
56
- - **`getBrokerUrl(brokerName)`**: Get URL for a specific broker by name. Returns `null` if not found.
57
- - **`getBrokerNames()`**: Get array of all available broker names.
58
- - **`setActiveBroker(brokerName)`**: Set the active broker for the session. Returns `true` if successful.
59
- - **`getActiveBrokerName()`**: Get the name of the currently active broker.
60
- - **`getActiveBrokerUrl()`**: Get the URL of the currently active broker.
61
- - **`getDefaultBrokerName()`**: Get the name of the default broker (first one defined).
62
- - **`selectBrokerFromEnv()`**: Set active broker from `KADI_BROKER` environment variable.
237
+ // Start serving
238
+ ability.serve().catch(console.error);
239
+ ```
63
240
 
64
- ### Process Management Functions
241
+ ### Event Publishing and Subscription
65
242
 
66
- - **`runExecCommand(name, version, command)`**: Executes a command for initializing abilities.
67
- - **`runSpawnCommand(name, version, command)`**: Uses `spawn` to execute commands for subprocesses.
243
+ **Note**: Events currently work for `native` and `stdio` protocols. Broker protocol event support is planned for future releases.
68
244
 
69
- ## Usage Examples
245
+ Abilities can publish events that agents can subscribe to:
70
246
 
71
247
  ```javascript
72
- import {
73
- getProjectJSON,
74
- runExecCommand,
75
- IPCManager,
76
- Broker
77
- } from '@kadi.build/core';
248
+ // In your ability (service.js)
249
+ const ability = new KadiAbility({ name: 'my-ability' });
250
+
251
+ // Publish events from method handlers
252
+ ability.method('echo', async ({ message }) => {
253
+ // Publish an event before returning the result
254
+ ability.publishEvent('echo:test-event', { from: 'message echo' });
255
+ ability.publishEvent('echo:test-event', { from: 'message echo 2' });
256
+
257
+ return { echo: message, timestamp: new Date().toISOString() };
258
+ });
259
+
260
+ // In your agent (index.js)
261
+ const ability = await loadAbility('my-ability');
262
+
263
+ // Subscribe to events BEFORE calling methods that emit them
264
+ ability.events.on('echo:test-event', (data) => {
265
+ console.log(`[NATIVE] echo:test-event received:`, data);
266
+ });
267
+
268
+ // Now call the method that emits events
269
+ const result = await ability.echo({ message: 'Hello world' });
270
+ ```
271
+
272
+ **Important**: Always subscribe to events before calling methods that emit them, as events are emitted immediately when `publishEvent()` is called.
273
+
274
+ ### Ability Configuration (agent.json)
275
+
276
+ Every ability should have an `agent.json` file that defines its capabilities and interfaces:
78
277
 
79
- async function setupProject() {
80
- const projectConfig = getProjectJSON();
81
- console.log(projectConfig);
82
-
83
- await runExecCommand('example', '1.0', 'npm install');
84
-
85
- // Broker management examples
86
- console.log('Available brokers:', getBrokerNames());
87
- console.log('All brokers:', KADI_BROKERS);
88
-
89
- // Set active broker
90
- setActiveBroker('remote');
91
- console.log('Active broker:', getActiveBrokerUrl());
92
-
93
- // IPC setup
94
- const ipc = new IPCManager();
95
- ipc.createInstance('python', 'pythonScript.py', 'pythonInstance');
96
-
97
- // Traditional broker setup
98
- Broker.addBroker('ws://example.com', 'exampleBroker');
99
- let broker = Broker.getBroker('default');
100
- console.log(`Connected to ${broker.url}`);
101
- broker.send(
102
- BrokerMessageBuilder.setup(
103
- 'TestAgent',
104
- 'A Broker Testing Agent',
105
- null,
106
- null
107
- )
108
- );
278
+ ```json
279
+ {
280
+ "name": "echo-ability",
281
+ "kind": "ability",
282
+ "version": "1.0.0",
283
+ "license": "MIT",
284
+ "description": "Echo service with multiple transport support",
285
+ "scripts": {
286
+ "setup": "npm install",
287
+ "start": "node service.js"
288
+ },
289
+ "interfaces": {
290
+ "native": {
291
+ "entry": "service.js"
292
+ },
293
+ "stdio": {
294
+ "discover": true,
295
+ "timeoutMs": 10000
296
+ },
297
+ "broker": {
298
+ "discover": true,
299
+ "timeoutMs": 15000,
300
+ "serviceName": "echo-service"
301
+ }
302
+ },
303
+ "exports": [
304
+ {
305
+ "name": "echo",
306
+ "description": "Echo the input message",
307
+ "inputSchema": {
308
+ "type": "object",
309
+ "properties": {
310
+ "message": { "type": "string" }
311
+ },
312
+ "required": ["message"]
313
+ },
314
+ "outputSchema": {
315
+ "type": "object",
316
+ "properties": {
317
+ "echo": { "type": "string" },
318
+ "timestamp": { "type": "string" }
319
+ }
320
+ }
321
+ }
322
+ ],
323
+ "brokers": {
324
+ "local": "ws://localhost:8080",
325
+ "remote": "ws://api.example.com:8080"
326
+ }
109
327
  }
110
328
  ```
111
329
 
112
- ### Broker Functions
330
+ ### Schema Definition Options
113
331
 
114
- - **`Broker`**: Manages interactions with broker systems.
115
- - **`IBroker`**: Broker instance interface with methods to manage its lifecycle and communications.
116
- - **`BrokerMessageBuilder`**: Assists in constructing messages for broker communication.
332
+ The ability system provides two ways to define method schemas:
117
333
 
118
- ## Broker Functions
334
+ 1. **Export Schemas** (defined in `agent.json` exports section)
335
+ 2. **Inline Schemas** (passed directly to `.method()`)
119
336
 
120
- The Broker system facilitates interaction between different agents and services within the Kadi environment.
337
+ ```javascript
338
+ // Option 1: Define in agent.json exports section
339
+ // The schema is picked up automatically if the method name matches
340
+ // agent.json:
341
+ {
342
+ "exports": [
343
+ {
344
+ "name": "process",
345
+ "description": "Process data",
346
+ "inputSchema": { /* ... */ },
347
+ "outputSchema": { /* ... */ }
348
+ }
349
+ ]
350
+ }
121
351
 
122
- ### `Broker` Interface
352
+ // Option 2: Define inline when registering the method
353
+ ability.method('process', handler, {
354
+ description: 'Process data',
355
+ inputSchema: { /* ... */ },
356
+ outputSchema: { /* ... */ }
357
+ });
123
358
 
124
- - **`addBroker(url, name = 'default')`**: Adds a new broker connection. If a broker with the same name exists, it will be reused.
125
- - **`disconnect(name = 'default')`**: Disconnects the broker specified by the name.
126
- - **`deleteBroker(name = 'default')`**: Deletes the broker specified by the name and closes its connection.
127
- - **`send(message, brokerName = 'default')`**: Sends a message through the broker specified by the name.
128
- - **`addEventListener(event, listener, brokerName = 'default')`**: Adds an event listener to the specified broker.
129
- - **`removeEventListener(event, listener, brokerName = 'default')`**: Removes an event listener from the specified broker.
130
- - **`removeAllListeners(brokerName = 'default')`**: Removes all event listeners from the specified broker.
131
- - **`getBroker(brokerName = 'default')`**: Retrieves a broker instance by name.
359
+ // Note: If both are defined, inline schemas take precedence over exports
360
+ ```
132
361
 
133
- ### `IBroker` Instance
362
+ **Important**: Specifying MCP schemas for ability handlers is particularly important for `broker` communication. If you only need `native` and `stdio` protocols, schemas can be omitted.
363
+
364
+ ## 🔄 Architecture Deep Dive
365
+
366
+ ### Ability Loading Sequence
367
+
368
+ The loading process adapts based on the selected protocol:
369
+
370
+ ```mermaid
371
+ sequenceDiagram
372
+ participant Client
373
+ participant loadAbility
374
+ participant FileSystem
375
+ participant ChildProcess
376
+ participant Ability
377
+ participant Broker
378
+
379
+ Client->>loadAbility: loadAbility('echo-js', protocol?)
380
+ loadAbility->>FileSystem: Read project/ability agent.json
381
+ FileSystem-->>loadAbility: Return ability version & config
382
+ loadAbility->>FileSystem: Check ability directory exists
383
+ loadAbility->>FileSystem: Read ability's agent.json manifest
384
+
385
+ alt protocol === 'native'
386
+ loadAbility->>FileSystem: Read entry point from manifest
387
+ loadAbility->>Ability: import(modulePath)
388
+ Ability-->>loadAbility: Return module exports
389
+ loadAbility->>loadAbility: Extract functions from module
390
+ loadAbility->>loadAbility: Build proxy with direct function calls
391
+ else protocol === 'stdio'
392
+ loadAbility->>ChildProcess: spawn(startCmd, { env: { KADI_PROTOCOL: 'stdio' }})
393
+ ChildProcess->>Ability: Start ability process
394
+ Ability->>Ability: Create StdioRpcServer
395
+ loadAbility->>Ability: Send __kadi_init via LSP frames
396
+ Ability-->>loadAbility: Return { name, version, functions }
397
+
398
+ opt if discover enabled
399
+ loadAbility->>Ability: Send __kadi_discover
400
+ Ability-->>loadAbility: Return { functions: {...} }
401
+ end
402
+
403
+ loadAbility->>loadAbility: Merge static exports + discovered functions
404
+ loadAbility->>loadAbility: Build proxy with RPC calls
405
+ else protocol === 'broker'
406
+ loadAbility->>loadAbility: Generate scope UUID
407
+ loadAbility->>ChildProcess: spawn(startCmd, { env: { KADI_PROTOCOL, KADI_BROKER_URL, KADI_AGENT_SCOPE, KADI_SERVICE_NAME }})
408
+ ChildProcess->>Ability: Start ability process
409
+
410
+ Note over Ability,Broker: Ability connects to broker
411
+ Ability->>Broker: WebSocket connect
412
+ Ability->>Broker: hello({ role: 'agent' })
413
+ Broker-->>Ability: { nonce }
414
+ Ability->>Broker: authenticate({ publicKey, signature })
415
+ Broker-->>Ability: { agentId }
416
+ Ability->>Broker: registerCapabilities({ tools, scopes: [KADI_AGENT_SCOPE] })
417
+
418
+ Note over loadAbility,Broker: Parent connects to broker
419
+ loadAbility->>Broker: WebSocket connect
420
+ loadAbility->>Broker: hello/auth/register sequence
421
+
422
+ opt if discover enabled
423
+ loadAbility->>Broker: listTools({ scopes: [scope] })
424
+ Broker-->>loadAbility: Return available tools
425
+ end
426
+
427
+ loadAbility->>loadAbility: Merge static exports + broker tools
428
+ loadAbility->>loadAbility: Build proxy with broker RPC
429
+ end
430
+
431
+ loadAbility->>Client: Return ability proxy
432
+ Client->>Client: ability.method({ params })
433
+ ```
134
434
 
135
- This class extends `EventEmitter` and manages individual broker connections:
435
+ ### Method Registration & Serving
436
+
437
+ How abilities register methods and start serving:
438
+
439
+ ```mermaid
440
+ sequenceDiagram
441
+ participant Dev as Developer
442
+ participant KA as KadiAbility
443
+ participant Server as RpcServer
444
+ participant Transport
445
+ participant Client
446
+
447
+ Note over Dev,KA: Registration Phase
448
+ Dev->>KA: new KadiAbility({ name, scope: process.env.KADI_AGENT_SCOPE })
449
+ Dev->>KA: ability.method('echo', handler)
450
+ KA->>KA: Store in methodHandlers Map
451
+
452
+ Dev->>KA: ability.method('transform', handler, schema)
453
+ KA->>KA: Store handler in methodHandlers
454
+ KA->>KA: Store schema in methodMCPSchemas
455
+
456
+ Note over KA,Transport: Serving Phase
457
+ Dev->>KA: ability.serve()
458
+ KA->>KA: Detect protocol from env/options
459
+
460
+ alt protocol === 'stdio'
461
+ KA->>Server: new StdioRpcServer()
462
+ Server->>Transport: new StdioFrameReader(stdin)
463
+ Server->>Transport: new StdioFrameWriter(stdout)
464
+ Transport->>Transport: Listen for LSP frames on stdin
465
+ else protocol === 'broker'
466
+ KA->>Server: new BrokerRpcServer()
467
+ Server->>Transport: WebSocket connect to broker
468
+ Server->>Transport: Handshake (hello/auth)
469
+ Server->>KA: ability.extractToolsForBroker()
470
+ KA-->>Server: Return tools from exports + inline schemas
471
+ Server->>Transport: registerCapabilities({ tools, scopes })
472
+ end
473
+
474
+ Server->>KA: Forward events (start, request, response, error)
475
+
476
+ Note over Transport,Client: Request Handling
477
+ Client->>Transport: Incoming request
478
+ Transport->>Server: Parse & validate request
479
+ Server->>KA: getMethodHandler(method)
480
+ KA-->>Server: Return handler function
481
+ Server->>Server: Execute handler with timeout
482
+ Server->>Transport: Send response
483
+ Transport->>Client: Return result
484
+ ```
136
485
 
137
- - **`constructor(url, name)`**: Initializes a new broker connection.
138
- - **`send(message)`**: Sends a message through the WebSocket connection.
139
- - **`getConnectedAgents()`**: Retrieves a list of currently connected agents.
486
+ ### Broker Protocol Communication Flow
487
+
488
+ Detailed view of broker-based communication:
489
+
490
+ ```mermaid
491
+ sequenceDiagram
492
+ participant Agent as Agent (Parent)
493
+ participant Ability as Ability (Child)
494
+ participant Broker as KADI Broker
495
+ participant Consumer as Other Agent/Client
496
+
497
+ Note over Agent,Ability: Initialization
498
+ Agent->>Agent: Generate scope UUID
499
+ Agent->>Ability: spawn(env: { KADI_AGENT_SCOPE: uuid })
500
+
501
+ Note over Ability,Broker: Ability Registration
502
+ Ability->>Broker: Connect WebSocket
503
+ Ability->>Broker: hello({ role: 'agent' })
504
+ Broker-->>Ability: { nonce: 'abc123' }
505
+ Ability->>Ability: Generate ephemeral Ed25519 keypair
506
+ Ability->>Broker: authenticate({ publicKey, signature(nonce) })
507
+ Broker-->>Ability: { agentId: 'ability-123' }
508
+ Ability->>Broker: registerCapabilities({ tools: [...], scopes: [uuid] })
509
+ Broker->>Broker: Store capabilities in scope
510
+
511
+ Note over Agent,Broker: Agent Connection
512
+ Agent->>Broker: Connect WebSocket
513
+ Agent->>Broker: hello/authenticate sequence
514
+ Broker-->>Agent: { agentId: 'agent-456' }
515
+
516
+ Note over Agent,Broker: Method Call via Broker
517
+ Agent->>Broker: callAbility({ toolName: 'echo', args: { message: 'test' }, requestId })
518
+ Broker-->>Agent: { requestId }
519
+ Broker->>Ability: agent.message({ toolName, args, requestId, from: 'agent-456' })
520
+ Ability->>Ability: Execute handler
521
+ Ability->>Broker: ability.result({ requestId, toSessionId: 'agent-456', result })
522
+ Broker->>Agent: ability.result({ requestId, result })
523
+
524
+ Note over Broker,Consumer: Scope Isolation
525
+ Consumer->>Broker: listTools({ scopes: ['global'] })
526
+ Broker-->>Consumer: { tools: [] } // Ability not visible (wrong scope)
527
+ Consumer->>Broker: listTools({ scopes: [uuid] })
528
+ Broker-->>Consumer: { tools: ['echo', 'transform'] } // Now visible
529
+ ```
140
530
 
141
- ### `BrokerMessageBuilder`
531
+ ### Stdio Protocol Frame Processing
532
+
533
+ How LSP-style frames are processed:
534
+
535
+ ```mermaid
536
+ sequenceDiagram
537
+ participant Parent as Parent Process
538
+ participant SR as StdioFrameReader
539
+ participant SW as StdioFrameWriter
540
+ participant Server as StdioRpcServer
541
+ participant Ability as KadiAbility
542
+
543
+ Note over Parent,SR: Incoming Request
544
+ Parent->>SR: Write to stdin: "Kadi-Content-Length: 52\r\n\r\n{...}"
545
+ SR->>SR: Buffer incoming data
546
+ SR->>SR: Look for Content-Length header
547
+ SR->>SR: Parse header, extract body length
548
+ SR->>SR: Wait for complete body
549
+ SR->>SR: Parse JSON from body
550
+ SR->>Server: onMessage({ success: true, data: request })
551
+
552
+ Server->>Server: Validate JSON-RPC format
553
+ Server->>Ability: getMethodHandler(request.method)
554
+ Ability-->>Server: Return handler
555
+ Server->>Server: Execute handler(params) with timeout
556
+
557
+ Note over Server,Parent: Response
558
+ Server->>SW: write(response)
559
+ SW->>SW: JSON.stringify(response)
560
+ SW->>SW: Calculate byte length
561
+ SW->>SW: Create header: "Kadi-Content-Length: N\r\n\r\n"
562
+ SW->>Parent: Write header + body to stdout
563
+
564
+ Note over SR: Error Recovery
565
+ alt Frame Corruption
566
+ SR->>SR: Detect invalid frame
567
+ SR->>SR: Search for next Content-Length marker
568
+ SR->>SR: Skip corrupted bytes
569
+ SR->>Server: onMessage({ success: false, error: 'FRAME_CORRUPTION' })
570
+ Server->>Server: Log error, continue processing
571
+ end
572
+ ```
142
573
 
143
- Utility class for constructing broker messages:
574
+ ## 📥 Loading Abilities
144
575
 
145
- - **`create_message(type, data)`**: Creates a JSON string with the specified type and data.
146
- - **`message(to, content)`**: Constructs a message directed to a specific peer.
147
- - **`setup(name, description, limit, uuid)`**: Creates a setup message for registering the broker.
148
- - **`suspend()`**: Constructs a suspend message.
149
- - **`finish()`**: Constructs a finish message.
150
- - **`list()`**: Constructs a list message to request a list of connected agents.
576
+ ### Basic Loading
151
577
 
152
- ### IPC Functions
578
+ ```javascript
579
+ import { loadAbility } from '@kadi.build/core';
153
580
 
154
- - **`IPCMessageBuilder`**: Constructs messages for IPC interactions.
155
- - **`IPCManager`**: Manages setup and lifecycle of IPC connections.
156
- - **`IAbilityIPC`**: Represents an IPC ability instance with methods to manage its lifecycle and communications.
581
+ // Auto-detect protocol from agent.json
582
+ const ability = await loadAbility('my-ability');
157
583
 
158
- ### `IPCManager`
584
+ // Explicitly specify protocol
585
+ const stdioAbility = await loadAbility('my-ability', 'stdio');
586
+ const brokerAbility = await loadAbility('my-ability', 'broker');
587
+ const nativeAbility = await loadAbility('my-ability', 'native');
588
+ ```
159
589
 
160
- Manages the lifecycle and setup of interprocess communications.
590
+ ### Working with Loaded Abilities
161
591
 
162
- - **`createInstance(language, commandString, name = 'default')`**: Creates and manages a new IPC instance for the specified language.
163
- - **`getInstance(name)`**: Retrieves an existing IPC instance by name.
164
- - **`shutdownInstance(name)`**: Shuts down an existing IPC instance by name.
592
+ ```javascript
593
+ // List available methods
594
+ const methods = ability.__list();
595
+ console.log('Available methods:', methods);
165
596
 
166
- ### `IAbilityIPC` Instance
597
+ // Call methods
598
+ const result = await ability.someMethod({ param: 'value' });
167
599
 
168
- Manages an individual interprocess communication instance:
600
+ // Direct RPC call (bypasses method validation)
601
+ const response = await ability.call('someMethod', { param: 'value' });
169
602
 
170
- - **`start()`**: Starts the child process associated with this IPC instance.
171
- - **`launch()`**: Sends the launch command to the associated process.
172
- - **`sendMessage(message)`**: Sends a JSON-formatted message to the child process.
173
- - **`shutdown()`**: Sends a shutdown command to the child process and terminates it.
603
+ // Subscribe to events
604
+ ability.events.on('custom:event', (data) => {
605
+ console.log('Event received:', data);
606
+ });
174
607
 
175
- ### `IPCMessageBuilder`
608
+ // Note: Events work for native and stdio protocols
609
+ // Subscribe before calling methods that emit events
610
+ ```
176
611
 
177
- Utility class for constructing IPC messages:
612
+ ### Loading Context
178
613
 
179
- - **`createMessage(type, contents)`**: Creates a JSON string with the specified type and contents.
180
- - **`launch(commandString)`**: Constructs a launch message with the specified command string.
181
- - **`shutdown()`**: Constructs a shutdown message.
182
- - **`message(content)`**: Constructs a general message with the specified content.
614
+ The loader automatically resolves ability versions from:
183
615
 
184
- ## Usage Examples
616
+ 1. **Project context**: Reads from project's `agent.json`
617
+ 2. **Nested context**: When loading from within another ability
618
+ 3. **Explicit version**: Can be specified in ability name
185
619
 
186
- Here's how you might use the IPC and Broker functionalities:
620
+ ## ⚙️ Configuration
187
621
 
188
- ### Interacting with IPC
622
+ ### Environment Variables
623
+
624
+ ```bash
625
+ # Set default protocol
626
+ export KADI_PROTOCOL=stdio
627
+
628
+ # Configure broker
629
+ export KADI_BROKER_URL=ws://localhost:8080
630
+ export KADI_SERVICE_NAME=my-service
631
+ export KADI_AGENT_SCOPE=project-123
632
+ ```
633
+
634
+ ### Environment Variables for Child Processes
635
+
636
+ When abilities are spawned as child processes (stdio and broker protocols), the parent process passes down important environment variables:
189
637
 
190
638
  ```javascript
191
- console.log('Starting IPC Ability Interface test...');
639
+ // In your ability code, you can access these variables:
640
+ const protocol = process.env.KADI_PROTOCOL; // 'stdio' or 'broker'
641
+ const brokerUrl = process.env.KADI_BROKER_URL; // For broker protocol
642
+ const serviceName = process.env.KADI_SERVICE_NAME; // For broker protocol
643
+ const agentScope = process.env.KADI_AGENT_SCOPE; // For broker protocol
644
+
645
+ // Use them to configure your ability behavior
646
+ const ability = new KadiAbility({
647
+ name: 'my-ability',
648
+ scope: process.env.KADI_AGENT_SCOPE || 'global'
649
+ // Ability automatically uses KADI_PROTOCOL to determine transport
650
+ });
651
+ ```
192
652
 
193
- // Create Python IPC instance
194
- const pythonIPC = IPCManager.createInstance(
195
- 'python',
196
- 'echo "Hello, World!"',
197
- 'pythonTest'
198
- );
653
+ ### Project Structure
199
654
 
200
- pythonIPC.on('error', (error) => {
201
- console.error('Handled Python Error:', error);
202
- // Consider appropriate error handling or recovery actions here
655
+ ```
656
+ my-project/
657
+ ├── agent.json # Project configuration
658
+ ├── abilities/ # Installed abilities
659
+ │ └── echo-ability/
660
+ │ └── 1.0.0/
661
+ │ ├── agent.json # Ability configuration
662
+ │ ├── service.js # Ability implementation
663
+ │ └── package.json
664
+ ├── modules/ # Source modules
665
+ │ └── echo-ability/ # Development version
666
+ └── index.js # Main entry point
667
+ ```
668
+
669
+ ## 🚀 Advanced Usage
670
+
671
+ ### Event System
672
+
673
+ KADI abilities support two types of events:
674
+
675
+ #### 1. Lifecycle Events (All Protocols)
676
+
677
+ These are internal ability lifecycle events that work across all protocols:
678
+
679
+ ```javascript
680
+ const ability = new KadiAbility({ name: 'events-ability' });
681
+
682
+ // Listen to lifecycle events
683
+ ability.on('start', ({ protocol, methods }) => {
684
+ console.log(`Started with ${protocol}, methods: ${methods.join(', ')}`);
203
685
  });
204
686
 
205
- pythonIPC.on('critical-error', (error) => {
206
- console.error('Critical Error from Python:', error);
207
- // Handle critical errors, potentially restarting the process or alerting administrators
687
+ ability.on('request', ({ id, method, params }) => {
688
+ console.log(`Request ${id}: ${method}`);
208
689
  });
209
690
 
210
- pythonIPC.on('message', (msg) => {
211
- console.log('Message from Python:', msg);
691
+ ability.on('response', ({ id, result, error }) => {
692
+ if (error) console.error(`Error in ${id}:`, error);
693
+ else console.log(`Response ${id}:`, result);
212
694
  });
213
695
 
214
- pythonIPC.on('broker', (msg) => {
215
- console.log('Broker message from Python:', msg);
696
+ ability.on('error', (error) => {
697
+ console.error('Ability error:', error);
216
698
  });
699
+ ```
217
700
 
218
- pythonIPC.on('shutdown', () => {
219
- console.log('Python IPC has shut down.');
701
+ #### 2. Custom Events (Native and Stdio Protocols Only - for now)
702
+
703
+ Abilities can publish custom events that agents can subscribe to:
704
+
705
+ ```javascript
706
+ // In your ability
707
+ const ability = new KadiAbility({ name: 'my-ability' });
708
+
709
+ ability.method('process', async ({ data }) => {
710
+ // Publish events during processing
711
+ ability.publishEvent('process:started', {
712
+ timestamp: new Date().toISOString()
713
+ });
714
+
715
+ // ... do work ...
716
+
717
+ ability.publishEvent('process:completed', {
718
+ result: 'success',
719
+ timestamp: new Date().toISOString()
720
+ });
721
+
722
+ return { processed: data };
220
723
  });
221
724
 
222
- Broker.addBroker('http://your.broker.com', 'default');
223
- let brk = Broker.getBroker('default');
224
- broker = brk;
225
-
226
- pythonIPC.attachBroker(brk);
227
-
228
- brk.on('open', function open() {
229
- console.log(`Connected to ${brk.url}`);
230
- const jsonObject = BrokerMessageBuilder.setup(
231
- 'TestAgent',
232
- 'A Broker Testing Agent',
233
- null,
234
- null
235
- );
236
- brk.send(jsonObject);
725
+ // In your agent (index.js)
726
+ const ability = await loadAbility('my-ability');
727
+
728
+ // Subscribe to custom events BEFORE calling methods
729
+ ability.events.on('process:started', (data) => {
730
+ console.log(`Process started at: ${data.timestamp}`);
237
731
  });
238
732
 
239
- brk.on('message', function incoming(data) {
240
- let msg = JSON.parse(data);
241
- if (msg.type == 'setup') {
242
- (async () => {
243
- try {
244
- await GetLLMProxy();
245
- } catch (error) {
246
- console.error('Error during GetLLMProxy:', error.message);
247
- }
248
- })();
733
+ ability.events.on('process:completed', (data) => {
734
+ console.log(`Process completed: ${data.result} at ${data.timestamp}`);
735
+ });
736
+
737
+ // Now call the method that emits events
738
+ await ability.process({ data: 'test' });
739
+ ```
740
+
741
+ **Important Notes:**
742
+
743
+ - Custom events only work with `native` and `stdio` protocols for now
744
+ - Always subscribe to events before calling methods that emit them
745
+ - Events are emitted immediately when `publishEvent()` is called
746
+ - Broker protocol event support is in progress
747
+
748
+ ### Error Handling
749
+
750
+ ```javascript
751
+ try {
752
+ const ability = await loadAbility('my-ability', 'stdio');
753
+ const result = await ability.riskyMethod({ data: 'test' });
754
+ } catch (error) {
755
+ if (error.message.includes('not found')) {
756
+ console.error('Ability not installed. Run: kadi install');
757
+ } else if (error.message.includes('timeout')) {
758
+ console.error('Method timed out. Check ability health.');
759
+ } else if (error.message.includes('not exposed by this ability')) {
760
+ console.error(
761
+ 'Method not available. Use ability.__list() to see available methods.'
762
+ );
763
+ } else {
764
+ console.error('Unexpected error:', error);
249
765
  }
250
- // console.log('Received:', formatJSON(data));
766
+ }
767
+ ```
768
+
769
+ ## 🔧 Development Workflow
770
+
771
+ ### Testing Local Changes
772
+
773
+ When developing new features or testing changes to `@kadi.build/core` before publishing to NPM:
774
+
775
+ 0. **clone the repository**
776
+
777
+ ```bash
778
+ git clone https://gitlab.com/humin-game-lab/kadi/kadi-core.git
779
+ ```
780
+
781
+ 1. **Pack the local version** (in the kadi-core directory):
782
+
783
+ ```bash
784
+ cd /path/to/kadi-core
785
+ npm pack
786
+ # This creates: kadi.build-core-X.Y.Z.tgz
787
+ ```
788
+
789
+ 1. **Install in your project**:
790
+
791
+ ```bash
792
+ cd /path/to/your-project
793
+ npm install /path/to/kadi-core/kadi.build-core-X.Y.Z.tgz
794
+ ```
795
+
796
+ 1. **For ability development**, update the ability's preflight script:
797
+
798
+ ```json
799
+ {
800
+ "scripts": {
801
+ "preflight": "npm uninstall @kadi.build/core && npm install /path/to/kadi.build-core-X.Y.Z.tgz"
802
+ }
803
+ }
804
+ ```
805
+
806
+ ## 📖 API Reference
807
+
808
+ ### Core Classes
809
+
810
+ #### `KadiAbility`
811
+
812
+ The main class for creating abilities.
813
+
814
+ ```javascript
815
+ import { KadiAbility } from '@kadi.build/core';
816
+
817
+ const ability = new KadiAbility({
818
+ name: 'my-ability',
819
+ version: '1.0.0',
820
+ description: 'My ability description',
821
+ scope: 'global', // or process.env.KADI_AGENT_SCOPE
822
+ protocol: 'stdio' // 'native', 'stdio', or 'broker'
251
823
  });
824
+ ```
825
+
826
+ **Methods:**
827
+
828
+ - `ability.method(name, handler, schema?)` - Register a method
829
+ - `ability.serve()` - Start serving requests
830
+ - `ability.publishEvent(eventName, data)` - Publish an event
831
+ - `ability.on(event, handler)` - Listen to lifecycle events
832
+
833
+ #### `loadAbility`
834
+
835
+ Load an ability by name and protocol.
836
+
837
+ ```javascript
838
+ import { loadAbility } from '@kadi.build/core';
839
+
840
+ const ability = await loadAbility('ability-name', 'protocol');
841
+ ```
842
+
843
+ **Returns:** A proxy object with:
844
+
845
+ - Direct method calls (e.g., `ability.echo({ message: 'hello' })`)
846
+ - `ability.__list()` - List available methods
847
+ - `ability.events` - EventEmitter for subscribing to events (native/stdio protocols only)
848
+ - `ability.call(method, params)` - Direct RPC call
849
+
850
+ ### Utility Functions
851
+
852
+ ```javascript
853
+ import {
854
+ createLogger,
855
+ getProjectJSON,
856
+ getAbilityJSON,
857
+ getBrokerUrl,
858
+ runExecCommand
859
+ } from '@kadi.build/core';
860
+ ```
861
+
862
+ ## 🎮 Running the Examples
863
+
864
+ ### Quick Start with Example Agent
865
+
866
+ The `@kadi.build/core` package includes a complete example demonstrating multi-language abilities (JavaScript and Go) with all three transport protocols.
867
+
868
+ The `examples` folder contains:
869
+
870
+ ```
871
+ examples/
872
+ ├── example-abilities/ # Sample abilities in different languages
873
+ │ ├── echo-js/ # JavaScript ability using KadiAbility
874
+ │ │ ├── agent.json # Ability configuration
875
+ │ │ ├── service.js # Implementation
876
+ │ │ └── package.json
877
+ │ └── hash-go/ # Go ability demonstrating polyglot support
878
+ │ ├── agent.json
879
+ │ ├── go.mod
880
+ │ └── cmd/hash_ability/main.go
881
+ └── example-agent/ # Sample agent using the abilities
882
+ ├── agent.json # Declares dependencies on abilities
883
+ ├── index.js # Demonstrates all loading patterns
884
+ └── package.json
885
+ ```
886
+
887
+ #### Step 0: Have the KADI broker running locally
888
+
889
+ ```bash
890
+ kadi broker up
891
+ ```
892
+
893
+ #### Step 1: Navigate to the example agent
252
894
 
253
- brk.on('error', function error(error) {
254
- console.error('WebSocket error:', error);
895
+ ```bash
896
+ cd /path/to/kadi-core/examples/example-agent
897
+ ```
898
+
899
+ #### Step 2: Install abilities
900
+
901
+ ```bash
902
+ # Inside 'example-agent' folder
903
+ kadi install
904
+ ```
905
+
906
+ This installs the `echo-js` (JavaScript) and `hash-go` (Go) abilities defined in the agent.json.
907
+
908
+ #### Step 3: Run the agent
909
+
910
+ ```bash
911
+ kadi run
912
+ ```
913
+
914
+ #### Expected Output
915
+
916
+ You should see the agent testing various abilities and protocols:
917
+
918
+ ```
919
+ === WORKING ABILITIES ===
920
+
921
+ 🔧 Loading echo-js ability using native (Javascript)...
922
+ Available methods: [ 'echo', 'say_message' ]
923
+ Echo-js echo result: {
924
+ echo: 'I am calling echo-js echo method from Javascript',
925
+ timestamp: '2025-08-15T00:20:23.276Z',
926
+ length: 48
927
+ }
928
+
929
+ 🔧 Loading echo-js ability using stdio (Javascript)...
930
+ Available methods: [ 'echo', 'say_message' ]
931
+ Echo-js echo result: {
932
+ echo: 'I am calling echo-js echo method from Javascript',
933
+ timestamp: '2025-08-15T00:20:23.339Z',
934
+ length: 48
935
+ }
936
+ ```
937
+
938
+ #### Step 4: Stop and Clean Up
939
+
940
+ ```bash
941
+ # Stop the agent
942
+ Ctrl + C
943
+
944
+ # Clean up installed abilities and node_modules
945
+ kadi run clean
946
+ ```
947
+
948
+ ### What the Example Demonstrates
949
+
950
+ 1. **Polyglot Abilities**: Go (`hash-go`) and JavaScript (`echo-js`) abilities working together
951
+ 2. **Protocol Flexibility**: Same ability (`echo-js`) loaded via native, stdio, and broker
952
+ 3. **Error Handling**: Graceful handling when calling non-existent methods
953
+ 4. **Method Discovery**: Using `__list()` to discover available methods
954
+ 5. **Schema Support**: Both inline and export-based schema definitions
955
+ 6. **Event System**: Publishing and subscribing to events across protocols
956
+ 7. **Event Timing**: Demonstrates the importance of subscribing to events before calling methods
957
+
958
+ ### Exploring the Code
959
+
960
+ **echo-js ability** (`examples/example-abilities/echo-js/service.js`):
961
+
962
+ - Shows method registration with and without schemas
963
+ - Demonstrates scope configuration for broker visibility
964
+ - Uses environment variables for protocol adaptation
965
+ - Includes event publishing examples using `publishEvent()`
966
+ - Demonstrates how to emit events from method handlers
967
+
968
+ **hash-go ability** (`examples/example-abilities/hash-go/`):
969
+
970
+ - Demonstrates Go SDK integration
971
+ - Shows stdio protocol with a compiled language
972
+ - Implements SHA256 hashing as a service
973
+
974
+ **example-agent** (`examples/example-agent/index.js`):
975
+
976
+ - Shows all three loading patterns
977
+ - Demonstrates error handling
978
+ - Tests cross-language ability communication
979
+ - Includes event subscription examples using `ability.events.on()`
980
+ - Shows how to subscribe to events before calling methods
981
+ - Demonstrates event handling across different protocols (native/stdio)
982
+
983
+ ## 💡 Additional Examples
984
+
985
+ ### Complete Echo Service Example
986
+
987
+ **service.js**:
988
+
989
+ ```javascript
990
+ #!/usr/bin/env node
991
+ import { KadiAbility } from '@kadi.build/core';
992
+
993
+ const echoAbility = new KadiAbility({
994
+ name: 'echo-service',
995
+ version: '1.0.0',
996
+ description: 'Multi-protocol echo service',
997
+ scope: process.env.KADI_AGENT_SCOPE // Important for broker visibility
255
998
  });
256
999
 
257
- brk.on('close', function close() {
258
- console.log('Disconnected from the server');
259
- process.exit(0); // Exit the process when the WebSocket connection is closed
1000
+ // Simple echo with metadata
1001
+ echoAbility.method('echo', async ({ message }) => {
1002
+ // Publish events (works for native and stdio protocols)
1003
+ echoAbility.publishEvent('echo:test-event', { from: 'message echo' });
1004
+ echoAbility.publishEvent('echo:test-event', { from: 'message echo 2' });
1005
+
1006
+ return {
1007
+ echo: message,
1008
+ timestamp: new Date().toISOString(),
1009
+ length: message?.length || 0
1010
+ };
260
1011
  });
261
1012
 
262
- // Send launch command
1013
+ // Transform text
1014
+ echoAbility.method(
1015
+ 'transform',
1016
+ async ({ text, operations }) => {
1017
+ let result = text;
1018
+ for (const op of operations) {
1019
+ switch (op) {
1020
+ case 'upper':
1021
+ result = result.toUpperCase();
1022
+ break;
1023
+ case 'lower':
1024
+ result = result.toLowerCase();
1025
+ break;
1026
+ case 'reverse':
1027
+ result = result.split('').reverse().join('');
1028
+ break;
1029
+ }
1030
+ }
1031
+ return { transformed: result };
1032
+ },
1033
+ {
1034
+ description: 'Transform text with multiple operations',
1035
+ inputSchema: {
1036
+ type: 'object',
1037
+ properties: {
1038
+ text: { type: 'string' },
1039
+ operations: {
1040
+ type: 'array',
1041
+ items: { enum: ['upper', 'lower', 'reverse'] }
1042
+ }
1043
+ },
1044
+ required: ['text', 'operations']
1045
+ }
1046
+ }
1047
+ );
1048
+
1049
+ echoAbility.serve().catch(console.error);
1050
+ ```
1051
+
1052
+ **Using the service**:
1053
+
1054
+ ```javascript
1055
+ import { loadAbility } from '@kadi.build/core';
1056
+
1057
+ async function demo() {
1058
+ // Try all protocols
1059
+ for (const protocol of ['native', 'stdio', 'broker']) {
1060
+ console.log(`\nTesting ${protocol} protocol:`);
1061
+
1062
+ try {
1063
+ const echo = await loadAbility('echo-service', protocol);
1064
+
1065
+ // Subscribe to events (works for native and stdio protocols)
1066
+ echo.events.on('echo:test-event', (data) => {
1067
+ console.log(`Echo event received: ${data.from}`);
1068
+ });
1069
+
1070
+ // Simple echo
1071
+ const result1 = await echo.echo({
1072
+ message: `Hello from ${protocol}!`
1073
+ });
1074
+ console.log('Echo:', result1);
1075
+
1076
+ // Transform
1077
+ const result2 = await echo.transform({
1078
+ text: 'Hello World',
1079
+ operations: ['lower', 'reverse']
1080
+ });
1081
+ console.log('Transform:', result2);
1082
+ } catch (error) {
1083
+ console.error(`${protocol} failed:`, error.message);
1084
+ }
1085
+ }
1086
+ }
1087
+
1088
+ demo();
1089
+ ```
1090
+
1091
+ ## 🎯 Real-World Event Example
1092
+
1093
+ Here's the exact event pattern used in the example code:
1094
+
1095
+ **In your ability (service.js):**
1096
+
1097
+ ```javascript
1098
+ const echoAbility = new KadiAbility({
1099
+ name: 'echo-js',
1100
+ version: '0.0.1',
1101
+ scope: process.env.KADI_AGENT_SCOPE
1102
+ });
1103
+
1104
+ async function echo(message) {
1105
+ const timestamp = new Date().toISOString();
1106
+ const messageText = message?.message ?? message ?? '';
1107
+ const length = messageText.length;
263
1108
 
264
- //Utilize log files
265
- // pythonIPC.sendMessage(IPCMessageBuilder.launch(pythonIPC.commandString, process.cwd() + "/logs"));
1109
+ // Publish events (works for native and stdio protocols)
1110
+ echoAbility.publishEvent('echo:test-event', { from: 'message echo' });
1111
+ echoAbility.publishEvent('echo:test-event', { from: 'message echo 2' });
266
1112
 
267
- //No log files created
268
- pythonIPC.sendMessage(IPCMessageBuilder.launch(pythonIPC.commandString));
1113
+ return { echo: messageText, timestamp, length };
1114
+ }
1115
+
1116
+ echoAbility.method('echo', echo);
269
1117
  ```
270
1118
 
271
- ### Handling IPC
1119
+ **In your agent (index.js):**
272
1120
 
273
1121
  ```javascript
274
- const ipcManager = new IPCManager();
275
- const ipcInstance = ipcManager.createInstance('node', 'app.js', 'nodeApp');
276
- ipcInstance.on('message', (message) => console.log(message));
1122
+ // Subscribe first
1123
+ echoJsAbility.events.on('echo:test-event', (data) => {
1124
+ console.log(`[NATIVE] echo:test-event received:`, data);
1125
+ });
1126
+
1127
+ // Now trigger the method that emits the event
1128
+ const echoJsResult = await echoJsAbility.echo({
1129
+ message: 'I am calling echo-js echo method from Javascript'
1130
+ });
1131
+ ```
1132
+
1133
+ **Key Points:**
1134
+
1135
+ - Events work for `native` and `stdio` protocols only
1136
+ - Always subscribe to events BEFORE calling methods that emit them
1137
+ - Events are emitted immediately when `publishEvent()` is called
1138
+ - Use `ability.events.on()` to subscribe to custom events
1139
+ - Use `ability.on()` to listen to lifecycle events
1140
+
1141
+ ## 🐛 Troubleshooting
1142
+
1143
+ ### Common Issues
1144
+
1145
+ **Ability Not Found**
1146
+
1147
+ ```
1148
+ Error: Ability 'my-ability' version '1.0.0' is not installed
1149
+ ```
1150
+
1151
+ **Solution**: Run `kadi install` to install project dependencies
1152
+
1153
+ **Broker Connection Failed**
1154
+
1155
+ ```
1156
+ Error: Failed to connect to KADI Broker at ws://localhost:8080
1157
+ ```
1158
+
1159
+ **Solution**: Ensure the broker is running: `kadi broker start`
1160
+
1161
+ **Method Not Found**
1162
+
1163
+ ```
1164
+ Error: Method 'someMethod' is not exposed by this ability
1165
+ ```
1166
+
1167
+ **Solution**: Check ability exports or use `__list()` to see available methods
1168
+
1169
+ **Protocol Not Supported**
1170
+
1171
+ ```
1172
+ Error: Unsupported ability interface protocol: custom
1173
+ ```
1174
+
1175
+ **Solution**: Ability must define the protocol in its `agent.json` interfaces
1176
+
1177
+ **Event Subscription Issues**
1178
+
1179
+ ```
1180
+ TypeError: Cannot read property 'on' of undefined
1181
+ ```
1182
+
1183
+ **Solution**: Make sure you're accessing `ability.events.on()` not `ability.on()` for custom events
1184
+
1185
+ **Event Protocol Support**
1186
+
1187
+ ```
1188
+ Events are only supported for 'native' and 'stdio' protocols
277
1189
  ```
278
1190
 
1191
+ **Solution**: Events currently work for `native` and `stdio` protocols only. Broker protocol event support is planned for future releases.
1192
+
1193
+ ### Debug Mode
1194
+
1195
+ Enable detailed logging:
1196
+
1197
+ ```bash
1198
+ DEBUG=kadi:* node index.js
1199
+ ```
1200
+
1201
+ Check ability logs:
1202
+
1203
+ ```bash
1204
+ tail -f abilities/my-ability/1.0.0/my-ability.log
1205
+ ```
1206
+
1207
+ ## 🤝 Contributing
1208
+
1209
+ We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
1210
+
1211
+ 1. Fork the repository
1212
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
1213
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
1214
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
1215
+ 5. Open a Pull Request
1216
+
1217
+ ## 📄 License
1218
+
1219
+ MIT License - see [LICENSE](LICENSE) file for details
1220
+
1221
+ ## 🔗 Related Projects
1222
+
1223
+ - [@kadi.build/cli](https://gitlab.com/humin-game-lab/kadi/kadi) - Command-line interface
1224
+ - [@kadi.build/broker](https://gitlab.com/humin-game-lab/kadi/kadi-broker) - The KADI broker
1225
+
1226
+ ## 📚 Resources
1227
+
1228
+ - [Official Documentation](https://docs.kadi.build)
1229
+ - [API Reference](https://docs.kadi.build/api)
1230
+ - [Tutorial: Building Your First Ability](https://docs.kadi.build/tutorial)
1231
+ - [Architecture Deep Dive](https://docs.kadi.build/architecture)
1232
+
279
1233
  ---
280
1234
 
281
- ## TODO:
282
-
283
- - Once @kadi.build/core and dependencies are done, @kadi.build/cli (and all commands), need to be rewritten, using the @kadi.build/core module. this will be v1.0 release
284
-
285
- * [ ] add getConnectedAgents to the Broker, it should call the IBroker method
286
-
287
- * [x] add key to IBroker object
288
- * [ ] IBroker on 'message' should listen for setup event, and update teh uuid and key for the IBroker object
289
-
290
- * [ ] Extract Broker and IPC to their own node module and host on npm
291
- * [x] Publish @kadi.build/core to npm
292
- * [x] Setup of python environment needs to happen once an IPC Inteface is created
293
- This include setting up the virtual environment and installing the required packages
294
- * [x] Add method to IPCManger to set IPCInterfaces from agent.json. If IPCManger has empty list, it thorws an error that no IAbilityIPC's can be created. IPCManger passes the proper interface command to IAbilityIPC on creation from this list, IAbilityIPC does not need access directly to agent.json. Controlling app can update this list anytime a new IAbilityInterface is created, allowing the agent.json to be updated during runtime.
295
- * [x] Need to adapt ipc.js to read from the @kadi.build/core folder when trying to open IPC instances, instead of project folder. @kadi.build/core should be in the npm_modules folder (when deployed)
296
- * [x] The test code needs to fully test the IPCManger interface, and not just the IPCAbility
297
- * [x] If node fails, we need to make sure python IPC interface and child processes also close out.
298
- * [x] Add buffer to error handeling in IAbilityIPC, mimic same process used in std input
299
- * [x] Verify if Python IAbilityIPC needs to utilize buffering, and node passing '\n' at end of jso
300
- * [x] Setup passthrough for broker messages to/from Language IPC interface
301
- * [x] Add broker message builder/parser in python IAbilityIPC
302
- * [x] Update BrokerMessageBuilder to use same format at IPCMessageBuilder
303
- * [x] Update Broker so it extends EventEmitter, rather than have a property of event emitter (similar to IAbilityIPC)
304
- * [x] update python IAbility so that it creates a timestamped command_output.log file, new file for each run, in the project root directory... not the @kadi.build/core directory
305
- * [x] Comment out debug messages
306
- * [x] Add ability to allow for launched process to be piped to event handler (launch command should default to process, but allow user to set flag that will save all outputs to file in the event handler)
1235
+ Built with ❤️ by the KADI team