@kadi.build/core 0.0.1-alpha.9 → 0.1.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 (126) hide show
  1. package/README.md +362 -1305
  2. package/dist/client.d.ts +573 -0
  3. package/dist/client.d.ts.map +1 -0
  4. package/dist/client.js +1673 -0
  5. package/dist/client.js.map +1 -0
  6. package/dist/errors.d.ts +107 -0
  7. package/dist/errors.d.ts.map +1 -0
  8. package/dist/errors.js +147 -0
  9. package/dist/errors.js.map +1 -0
  10. package/dist/index.d.ts +37 -14
  11. package/dist/index.d.ts.map +1 -1
  12. package/dist/index.js +40 -23
  13. package/dist/index.js.map +1 -1
  14. package/dist/lockfile.d.ts +190 -0
  15. package/dist/lockfile.d.ts.map +1 -0
  16. package/dist/lockfile.js +373 -0
  17. package/dist/lockfile.js.map +1 -0
  18. package/dist/transports/broker.d.ts +75 -0
  19. package/dist/transports/broker.d.ts.map +1 -0
  20. package/dist/transports/broker.js +383 -0
  21. package/dist/transports/broker.js.map +1 -0
  22. package/dist/transports/native.d.ts +39 -0
  23. package/dist/transports/native.d.ts.map +1 -0
  24. package/dist/transports/native.js +189 -0
  25. package/dist/transports/native.js.map +1 -0
  26. package/dist/transports/stdio.d.ts +46 -0
  27. package/dist/transports/stdio.d.ts.map +1 -0
  28. package/dist/transports/stdio.js +460 -0
  29. package/dist/transports/stdio.js.map +1 -0
  30. package/dist/types.d.ts +664 -0
  31. package/dist/types.d.ts.map +1 -0
  32. package/dist/types.js +16 -0
  33. package/dist/types.js.map +1 -0
  34. package/dist/zod.d.ts +34 -0
  35. package/dist/zod.d.ts.map +1 -0
  36. package/dist/zod.js +60 -0
  37. package/dist/zod.js.map +1 -0
  38. package/package.json +13 -28
  39. package/dist/KadiClient.d.ts +0 -470
  40. package/dist/KadiClient.d.ts.map +0 -1
  41. package/dist/KadiClient.js +0 -1572
  42. package/dist/KadiClient.js.map +0 -1
  43. package/dist/errors/error-codes.d.ts +0 -985
  44. package/dist/errors/error-codes.d.ts.map +0 -1
  45. package/dist/errors/error-codes.js +0 -638
  46. package/dist/errors/error-codes.js.map +0 -1
  47. package/dist/loadAbility.d.ts +0 -105
  48. package/dist/loadAbility.d.ts.map +0 -1
  49. package/dist/loadAbility.js +0 -370
  50. package/dist/loadAbility.js.map +0 -1
  51. package/dist/messages/BrokerMessages.d.ts +0 -84
  52. package/dist/messages/BrokerMessages.d.ts.map +0 -1
  53. package/dist/messages/BrokerMessages.js +0 -125
  54. package/dist/messages/BrokerMessages.js.map +0 -1
  55. package/dist/messages/MessageBuilder.d.ts +0 -83
  56. package/dist/messages/MessageBuilder.d.ts.map +0 -1
  57. package/dist/messages/MessageBuilder.js +0 -144
  58. package/dist/messages/MessageBuilder.js.map +0 -1
  59. package/dist/schemas/events.schemas.d.ts +0 -177
  60. package/dist/schemas/events.schemas.d.ts.map +0 -1
  61. package/dist/schemas/events.schemas.js +0 -265
  62. package/dist/schemas/events.schemas.js.map +0 -1
  63. package/dist/schemas/index.d.ts +0 -3
  64. package/dist/schemas/index.d.ts.map +0 -1
  65. package/dist/schemas/index.js +0 -4
  66. package/dist/schemas/index.js.map +0 -1
  67. package/dist/schemas/kadi.schemas.d.ts +0 -70
  68. package/dist/schemas/kadi.schemas.d.ts.map +0 -1
  69. package/dist/schemas/kadi.schemas.js +0 -120
  70. package/dist/schemas/kadi.schemas.js.map +0 -1
  71. package/dist/transports/BrokerTransport.d.ts +0 -102
  72. package/dist/transports/BrokerTransport.d.ts.map +0 -1
  73. package/dist/transports/BrokerTransport.js +0 -177
  74. package/dist/transports/BrokerTransport.js.map +0 -1
  75. package/dist/transports/NativeTransport.d.ts +0 -82
  76. package/dist/transports/NativeTransport.d.ts.map +0 -1
  77. package/dist/transports/NativeTransport.js +0 -263
  78. package/dist/transports/NativeTransport.js.map +0 -1
  79. package/dist/transports/StdioTransport.d.ts +0 -112
  80. package/dist/transports/StdioTransport.d.ts.map +0 -1
  81. package/dist/transports/StdioTransport.js +0 -450
  82. package/dist/transports/StdioTransport.js.map +0 -1
  83. package/dist/transports/Transport.d.ts +0 -93
  84. package/dist/transports/Transport.d.ts.map +0 -1
  85. package/dist/transports/Transport.js +0 -13
  86. package/dist/transports/Transport.js.map +0 -1
  87. package/dist/types/broker.d.ts +0 -31
  88. package/dist/types/broker.d.ts.map +0 -1
  89. package/dist/types/broker.js +0 -6
  90. package/dist/types/broker.js.map +0 -1
  91. package/dist/types/core.d.ts +0 -139
  92. package/dist/types/core.d.ts.map +0 -1
  93. package/dist/types/core.js +0 -26
  94. package/dist/types/core.js.map +0 -1
  95. package/dist/types/events.d.ts +0 -186
  96. package/dist/types/events.d.ts.map +0 -1
  97. package/dist/types/events.js +0 -16
  98. package/dist/types/events.js.map +0 -1
  99. package/dist/types/index.d.ts +0 -9
  100. package/dist/types/index.d.ts.map +0 -1
  101. package/dist/types/index.js +0 -13
  102. package/dist/types/index.js.map +0 -1
  103. package/dist/types/protocol.d.ts +0 -160
  104. package/dist/types/protocol.d.ts.map +0 -1
  105. package/dist/types/protocol.js +0 -5
  106. package/dist/types/protocol.js.map +0 -1
  107. package/dist/utils/agentUtils.d.ts +0 -187
  108. package/dist/utils/agentUtils.d.ts.map +0 -1
  109. package/dist/utils/agentUtils.js +0 -185
  110. package/dist/utils/agentUtils.js.map +0 -1
  111. package/dist/utils/commandUtils.d.ts +0 -45
  112. package/dist/utils/commandUtils.d.ts.map +0 -1
  113. package/dist/utils/commandUtils.js +0 -145
  114. package/dist/utils/commandUtils.js.map +0 -1
  115. package/dist/utils/configUtils.d.ts +0 -55
  116. package/dist/utils/configUtils.d.ts.map +0 -1
  117. package/dist/utils/configUtils.js +0 -100
  118. package/dist/utils/configUtils.js.map +0 -1
  119. package/dist/utils/logger.d.ts +0 -59
  120. package/dist/utils/logger.d.ts.map +0 -1
  121. package/dist/utils/logger.js +0 -122
  122. package/dist/utils/logger.js.map +0 -1
  123. package/dist/utils/pathUtils.d.ts +0 -48
  124. package/dist/utils/pathUtils.d.ts.map +0 -1
  125. package/dist/utils/pathUtils.js +0 -128
  126. package/dist/utils/pathUtils.js.map +0 -1
package/README.md CHANGED
@@ -1,1479 +1,536 @@
1
- # @kadi.build/core
1
+ # kadi-core
2
2
 
3
- > A comprehensive toolkit for building and managing KADI abilities with multiple transport protocols
3
+ TypeScript SDK for building KADI agents. Register tools, connect to brokers, load abilities, publish events.
4
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)
5
+ kadi-core lets you:
6
+ - **Expose functions** as callable tools over the network
7
+ - **Discover and invoke** remote services without hardcoding URLs
8
+ - **Communicate via events** across your service mesh
7
9
 
8
- ## 🎯 Overview
9
-
10
- `@kadi.build/core` is the foundational library for creating KADI abilities - modular, protocol-agnostic components that can communicate via multiple transport layers. Whether you're building local tools, distributed systems, 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
- - [🤖 Creating Agents](#-creating-agents)
20
- - [🔄 Architecture Deep Dive](#-architecture-deep-dive)
21
- - [📥 Loading Abilities](#-loading-abilities)
22
- - [⚙️ Configuration](#-configuration)
23
- - [🚀 Advanced Usage](#-advanced-usage)
24
- - [🔧 Development Workflow](#-development-workflow)
25
- - [📖 API Reference](#-api-reference)
26
- - [🎮 Running the Examples](#-running-the-examples)
27
- - [💡 Additional Examples](#-additional-examples)
28
- - [🐛 Troubleshooting](#-troubleshooting)
29
- - [🤝 Contributing](#-contributing)
30
- - [📄 License](#-license)
31
- - [🔗 Related Projects](#-related-projects)
32
- - [📚 Resources](#-resources)
33
-
34
- ## 📦 Installation
10
+ ## Install
35
11
 
36
12
  ```bash
37
13
  npm install @kadi.build/core
38
14
  ```
39
15
 
40
- For global CLI tools:
41
-
42
- ```bash
43
- npm install -g @kadi.build/cli
44
- ```
45
-
46
- ## 🚀 Quick Start
47
-
48
- The library provides a unified way to work with KADI through the **KadiClient** class, which can operate in multiple roles:
16
+ **Requirements:**
17
+ - Node.js 18+
18
+ - TypeScript 5.0+ (recommended for full type inference)
19
+ - ESM only (`"type": "module"` in your package.json)
49
20
 
50
- - **As an Ability**: Serving tools/methods that others can call
51
- - **As an Agent**: Calling remote tools via broker protocol
52
-
53
- ### Creating Your First Ability
54
-
55
- ```javascript
56
- #!/usr/bin/env node
57
- import { KadiClient } from '@kadi.build/core';
58
-
59
- // Create a ability instance
60
- const mathAbility = new KadiClient({
61
- name: 'math-ability',
62
- role: 'ability', // 'agent', 'ability'
63
- protocol: 'stdio' // 'native', 'stdio', or 'broker'
64
- });
65
-
66
- // Register a tool
67
- mathAbility.registerTool('add', async ({ a, b }) => {
68
- return { result: a + b };
69
- });
70
-
71
- // Start serving requests
72
- mathAbility.serve().catch(console.error);
73
- ```
21
+ ## Quick Start
74
22
 
75
- ### Loading and Using Abilities
76
-
77
- ```javascript
78
- import { loadAbility } from '@kadi.build/core';
79
-
80
- async function main() {
81
- // Load an ability (uses first interface defined in agent.json)
82
- const math = await loadAbility('math-ability');
83
-
84
- // Call methods like regular functions
85
- const result = await math.add({ a: 5, b: 3 });
86
- console.log(result); // { result: 8 }
87
- }
88
-
89
- main().catch(console.error);
90
- ```
91
-
92
- ### Creating a Broker-Connected Agent
93
-
94
- ```javascript
95
- import { KadiClient } from '@kadi.build/core';
96
-
97
- // Create an agent that connects to broker
98
- const agent = new KadiClient({
99
- name: 'my-agent',
100
- role: 'agent',
101
- protocol: 'broker',
102
- brokers: {
103
- prod: 'ws://localhost:8080',
104
- dev: 'ws://localhost:8081'
105
- },
106
- defaultBroker: 'prod',
107
- networks: ['global']
108
- });
109
-
110
- // Register tools that other agents can call
111
- agent.registerTool(
112
- 'greet',
113
- async ({ name }) => {
114
- return { greeting: `Hello, ${name}!` };
115
- },
116
- {
117
- description: 'Greet someone by name',
118
- inputSchema: {
119
- type: 'object',
120
- properties: {
121
- name: { type: 'string', description: 'Name to greet' }
122
- },
123
- required: ['name']
124
- }
125
- }
126
- );
23
+ Create a simple calculator agent:
127
24
 
128
- // Connect to brokers and start serving
129
- await agent.connectToBrokers();
25
+ ```typescript
26
+ // calculator.ts
27
+ import { KadiClient, z } from '@kadi.build/core';
130
28
 
131
- // Call tools from OTHER agents connected to the same broker
132
- const result = await agent.callTool('translate', 'text-tool', {
133
- text: 'Hello world',
134
- language: 'spanish'
29
+ const client = new KadiClient({
30
+ name: 'calculator',
31
+ version: '1.0.0',
135
32
  });
136
- console.log(result);
137
- ```
138
-
139
- ## 🔌 Transport Protocols
140
33
 
141
- KADI abilities support three transport protocols, each optimized for different use cases:
34
+ client.registerTool({
35
+ name: 'add',
36
+ description: 'Add two numbers',
37
+ input: z.object({ a: z.number(), b: z.number() }),
38
+ }, async ({ a, b }) => ({ result: a + b }));
142
39
 
143
- ### 1. Native Protocol (Fastest)
144
-
145
- - **Use Case**: High-performance, in-process execution
146
- - **How it Works**: Loads abilities as ES modules, enabling direct function calls
147
- - **Performance**: Zero IPC overhead - functions run in the same process
148
- - **Best For**: Utility libraries, data processing, performance-critical operations
149
-
150
- ```javascript
151
- // Loads as native module by default if available
152
- const ability = await loadAbility('my-ability', 'native');
40
+ // Start listening for requests over stdio
41
+ await client.serve('stdio');
153
42
  ```
154
43
 
155
- ### 2. Stdio Protocol (Balanced)
44
+ Now another agent can load and use it:
156
45
 
157
- - **Use Case**: Language-agnostic local execution
158
- - **How it Works**: Spawns abilities as child processes, communicates via LSP-style JSON-RPC over stdin/stdout
159
- - **Performance**: Minimal overhead, reliable local IPC
160
- - **Best For**: Python scripts, Go binaries, Node.js abilities, development/testing
46
+ ---
161
47
 
162
- ```javascript
163
- // Force stdio protocol
164
- const ability = await loadAbility('my-ability', 'stdio');
165
- ```
48
+ ## Table of Contents
166
49
 
167
- **Environment Variables Passed to Child Process:**
50
+ - [Core Concepts](#core-concepts)
51
+ - [Registering Tools](#registering-tools)
52
+ - [Connecting to Brokers](#connecting-to-brokers)
53
+ - [Loading Abilities](#loading-abilities)
54
+ - [Events from Abilities](#events-from-abilities)
55
+ - [Broker Events (Pub/Sub)](#broker-events-pubsub)
56
+ - [Serving as an Ability](#serving-as-an-ability)
57
+ - [Error Handling](#error-handling)
58
+ - [API Reference](#api-reference)
59
+ - [Troubleshooting](#troubleshooting)
168
60
 
169
- ```bash
170
- KADI_PROTOCOL=stdio # Identifies the protocol being used
171
- # Plus all parent process.env variables
172
- ```
61
+ ---
173
62
 
174
- ### 3. Broker Protocol (Distributed)
63
+ ## Core Concepts
175
64
 
176
- - **Use Case**: Distributed systems, containerized deployments
177
- - **How it Works**: Abilities connect to a WebSocket broker for network communication
178
- - **Performance**: Network overhead, but enables scaling and distribution
179
- - **Best For**: Distributed abilities, cloud deployments, load-balanced systems
65
+ | Concept | Plain English |
66
+ |---------|---------------|
67
+ | **Tool** | A function other services can call over the network (like an API endpoint) |
68
+ | **Agent** | Your service. One codebase = one agent. Agents expose tools and call other agents' tools. |
69
+ | **Broker** | The router/registry. Agents register with brokers so they can find each other (like DNS + API gateway combined) |
70
+ | **Ability** | A tool provider you connect to. Could be in-process, a subprocess, or a remote service. |
71
+ | **Event** | Fire-and-forget notification. Publish events, other agents can subscribe. |
180
72
 
181
- ```javascript
182
- // Use broker for distributed execution
183
- const ability = await loadAbility('my-ability', 'broker');
184
- ```
73
+ ---
185
74
 
186
- **Environment Variables Passed to Child Process:**
75
+ ## Registering Tools
187
76
 
188
- ```bash
189
- KADI_PROTOCOL=broker # Identifies the protocol
190
- KADI_BROKER_URL=ws://localhost:8080 # Broker connection URL
191
- KADI_ABILITY_NAME=ability.echo.1_0_0 # Ability identifier
192
- KADI_AGENT_SCOPE=uuid-here # Agent's scope/namespace
193
- # Plus all parent process.env variables
194
- ```
77
+ Tools are functions you expose for other agents to call.
195
78
 
196
- **Network/Namespace Visibility:**
79
+ ```typescript
80
+ import { KadiClient, z } from '@kadi.build/core';
197
81
 
198
- 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 network:
82
+ const client = new KadiClient({ name: 'weather-service' });
199
83
 
200
- ```javascript
201
- // In your ability code:
202
- const echoAbility = new KadiClient({
203
- name: 'echo-js',
204
- version: '0.0.1',
205
- description: 'Echo ability with broker support',
206
- network: process.env.KADI_AGENT_SCOPE // Use agent's network for visibility
84
+ // Define input schema with Zod
85
+ const weatherInput = z.object({
86
+ city: z.string().describe('City name'),
87
+ units: z.enum(['celsius', 'fahrenheit']).default('celsius'),
207
88
  });
208
89
 
209
- // Without this, the ability would only be visible in the 'global' network
210
- // and the parent agent wouldn't be able to communicate with it
90
+ // Register tool
91
+ client.registerTool({
92
+ name: 'getWeather',
93
+ description: 'Get current weather for a city',
94
+ input: weatherInput,
95
+ }, async ({ city, units }) => {
96
+ const temp = await fetchTemperature(city);
97
+ return { city, temperature: temp, units };
98
+ });
211
99
  ```
212
100
 
213
- ### Protocol Selection Strategy
101
+ **Why Zod?** Type-safe schemas, 70% less code than JSON Schema, automatic TypeScript inference.
214
102
 
215
- When no protocol is specified, `loadAbility` defaults to the 'native' protocol. You must explicitly specify the protocol if you want to use 'stdio' or 'broker'.
103
+ ### Targeting Specific Brokers
216
104
 
217
- ```mermaid
218
- graph TD
219
- A[Load Ability] --> B{Protocol Specified?}
220
- B -->|Yes| C[Use Specified Protocol]
221
- B -->|No| D[Default to 'native']
105
+ ```typescript
106
+ // Register tool only on specific brokers
107
+ client.registerTool(definition, handler, { brokers: ['internal'] });
222
108
  ```
223
109
 
224
- ## 🛠️ Creating Abilities with KadiClient
110
+ ---
225
111
 
226
- ### Basic Ability Structure
112
+ ## Connecting to Brokers
227
113
 
228
- ```javascript
229
- import { KadiClient } from '@kadi.build/core';
114
+ Brokers let agents discover and call each other without knowing URLs.
230
115
 
231
- const ability = new KadiClient({
232
- name: 'echo-ability',
233
- role: 'ability', // 'agent', 'ability',
234
- protocol: 'stdio', // 'native', 'stdio', or 'broker'
116
+ ```typescript
117
+ const client = new KadiClient({
118
+ name: 'my-agent',
235
119
  brokers: {
236
- local: 'ws://localhost:8080',
237
- prod: 'ws://api.example.com:8080'
120
+ default: 'ws://localhost:8080/kadi',
121
+ production: 'wss://broker.example.com/kadi',
238
122
  },
239
- defaultBroker: 'local'
240
- });
241
-
242
- // Method 1: Simple tool registration
243
- ability.registerTool('echo', async ({ message }) => ({
244
- echo: message,
245
- timestamp: new Date().toISOString()
246
- }));
247
-
248
- // Method 2: Tool with inline schema
249
- ability.registerTool(
250
- 'format',
251
- async ({ text, style }) => ({
252
- formatted: style === 'upper' ? text.toUpperCase() : text.toLowerCase()
253
- }),
254
- {
255
- description: 'Format text with specified style',
256
- inputSchema: {
257
- type: 'object',
258
- properties: {
259
- text: { type: 'string', description: 'Text to format' },
260
- style: {
261
- type: 'string',
262
- enum: ['upper', 'lower'],
263
- description: 'Formatting style'
264
- }
265
- },
266
- required: ['text', 'style']
267
- },
268
- outputSchema: {
269
- type: 'object',
270
- properties: {
271
- formatted: { type: 'string', description: 'Formatted text' }
272
- }
273
- }
274
- }
275
- );
276
-
277
- // Start serving
278
- ability.serve().catch(console.error);
279
- ```
280
-
281
- ### Event Publishing and Subscription
282
-
283
- **Full Support**: Events work across all protocols - native, stdio, and broker.
284
-
285
- KadiClient provides a unified event system that works consistently across all transport protocols:
286
-
287
- ```javascript
288
- // In your ability (ability.js)
289
- const ability = new KadiClient({
290
- name: 'my-ability',
291
- role: 'ability',
292
- protocol: 'broker' // Works with all protocols
293
- });
294
-
295
- // Publish events from tool handlers
296
- ability.registerTool('echo', async ({ message }) => {
297
- // Publish events during processing
298
- await ability.publishEvent('echo.processing', { status: 'started' });
299
-
300
- // Do work...
301
- const result = { echo: message, timestamp: new Date().toISOString() };
302
-
303
- await ability.publishEvent('echo.completed', { result });
304
- return result;
123
+ defaultBroker: 'default',
124
+ networks: ['global'], // Which networks to join
305
125
  });
306
126
 
307
- // In your agent (agent.js)
308
- const agent = new KadiClient({
309
- name: 'my-client',
310
- role: 'agent',
311
- protocol: 'broker'
312
- });
127
+ // Connect (uses Ed25519 authentication)
128
+ await client.connect();
313
129
 
314
- // Subscribe to events using patterns
315
- agent.subscribeToEvent('echo.*', (data) => {
316
- console.log('Echo event received:', data);
317
- });
130
+ // Invoke a tool - broker routes to any available provider
131
+ const result = await client.invokeRemote('add', { a: 5, b: 3 });
318
132
 
319
- // For loaded abilities with native/stdio protocols
320
- const ability = await loadAbility('my-ability', 'stdio');
321
- ability.events.on('echo.completed', (data) => {
322
- console.log('Completed:', data);
323
- });
133
+ // Disconnect when done
134
+ await client.disconnect();
324
135
  ```
325
136
 
326
- **Event Patterns**:
327
-
328
- - Use wildcards: `math.*` matches `math.operation`, `math.milestone`
329
- - Protocol-specific delivery:
330
- - **Native**: Direct EventEmitter (synchronous)
331
- - **Stdio**: JSON-RPC notifications over stdout
332
- - **Broker**: RabbitMQ pub/sub via WebSocket
333
-
334
- ### Ability Configuration (agent.json)
137
+ ### Reconnection
335
138
 
336
- Every ability should have an `agent.json` file that defines its capabilities:
139
+ Auto-reconnects with exponential backoff (1s 2s 4s ... → 30s max) with jitter:
337
140
 
338
- ```json
339
- {
340
- "name": "echo-ability",
341
- "kind": "ability",
342
- "version": "1.0.0",
343
- "license": "MIT",
344
- "description": "Echo ability with multiple transport support",
345
- "scripts": {
346
- "setup": "npm install",
347
- "start": "node ability.js"
348
- },
349
- "exports": [
350
- {
351
- "name": "echo",
352
- "description": "Echo the input message",
353
- "inputSchema": {
354
- "type": "object",
355
- "properties": {
356
- "message": { "type": "string" }
357
- },
358
- "required": ["message"]
359
- },
360
- "outputSchema": {
361
- "type": "object",
362
- "properties": {
363
- "echo": { "type": "string" },
364
- "timestamp": { "type": "string" }
365
- }
366
- }
367
- }
368
- ],
369
- "brokers": {
370
- "local": "ws://localhost:8080",
371
- "remote": "ws://api.example.com:8080"
372
- },
373
- "defaultBroker": "local"
374
- }
375
- ```
376
-
377
- ### Schema Definition Options
378
-
379
- The ability system provides two ways to define method schemas:
380
-
381
- 1. **Export Schemas** (defined in `agent.json` exports section)
382
- 2. **Inline Schemas** (passed directly to `registerTool()`)
383
-
384
- ```javascript
385
- // Option 1: Define in agent.json exports section
386
- // The schema is picked up automatically if the method name matches
387
- // agent.json:
388
- {
389
- "exports": [
390
- {
391
- "name": "process",
392
- "description": "Process data",
393
- "inputSchema": { /* ... */ },
394
- "outputSchema": { /* ... */ }
395
- }
396
- ]
397
- }
398
-
399
- // Option 2: Define inline when registering the method
400
- ability.registerTool('process', handler, {
401
- description: 'Process data',
402
- inputSchema: { /* ... */ },
403
- outputSchema: { /* ... */ }
141
+ ```typescript
142
+ const client = new KadiClient({
143
+ name: 'resilient-agent',
144
+ brokers: { default: 'ws://localhost:8080/kadi' },
145
+ autoReconnect: true, // Default: true
146
+ maxReconnectDelay: 30000, // Cap at 30 seconds
404
147
  });
405
-
406
- // Note: If both are defined, inline schemas take precedence over exports
407
148
  ```
408
149
 
409
- **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.
410
-
411
- ## 🤖 Working with KadiClient
412
-
413
- KadiClient is the unified API for all KADI operations. It replaces the previous separate KadiAbility and KadiAgent classes with a single, powerful interface that can operate in multiple roles.
414
-
415
- ### Direct Approach (using loadAbility)
416
-
417
- ```javascript
418
- import { loadAbility } from '@kadi.build/core';
419
-
420
- // Load and call abilities directly
421
- const echoAbility = await loadAbility('echo-js', 'stdio');
422
- const result = await echoAbility.echo({ message: 'Hello world' });
150
+ ---
423
151
 
424
- // Load broker tools directly
425
- const remoteAbility = await loadAbility('remote-ability', 'broker', {
426
- brokerUrl: 'ws://localhost:8080',
427
- networks: ['global']
428
- });
429
- const brokerResult = await remoteAbility.process({ data: 'test' });
430
- ```
152
+ ## Loading Abilities
431
153
 
432
- ### Using KadiClient with Named Brokers
154
+ Three ways to connect to other agents:
433
155
 
434
- ```javascript
435
- import { KadiClient } from '@kadi.build/core';
156
+ | Method | Use When | How It Works |
157
+ |--------|----------|--------------|
158
+ | `loadNative(name)` | Ability is a local TypeScript/JavaScript module | Runs in your process |
159
+ | `loadStdio(name)` | Ability runs as a separate process (any language) | JSON-RPC over stdin/stdout |
160
+ | `loadBroker(name)` | Ability is a remote service | WebSocket via broker |
436
161
 
437
- // Create a unified client with named brokers
438
- const client = new KadiClient({
439
- name: 'my-ability',
440
- role: 'ability', // Provider role
441
- protocol: 'broker',
442
- brokers: {
443
- dev: 'ws://localhost:8080',
444
- staging: 'ws://staging.example.com:8080',
445
- prod: 'ws://prod.example.com:8080'
446
- },
447
- defaultBroker: 'dev',
448
- networks: ['global']
449
- });
162
+ ### loadNative In-Process
450
163
 
451
- // Register tools for others to call
452
- client.registerTool(
453
- 'greet',
454
- async ({ name }) => {
455
- return { greeting: `Hello, ${name}!` };
456
- },
457
- {
458
- description: 'Greet someone by name',
459
- inputSchema: {
460
- type: 'object',
461
- properties: { name: { type: 'string' } },
462
- required: ['name']
463
- }
464
- }
465
- );
164
+ ```typescript
165
+ // Resolves path from agent-lock.json
166
+ const calc = await client.loadNative('calculator');
466
167
 
467
- // Connect to all configured brokers
468
- await client.connectToBrokers();
168
+ // Or specify path directly
169
+ const calc = await client.loadNative('calculator', { path: './abilities/calc' });
469
170
 
470
- // Call remote tools
471
- const result = await client.callTool('translator', 'translate', {
472
- text: 'Hello',
473
- to: 'es'
474
- });
171
+ // Use it
172
+ const result = await calc.invoke('add', { a: 5, b: 3 });
173
+ console.log(result); // { result: 8 }
475
174
 
476
- // Load abilities for compatibility
477
- const ability = await client.loadAbility('echo-js');
175
+ // Cleanup (no-op for native, but good practice)
176
+ await calc.disconnect();
478
177
  ```
479
178
 
480
- Use `loadAbility()` for simple consumption, or KadiClient for full-featured ability development with tool registration, event publishing, and remote tool invocation.
481
-
482
- ## 🔄 Architecture Deep Dive
483
-
484
- ### Transport Architecture and Loading Sequence
485
-
486
- The loading process uses modular transports based on the selected protocol:
487
-
488
- ```mermaid
489
- sequenceDiagram
490
- participant Client
491
- participant loadAbility
492
- participant Transport
493
- participant FileSystem
494
- participant ChildProcess
495
- participant Ability
496
- participant Broker
497
-
498
- Client->>loadAbility: loadAbility('echo-js', protocol?)
499
- loadAbility->>FileSystem: Read agent.json manifests
500
- FileSystem-->>loadAbility: Return config & version
501
-
502
- alt protocol === 'native'
503
- loadAbility->>Transport: new NativeTransport(config)
504
- Transport->>FileSystem: import(entry point)
505
- FileSystem-->>Transport: Module with KadiClient instance
506
- Transport->>Ability: Extract tool handlers via getToolNames()
507
- Ability-->>Transport: Return registered tools
508
- Transport->>Transport: Store handlers in Map
509
- else protocol === 'stdio'
510
- loadAbility->>Transport: new StdioTransport(config)
511
- Transport->>ChildProcess: spawn(startCmd, env: {KADI_PROTOCOL})
512
- ChildProcess->>Ability: KadiClient detects stdio mode
513
- Ability->>Ability: Setup FrameReader/Writer
514
- Transport->>Ability: Send discovery request
515
- Ability-->>Transport: Return tool list
516
- Transport->>Transport: Setup JSON-RPC proxy
517
- else protocol === 'broker'
518
- loadAbility->>Transport: new BrokerTransport(config)
519
- Transport->>Transport: Reuse existing KadiClient
520
- Transport->>Broker: WebSocket connect
521
- Transport->>Broker: kadi.session.hello
522
- Broker-->>Transport: { nonce, heartbeatIntervalSec }
523
- Transport->>Broker: kadi.session.authenticate
524
- Broker-->>Transport: { sessionId }
525
- Transport->>Broker: kadi.ability.list
526
- Broker-->>Transport: Available tools
527
- Transport->>Transport: Start heartbeat timer
528
- end
529
-
530
- loadAbility->>loadAbility: Create proxy with tool methods
531
- loadAbility-->>Client: Return ability proxy
532
-
533
- Note over Client,Ability: Tool invocation
534
- Client->>Transport: ability.echo({ message })
535
-
536
- alt native
537
- Transport->>Ability: Direct handler call
538
- else stdio
539
- Transport->>Ability: JSON-RPC over frames
540
- else broker
541
- Transport->>Broker: kadi.ability.invoke
542
- Broker->>Ability: Route to target
543
- Ability->>Broker: kadi.ability.result
544
- Broker-->>Transport: Forward result
545
- end
546
-
547
- Transport-->>Client: Return result
548
- ```
179
+ ### loadStdio Child Process
549
180
 
550
- ### KadiClient Architecture: Tool Registration & Serving
551
-
552
- How KadiClient registers tools and starts serving:
553
-
554
- ```mermaid
555
- sequenceDiagram
556
- participant Dev as Developer
557
- participant KC as KadiClient
558
- participant Transport
559
- participant Network as Network Layer
560
- participant RemoteClient
561
-
562
- Note over Dev,KC: Tool Registration Phase
563
- Dev->>KC: new KadiClient({ name, role, protocol })
564
- KC->>KC: Initialize based on role & protocol
565
-
566
- Dev->>KC: registerTool('echo', handler, schema?)
567
- KC->>KC: Store in toolHandlers Map<name, {handler, schema}>
568
-
569
- Dev->>KC: registerTool('process', handler, schema)
570
- KC->>KC: Add to toolHandlers Map
571
-
572
- Note over KC,Network: Connection & Serving Phase
573
- Dev->>KC: serve() or connectToBrokers()
574
-
575
- alt protocol === 'native'
576
- KC->>KC: Tools available via direct reference
577
- KC->>KC: Emit 'start' event
578
- else protocol === 'stdio'
579
- KC->>Transport: Setup StdioTransport
580
- Transport->>Transport: new FrameReader(stdin)
581
- Transport->>Transport: new FrameWriter(stdout)
582
- Transport->>KC: on('message', handleStdioMessage)
583
- KC->>KC: Process JSON-RPC requests
584
- else protocol === 'broker'
585
- KC->>Network: WebSocket.connect(brokerUrl)
586
- KC->>Network: kadi.session.hello({ role })
587
- Network-->>KC: { nonce, heartbeatIntervalSec: 30 }
588
- KC->>KC: Sign nonce with Ed25519
589
- KC->>Network: kadi.session.authenticate({ signature })
590
- Network-->>KC: { sessionId }
591
- KC->>Network: kadi.agent.register({ tools, networks })
592
- KC->>KC: Start heartbeat timer (30s interval)
593
- end
594
-
595
- Note over RemoteClient,KC: Tool Invocation Flow
596
- RemoteClient->>Network: Request tool invocation
597
-
598
- alt native
599
- RemoteClient->>KC: Direct method call
600
- KC->>KC: toolHandlers.get(name).handler(params)
601
- else stdio
602
- Network->>Transport: JSON-RPC frame
603
- Transport->>KC: Parsed request
604
- KC->>KC: toolHandlers.get(name).handler(params)
605
- KC->>Transport: JSON-RPC response
606
- Transport->>Network: Framed response
607
- else broker
608
- Network->>KC: kadi.ability.invoke message
609
- KC->>KC: toolHandlers.get(toolName).handler(params)
610
- KC->>Network: kadi.ability.result message
611
- end
612
-
613
- Network-->>RemoteClient: Tool result
614
- ```
181
+ By default, launches the ability using `scripts.start` from the ability's `agent.json`:
615
182
 
616
- ### Event System Architecture
617
-
618
- KadiClient provides a unified event API that works across all transport protocols:
619
-
620
- ```mermaid
621
- sequenceDiagram
622
- participant Publisher as Ability (Publisher)
623
- participant Transport as Transport Layer
624
- participant EventBus as Event Bus
625
- participant Subscriber as Client (Subscriber)
626
-
627
- Note over Subscriber: Setup subscription
628
- Subscriber->>Subscriber: subscribeToEvent('math.*', callback)
629
-
630
- alt protocol === 'broker'
631
- Subscriber->>EventBus: kadi.event.subscribe({channels: ['math.*']})
632
- EventBus-->>Subscriber: {subscribed: ['math.*'], queueName}
633
- else protocol === 'stdio'
634
- Subscriber->>Transport: Setup event listener
635
- Transport->>Subscriber: on('transport:event', dispatcher)
636
- else protocol === 'native'
637
- Subscriber->>Publisher: Direct EventEmitter reference
638
- Publisher->>Subscriber: on('ability:event', dispatcher)
639
- end
640
-
641
- Note over Publisher: Publish event
642
- Publisher->>Publisher: publishEvent('math.operation', data)
643
-
644
- alt protocol === 'native'
645
- Publisher->>Publisher: emit('ability:event', {eventName, data})
646
- Publisher-->>Subscriber: Direct EventEmitter delivery
647
- Subscriber->>Subscriber: Pattern match & invoke callback
648
- else protocol === 'stdio'
649
- Publisher->>Transport: writer.write(__kadi_event notification)
650
- Transport->>Transport: Frame with LSP headers
651
- Transport-->>Subscriber: Framed event over stdout
652
- Subscriber->>Transport: FrameReader parses
653
- Transport->>Subscriber: emit('transport:event', {name, data})
654
- Subscriber->>Subscriber: Pattern match & invoke callback
655
- else protocol === 'broker'
656
- Publisher->>EventBus: kadi.event.publish({channel, data})
657
- EventBus->>EventBus: Route via RabbitMQ
658
- EventBus-->>Subscriber: kadi.event.delivery
659
- Subscriber->>Subscriber: Pattern match & invoke callback
660
- end
661
-
662
- Note over Subscriber: Cleanup
663
- Subscriber->>Subscriber: unsubscribe()
664
- alt protocol === 'broker'
665
- Subscriber->>EventBus: kadi.event.unsubscribe
666
- else
667
- Subscriber->>Subscriber: Remove local listener
668
- end
669
- ```
183
+ ```typescript
184
+ // Runs scripts.start from ability's agent.json
185
+ const processor = await client.loadStdio('image-processor');
670
186
 
671
- ### Broker Protocol Communication Flow
672
-
673
- Detailed view of broker-based communication with heartbeat:
674
-
675
- ```mermaid
676
- sequenceDiagram
677
- participant Client as KadiClient
678
- participant Broker as KADI Broker
679
- participant Target as Target Ability
680
-
681
- Note over Client,Broker: Connection & Authentication
682
- Client->>Broker: WebSocket connect
683
- Client->>Broker: kadi.session.hello({ role })
684
- Broker-->>Client: { nonce, heartbeatIntervalSec: 30 }
685
- Client->>Client: Generate Ed25519 keypair
686
- Client->>Client: Sign nonce
687
- Client->>Broker: kadi.session.authenticate({ signature })
688
- Broker-->>Client: { sessionId }
689
-
690
- Note over Client,Broker: Heartbeat Management
691
- Client->>Client: Start heartbeat timer (30s)
692
- loop Every 30 seconds
693
- Client->>Broker: kadi.session.ping
694
- Note over Broker: Reset connection timeout (90s)
695
- end
696
-
697
- Note over Client,Broker: Tool Registration
698
- Client->>Broker: kadi.agent.register({ tools, networks })
699
- Broker->>Broker: Store in registry
700
- Broker-->>Client: { registered: true }
701
-
702
- Note over Client,Target: Remote Tool Invocation
703
- Client->>Client: callTool('ability-b', 'process', params)
704
- Client->>Broker: kadi.ability.invoke({
705
- targetAgent: 'ability-b',
706
- toolName: 'process',
707
- toolInput: params
708
- })
709
- Broker->>Broker: Lookup ability-b
710
- Broker->>Target: kadi.ability.invoke
711
- Target->>Target: Execute tool handler
712
- Target->>Broker: JSON-RPC response
713
- Broker-->>Client: Forward response
714
- Client->>Client: Resolve promise
715
-
716
- Note over Client,Broker: Event Publishing
717
- Client->>Broker: kadi.event.publish({
718
- channel: 'system.status',
719
- data: { status: 'healthy' }
720
- })
721
- Broker->>Broker: Publish to RabbitMQ exchange
722
-
723
- Note over Client,Broker: Graceful Shutdown
724
- Client->>Client: Stop heartbeat timer
725
- Client->>Broker: kadi.session.goodbye
726
- Client->>Client: Close WebSocket
727
- ```
187
+ // Use it
188
+ const result = await processor.invoke('resize', { width: 800 });
189
+ console.log(result);
728
190
 
729
- ### Stdio Protocol: LSP-Style Frame Processing
730
-
731
- How KadiClient handles stdio communication with LSP-style framing:
732
-
733
- ```mermaid
734
- sequenceDiagram
735
- participant Parent as Parent Process
736
- participant SR as StdioFrameReader
737
- participant SW as StdioFrameWriter
738
- participant Server as StdioRpcServer
739
- participant Ability as KadiClient
740
-
741
- Note over Parent,SR: Incoming Request
742
- Parent->>SR: Write to stdin: "Content-Length: 52\r\n\r\n{...}"
743
- SR->>SR: Buffer incoming data
744
- SR->>SR: Look for Content-Length header
745
- SR->>SR: Parse header, extract body length
746
- SR->>SR: Wait for complete body
747
- SR->>SR: Parse JSON from body
748
- SR->>Server: onMessage({ success: true, data: request })
749
-
750
- Server->>Server: Validate JSON-RPC format
751
- Server->>Ability: getMethodHandler(request.method)
752
- Ability-->>Server: Return handler
753
- Server->>Server: Execute handler(params) with timeout
754
-
755
- Note over Server,Parent: Response
756
- Server->>SW: write(response)
757
- SW->>SW: JSON.stringify(response)
758
- SW->>SW: Calculate byte length
759
- SW->>SW: Create header: "Content-Length: N\r\n\r\n"
760
- SW->>Parent: Write header + body to stdout
761
-
762
- Note over SR: Error Recovery
763
- alt Frame Corruption
764
- SR->>SR: Detect invalid frame
765
- SR->>SR: Search for next Content-Length marker
766
- SR->>SR: Skip corrupted bytes
767
- SR->>Server: onMessage({ success: false, error: 'FRAME_CORRUPTION' })
768
- Server->>Server: Log error, continue processing
769
- end
191
+ // Cleanup (terminates child process)
192
+ await processor.disconnect();
770
193
  ```
771
194
 
772
- ## 📥 Loading Abilities
773
-
774
- ### Basic Loading
775
-
776
- ```javascript
777
- import { loadAbility } from '@kadi.build/core';
195
+ You can specify a different script to launch:
778
196
 
779
- // Auto-detect protocol from agent.json
780
- const ability = await loadAbility('my-ability');
781
-
782
- // Explicitly specify protocol
783
- const stdioAbility = await loadAbility('my-ability', 'stdio');
784
- const brokerAbility = await loadAbility('my-ability', 'broker');
785
- const nativeAbility = await loadAbility('my-ability', 'native');
197
+ ```typescript
198
+ // Run scripts.dev instead of scripts.start
199
+ const processor = await client.loadStdio('image-processor', { script: 'dev' });
786
200
  ```
787
201
 
788
- ### Working with Loaded Abilities
789
-
790
- ```javascript
791
- // Call methods directly - no need to discover them first
792
- const result = await ability.echo({ message: 'hello' });
793
-
794
- // Call methods
795
- const result = await ability.someMethod({ param: 'value' });
796
-
797
- // Direct RPC call (bypasses method validation)
798
- const response = await ability.__call('someMethod', { param: 'value' });
202
+ Or bypass `agent.json` entirely with an explicit command:
799
203
 
800
- // Subscribe to events
801
- ability.events.on('custom:event', (data) => {
802
- console.log('Event received:', data);
204
+ ```typescript
205
+ // Explicit command (ignores agent.json)
206
+ const processor = await client.loadStdio('processor', {
207
+ command: 'python3',
208
+ args: ['main.py'],
803
209
  });
804
-
805
- // Note: Events work for all protocols (native, stdio, and broker)
806
- // Subscribe before calling methods that emit events
807
210
  ```
808
211
 
809
- ### KadiClient transport vs loadAbility transport
810
-
811
- KADI uses “transport” in two places and they serve different purposes:
812
-
813
- - KadiClient transport — how this client exposes its own tools after `registerTool(...)` and `serve()`.
814
- - `transport: 'native'` — in‑process only. There is no network or stdio listener. Code that loads you natively (via `loadAbility('you','native')`) can call your tools directly. `serve()` simply keeps the process alive if you want a long‑running ability.
815
- - `transport: 'stdio'` — the client runs a stdio server (JSON‑RPC over stdin/stdout) and answers requests with your registered handlers. `serve()` wires up the stdio loop.
816
- - `transport: 'broker'` — the client connects to the broker, registers your tools, and receives remote invocations over WebSocket. `serve()` (or `connectToBrokers()`) handles connection and registration.
817
-
818
- - loadAbility transport — how this client connects to a specific ability it wants to use.
819
- - `'native'` loads a module in‑process
820
- - `'stdio'` talks to a spawned child process (uses the ability’s `agent.json` `scripts.start`)
821
- - `'broker'` talks to a remote ability over the broker
822
-
823
- These two choices are independent: setting the client’s `transport` does not change how you load another ability, and the transport you choose in `loadAbility(...)` does not change how your client serves its own tools.
824
-
825
- Examples
826
-
827
- Provide tools over broker
212
+ ### loadBroker Remote via Broker
828
213
 
829
- ```ts
830
- import { KadiClient } from '@kadi.build/core';
214
+ ```typescript
215
+ // Must be connected to broker first
216
+ await client.connect();
831
217
 
832
- const svc = new KadiClient({
833
- name: 'math',
834
- role: 'ability',
835
- transport: 'broker',
836
- brokers: { local: 'ws://localhost:8080' },
837
- defaultBroker: 'local'
218
+ // Find and connect to a remote ability
219
+ const gpu = await client.loadBroker('gpu-service', {
220
+ networks: ['gpu-cluster'],
838
221
  });
839
222
 
840
- svc.registerTool('add', async ({ a, b }) => ({ result: a + b }));
841
- await svc.serve(); // registers with the broker so others can call math.add
223
+ const result = await gpu.invoke('inference', { model: 'llama3' });
224
+ await gpu.disconnect();
842
225
  ```
843
226
 
844
- Consume a stdio ability
845
-
846
- ```ts
847
- const agent = new KadiClient({
848
- name: 'client',
849
- role: 'agent',
850
- transport: 'native'
851
- });
852
- const math = await agent.loadAbility('simple-math', 'stdio');
853
- const res = await math.add({ a: 2, b: 3 });
854
- ```
855
-
856
- Consume a broker ability
857
-
858
- ```ts
859
- const agent = new KadiClient({
860
- name: 'client',
861
- role: 'agent',
862
- transport: 'broker',
863
- brokers: { local: 'ws://localhost:8080' },
864
- defaultBroker: 'local'
865
- });
227
+ ---
866
228
 
867
- const echo = await agent.loadAbility('echo-js', 'broker', {
868
- networks: ['global']
869
- });
870
- const r = await echo.say_message({ message: 'hi' });
871
- ```
229
+ ## Events from Abilities
872
230
 
873
- ### Loading Context
231
+ Abilities can emit real-time events. Subscribe with `on()`, unsubscribe with `off()`.
874
232
 
875
- The loader automatically resolves ability versions from:
233
+ ```typescript
234
+ const watcher = await client.loadStdio('file-watcher');
876
235
 
877
- 1. **Project context**: Reads from project's `agent.json`
878
- 2. **Nested context**: When loading from within another ability
879
- 3. **Explicit version**: Can be specified in ability name
236
+ // Define handlers (need references to unsubscribe later)
237
+ const onFileChanged = (data) => {
238
+ console.log('File changed:', data.path, data.action);
239
+ };
880
240
 
881
- ## ⚙️ Configuration
241
+ const onError = (data) => {
242
+ console.error('Watch error:', data.message);
243
+ };
882
244
 
883
- ### Environment Variables
245
+ // Subscribe to events
246
+ watcher.on('file.changed', onFileChanged);
247
+ watcher.on('file.error', onError);
884
248
 
885
- ```bash
886
- # Set default protocol
887
- export KADI_PROTOCOL=stdio
249
+ // Later, unsubscribe
250
+ watcher.off('file.changed', onFileChanged);
888
251
 
889
- # Configure broker
890
- export KADI_BROKER_URL=ws://localhost:8080
891
- export KADI_ABILITY_NAME=my-ability
892
- export KADI_AGENT_SCOPE=project-123
252
+ // Cleanup (terminates child process)
253
+ await watcher.disconnect();
893
254
  ```
894
255
 
895
- ### Environment Variables for Child Processes
896
-
897
- When abilities are spawned as child processes (stdio and broker protocols), the parent process passes down important environment variables:
256
+ ### Emitting Events (from inside an ability)
898
257
 
899
- ```javascript
900
- // In your ability code, you can access these variables:
901
- const protocol = process.env.KADI_PROTOCOL; // 'stdio' or 'broker'
902
- const brokerUrl = process.env.KADI_BROKER_URL;
903
- const abilityName = process.env.KADI_ABILITY_NAME;
904
- const agentScope = process.env.KADI_AGENT_SCOPE;
258
+ If you're building an ability that emits events:
905
259
 
906
- // Use them to configure your ability behavior
907
- const ability = new KadiClient({
908
- name: 'my-ability',
909
- network: process.env.KADI_AGENT_SCOPE || 'global'
910
- // Ability automatically uses KADI_PROTOCOL to determine transport
911
- });
260
+ ```typescript
261
+ // Inside your ability code
262
+ client.emit('job.progress', { percent: 50, message: 'Halfway done' });
263
+ client.emit('file.changed', { path: '/tmp/foo.txt', action: 'modified' });
912
264
  ```
913
265
 
914
- ### Example Project Structure
915
-
916
- ```
917
- my-project/
918
- ├── agent.json # Project configuration
919
- ├── abilities/ # Installed abilities
920
- │ └── echo-ability/
921
- │ └── 1.0.0/
922
- │ ├── agent.json # Ability configuration
923
- │ ├── ability.js # Ability implementation
924
- │ └── package.json
925
- ├── modules/ # Source modules
926
- │ └── echo-ability/ # Development version
927
- └── index.js # Main entry point
928
- ```
929
-
930
- ## 🚀 Advanced Usage
266
+ ---
931
267
 
932
- ### Event System
268
+ ## Broker Events (Pub/Sub)
933
269
 
934
- KadiClient provides a comprehensive event system that works across all protocols:
270
+ When connected to a broker, agents can publish and subscribe to events across the network.
935
271
 
936
- #### 1. Lifecycle Events
272
+ ### Subscribing
937
273
 
938
- Monitor the ability lifecycle:
274
+ ```typescript
275
+ await client.connect();
939
276
 
940
- ```javascript
941
- const client = new KadiClient({ name: 'my-ability' });
942
-
943
- // Connection events
944
- client.on('connected', ({ broker }) => {
945
- console.log(`Connected to broker: ${broker}`);
277
+ // Subscribe to patterns (RabbitMQ topic exchange style)
278
+ client.subscribe('user.*', (event) => {
279
+ console.log(`Channel: ${event.channel}`); // 'user.login'
280
+ console.log(`Data:`, event.data); // { userId: '123' }
281
+ console.log(`Network: ${event.networkId}`); // 'global'
282
+ console.log(`Source: ${event.source}`); // Publisher's session ID
946
283
  });
284
+ ```
947
285
 
948
- client.on('disconnected', () => {
949
- console.log('Disconnected from broker');
950
- });
286
+ **Pattern matching:**
287
+ - `user.*` → matches `user.login`, `user.logout` (exactly one word after dot)
288
+ - `user.#` → matches `user`, `user.login`, `user.profile.update` (zero or more words)
289
+ - `order.new` → matches exactly `order.new`
951
290
 
952
- // Tool invocation events
953
- client.on('tool:invoked', ({ toolName, params }) => {
954
- console.log(`Tool ${toolName} invoked with:`, params);
955
- });
291
+ ### Publishing
956
292
 
957
- client.on('tool:completed', ({ toolName, result }) => {
958
- console.log(`Tool ${toolName} completed:`, result);
959
- });
293
+ ```typescript
294
+ // Publish to default network
295
+ await client.publish('user.login', { userId: '123', timestamp: Date.now() });
960
296
 
961
- client.on('error', (error) => {
962
- console.error('Ability error:', error);
963
- });
297
+ // Publish to specific network
298
+ await client.publish('order.created', orderData, { network: 'internal' });
964
299
  ```
965
300
 
966
- #### 2. Custom Events (All Protocols)
301
+ ### Unsubscribing
967
302
 
968
- Publish and subscribe to custom events across all transport protocols:
303
+ ```typescript
304
+ const handler = (event) => console.log(event);
305
+ client.subscribe('user.*', handler);
969
306
 
970
- ```javascript
971
- // Publishing events
972
- const publisher = new KadiClient({
973
- name: 'event-publisher',
974
- role: 'ability',
975
- protocol: 'broker' // Works with all protocols
976
- });
977
-
978
- publisher.registerTool('process', async ({ data }) => {
979
- // Publish events during processing
980
- await publisher.publishEvent('process.started', {
981
- timestamp: Date.now(),
982
- data
983
- });
307
+ // Later, remove this specific handler
308
+ client.unsubscribe('user.*', handler);
309
+ ```
984
310
 
985
- // Do work...
986
- const result = await processData(data);
311
+ ---
987
312
 
988
- await publisher.publishEvent('process.completed', {
989
- timestamp: Date.now(),
990
- result
991
- });
313
+ ## Serving as an Ability
992
314
 
993
- return result;
994
- });
315
+ To make your agent callable by others:
995
316
 
996
- // Subscribing to events
997
- const subscriber = new KadiClient({
998
- name: 'event-subscriber',
999
- role: 'agent',
1000
- protocol: 'broker'
1001
- });
317
+ ### Stdio Mode (for loadStdio)
1002
318
 
1003
- // Subscribe with wildcards
1004
- subscriber.subscribeToEvent('process.*', (data) => {
1005
- console.log('Process event:', data);
1006
- });
319
+ ```typescript
320
+ const client = new KadiClient({ name: 'my-ability' });
1007
321
 
1008
- // One-time subscription
1009
- subscriber.onceEvent('process.completed', (data) => {
1010
- console.log('First completion:', data);
322
+ client.registerTool({
323
+ name: 'process',
324
+ description: 'Process data',
325
+ input: z.object({ data: z.string() }),
326
+ }, async ({ data }) => {
327
+ return { processed: data.toUpperCase() };
1011
328
  });
1012
329
 
1013
- // Multiple pattern subscription
1014
- subscriber.subscribeToEvents(['process.*', 'system.*'], (pattern, data) => {
1015
- console.log(`Event from ${pattern}:`, data);
1016
- });
330
+ // Serve over stdio - blocks until SIGTERM/SIGINT
331
+ await client.serve('stdio');
1017
332
  ```
1018
333
 
1019
- **Protocol-Specific Behavior:**
1020
-
1021
- - **Native**: Direct EventEmitter, synchronous delivery
1022
- - **Stdio**: JSON-RPC notifications with LSP framing
1023
- - **Broker**: RabbitMQ pub/sub via WebSocket, persistent queues available
1024
-
1025
- ### Multi-Broker Configuration
334
+ ### Broker Mode (for loadBroker)
1026
335
 
1027
- KadiClient supports connecting to multiple brokers simultaneously for redundancy and load distribution:
1028
-
1029
- ```javascript
336
+ ```typescript
1030
337
  const client = new KadiClient({
1031
- name: 'multi-broker-ability',
1032
- role: 'ability',
1033
- protocol: 'broker',
1034
- brokers: {
1035
- primary: 'ws://broker1.example.com:8080',
1036
- secondary: 'ws://broker2.example.com:8080',
1037
- backup: 'ws://broker3.example.com:8080'
1038
- },
1039
- defaultBroker: 'primary', // Used for sending if no specific broker targeted
1040
- networks: ['production']
338
+ name: 'my-service',
339
+ brokers: { default: 'ws://localhost:8080/kadi' },
1041
340
  });
1042
341
 
1043
- // Connect to all configured brokers
1044
- await client.connectToBrokers();
342
+ client.registerTool({ /* ... */ }, handler);
1045
343
 
1046
- // The client will now:
1047
- // 1. Maintain connections to all three brokers
1048
- // 2. Receive messages from any broker
1049
- // 3. Send messages to the default broker unless specified
1050
- // 4. Automatically handle broker failover
344
+ // Connect to broker and wait for requests
345
+ await client.serve('broker');
1051
346
  ```
1052
347
 
1053
- ### Cross-Language Ability Support
348
+ ---
1054
349
 
1055
- KADI now fully supports abilities written in any language through the stdio protocol:
350
+ ## Error Handling
1056
351
 
1057
- ```javascript
1058
- // Go ability with agent.json:
1059
- {
1060
- "name": "hash-go",
1061
- "scripts": {
1062
- "setup": "go build -o bin/hash_ability",
1063
- "start": "./bin/hash_ability" // Binary executable
1064
- }
1065
- }
352
+ All errors are `KadiError` with structured codes and context:
1066
353
 
1067
- // Python ability:
1068
- {
1069
- "name": "ml-processor",
1070
- "scripts": {
1071
- "start": "python3 main.py"
1072
- }
1073
- }
354
+ ```typescript
355
+ import { KadiError } from '@kadi.build/core';
1074
356
 
1075
- // Rust ability:
1076
- {
1077
- "name": "crypto-rust",
1078
- "scripts": {
1079
- "setup": "cargo build --release",
1080
- "start": "./target/release/crypto_ability"
357
+ try {
358
+ await client.invokeRemote('unknown-tool', {});
359
+ } catch (error) {
360
+ if (KadiError.isKadiError(error)) {
361
+ console.log(error.code); // 'TOOL_NOT_FOUND'
362
+ console.log(error.message); // Human-readable message
363
+ console.log(error.context); // { toolName: '...', hint: '...' }
1081
364
  }
1082
365
  }
1083
-
1084
- // Load and use regardless of implementation language:
1085
- const hashAbility = await loadAbility('hash-go', 'stdio');
1086
- const result = await hashAbility.sha256({ data: 'hello' });
1087
366
  ```
1088
367
 
1089
- ## 🔧 Development Workflow
1090
-
1091
- ### Testing Local Changes
368
+ ### Error Codes
1092
369
 
1093
- When developing new features or testing changes to `@kadi.build/core` before publishing to NPM:
370
+ | Code | When It Happens |
371
+ |------|-----------------|
372
+ | `TOOL_NOT_FOUND` | `invoke()` or `invokeRemote()` with unknown tool name |
373
+ | `BROKER_NOT_CONNECTED` | Any broker operation before calling `connect()` |
374
+ | `ABILITY_NOT_FOUND` | `loadNative/loadStdio` when ability not in agent-lock.json |
375
+ | `ABILITY_LOAD_FAILED` | Ability exists but failed to start or initialize |
376
+ | `TOOL_INVOCATION_FAILED` | Tool threw an error during execution |
377
+ | `BROKER_TIMEOUT` | Request to broker timed out |
378
+ | `INVALID_CONFIG` | Configuration error (missing required fields, etc.) |
379
+ | `LOCKFILE_NOT_FOUND` | `loadNative/loadStdio` when agent-lock.json doesn't exist |
1094
380
 
1095
- 0. **Clone the repository**
1096
-
1097
- ```bash
1098
- git clone https://gitlab.com/humin-game-lab/kadi/kadi-core.git
1099
- ```
1100
-
1101
- 1. **Pack the local version** (in the kadi-core directory):
1102
-
1103
- ```bash
1104
- cd /path/to/kadi-core
1105
- npm pack
1106
- # This creates: kadi.build-core-X.Y.Z.tgz
1107
- ```
1108
-
1109
- 2. **Install in your project**:
1110
-
1111
- ```bash
1112
- cd /path/to/your-project
1113
- npm install /path/to/kadi-core/kadi.build-core-X.Y.Z.tgz
1114
- ```
1115
-
1116
- 3. **For ability development**, update the ability's preflight script:
381
+ ---
1117
382
 
1118
- ```json
1119
- {
1120
- "scripts": {
1121
- "preflight": "npm uninstall @kadi.build/core && npm install /path/to/kadi.build-core-X.Y.Z.tgz"
1122
- }
383
+ ## API Reference
384
+
385
+ ### KadiClient
386
+
387
+ ```typescript
388
+ new KadiClient(config: ClientConfig)
389
+ ```
390
+
391
+ **Configuration:**
392
+
393
+ | Option | Type | Default | Description |
394
+ |--------|------|---------|-------------|
395
+ | `name` | `string` | required | Agent name |
396
+ | `version` | `string` | `'1.0.0'` | Agent version |
397
+ | `brokers` | `Record<string, string>` | `{}` | Broker name → URL map |
398
+ | `defaultBroker` | `string` | first broker | Default broker to use |
399
+ | `networks` | `string[]` | `['global']` | Networks to join |
400
+ | `autoReconnect` | `boolean` | `true` | Auto-reconnect on disconnect |
401
+ | `maxReconnectDelay` | `number` | `30000` | Max backoff delay (ms) |
402
+ | `requestTimeout` | `number` | `30000` | Default request timeout (ms) |
403
+
404
+ **Methods:**
405
+
406
+ | Method | Description |
407
+ |--------|-------------|
408
+ | `registerTool(def, handler, options?)` | Register a tool. See [Registering Tools](#registering-tools) |
409
+ | `connect(broker?)` | Connect to broker |
410
+ | `disconnect(broker?)` | Disconnect from broker |
411
+ | `invokeRemote(tool, params, options?)` | Invoke tool via broker (broker picks provider) |
412
+ | `loadNative(name, options?)` | Load ability in-process. See [Loading Abilities](#loading-abilities) |
413
+ | `loadStdio(name, options?)` | Load ability as child process |
414
+ | `loadBroker(name, options?)` | Load ability via broker |
415
+ | `subscribe(pattern, handler, options?)` | Subscribe to broker events. See [Broker Events](#broker-events-pubsub) |
416
+ | `unsubscribe(pattern, handler, options?)` | Unsubscribe from events |
417
+ | `publish(channel, data, options?)` | Publish event to broker |
418
+ | `emit(event, data)` | Emit event to consumer (when serving as ability) |
419
+ | `serve(mode)` | Serve as ability (`'stdio'` or `'broker'`) |
420
+ | `getConnectedBrokers()` | List connected broker names |
421
+
422
+ ### LoadedAbility
423
+
424
+ Returned by `loadNative()`, `loadStdio()`, `loadBroker()`:
425
+
426
+ | Property/Method | Description |
427
+ |-----------------|-------------|
428
+ | `name` | Ability name |
429
+ | `transport` | `'native'` \| `'stdio'` \| `'broker'` |
430
+ | `invoke(tool, params)` | Invoke a tool |
431
+ | `getTools()` | Get array of tool definitions |
432
+ | `on(event, handler)` | Subscribe to events |
433
+ | `off(event, handler)` | Unsubscribe from events |
434
+ | `disconnect()` | Cleanup resources |
435
+
436
+ ### BrokerEvent
437
+
438
+ Received when subscribed to broker events:
439
+
440
+ ```typescript
441
+ interface BrokerEvent {
442
+ channel: string; // 'user.login'
443
+ data: unknown; // Event payload
444
+ networkId: string; // Which network
445
+ source: string; // Publisher's session ID
446
+ timestamp: number; // Unix timestamp (ms)
1123
447
  }
1124
448
  ```
1125
449
 
1126
- ## 📖 API Reference
1127
-
1128
- ### Core Classes
1129
-
1130
- #### `KadiClient`
1131
-
1132
- The unified class for all KADI operations - serving tools, calling remote abilities, and managing events.
1133
-
1134
- ```javascript
1135
- import { KadiClient } from '@kadi.build/core';
1136
-
1137
- const client = new KadiClient({
1138
- name: 'my-ability',
1139
- role: 'ability', // 'agent' or 'ability'
1140
- protocol: 'broker', // 'native', 'stdio', or 'broker'
1141
- brokers: {
1142
- dev: 'ws://localhost:8080',
1143
- prod: 'ws://prod.example.com:8080'
1144
- },
1145
- defaultBroker: 'dev',
1146
- network: 'global', // Primary network
1147
- networks: ['global', 'custom-network'] // All networks
1148
- });
1149
- ```
1150
-
1151
- **Configuration Options:**
1152
-
1153
- - `name` - Ability/agent name
1154
- - `role` - Operating role: 'agent' (consumer) or 'ability' (provider)
1155
- - `protocol` - Transport protocol to use
1156
- - `brokers` - Named broker configurations (object mapping names to URLs)
1157
- - `defaultBroker` - Default broker name for sending messages
1158
- - `network` - Primary network segment for message routing
1159
- - `networks` - All network namespaces for tool discovery
1160
- - `heartbeatIntervalSec` - Custom heartbeat interval (default: from broker)
1161
-
1162
- **Tool Registration Methods:**
1163
-
1164
- - `registerTool(name, handler, schema?)` - Register a single tool
1165
- - `getTools()` - Get list of registered tool names
1166
- - `getToolNames()` - Get filtered list of tool names
1167
- - `getToolHandler(name)` - Get a specific tool's handler
1168
- - `getToolSchema(name)` - Get a specific tool's schema
1169
- - `hasTool(name)` - Check if a tool is registered
1170
-
1171
- **Remote Tool Invocation:**
1172
-
1173
- - `callTool(targetAgent, toolName, params)` - Call a remote tool via broker
1174
- - `discoverRemoteTools(targetAgent)` - List tools from a remote agent
1175
- - `loadAbility(name, protocol?, options?)` - Load an ability (compatibility)
450
+ ---
1176
451
 
1177
- **Event System:**
452
+ ## Troubleshooting
1178
453
 
1179
- - `publishEvent(eventName, data)` - Publish an event
1180
- - `subscribeToEvent(pattern, callback)` - Subscribe with wildcards
1181
- - `subscribeToEvents(patterns[], callback)` - Multiple subscriptions
1182
- - `unsubscribeFromEvent(pattern, callback)` - Remove subscription
1183
- - `onceEvent(pattern, callback)` - One-time subscription
454
+ ### "Cannot find module" with loadNative/loadStdio
1184
455
 
1185
- **Connection Management:**
456
+ The ability isn't in your `agent-lock.json`. Either:
457
+ - Run `kadi install` to install abilities and generate the lock file
458
+ - Use the `path` option: `loadNative('calc', { path: './path/to/ability' })`
459
+ - Use explicit command: `loadStdio('calc', { command: 'node', args: ['calc.js'] })`
1186
460
 
1187
- - `serve()` - Start serving (stdio/native modes)
1188
- - `connectToBrokers()` - Connect to all configured brokers
1189
- - `disconnect()` - Clean disconnect
1190
- - `isConnected` - Check connection status
461
+ ### Connection timeout to broker
1191
462
 
1192
- **Properties:**
463
+ 1. Check the broker is running
464
+ 2. Verify the URL uses `ws://` or `wss://`, not `http://`
465
+ 3. Check firewall/network allows WebSocket connections
1193
466
 
1194
- - `agentId` - Unique agent identifier
1195
- - `name` - Ability name
1196
- - `role` - Current operating role
1197
- - `protocol` - Active protocol
1198
- - `brokers` - Configured broker map
1199
- - `defaultBroker` - Default broker name
1200
- - `currentBroker` - Currently active broker for sending
467
+ ### Events not being received
1201
468
 
1202
- **Complete Usage Example:**
469
+ 1. Verify both publisher and subscriber are on the same network (check `networks` config)
470
+ 2. Check your pattern matches the channel (remember: `*` = one word, `#` = zero or more)
471
+ 3. Ensure you're connected before subscribing: `await client.connect()`
1203
472
 
1204
- ```javascript
1205
- import { KadiClient } from '@kadi.build/core';
473
+ ### Tool invocation times out
1206
474
 
1207
- // Create an ability that can serve tools
1208
- const ability = new KadiClient({
1209
- name: 'math-ability',
1210
- role: 'ability',
1211
- protocol: 'broker',
1212
- brokers: {
1213
- local: 'ws://localhost:8080',
1214
- cloud: 'wss://api.example.com'
1215
- },
1216
- defaultBroker: 'local',
1217
- networks: ['global']
1218
- });
1219
-
1220
- // Register tools with schemas
1221
- ability.registerTool(
1222
- 'add',
1223
- async ({ a, b }) => {
1224
- // Publish event before processing
1225
- await ability.publishEvent('math.operation', {
1226
- operation: 'add',
1227
- inputs: { a, b }
1228
- });
1229
-
1230
- const result = a + b;
1231
-
1232
- // Publish completion event
1233
- await ability.publishEvent('math.completed', {
1234
- operation: 'add',
1235
- result
1236
- });
1237
-
1238
- return { result };
1239
- },
1240
- {
1241
- description: 'Add two numbers',
1242
- inputSchema: {
1243
- type: 'object',
1244
- properties: {
1245
- a: { type: 'number', description: 'First number' },
1246
- b: { type: 'number', description: 'Second number' }
1247
- },
1248
- required: ['a', 'b']
1249
- },
1250
- outputSchema: {
1251
- type: 'object',
1252
- properties: {
1253
- result: { type: 'number', description: 'Sum of a and b' }
1254
- }
1255
- }
1256
- }
1257
- );
1258
-
1259
- // Subscribe to events from other abilities
1260
- ability.subscribeToEvent('system.*', (data) => {
1261
- console.log('System event:', data);
1262
- });
475
+ Increase the timeout:
476
+ ```typescript
477
+ await client.invokeRemote('slow-tool', params, { timeout: 60000 });
478
+ ```
1263
479
 
1264
- // Connect to brokers
1265
- await ability.connectToBrokers();
480
+ ---
1266
481
 
1267
- // Call remote tools
1268
- const result = await ability.callTool('translator-ability', 'translate', {
1269
- text: 'Hello',
1270
- to: 'es'
1271
- });
482
+ ## Advanced: Building CLI Tools
1272
483
 
1273
- // Graceful shutdown
1274
- process.on('SIGTERM', async () => {
1275
- await ability.disconnect();
1276
- process.exit(0);
1277
- });
1278
- ```
484
+ These utilities are exported for building tooling on top of kadi-core.
1279
485
 
1280
- #### `loadAbility`
486
+ ### Zod to JSON Schema
1281
487
 
1282
- Load an ability by name and protocol.
488
+ Convert Zod schemas to JSON Schema (useful for generating documentation or OpenAPI specs):
1283
489
 
1284
- ```javascript
1285
- import { loadAbility } from '@kadi.build/core';
490
+ ```typescript
491
+ import { zodToJsonSchema, z } from '@kadi.build/core';
1286
492
 
1287
- const ability = await loadAbility('ability-name', 'protocol', {
1288
- brokerUrl: 'ws://localhost:8080', // For broker protocol
1289
- brokerName: 'dev', // Named broker to use
1290
- networks: ['global'], // Network segments
1291
- existingClient: client // Reuse existing KadiClient for broker
1292
- });
493
+ const schema = z.object({ name: z.string(), age: z.number() });
494
+ const jsonSchema = zodToJsonSchema(schema);
495
+ // { type: 'object', properties: { name: { type: 'string' }, ... } }
1293
496
  ```
1294
497
 
1295
- **Returns:** A proxy object with:
1296
-
1297
- - Direct method calls (e.g., `ability.echo({ message: 'hello' })`)
1298
- - `ability.events` - EventEmitter for subscribing to events (all protocols)
1299
- - `ability.__call(method, params)` - Direct RPC call
1300
- - `ability.__disconnect()` - Clean up resources
498
+ ### Lock File Resolution
1301
499
 
1302
- ### Utility Functions
500
+ Utilities for working with `agent-lock.json` (the file that tracks installed abilities):
1303
501
 
1304
- ```javascript
502
+ ```typescript
1305
503
  import {
1306
- createLogger,
1307
- getProjectJSON,
1308
- getAbilityJSON,
1309
- getAgentJSON,
1310
- getBrokerUrl,
1311
- runExecCommand
504
+ findProjectRoot,
505
+ readLockFile,
506
+ resolveAbilityPath,
507
+ getInstalledAbilityNames,
1312
508
  } from '@kadi.build/core';
1313
- ```
1314
-
1315
- ### Debug Mode
1316
-
1317
- Enable detailed logging:
1318
-
1319
- ```bash
1320
- DEBUG=kadi:* node index.js
1321
- ```
1322
-
1323
- Check ability logs:
1324
-
1325
- ```bash
1326
- tail -f abilities/my-ability/1.0.0/my-ability.log
1327
- ```
1328
-
1329
- ## 💡 Additional Examples
1330
-
1331
- ### Error Handling Pattern
1332
-
1333
- ```javascript
1334
- const client = new KadiClient({
1335
- name: 'robust-ability',
1336
- role: 'ability'
1337
- });
1338
-
1339
- client.registerTool('riskyOperation', async (params) => {
1340
- try {
1341
- const result = await performOperation(params);
1342
- await client.publishEvent('operation.success', { result });
1343
- return { success: true, result };
1344
- } catch (error) {
1345
- await client.publishEvent('operation.error', {
1346
- error: error.message,
1347
- params
1348
- });
1349
- // Return error in structured format
1350
- return {
1351
- success: false,
1352
- error: error.message
1353
- };
1354
- }
1355
- });
1356
- ```
1357
-
1358
- ### Health Check Pattern
1359
-
1360
- ```javascript
1361
- // Register a standard health check tool
1362
- client.registerTool('health', async () => {
1363
- const checks = {
1364
- memory: process.memoryUsage(),
1365
- uptime: process.uptime(),
1366
- brokers: client.isConnected ? 'connected' : 'disconnected',
1367
- timestamp: Date.now()
1368
- };
1369
-
1370
- return {
1371
- status: 'healthy',
1372
- checks
1373
- };
1374
- });
1375
- ```
1376
-
1377
- ## 🐛 Troubleshooting
1378
-
1379
- ### Common Issues and Solutions
1380
-
1381
- #### Broker Connection Failures
1382
-
1383
- **Problem**: "Failed to connect to any brokers"
1384
-
1385
- **Solutions**:
1386
-
1387
- - Verify broker URLs are correct
1388
- - Check network connectivity
1389
- - Ensure broker is running
1390
- - Try connecting with lower security (ws:// before wss://)
1391
-
1392
- #### Events Not Arriving
1393
-
1394
- **Problem**: Subscribed to events but callbacks not triggered
1395
-
1396
- **Solutions**:
1397
-
1398
- - Subscribe BEFORE triggering events
1399
- - Check pattern matching (use exact match first)
1400
- - Verify publisher and subscriber are on same network
1401
- - Enable debug logging to trace event flow
1402
-
1403
- #### Cross-Language Abilities Not Loading
1404
-
1405
- **Problem**: "Cannot find module 'ability.js'" when loading Go/Python abilities
1406
-
1407
- **Solutions**:
1408
-
1409
- - Ensure `scripts.start` field exists in agent.json
1410
- - Verify the binary/script is executable
1411
- - Check that setup script has been run
1412
- - Use stdio protocol, not native
1413
509
 
1414
- #### Memory Leaks
510
+ // Find the nearest directory containing agent-lock.json
511
+ const root = findProjectRoot();
1415
512
 
1416
- **Problem**: Memory usage grows over time
513
+ // Read and parse the lock file
514
+ const lock = readLockFile(root);
1417
515
 
1418
- **Solutions**:
516
+ // List installed ability names
517
+ const abilities = getInstalledAbilityNames(lock);
518
+ // ['calculator', 'image-processor', ...]
1419
519
 
1420
- - Always call unsubscribe functions
1421
- - Use `onceEvent` for single-use subscriptions
1422
- - Disconnect abilities when done
1423
- - Clear event listeners on cleanup
1424
-
1425
- ## 🤝 Contributing
1426
-
1427
- We welcome contributions! Please see our [Contributing Guide](CONTRIBUTING.md) for details.
1428
-
1429
- 1. Fork the repository
1430
- 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
1431
- 3. Commit your changes (`git commit -m 'Add amazing feature'`)
1432
- 4. Push to the branch (`git push origin feature/amazing-feature`)
1433
- 5. Open a Pull Request
1434
-
1435
- ## 📄 License
1436
-
1437
- This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
1438
-
1439
- ## 🔗 Related Projects
1440
-
1441
- - [@kadi.build/cli](https://gitlab.com/humin-game-lab/kadi/kadi) - Command-line interface
1442
- - [@kadi.build/broker](https://gitlab.com/humin-game-lab/kadi/kadi-broker) - The KADI broker
1443
-
1444
- ## 📚 Resources
1445
-
1446
- ## ⚠️ Error Codes
1447
-
1448
- KADI core exposes a unified, typed error catalog to keep errors consistent across core and the broker.
1449
-
1450
- - CoreErrorCodes: CORE\_\* errors for library/transport concerns
1451
- - BrokerErrorCodes: BROKER\_\* errors mirrored for broker interactions
1452
- - ErrorCodes: merged view of both
1453
-
1454
- Usage:
1455
-
1456
- ```ts
1457
- import { KadiError, CoreErrorCodes } from '@kadi.build/core';
1458
-
1459
- throw new KadiError(
1460
- CoreErrorCodes.CORE_CONFIG_MISSING.code,
1461
- 'scripts.start is required for stdio',
1462
- { transport: 'stdio', abilityName: 'echo' }
1463
- );
520
+ // Get the filesystem path to an ability
521
+ const calcPath = resolveAbilityPath('calculator', root);
522
+ // '/path/to/project/abilities/calculator@1.0.0'
1464
523
  ```
1465
524
 
1466
- Guidelines:
525
+ ---
1467
526
 
1468
- - Do not use raw string codes like 'CONFIG_MISSING'; always use the exported constants.
1469
- - Prefer KadiError helper factories where available (e.g., KadiError.abilityNotFound()).
1470
- - KadiError automatically tags domain from the code prefix (core/broker) and preserves the original stack.
1471
- - Broker-side codes are fully prefixed: BROKER*SESSION*_, BROKER*AUTH*_, BROKER*AGENT*_, BROKER*NETWORK*_, etc.
527
+ ## Related
1472
528
 
1473
- - [Event System Deep Dive](docs/event-system.md) - Complete guide to the event architecture
1474
- - [API Documentation](https://docs.kadi.build) - Full API reference
1475
- - [Examples Repository](https://github.com/kadi-examples) - More examples and patterns
529
+ - [kadi-core-py](https://gitlab.com/humin-game-lab/kadi/kadi-core-py) Python SDK (mirrors this API)
530
+ - [kadi-broker](https://gitlab.com/humin-game-lab/kadi/kadi-broker) The broker server
1476
531
 
1477
532
  ---
1478
533
 
1479
- Built with ❤️ by the KADI team
534
+ ## License
535
+
536
+ MIT