agents-library 0.1.0 → 0.1.1

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 (110) hide show
  1. package/README.md +178 -0
  2. package/dist/base-agent.d.ts +10 -8
  3. package/dist/base-agent.d.ts.map +1 -1
  4. package/dist/base-agent.js +30 -26
  5. package/dist/base-agent.js.map +1 -1
  6. package/dist/base-bot.d.ts +0 -0
  7. package/dist/base-bot.d.ts.map +0 -0
  8. package/dist/base-bot.js +0 -0
  9. package/dist/base-bot.js.map +0 -0
  10. package/dist/common/result.d.ts +0 -0
  11. package/dist/common/result.d.ts.map +0 -0
  12. package/dist/common/result.js +0 -0
  13. package/dist/common/result.js.map +0 -0
  14. package/dist/common/types.d.ts +0 -0
  15. package/dist/common/types.d.ts.map +0 -0
  16. package/dist/common/types.js +0 -0
  17. package/dist/common/types.js.map +0 -0
  18. package/dist/index.d.ts +12 -6
  19. package/dist/index.d.ts.map +1 -1
  20. package/dist/index.js +7 -3
  21. package/dist/index.js.map +1 -1
  22. package/dist/kadi-event-publisher.d.ts +0 -0
  23. package/dist/kadi-event-publisher.d.ts.map +0 -0
  24. package/dist/kadi-event-publisher.js +0 -0
  25. package/dist/kadi-event-publisher.js.map +0 -0
  26. package/dist/memory/arcadedb-adapter.d.ts +8 -0
  27. package/dist/memory/arcadedb-adapter.d.ts.map +1 -1
  28. package/dist/memory/arcadedb-adapter.js +8 -0
  29. package/dist/memory/arcadedb-adapter.js.map +1 -1
  30. package/dist/memory/entity-extractor.d.ts +110 -0
  31. package/dist/memory/entity-extractor.d.ts.map +1 -0
  32. package/dist/memory/entity-extractor.js +259 -0
  33. package/dist/memory/entity-extractor.js.map +1 -0
  34. package/dist/memory/file-storage-adapter.d.ts +0 -0
  35. package/dist/memory/file-storage-adapter.d.ts.map +0 -0
  36. package/dist/memory/file-storage-adapter.js +0 -0
  37. package/dist/memory/file-storage-adapter.js.map +0 -0
  38. package/dist/memory/memory-service.d.ts +123 -13
  39. package/dist/memory/memory-service.d.ts.map +1 -1
  40. package/dist/memory/memory-service.js +428 -72
  41. package/dist/memory/memory-service.js.map +1 -1
  42. package/dist/memory/types.d.ts +0 -0
  43. package/dist/memory/types.d.ts.map +0 -0
  44. package/dist/memory/types.js +0 -0
  45. package/dist/memory/types.js.map +0 -0
  46. package/dist/producer-tool-utils.d.ts +0 -0
  47. package/dist/producer-tool-utils.d.ts.map +0 -0
  48. package/dist/producer-tool-utils.js +0 -0
  49. package/dist/producer-tool-utils.js.map +0 -0
  50. package/dist/providers/anthropic-provider.d.ts +0 -0
  51. package/dist/providers/anthropic-provider.d.ts.map +0 -0
  52. package/dist/providers/anthropic-provider.js +0 -0
  53. package/dist/providers/anthropic-provider.js.map +0 -0
  54. package/dist/providers/model-manager-provider.d.ts +0 -0
  55. package/dist/providers/model-manager-provider.d.ts.map +0 -0
  56. package/dist/providers/model-manager-provider.js +0 -0
  57. package/dist/providers/model-manager-provider.js.map +0 -0
  58. package/dist/providers/provider-manager.d.ts +0 -0
  59. package/dist/providers/provider-manager.d.ts.map +1 -1
  60. package/dist/providers/provider-manager.js +6 -2
  61. package/dist/providers/provider-manager.js.map +1 -1
  62. package/dist/providers/types.d.ts +0 -0
  63. package/dist/providers/types.d.ts.map +0 -0
  64. package/dist/providers/types.js +0 -0
  65. package/dist/providers/types.js.map +0 -0
  66. package/dist/shadow-agent-factory.d.ts +12 -96
  67. package/dist/shadow-agent-factory.d.ts.map +1 -1
  68. package/dist/shadow-agent-factory.js +73 -280
  69. package/dist/shadow-agent-factory.js.map +1 -1
  70. package/dist/types/agent-config.d.ts +62 -1
  71. package/dist/types/agent-config.d.ts.map +1 -1
  72. package/dist/types/agent-config.js +0 -0
  73. package/dist/types/agent-config.js.map +0 -0
  74. package/dist/types/event-schemas.d.ts +194 -0
  75. package/dist/types/event-schemas.d.ts.map +1 -1
  76. package/dist/types/event-schemas.js +77 -2
  77. package/dist/types/event-schemas.js.map +1 -1
  78. package/dist/types/tool-schemas.d.ts +0 -0
  79. package/dist/types/tool-schemas.d.ts.map +0 -0
  80. package/dist/types/tool-schemas.js +0 -0
  81. package/dist/types/tool-schemas.js.map +0 -0
  82. package/dist/utils/config.d.ts +48 -0
  83. package/dist/utils/config.d.ts.map +1 -0
  84. package/dist/utils/config.js +163 -0
  85. package/dist/utils/config.js.map +1 -0
  86. package/dist/utils/logger.d.ts +11 -1
  87. package/dist/utils/logger.d.ts.map +1 -1
  88. package/dist/utils/logger.js +26 -1
  89. package/dist/utils/logger.js.map +1 -1
  90. package/dist/utils/path-utils.d.ts +22 -0
  91. package/dist/utils/path-utils.d.ts.map +1 -0
  92. package/dist/utils/path-utils.js +51 -0
  93. package/dist/utils/path-utils.js.map +1 -0
  94. package/dist/utils/read-config.d.ts +43 -0
  95. package/dist/utils/read-config.d.ts.map +1 -0
  96. package/dist/utils/read-config.js +97 -0
  97. package/dist/utils/read-config.js.map +1 -0
  98. package/dist/utils/timer.d.ts +0 -0
  99. package/dist/utils/timer.d.ts.map +0 -0
  100. package/dist/utils/timer.js +0 -0
  101. package/dist/utils/timer.js.map +0 -0
  102. package/dist/utils/vault.d.ts +29 -0
  103. package/dist/utils/vault.d.ts.map +1 -0
  104. package/dist/utils/vault.js +79 -0
  105. package/dist/utils/vault.js.map +1 -0
  106. package/dist/worker-agent-factory.d.ts +37 -38
  107. package/dist/worker-agent-factory.d.ts.map +1 -1
  108. package/dist/worker-agent-factory.js +314 -171
  109. package/dist/worker-agent-factory.js.map +1 -1
  110. package/package.json +5 -3
@@ -24,11 +24,13 @@
24
24
  *
25
25
  * @module worker-agent-factory
26
26
  */
27
- import { KadiClient, z } from '@kadi.build/core';
27
+ import { z } from '@kadi.build/core';
28
28
  import { BaseBot } from './base-bot.js';
29
- import { TaskAssignedEventSchema } from './types/event-schemas.js';
29
+ import { BaseAgent } from './base-agent.js';
30
+ import { TaskAssignedEventSchema, } from './types/event-schemas.js';
30
31
  import { logger, MODULE_AGENT } from './utils/logger.js';
31
32
  import { timer } from './utils/timer.js';
33
+ import { formatMemoryContext } from './memory/memory-service.js';
32
34
  // ============================================================================
33
35
  // Configuration Validation Schema
34
36
  // ============================================================================
@@ -56,6 +58,7 @@ import { timer } from './utils/timer.js';
56
58
  * ```
57
59
  */
58
60
  const WorkerAgentConfigSchema = z.object({
61
+ agentId: z.string().optional(),
59
62
  role: z.enum(['artist', 'designer', 'programmer']),
60
63
  worktreePath: z.string().min(1, 'Worktree path is required'),
61
64
  brokerUrl: z.string()
@@ -107,30 +110,11 @@ const WorkerAgentConfigSchema = z.object({
107
110
  * // Agent now listens for {role}.task.assigned events and executes tasks
108
111
  * ```
109
112
  */
110
- export class BaseWorkerAgent {
113
+ export class BaseWorkerAgent extends BaseAgent {
111
114
  // ============================================================================
112
115
  // Protected Properties (accessible to subclasses/extensions)
113
116
  // ============================================================================
114
- /**
115
- * KĀDI client for broker communication
116
- *
117
- * Used for:
118
- * - Subscribing to events
119
- * - Publishing completion/failure events
120
- * - Invoking remote MCP tools (git, file management)
121
- */
122
- client;
123
- /**
124
- * LLM provider manager for model selection and chat
125
- *
126
- * Replaces direct Anthropic SDK usage. Provides:
127
- * - Model-based routing (claude→Anthropic, gpt→Model Manager)
128
- * - Automatic fallback on provider failure
129
- * - Tool-calling via ChatOptions.tools
130
- *
131
- * Optional — if null, agent cannot execute tasks requiring LLM.
132
- */
133
- providerManager = null;
117
+ // NOTE: client, providerManager, memoryService are inherited from BaseAgent
134
118
  /**
135
119
  * Agent role (artist, designer, programmer)
136
120
  *
@@ -218,17 +202,27 @@ export class BaseWorkerAgent {
218
202
  */
219
203
  baseBot = null;
220
204
  /**
221
- * Full agent configuration
222
- *
223
- * Stored for reference and potential reconfiguration.
205
+ * Worker-specific configuration (role, worktree, model, etc.)
224
206
  */
225
- config;
207
+ workerConfig;
226
208
  /**
227
209
  * Set of task IDs that have been processed or are currently in-flight.
228
210
  * Prevents duplicate execution when the same task.assigned event arrives
229
211
  * multiple times (e.g., retry re-publish while first execution is still running).
230
212
  */
231
213
  processedTaskIds = new Set();
214
+ /**
215
+ * Natively loaded ability-file-local for zero-latency file operations.
216
+ * Loaded in initializeClient(), null if not installed or load fails.
217
+ */
218
+ nativeFileLocal = null;
219
+ /** Tool names provided by ability-file-local (used for routing) */
220
+ static FILE_LOCAL_TOOLS = new Set([
221
+ 'list_files_and_folders', 'move_and_rename', 'copy_file',
222
+ 'delete_file_or_folder', 'create_folder', 'create_file',
223
+ 'watch_folder', 'unwatch_folder', 'compress_file', 'decompress_file',
224
+ 'compress_multiple_files', 'decompress_multiple_files', 'search_files',
225
+ ]);
232
226
  // ============================================================================
233
227
  // Constructor
234
228
  // ============================================================================
@@ -257,33 +251,68 @@ export class BaseWorkerAgent {
257
251
  * ```
258
252
  */
259
253
  constructor(config) {
254
+ // If WorkerAgentFullConfig (has agentId at top level), use BaseAgent inheritance
255
+ const isFullConfig = 'agentId' in config && 'brokerUrl' in config && 'role' in config && 'agentRole' in config;
256
+ if (isFullConfig) {
257
+ // WorkerAgentFullConfig path: BaseAgent handles client, providers, memory
258
+ const fullConfig = config;
259
+ super({
260
+ agentId: fullConfig.agentId,
261
+ agentRole: fullConfig.agentRole,
262
+ version: fullConfig.version,
263
+ brokerUrl: fullConfig.brokerUrl,
264
+ networks: fullConfig.networks,
265
+ additionalBrokers: fullConfig.additionalBrokers,
266
+ provider: fullConfig.provider,
267
+ memory: fullConfig.memory,
268
+ });
269
+ }
270
+ else {
271
+ // Legacy WorkerAgentConfig path: create minimal BaseAgent config
272
+ const legacyConfig = config;
273
+ super({
274
+ agentId: legacyConfig.agentId || `agent-${legacyConfig.role}`,
275
+ agentRole: legacyConfig.role,
276
+ brokerUrl: legacyConfig.brokerUrl,
277
+ networks: legacyConfig.networks,
278
+ provider: legacyConfig.anthropicApiKey ? {
279
+ anthropicApiKey: legacyConfig.anthropicApiKey,
280
+ } : undefined,
281
+ });
282
+ }
260
283
  // Start factory timer for lifetime tracking
261
284
  timer.start('factory');
262
- // Store full configuration
263
- this.config = config;
285
+ // Store worker-specific configuration
286
+ this.workerConfig = 'anthropicApiKey' in config
287
+ ? config
288
+ : {
289
+ // Convert WorkerAgentFullConfig to WorkerAgentConfig for internal use
290
+ role: config.role,
291
+ worktreePath: config.worktreePath,
292
+ brokerUrl: config.brokerUrl,
293
+ networks: config.networks,
294
+ anthropicApiKey: config.provider?.anthropicApiKey || '',
295
+ agentId: config.agentId,
296
+ claudeModel: config.claudeModel,
297
+ capabilities: config.capabilities,
298
+ customBehaviors: config.customBehaviors,
299
+ };
264
300
  // Extract and store individual config properties
265
- this.role = config.role;
266
- this.worktreePath = config.worktreePath;
301
+ this.role = 'role' in config ? config.role : config.role;
302
+ this.worktreePath = 'worktreePath' in config ? config.worktreePath : '';
267
303
  this.networks = config.networks;
268
304
  this.claudeModel = config.claudeModel || 'claude-sonnet-4-20250514';
269
305
  this.capabilities = config.capabilities || [];
270
306
  this.toolPrefixes = [];
271
- // Initialize KĀDI client
272
- this.client = new KadiClient({
273
- name: `agent-${config.role}`,
274
- version: '1.0.0',
275
- brokers: {
276
- default: { url: config.brokerUrl, networks: config.networks }
277
- },
278
- defaultBroker: 'default',
279
- });
280
307
  // COMPOSITION: Create BaseBot instance for circuit breaker and retry logic
281
- // Only created when anthropicApiKey is available (backward compatibility)
282
- if (config.anthropicApiKey) {
308
+ const apiKey = 'anthropicApiKey' in config
309
+ ? config.anthropicApiKey
310
+ : config.provider?.anthropicApiKey;
311
+ if (apiKey) {
283
312
  const baseBotConfig = {
284
- client: this.client,
285
- anthropicApiKey: config.anthropicApiKey,
286
- botUserId: `agent-${config.role}`
313
+ client: this.client, // inherited from BaseAgent
314
+ anthropicApiKey: apiKey,
315
+ botUserId: this.workerConfig.agentId || `agent-${this.role}`,
287
316
  };
288
317
  this.baseBot = new (class extends BaseBot {
289
318
  async handleMention(_event) { }
@@ -295,6 +324,20 @@ export class BaseWorkerAgent {
295
324
  // ============================================================================
296
325
  // Protected Initialization Methods
297
326
  // ============================================================================
327
+ /**
328
+ * Override BaseAgent.connect() to load native abilities after broker connection.
329
+ */
330
+ async connect() {
331
+ await super.connect();
332
+ // Load ability-file-local natively (zero-latency file ops, in-process)
333
+ try {
334
+ this.nativeFileLocal = await this.client.loadNative('ability-file-local');
335
+ logger.info(MODULE_AGENT, 'Loaded ability-file-local natively', timer.elapsed('factory'));
336
+ }
337
+ catch (err) {
338
+ logger.warn(MODULE_AGENT, `Could not load ability-file-local natively: ${err.message} — will use broker`, timer.elapsed('factory'));
339
+ }
340
+ }
298
341
  /**
299
342
  * Initialize KĀDI client and connect to broker
300
343
  *
@@ -316,14 +359,12 @@ export class BaseWorkerAgent {
316
359
  * ```
317
360
  */
318
361
  async initializeClient() {
319
- logger.info(MODULE_AGENT, '', timer.elapsed('factory'));
320
- logger.info(MODULE_AGENT, '🔌 Initializing KĀDI client...', timer.elapsed('factory'));
362
+ logger.debug(MODULE_AGENT, 'Initializing KADI client...', timer.elapsed('factory'));
321
363
  try {
322
364
  // Step 1: Start client connection to broker
323
- logger.info(MODULE_AGENT, ' → Connecting to broker...', timer.elapsed('factory'));
324
365
  try {
325
366
  await this.client.connect();
326
- logger.info(MODULE_AGENT, 'Connected to broker', timer.elapsed('factory'));
367
+ logger.debug(MODULE_AGENT, 'Connected to broker', timer.elapsed('factory'));
327
368
  }
328
369
  catch (error) {
329
370
  logger.error(MODULE_AGENT, `Client connection error: ${error.message || String(error)}`, timer.elapsed('factory'), error);
@@ -331,23 +372,15 @@ export class BaseWorkerAgent {
331
372
  }
332
373
  // Step 2: Initialize ability response subscription (if BaseBot available)
333
374
  if (this.baseBot) {
334
- logger.info(MODULE_AGENT, ' → Initializing ability response subscription...', timer.elapsed('factory'));
335
375
  await this.baseBot['initializeAbilityResponseSubscription']();
336
- logger.info(MODULE_AGENT, 'Ability response subscription initialized', timer.elapsed('factory'));
376
+ logger.debug(MODULE_AGENT, 'Ability response subscription initialized', timer.elapsed('factory'));
337
377
  }
338
- logger.info(MODULE_AGENT, '', timer.elapsed('factory'));
339
- logger.info(MODULE_AGENT, '✅ KĀDI client initialized successfully', timer.elapsed('factory'));
340
- logger.info(MODULE_AGENT, ` Networks: ${this.networks.join(', ')}`, timer.elapsed('factory'));
341
- logger.info(MODULE_AGENT, ` Protocol: Ready`, timer.elapsed('factory'));
342
- logger.info(MODULE_AGENT, '', timer.elapsed('factory'));
378
+ logger.debug(MODULE_AGENT, `KADI client ready (networks: ${this.networks.join(', ')})`, timer.elapsed('factory'));
343
379
  // Note: client.serve() continues running in background to handle incoming requests
344
380
  // We don't await it because it never resolves (blocks indefinitely)
345
381
  }
346
382
  catch (error) {
347
- logger.error(MODULE_AGENT, '', timer.elapsed('factory'));
348
- logger.error(MODULE_AGENT, 'Failed to initialize KĀDI client', timer.elapsed('factory'), error);
349
- logger.error(MODULE_AGENT, ` Error: ${error.message || String(error)}`, timer.elapsed('factory'));
350
- logger.error(MODULE_AGENT, '', timer.elapsed('factory'));
383
+ logger.error(MODULE_AGENT, 'Failed to initialize KADI client', timer.elapsed('factory'), error);
351
384
  throw error;
352
385
  }
353
386
  }
@@ -377,22 +410,15 @@ export class BaseWorkerAgent {
377
410
  async subscribeToTaskAssignments() {
378
411
  // Subscribe to generic task.assigned topic (role filtering done in handler)
379
412
  const topic = `task.assigned`;
380
- logger.info(MODULE_AGENT, '', timer.elapsed('factory'));
381
- logger.info(MODULE_AGENT, `📡 Subscribing to task assignments...`, timer.elapsed('factory'));
382
- logger.info(MODULE_AGENT, ` Topic: ${topic}`, timer.elapsed('factory'));
413
+ logger.debug(MODULE_AGENT, `Subscribing to task assignments (topic: ${topic})...`, timer.elapsed('factory'));
383
414
  try {
384
415
  // Subscribe to event topic with bound callback
385
416
  // Using .bind(this) to preserve instance context in callback
386
417
  await this.client.subscribe(topic, this.handleTaskAssignment.bind(this), { broker: 'default' });
387
- logger.info(MODULE_AGENT, `Subscribed successfully`, timer.elapsed('factory'));
388
- logger.info(MODULE_AGENT, '', timer.elapsed('factory'));
418
+ logger.debug(MODULE_AGENT, `Subscribed to ${topic}`, timer.elapsed('factory'));
389
419
  }
390
420
  catch (error) {
391
- logger.error(MODULE_AGENT, '', timer.elapsed('factory'));
392
- logger.error(MODULE_AGENT, `Failed to subscribe to task assignments`, timer.elapsed('factory'), error);
393
- logger.error(MODULE_AGENT, ` Topic: ${topic}`, timer.elapsed('factory'));
394
- logger.error(MODULE_AGENT, ` Error: ${error.message || String(error)}`, timer.elapsed('factory'));
395
- logger.error(MODULE_AGENT, '', timer.elapsed('factory'));
421
+ logger.error(MODULE_AGENT, `Failed to subscribe to task assignments (topic: ${topic})`, timer.elapsed('factory'), error);
396
422
  throw error;
397
423
  }
398
424
  }
@@ -466,8 +492,9 @@ export class BaseWorkerAgent {
466
492
  }
467
493
  // Mark task as in-flight before execution
468
494
  this.processedTaskIds.add(validatedEvent.taskId);
469
- // Capability validation: check if task matches this agent's capabilities
470
- if (this.capabilities.length > 0) {
495
+ // Capability validation: skip if task role matches this agent's role (role-based routing
496
+ // already ensures correct assignment). Only validate when roles don't match or are missing.
497
+ if (this.capabilities.length > 0 && validatedEvent.role !== this.role) {
471
498
  const rejectionReason = this.validateTaskCapability(validatedEvent);
472
499
  if (rejectionReason) {
473
500
  logger.warn(MODULE_AGENT, ` ⚠️ Task capability mismatch`, timer.elapsed('factory'));
@@ -479,6 +506,9 @@ export class BaseWorkerAgent {
479
506
  }
480
507
  logger.info(MODULE_AGENT, ` ✅ Capability check passed`, timer.elapsed('factory'));
481
508
  }
509
+ else if (validatedEvent.role === this.role) {
510
+ logger.info(MODULE_AGENT, ` ✅ Role match (${this.role}) — skipping capability check`, timer.elapsed('factory'));
511
+ }
482
512
  // Worktree scope validation: soft warning only (worker always operates within its worktree directory)
483
513
  const outOfScopeReason = this.validateTaskScope(validatedEvent);
484
514
  if (outOfScopeReason) {
@@ -579,9 +609,23 @@ export class BaseWorkerAgent {
579
609
  }
580
610
  // Step 4: Build tool definitions — local tools + dynamic discovery from broker
581
611
  const tools = await this.buildToolDefinitionsAsync();
582
- logger.info(MODULE_AGENT, `🔧 Available tools: ${tools.length} (prefixes: ${this.toolPrefixes.join(', ') || 'none'})`, timer.elapsed('factory'));
612
+ logger.info(MODULE_AGENT, `🔧 Available tools: ${tools.length}`, timer.elapsed('factory'));
613
+ // Step 4.5: Recall relevant past experience (non-blocking, best-effort)
614
+ let memoryContext = '';
615
+ if (this.memoryService) {
616
+ try {
617
+ const recallResult = await this.memoryService.recallRelevant(this.role, task.description, this.role, 3, ['*']);
618
+ if (recallResult.success && recallResult.data.length > 0) {
619
+ memoryContext = formatMemoryContext(recallResult.data);
620
+ logger.info(MODULE_AGENT, `Recalled ${recallResult.data.length} past patterns`, timer.elapsed('factory'));
621
+ }
622
+ }
623
+ catch (err) {
624
+ logger.warn(MODULE_AGENT, `Memory recall failed (non-fatal): ${err.message}`, timer.elapsed('factory'));
625
+ }
626
+ }
583
627
  // Step 5: Build initial system prompt
584
- const systemPrompt = this.buildTaskSystemPrompt(task, implementationGuide, verificationCriteria);
628
+ const systemPrompt = this.buildTaskSystemPrompt(task, implementationGuide, verificationCriteria, memoryContext);
585
629
  // Step 6: Enter tool-calling agent loop
586
630
  const messages = [
587
631
  { role: 'user', content: systemPrompt }
@@ -598,6 +642,7 @@ export class BaseWorkerAgent {
598
642
  const filesModified = [];
599
643
  let commitSha = 'unknown';
600
644
  let finalResponse = '';
645
+ let consecutiveFailures = 0;
601
646
  for (let iteration = 0; iteration < BaseWorkerAgent.MAX_TOOL_LOOP_ITERATIONS; iteration++) {
602
647
  logger.info(MODULE_AGENT, `🔄 Agent loop iteration ${iteration + 1}/${BaseWorkerAgent.MAX_TOOL_LOOP_ITERATIONS}`, timer.elapsed('factory'));
603
648
  const result = await this.providerManager.chat(messages, chatOptions);
@@ -641,9 +686,11 @@ export class BaseWorkerAgent {
641
686
  content: resultText,
642
687
  tool_call_id: toolCall.id
643
688
  });
689
+ consecutiveFailures = 0;
644
690
  logger.info(MODULE_AGENT, ` ✅ Tool ${toolName} succeeded`, timer.elapsed('factory'));
645
691
  }
646
692
  catch (error) {
693
+ consecutiveFailures++;
647
694
  const errorMsg = `Tool ${toolName} failed: ${error.message || String(error)}`;
648
695
  logger.error(MODULE_AGENT, ` ❌ ${errorMsg}`, timer.elapsed('factory'));
649
696
  messages.push({
@@ -653,6 +700,14 @@ export class BaseWorkerAgent {
653
700
  });
654
701
  }
655
702
  }
703
+ // Circuit breaker: if 3+ consecutive tool failures, tell LLM to stop retrying
704
+ if (consecutiveFailures >= 3) {
705
+ logger.warn(MODULE_AGENT, `⚠️ Circuit breaker: ${consecutiveFailures} consecutive tool failures — instructing LLM to wrap up`, timer.elapsed('factory'));
706
+ messages.push({
707
+ role: 'user',
708
+ content: 'SYSTEM: Multiple consecutive tool failures detected. Stop retrying failed tools and provide your final response summarizing what you accomplished so far.'
709
+ });
710
+ }
656
711
  // Continue loop — LLM will process tool results
657
712
  }
658
713
  else {
@@ -662,18 +717,49 @@ export class BaseWorkerAgent {
662
717
  break;
663
718
  }
664
719
  }
665
- // Step 7: Publish completion event
666
- const contentSummary = finalResponse.length > 500
667
- ? finalResponse.substring(0, 500) + `... (${finalResponse.length} chars total)`
668
- : finalResponse;
669
- await this.publishCompletion(task.taskId, task.questId, filesCreated, filesModified, commitSha, contentSummary);
720
+ // Step 7: Safety net — auto-commit if LLM forgot to commit
721
+ if (commitSha === 'unknown' && (filesCreated.length > 0 || filesModified.length > 0)) {
722
+ logger.warn(MODULE_AGENT, `⚠️ LLM did not commit auto-committing staged changes`, timer.elapsed('factory'));
723
+ try {
724
+ const commitMsg = this.commitFormat
725
+ ? this.commitFormat.replace('{taskId}', task.taskId)
726
+ : `feat(${this.role}): task ${task.taskId}`;
727
+ const commitResult = await this.executeRemoteTool('git_git_commit', { path: this.worktreePath, message: commitMsg });
728
+ const sha = this.extractCommitSha(commitResult);
729
+ if (sha) {
730
+ commitSha = sha;
731
+ logger.info(MODULE_AGENT, ` ✅ Auto-commit succeeded: ${sha.substring(0, 7)}`, timer.elapsed('factory'));
732
+ }
733
+ else {
734
+ logger.warn(MODULE_AGENT, ` ⚠️ Auto-commit returned no SHA`, timer.elapsed('factory'));
735
+ }
736
+ }
737
+ catch (err) {
738
+ logger.warn(MODULE_AGENT, ` ⚠️ Auto-commit failed: ${err.message}`, timer.elapsed('factory'));
739
+ }
740
+ }
741
+ // Step 8: Publish completion or failure event
742
+ if (!commitSha || commitSha === 'unknown') {
743
+ logger.warn(MODULE_AGENT, `⚠️ No valid commit SHA — publishing task.failed instead of review request`, timer.elapsed('factory'));
744
+ // Clear dedup so agent-lead retries are accepted
745
+ this.processedTaskIds.delete(task.taskId);
746
+ await this.publishFailure(task.taskId, new Error('Git commit failed — no valid commit SHA available. Files may have been written but were not committed.'), task.questId, task.retryAttempt);
747
+ }
748
+ else {
749
+ const contentSummary = finalResponse.length > 500
750
+ ? finalResponse.substring(0, 500) + `... (${finalResponse.length} chars total)`
751
+ : finalResponse;
752
+ await this.publishCompletion(task.taskId, task.questId, filesCreated, filesModified, commitSha, contentSummary, task.retryAttempt);
753
+ }
670
754
  logger.info(MODULE_AGENT, `✅ Task ${task.taskId} execution completed`, timer.elapsed('factory'));
671
755
  }
672
756
  catch (error) {
673
757
  logger.error(MODULE_AGENT, `Failed to execute task ${task.taskId}`, timer.elapsed('factory'), error);
674
758
  logger.error(MODULE_AGENT, ` Error: ${error.message || String(error)}`, timer.elapsed('factory'));
675
759
  logger.error(MODULE_AGENT, ` Stack: ${error.stack || 'No stack trace'}`, timer.elapsed('factory'));
676
- await this.publishFailure(task.taskId, error, task.questId);
760
+ // Clear dedup so agent-lead retries are accepted
761
+ this.processedTaskIds.delete(task.taskId);
762
+ await this.publishFailure(task.taskId, error, task.questId, task.retryAttempt);
677
763
  throw error;
678
764
  }
679
765
  }
@@ -686,10 +772,13 @@ export class BaseWorkerAgent {
686
772
  * Includes task context, role identity, worktree path, and instructions
687
773
  * for using available tools (git, file operations).
688
774
  */
689
- buildTaskSystemPrompt(task, implementationGuide, verificationCriteria) {
775
+ buildTaskSystemPrompt(task, implementationGuide, verificationCriteria, memoryContext) {
690
776
  const retryContext = task.feedback
691
777
  ? `\n⚠️ REVISION REQUIRED (Attempt #${task.retryAttempt || 1})\nPrevious attempt was rejected. Feedback:\n${task.feedback}\nPlease carefully address the feedback above.\n`
692
778
  : '';
779
+ const predecessorContext = task.predecessors && task.predecessors.length > 0
780
+ ? `\n## Predecessor Tasks\nThe following tasks were completed before yours. You can read their output files using git_git_show with the branch name:\n${task.predecessors.map(p => `- **${p.role}** task (${p.taskId}): branch \`${p.branch}\`${p.commitHash ? ` commit \`${p.commitHash}\`` : ''}\n To read a file: git_git_show with path="${this.worktreePath}" and object="${p.branch}:<filename>"`).join('\n')}\n`
781
+ : '';
693
782
  return `You are a ${this.role} agent working in the KĀDI multi-agent system.
694
783
  Your worktree directory is: ${this.worktreePath}
695
784
 
@@ -697,28 +786,30 @@ Task ID: ${task.taskId}
697
786
  Description: ${task.description}
698
787
  Implementation Guide: ${implementationGuide}
699
788
  ${verificationCriteria ? `Verification Criteria: ${verificationCriteria}` : ''}
700
- ${retryContext}
789
+ ${retryContext}${predecessorContext}
790
+ ${memoryContext ? `\n## Past Experience\nThe following are relevant outcomes from past similar tasks. Use these to avoid known mistakes and apply proven patterns:\n${memoryContext}` : ''}
701
791
 
702
792
  Instructions:
703
793
  1. Analyze the task requirements carefully
704
794
  2. Create the necessary files in the worktree using available tools
705
- 3. Stage and commit your changes using git tools
706
- 4. When done, provide a brief summary of what you created
795
+ 3. Stage ALL changed files with git_git_add (pass path: "${this.worktreePath}")
796
+ 4. CRITICAL: You MUST call git_git_commit with path: "${this.worktreePath}" to commit your staged changes. Do NOT return a summary until you have committed.
797
+ 5. When done, provide a brief summary of what you created
707
798
 
708
799
  Important:
709
800
  - All file operations must be within the worktree: ${this.worktreePath}
710
- - Use git_git_add to stage files and git_git_commit to commit
801
+ - For ALL git tools (git_git_add, git_git_commit, etc.), you MUST pass path: "${this.worktreePath}" as a parameter.
802
+ - You MUST complete the full cycle: write files → git_git_add → git_git_commit. Skipping the commit is a failure.
711
803
  - If the task description specifies an exact commit message, you MUST use that exact message
712
804
  - Default commit message format (use ONLY when no commit message is specified in the task): "${this.commitFormat ? this.commitFormat.replace('{taskId}', task.taskId) : `feat(${this.role}): <description> [${task.taskId}]`}"
713
- - Focus on ${this.role === 'artist' ? 'creative and artistic elements' : this.role === 'designer' ? 'design principles and aesthetics' : 'code quality and best practices'}`;
805
+ - Focus on ${this.role === 'artist' ? 'creative and artistic elements. IMPORTANT: You cannot generate binary image files (PNG, GIF, JPG). Instead, generate all pixel art and graphics as SVG files using <rect> elements on a grid. Use a limited palette (<=16 colors). Never create placeholder or fake image files — always produce real, renderable SVG that displays the intended artwork when opened in a browser.' : this.role === 'designer' ? 'design principles and aesthetics' : 'code quality and best practices'}`;
714
806
  }
715
807
  /**
716
808
  * Build tool definitions: local tools + dynamic discovery from KĀDI broker
717
809
  *
718
810
  * 1. Always includes local tools (write_file, read_file)
719
- * 2. Discovers network tools from broker via kadi.ability.list
720
- * 3. Filters network tools by toolPrefixes from role config
721
- * 4. Converts to OpenAI-compatible ToolDefinition format
811
+ * 2. Discovers all network tools from broker via kadi.ability.list
812
+ * 3. Converts to OpenAI-compatible ToolDefinition format
722
813
  */
723
814
  async buildToolDefinitionsAsync() {
724
815
  const tools = [];
@@ -752,29 +843,53 @@ Important:
752
843
  }
753
844
  }
754
845
  });
755
- // Discover network tools from broker, filtered by toolPrefixes
756
- if (this.toolPrefixes.length > 0 && this.client.isConnected()) {
846
+ // Include tools from natively loaded ability-file-local (available even without broker)
847
+ if (this.nativeFileLocal) {
848
+ const nativeTools = this.nativeFileLocal.getTools();
849
+ for (const tool of nativeTools) {
850
+ tools.push({
851
+ type: 'function',
852
+ function: {
853
+ name: tool.name,
854
+ description: tool.description || '',
855
+ parameters: tool.inputSchema || {
856
+ type: 'object',
857
+ properties: {},
858
+ required: []
859
+ }
860
+ }
861
+ });
862
+ }
863
+ logger.info(MODULE_AGENT, `Added ${nativeTools.length} tools from native ability-file-local`, timer.elapsed('factory'));
864
+ }
865
+ // Discover network tools from broker, filtered by toolPrefixes if configured
866
+ if (this.client.isConnected()) {
757
867
  try {
758
868
  const response = await this.client.invokeRemote('kadi.ability.list', { includeProviders: false });
759
869
  if (response?.tools && Array.isArray(response.tools)) {
760
870
  for (const tool of response.tools) {
761
- // Only include tools matching one of the configured prefixes
762
- if (this.toolPrefixes.some(prefix => tool.name.startsWith(prefix))) {
763
- tools.push({
764
- type: 'function',
765
- function: {
766
- name: tool.name,
767
- description: tool.description || '',
768
- parameters: tool.inputSchema || {
769
- type: 'object',
770
- properties: {},
771
- required: []
772
- }
773
- }
774
- });
871
+ // Skip tools already loaded natively (avoid duplicates)
872
+ if (this.nativeFileLocal && BaseWorkerAgent.FILE_LOCAL_TOOLS.has(tool.name)) {
873
+ continue;
775
874
  }
875
+ // If toolPrefixes configured, filter by prefix; otherwise include all
876
+ if (this.toolPrefixes.length > 0 && !this.toolPrefixes.some(prefix => tool.name.startsWith(prefix))) {
877
+ continue;
878
+ }
879
+ tools.push({
880
+ type: 'function',
881
+ function: {
882
+ name: tool.name,
883
+ description: tool.description || '',
884
+ parameters: tool.inputSchema || {
885
+ type: 'object',
886
+ properties: {},
887
+ required: []
888
+ }
889
+ }
890
+ });
776
891
  }
777
- logger.info(MODULE_AGENT, `Discovered ${tools.length - 2} network tools from broker (filtered by prefixes: ${this.toolPrefixes.join(', ')})`, timer.elapsed('factory'));
892
+ logger.info(MODULE_AGENT, `Discovered ${tools.length - 2} network tools from broker${this.toolPrefixes.length > 0 ? ` (filtered by prefixes: ${this.toolPrefixes.join(', ')})` : ''}`, timer.elapsed('factory'));
778
893
  }
779
894
  }
780
895
  catch (error) {
@@ -783,7 +898,7 @@ Important:
783
898
  this.appendHardcodedGitTools(tools);
784
899
  }
785
900
  }
786
- else if (this.toolPrefixes.length > 0) {
901
+ else {
787
902
  // Not connected to broker — use hardcoded fallback
788
903
  logger.warn(MODULE_AGENT, 'Not connected to broker — using hardcoded tool definitions', timer.elapsed('factory'));
789
904
  this.appendHardcodedGitTools(tools);
@@ -794,7 +909,7 @@ Important:
794
909
  * Fallback: append hardcoded git tool definitions when broker discovery fails
795
910
  */
796
911
  appendHardcodedGitTools(tools) {
797
- if (!this.toolPrefixes.some(p => p.startsWith('git_git_')))
912
+ if (this.toolPrefixes.length > 0 && !this.toolPrefixes.some(p => p.startsWith('git_git_')))
798
913
  return;
799
914
  tools.push({
800
915
  type: 'function',
@@ -908,8 +1023,17 @@ Important:
908
1023
  const content = await fs.readFile(filePath, 'utf-8');
909
1024
  return { success: true, content };
910
1025
  }
1026
+ // Route to native ability-file-local if loaded (zero-latency, in-process)
1027
+ if (this.nativeFileLocal && BaseWorkerAgent.FILE_LOCAL_TOOLS.has(toolName)) {
1028
+ return await this.nativeFileLocal.invoke(toolName, toolArgs);
1029
+ }
911
1030
  // Route to MCP tool via KĀDI broker
912
1031
  const result = await this.client.invokeRemote(toolName, toolArgs);
1032
+ // Check for error responses from the broker/tool
1033
+ if (result?.isError) {
1034
+ const errorText = result?.content?.[0]?.text ?? JSON.stringify(result);
1035
+ throw new Error(`Remote tool ${toolName} returned error: ${errorText}`);
1036
+ }
913
1037
  // invokeRemote returns { content: [{ type, text }] } — extract text
914
1038
  if (result?.content?.[0]?.text) {
915
1039
  try {
@@ -982,8 +1106,8 @@ Important:
982
1106
  * @returns Formatted commit message
983
1107
  */
984
1108
  formatCommitMessage(taskId, files, taskDescription) {
985
- if (this.config.customBehaviors?.formatCommitMessage) {
986
- return this.config.customBehaviors.formatCommitMessage(taskId, files);
1109
+ if (this.workerConfig.customBehaviors?.formatCommitMessage) {
1110
+ return this.workerConfig.customBehaviors.formatCommitMessage(taskId, files);
987
1111
  }
988
1112
  if (taskDescription) {
989
1113
  const commitMsgMatch = taskDescription.match(/(?:commit.*?(?:with\s+)?message|commit\s+message)[\s:]*['"`]([^'"`]+)['"`]/i);
@@ -996,8 +1120,8 @@ Important:
996
1120
  /**
997
1121
  * Publish task completion event
998
1122
  *
999
- * Publishes TaskCompletedEvent to KĀDI broker with topic pattern: {role}.task.completed
1000
- * Event payload matches TaskCompletedEvent schema exactly for backward compatibility.
1123
+ * Publishes TaskReviewRequestedPayload to KĀDI broker on the qa network.
1124
+ * Per QUEST_WORKFLOW_V2: worker task.review_requested agent-qa for validation.
1001
1125
  *
1002
1126
  * Publishing failures are handled gracefully - errors are logged but do not throw.
1003
1127
  * This ensures task execution completes even if event publishing fails.
@@ -1015,42 +1139,53 @@ Important:
1015
1139
  * [],
1016
1140
  * 'a1b2c3d4e5f6g7h8i9j0'
1017
1141
  * );
1018
- * // Publishes to: task.completed
1142
+ * // Publishes to: task.review_requested (→ agent-qa)
1019
1143
  * ```
1020
1144
  */
1021
- async publishCompletion(taskId, questId, filesCreated, filesModified, commitSha, contentSummary) {
1022
- // Include worktree path for independent verification by agent-producer
1023
- const worktreePath = this.worktreePath;
1024
- // Generic topic — agent identity is in the payload (agent, role fields)
1025
- const topic = `task.completed`;
1026
- // Create payload matching TaskCompletedEvent schema
1145
+ async publishCompletion(taskId, questId, _filesCreated, _filesModified, commitSha, _contentSummary, retryAttempt) {
1146
+ // Per QUEST_WORKFLOW_V2: worker publishes task.review_requested agent-qa for validation
1147
+ const topic = `task.review_requested`;
1148
+ // Create payload matching TaskReviewRequestedPayload schema
1027
1149
  const payload = {
1028
1150
  taskId,
1029
- questId,
1030
- role: this.role,
1031
- status: 'completed',
1032
- filesCreated,
1033
- filesModified,
1034
- commitSha,
1035
- timestamp: new Date().toISOString(),
1036
- agent: `agent-${this.role}`,
1037
- ...(contentSummary ? { contentSummary } : {}),
1038
- ...(worktreePath ? { worktreePath } : {})
1151
+ questId: questId || '',
1152
+ branch: this.worktreePath,
1153
+ commitHash: commitSha,
1154
+ ...(retryAttempt && { revisionCount: retryAttempt }),
1039
1155
  };
1040
1156
  try {
1041
- logger.info(MODULE_AGENT, `📢 Publishing completion event`, timer.elapsed('factory'));
1157
+ logger.info(MODULE_AGENT, `📢 Publishing review request event`, timer.elapsed('factory'));
1042
1158
  logger.info(MODULE_AGENT, ` Topic: ${topic}`, timer.elapsed('factory'));
1043
1159
  logger.info(MODULE_AGENT, ` Task ID: ${taskId}`, timer.elapsed('factory'));
1044
1160
  if (questId) {
1045
1161
  logger.info(MODULE_AGENT, ` Quest ID: ${questId}`, timer.elapsed('factory'));
1046
1162
  }
1047
1163
  logger.info(MODULE_AGENT, ` Commit SHA: ${commitSha.substring(0, 7)}`, timer.elapsed('factory'));
1048
- await this.client.publish(topic, payload, { broker: 'default', network: 'global' });
1049
- logger.info(MODULE_AGENT, ` ✅ Completion event published`, timer.elapsed('factory'));
1164
+ await this.client.publish(topic, payload, { broker: 'default', network: 'qa' });
1165
+ logger.info(MODULE_AGENT, ` ✅ Review request published to qa network`, timer.elapsed('factory'));
1166
+ // Store task memory (fire-and-forget) for learning from past outcomes
1167
+ if (this.memoryService) {
1168
+ this.memoryService.storeTaskMemory({
1169
+ taskId,
1170
+ questId: questId || '',
1171
+ agentId: this.client.readAgentJson().name || `agent-worker-${this.role}`,
1172
+ agentRole: this.role,
1173
+ taskType: this.role,
1174
+ description: _contentSummary || `Task ${taskId} completed`,
1175
+ outcome: 'success',
1176
+ context: `worktree: ${this.worktreePath}`,
1177
+ result: `commit: ${commitSha}`,
1178
+ entities: [],
1179
+ duration: 0,
1180
+ timestamp: Date.now(),
1181
+ }).catch((err) => {
1182
+ logger.warn(MODULE_AGENT, `Failed to store task memory (non-fatal): ${err.message || String(err)}`, timer.elapsed('factory'));
1183
+ });
1184
+ }
1050
1185
  }
1051
1186
  catch (error) {
1052
1187
  // Handle publishing failures gracefully - don't throw
1053
- logger.error(MODULE_AGENT, `Failed to publish completion event (non-fatal)`, timer.elapsed('factory'), error);
1188
+ logger.error(MODULE_AGENT, `Failed to publish review request event (non-fatal)`, timer.elapsed('factory'), error);
1054
1189
  logger.error(MODULE_AGENT, ` Error: ${error.message || String(error)}`, timer.elapsed('factory'));
1055
1190
  logger.error(MODULE_AGENT, ` Task execution succeeded despite event publishing failure`, timer.elapsed('factory'));
1056
1191
  // Don't throw - event publishing failure should not fail the task
@@ -1078,7 +1213,7 @@ Important:
1078
1213
  * }
1079
1214
  * ```
1080
1215
  */
1081
- async publishFailure(taskId, error, questId) {
1216
+ async publishFailure(taskId, error, questId, retryAttempt) {
1082
1217
  // Generic topic — agent identity is in the payload (agent, role fields)
1083
1218
  const topic = `task.failed`;
1084
1219
  // Create payload matching TaskFailedEvent schema
@@ -1088,14 +1223,15 @@ Important:
1088
1223
  role: this.role,
1089
1224
  error: error.message || String(error),
1090
1225
  timestamp: new Date().toISOString(),
1091
- agent: `agent-${this.role}`
1226
+ agent: `agent-${this.role}`,
1227
+ retryAttempt: retryAttempt ?? 0
1092
1228
  };
1093
1229
  try {
1094
1230
  logger.info(MODULE_AGENT, `📢 Publishing failure event`, timer.elapsed('factory'));
1095
1231
  logger.info(MODULE_AGENT, ` Topic: ${topic}`, timer.elapsed('factory'));
1096
1232
  logger.info(MODULE_AGENT, ` Task ID: ${taskId}`, timer.elapsed('factory'));
1097
1233
  logger.info(MODULE_AGENT, ` Error: ${error.message.substring(0, 100)}${error.message.length > 100 ? '...' : ''}`, timer.elapsed('factory'));
1098
- await this.client.publish(topic, payload, { broker: 'default', network: 'global' });
1234
+ await this.client.publish(topic, payload, { broker: 'default', network: this.role });
1099
1235
  logger.info(MODULE_AGENT, ` ✅ Failure event published`, timer.elapsed('factory'));
1100
1236
  }
1101
1237
  catch (publishError) {
@@ -1188,7 +1324,7 @@ Important:
1188
1324
  logger.info(MODULE_AGENT, ` Topic: ${topic}`, timer.elapsed('factory'));
1189
1325
  logger.info(MODULE_AGENT, ` Task ID: ${taskId}`, timer.elapsed('factory'));
1190
1326
  logger.info(MODULE_AGENT, ` Reason: ${reason.substring(0, 100)}${reason.length > 100 ? '...' : ''}`, timer.elapsed('factory'));
1191
- await this.client.publish(topic, payload, { broker: 'default', network: 'global' });
1327
+ await this.client.publish(topic, payload, { broker: 'default', network: this.role });
1192
1328
  logger.info(MODULE_AGENT, ` ✅ Rejection event published`, timer.elapsed('factory'));
1193
1329
  }
1194
1330
  catch (error) {
@@ -1222,20 +1358,24 @@ Important:
1222
1358
  * ```
1223
1359
  */
1224
1360
  async start() {
1225
- logger.info(MODULE_AGENT, '='.repeat(60), timer.elapsed('factory'));
1226
- logger.info(MODULE_AGENT, `Starting Worker Agent: ${this.role}`, timer.elapsed('factory'));
1227
- logger.info(MODULE_AGENT, '='.repeat(60), timer.elapsed('factory'));
1228
- logger.info(MODULE_AGENT, `Broker URL: ${this.config.brokerUrl}`, timer.elapsed('factory'));
1229
- logger.info(MODULE_AGENT, `Networks: ${this.networks.join(', ')}`, timer.elapsed('factory'));
1230
- logger.info(MODULE_AGENT, `Worktree Path: ${this.worktreePath}`, timer.elapsed('factory'));
1231
- logger.info(MODULE_AGENT, `LLM Model: ${this.claudeModel}`, timer.elapsed('factory'));
1232
- logger.info(MODULE_AGENT, '='.repeat(60), timer.elapsed('factory'));
1233
- // Initialize KĀDI client and connect to broker
1234
- await this.initializeClient();
1361
+ logger.debug(MODULE_AGENT, `Starting Worker Agent: ${this.role}`, timer.elapsed('factory'));
1362
+ logger.debug(MODULE_AGENT, `Broker URL: ${this.workerConfig.brokerUrl}`, timer.elapsed('factory'));
1363
+ logger.debug(MODULE_AGENT, `Networks: ${this.networks.join(', ')}`, timer.elapsed('factory'));
1364
+ logger.debug(MODULE_AGENT, `Worktree Path: ${this.worktreePath}`, timer.elapsed('factory'));
1365
+ logger.debug(MODULE_AGENT, `LLM Model: ${this.claudeModel}`, timer.elapsed('factory'));
1366
+ // If already connected (via BaseAgent.connect()), skip client initialization
1367
+ if (!this.connected) {
1368
+ await this.initializeClient();
1369
+ }
1370
+ else {
1371
+ // Still need ability response subscription even if already connected
1372
+ if (this.baseBot) {
1373
+ await this.baseBot['initializeAbilityResponseSubscription']();
1374
+ logger.debug(MODULE_AGENT, 'Ability response subscription initialized', timer.elapsed('factory'));
1375
+ }
1376
+ }
1235
1377
  // Subscribe to task assignment events
1236
1378
  await this.subscribeToTaskAssignments();
1237
- // TODO: Implement remaining start logic in next tasks
1238
- // 1. Register tools (if any)
1239
1379
  }
1240
1380
  /**
1241
1381
  * Stop the worker agent
@@ -1255,26 +1395,17 @@ Important:
1255
1395
  * ```
1256
1396
  */
1257
1397
  async stop() {
1258
- logger.info(MODULE_AGENT, '='.repeat(60), timer.elapsed('factory'));
1259
- logger.info(MODULE_AGENT, `Stopping Worker Agent: ${this.role}`, timer.elapsed('factory'));
1260
- logger.info(MODULE_AGENT, '='.repeat(60), timer.elapsed('factory'));
1398
+ logger.debug(MODULE_AGENT, `Stopping Worker Agent: ${this.role}`, timer.elapsed('factory'));
1261
1399
  try {
1262
- // TODO: Unsubscribe from events in next task
1263
- // Disconnect KĀDI client
1264
- logger.info(MODULE_AGENT, ' → Disconnecting KĀDI client...', timer.elapsed('factory'));
1265
- await this.client.disconnect();
1266
- logger.info(MODULE_AGENT, ' ✅ KĀDI client disconnected', timer.elapsed('factory'));
1267
- // Clear references
1268
- this.providerManager = null;
1269
- logger.info(MODULE_AGENT, '', timer.elapsed('factory'));
1270
- logger.info(MODULE_AGENT, '✅ Worker agent stopped successfully', timer.elapsed('factory'));
1271
- logger.info(MODULE_AGENT, '='.repeat(60), timer.elapsed('factory'));
1400
+ // Delegate to BaseAgent.shutdown() which handles:
1401
+ // - ProviderManager disposal
1402
+ // - MemoryService disposal
1403
+ // - Client disconnect
1404
+ await this.shutdown();
1405
+ logger.debug(MODULE_AGENT, 'Worker agent stopped', timer.elapsed('factory'));
1272
1406
  }
1273
1407
  catch (error) {
1274
- logger.error(MODULE_AGENT, '', timer.elapsed('factory'));
1275
1408
  logger.error(MODULE_AGENT, 'Error during shutdown', timer.elapsed('factory'), error);
1276
- logger.error(MODULE_AGENT, ` Error: ${error.message || String(error)}`, timer.elapsed('factory'));
1277
- logger.error(MODULE_AGENT, '', timer.elapsed('factory'));
1278
1409
  // Don't throw - best effort cleanup
1279
1410
  }
1280
1411
  }
@@ -1332,6 +1463,17 @@ Important:
1332
1463
  setProviderManager(pm) {
1333
1464
  this.providerManager = pm;
1334
1465
  }
1466
+ /**
1467
+ * Set the MemoryService for task memory storage and recall
1468
+ *
1469
+ * Called by the agent entry point after constructing BaseAgent
1470
+ * (which creates the MemoryService).
1471
+ *
1472
+ * @param ms - MemoryService instance from BaseAgent
1473
+ */
1474
+ setMemoryService(ms) {
1475
+ this.memoryService = ms;
1476
+ }
1335
1477
  /**
1336
1478
  * Configure role-specific settings from a loaded RoleConfig
1337
1479
  *
@@ -1471,17 +1613,18 @@ export class WorkerAgentFactory {
1471
1613
  */
1472
1614
  static createAgent(config) {
1473
1615
  try {
1474
- // Validate configuration with Zod schema
1475
- const validatedConfig = WorkerAgentConfigSchema.parse(config);
1476
- // Create and return BaseWorkerAgent instance
1477
- // Agent is not started automatically - caller must call start()
1478
- return new BaseWorkerAgent(validatedConfig);
1616
+ // Only validate with Zod schema for legacy WorkerAgentConfig
1617
+ if ('anthropicApiKey' in config) {
1618
+ const validatedConfig = WorkerAgentConfigSchema.parse(config);
1619
+ return new BaseWorkerAgent(validatedConfig);
1620
+ }
1621
+ // WorkerAgentFullConfig — skip Zod validation (BaseAgent handles its own)
1622
+ return new BaseWorkerAgent(config);
1479
1623
  }
1480
1624
  catch (error) {
1481
1625
  // Re-throw Zod validation errors with context
1482
1626
  if (error.name === 'ZodError') {
1483
1627
  logger.error(MODULE_AGENT, 'Worker agent configuration validation failed', timer.elapsed('factory'), error);
1484
- logger.error(MODULE_AGENT, ' Validation errors:', timer.elapsed('factory'));
1485
1628
  for (const issue of error.issues || []) {
1486
1629
  logger.error(MODULE_AGENT, ` - ${issue.path.join('.')}: ${issue.message}`, timer.elapsed('factory'));
1487
1630
  }