@desplega.ai/agent-swarm 1.9.0 → 1.10.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.
@@ -33,6 +33,8 @@ export interface RunnerConfig {
33
33
  defaultPrompt: string;
34
34
  /** Metadata type for log files, e.g., "worker_metadata" */
35
35
  metadataType: string;
36
+ /** Optional capabilities of the agent */
37
+ capabilities?: string[];
36
38
  }
37
39
 
38
40
  export interface RunnerOptions {
@@ -40,7 +42,9 @@ export interface RunnerOptions {
40
42
  yolo?: boolean;
41
43
  systemPrompt?: string;
42
44
  systemPromptFile?: string;
45
+ logsDir?: string;
43
46
  additionalArgs?: string[];
47
+ aiLoop?: boolean; // Use AI-based loop (old behavior)
44
48
  }
45
49
 
46
50
  interface RunClaudeIterationOptions {
@@ -51,6 +55,118 @@ interface RunClaudeIterationOptions {
51
55
  role: string;
52
56
  }
53
57
 
58
+ /** Trigger types returned by the poll API */
59
+ interface Trigger {
60
+ type: "task_assigned" | "task_offered" | "unread_mentions" | "pool_tasks_available";
61
+ taskId?: string;
62
+ task?: unknown;
63
+ mentionsCount?: number;
64
+ count?: number;
65
+ }
66
+
67
+ /** Options for polling */
68
+ interface PollOptions {
69
+ apiUrl: string;
70
+ apiKey: string;
71
+ agentId: string;
72
+ pollInterval: number;
73
+ pollTimeout: number;
74
+ }
75
+
76
+ /** Register agent via HTTP API */
77
+ async function registerAgent(opts: {
78
+ apiUrl: string;
79
+ apiKey: string;
80
+ agentId: string;
81
+ name: string;
82
+ isLead: boolean;
83
+ capabilities?: string[];
84
+ }): Promise<void> {
85
+ const headers: Record<string, string> = {
86
+ "Content-Type": "application/json",
87
+ "X-Agent-ID": opts.agentId,
88
+ };
89
+ if (opts.apiKey) {
90
+ headers.Authorization = `Bearer ${opts.apiKey}`;
91
+ }
92
+
93
+ const response = await fetch(`${opts.apiUrl}/api/agents`, {
94
+ method: "POST",
95
+ headers,
96
+ body: JSON.stringify({
97
+ name: opts.name,
98
+ isLead: opts.isLead,
99
+ capabilities: opts.capabilities,
100
+ }),
101
+ });
102
+
103
+ if (!response.ok) {
104
+ const error = await response.text();
105
+ throw new Error(`Failed to register agent: ${response.status} ${error}`);
106
+ }
107
+ }
108
+
109
+ /** Poll for triggers via HTTP API */
110
+ async function pollForTrigger(opts: PollOptions): Promise<Trigger | null> {
111
+ const startTime = Date.now();
112
+ const headers: Record<string, string> = {
113
+ "X-Agent-ID": opts.agentId,
114
+ };
115
+ if (opts.apiKey) {
116
+ headers.Authorization = `Bearer ${opts.apiKey}`;
117
+ }
118
+
119
+ while (Date.now() - startTime < opts.pollTimeout) {
120
+ try {
121
+ const response = await fetch(`${opts.apiUrl}/api/poll`, {
122
+ method: "GET",
123
+ headers,
124
+ });
125
+
126
+ if (!response.ok) {
127
+ console.warn(`[runner] Poll request failed: ${response.status}`);
128
+ await Bun.sleep(opts.pollInterval);
129
+ continue;
130
+ }
131
+
132
+ const data = (await response.json()) as { trigger: Trigger | null };
133
+ if (data.trigger) {
134
+ return data.trigger;
135
+ }
136
+ } catch (error) {
137
+ console.warn(`[runner] Poll request error: ${error}`);
138
+ }
139
+
140
+ await Bun.sleep(opts.pollInterval);
141
+ }
142
+
143
+ return null; // Timeout reached, no trigger found
144
+ }
145
+
146
+ /** Build prompt based on trigger type */
147
+ function buildPromptForTrigger(trigger: Trigger, defaultPrompt: string): string {
148
+ switch (trigger.type) {
149
+ case "task_assigned":
150
+ // Use the work-on-task command with task ID
151
+ return `/work-on-task ${trigger.taskId}`;
152
+
153
+ case "task_offered":
154
+ // Use the review-offered-task command to accept/reject
155
+ return `/review-offered-task ${trigger.taskId}`;
156
+
157
+ case "unread_mentions":
158
+ // Check messages
159
+ return "/swarm-chat";
160
+
161
+ case "pool_tasks_available":
162
+ // Let lead review and assign tasks
163
+ return defaultPrompt;
164
+
165
+ default:
166
+ return defaultPrompt;
167
+ }
168
+ }
169
+
54
170
  async function runClaudeIteration(opts: RunClaudeIterationOptions): Promise<number> {
55
171
  const { role } = opts;
56
172
  const CMD = [
@@ -139,7 +255,7 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
139
255
  setupShutdownHandlers(role);
140
256
 
141
257
  const sessionId = process.env.SESSION_ID || crypto.randomUUID().slice(0, 8);
142
- const baseLogDir = process.env.LOG_DIR || "/logs";
258
+ const baseLogDir = opts.logsDir || process.env.LOG_DIR || "/logs";
143
259
  const logDir = `${baseLogDir}/${sessionId}`;
144
260
 
145
261
  await mkdir(logDir, { recursive: true });
@@ -149,10 +265,14 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
149
265
 
150
266
  // Get agent identity and swarm URL for base prompt
151
267
  const agentId = process.env.AGENT_ID || "unknown";
268
+
269
+ const apiUrl = process.env.MCP_BASE_URL || "http://localhost:3013";
152
270
  const swarmUrl = process.env.SWARM_URL || "localhost";
153
271
 
272
+ const capabilities = config.capabilities;
273
+
154
274
  // Generate base prompt that's always included
155
- const basePrompt = getBasePrompt({ role, agentId, swarmUrl });
275
+ const basePrompt = getBasePrompt({ role, agentId, swarmUrl, capabilities });
156
276
 
157
277
  // Resolve additional system prompt: CLI flag > env var
158
278
  let additionalSystemPrompt: string | undefined;
@@ -189,67 +309,175 @@ export async function runAgent(config: RunnerConfig, opts: RunnerOptions) {
189
309
  : basePrompt;
190
310
 
191
311
  console.log(`[${role}] Starting ${role}`);
312
+ console.log(`[${role}] Agent ID: ${agentId}`);
192
313
  console.log(`[${role}] Session ID: ${sessionId}`);
193
314
  console.log(`[${role}] Log directory: ${logDir}`);
194
315
  console.log(`[${role}] YOLO mode: ${isYolo ? "enabled" : "disabled"}`);
195
316
  console.log(`[${role}] Prompt: ${prompt}`);
196
- console.log(`[${role}] Swarm URL: ${swarmUrl}`);
317
+ console.log(`[${role}] API URL: ${apiUrl}`);
318
+ console.log(`[${role}] Swarm URL: ${apiUrl}`);
197
319
  console.log(`[${role}] Base prompt: included (${basePrompt.length} chars)`);
198
320
  console.log(
199
321
  `[${role}] Additional system prompt: ${additionalSystemPrompt ? "provided" : "none"}`,
200
322
  );
201
323
  console.log(`[${role}] Total system prompt length: ${resolvedSystemPrompt.length} chars`);
202
324
 
325
+ const isAiLoop = opts.aiLoop || process.env.AI_LOOP === "true";
326
+ const apiKey = process.env.API_KEY || "";
327
+
328
+ // Constants for polling
329
+ const POLL_INTERVAL_MS = 2000; // 2 seconds between polls
330
+ const POLL_TIMEOUT_MS = 60000; // 1 minute timeout before retrying
331
+
203
332
  let iteration = 0;
204
333
 
205
- while (true) {
206
- iteration++;
207
- const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
208
- const logFile = `${logDir}/${timestamp}.jsonl`;
209
-
210
- console.log(`\n[${role}] === Iteration ${iteration} ===`);
211
- console.log(`[${role}] Logging to: ${logFile}`);
212
-
213
- const metadata = {
214
- type: metadataType,
215
- sessionId,
216
- iteration,
217
- timestamp: new Date().toISOString(),
218
- prompt,
219
- yolo: isYolo,
220
- };
221
- await Bun.write(logFile, `${JSON.stringify(metadata)}\n`);
222
-
223
- const exitCode = await runClaudeIteration({
224
- prompt,
225
- logFile,
226
- systemPrompt: resolvedSystemPrompt,
227
- additionalArgs: opts.additionalArgs,
228
- role,
229
- });
230
-
231
- if (exitCode !== 0) {
232
- const errorLog = {
233
- timestamp: new Date().toISOString(),
334
+ if (!isAiLoop) {
335
+ // NEW: Runner-level polling mode
336
+ console.log(`[${role}] Mode: runner-level polling (use --ai-loop for AI-based polling)`);
337
+
338
+ // Register agent before starting
339
+ const agentName = process.env.AGENT_NAME || `${role}-${agentId.slice(0, 8)}`;
340
+ try {
341
+ await registerAgent({
342
+ apiUrl,
343
+ apiKey,
344
+ agentId,
345
+ name: agentName,
346
+ isLead: role === "lead",
347
+ capabilities: config.capabilities,
348
+ });
349
+ console.log(`[${role}] Registered as "${agentName}" (ID: ${agentId})`);
350
+ } catch (error) {
351
+ console.error(`[${role}] Failed to register: ${error}`);
352
+ process.exit(1);
353
+ }
354
+
355
+ while (true) {
356
+ console.log(`\n[${role}] Polling for triggers...`);
357
+
358
+ const trigger = await pollForTrigger({
359
+ apiUrl,
360
+ apiKey,
361
+ agentId,
362
+ pollInterval: POLL_INTERVAL_MS,
363
+ pollTimeout: POLL_TIMEOUT_MS,
364
+ });
365
+
366
+ if (!trigger) {
367
+ console.log(`[${role}] No trigger found, polling again...`);
368
+ continue;
369
+ }
370
+
371
+ console.log(`[${role}] Trigger received: ${trigger.type}`);
372
+
373
+ // Build prompt based on trigger
374
+ const triggerPrompt = buildPromptForTrigger(trigger, prompt);
375
+
376
+ iteration++;
377
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
378
+ const logFile = `${logDir}/${timestamp}.jsonl`;
379
+
380
+ console.log(`\n[${role}] === Iteration ${iteration} ===`);
381
+ console.log(`[${role}] Logging to: ${logFile}`);
382
+ console.log(`[${role}] Prompt: ${triggerPrompt}`);
383
+
384
+ const metadata = {
385
+ type: metadataType,
386
+ sessionId,
234
387
  iteration,
235
- exitCode,
236
- error: true,
388
+ timestamp: new Date().toISOString(),
389
+ prompt: triggerPrompt,
390
+ trigger: trigger.type,
391
+ yolo: isYolo,
237
392
  };
393
+ await Bun.write(logFile, `${JSON.stringify(metadata)}\n`);
394
+
395
+ const exitCode = await runClaudeIteration({
396
+ prompt: triggerPrompt,
397
+ logFile,
398
+ systemPrompt: resolvedSystemPrompt,
399
+ additionalArgs: opts.additionalArgs,
400
+ role,
401
+ });
402
+
403
+ if (exitCode !== 0) {
404
+ const errorLog = {
405
+ timestamp: new Date().toISOString(),
406
+ iteration,
407
+ exitCode,
408
+ trigger: trigger.type,
409
+ error: true,
410
+ };
411
+
412
+ const errorsFile = `${logDir}/errors.jsonl`;
413
+ const errorsFileRef = Bun.file(errorsFile);
414
+ const existingErrors = (await errorsFileRef.exists()) ? await errorsFileRef.text() : "";
415
+ await Bun.write(errorsFile, `${existingErrors}${JSON.stringify(errorLog)}\n`);
416
+
417
+ if (!isYolo) {
418
+ console.error(`[${role}] Claude exited with code ${exitCode}. Stopping.`);
419
+ console.error(`[${role}] Error logged to: ${errorsFile}`);
420
+ process.exit(exitCode);
421
+ }
238
422
 
239
- const errorsFile = `${logDir}/errors.jsonl`;
240
- const errorsFileRef = Bun.file(errorsFile);
241
- const existingErrors = (await errorsFileRef.exists()) ? await errorsFileRef.text() : "";
242
- await Bun.write(errorsFile, `${existingErrors}${JSON.stringify(errorLog)}\n`);
243
-
244
- if (!isYolo) {
245
- console.error(`[${role}] Claude exited with code ${exitCode}. Stopping.`);
246
- console.error(`[${role}] Error logged to: ${errorsFile}`);
247
- process.exit(exitCode);
423
+ console.warn(`[${role}] Claude exited with code ${exitCode}. YOLO mode - continuing...`);
248
424
  }
249
425
 
250
- console.warn(`[${role}] Claude exited with code ${exitCode}. YOLO mode - continuing...`);
426
+ console.log(`[${role}] Iteration ${iteration} complete. Polling for next trigger...`);
251
427
  }
428
+ } else {
429
+ // Original AI-loop mode (existing behavior)
430
+ console.log(`[${role}] Mode: AI-based polling (legacy)`);
431
+
432
+ while (true) {
433
+ iteration++;
434
+ const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
435
+ const logFile = `${logDir}/${timestamp}.jsonl`;
436
+
437
+ console.log(`\n[${role}] === Iteration ${iteration} ===`);
438
+ console.log(`[${role}] Logging to: ${logFile}`);
252
439
 
253
- console.log(`[${role}] Iteration ${iteration} complete. Starting next iteration...`);
440
+ const metadata = {
441
+ type: metadataType,
442
+ sessionId,
443
+ iteration,
444
+ timestamp: new Date().toISOString(),
445
+ prompt,
446
+ yolo: isYolo,
447
+ };
448
+ await Bun.write(logFile, `${JSON.stringify(metadata)}\n`);
449
+
450
+ const exitCode = await runClaudeIteration({
451
+ prompt,
452
+ logFile,
453
+ systemPrompt: resolvedSystemPrompt,
454
+ additionalArgs: opts.additionalArgs,
455
+ role,
456
+ });
457
+
458
+ if (exitCode !== 0) {
459
+ const errorLog = {
460
+ timestamp: new Date().toISOString(),
461
+ iteration,
462
+ exitCode,
463
+ error: true,
464
+ };
465
+
466
+ const errorsFile = `${logDir}/errors.jsonl`;
467
+ const errorsFileRef = Bun.file(errorsFile);
468
+ const existingErrors = (await errorsFileRef.exists()) ? await errorsFileRef.text() : "";
469
+ await Bun.write(errorsFile, `${existingErrors}${JSON.stringify(errorLog)}\n`);
470
+
471
+ if (!isYolo) {
472
+ console.error(`[${role}] Claude exited with code ${exitCode}. Stopping.`);
473
+ console.error(`[${role}] Error logged to: ${errorsFile}`);
474
+ process.exit(exitCode);
475
+ }
476
+
477
+ console.warn(`[${role}] Claude exited with code ${exitCode}. YOLO mode - continuing...`);
478
+ }
479
+
480
+ console.log(`[${role}] Iteration ${iteration} complete. Starting next iteration...`);
481
+ }
254
482
  }
255
483
  }
@@ -1,3 +1,4 @@
1
+ import { getEnabledCapabilities } from "@/server.ts";
1
2
  import { type RunnerConfig, type RunnerOptions, runAgent } from "./runner.ts";
2
3
 
3
4
  export type WorkerOptions = RunnerOptions;
@@ -6,6 +7,7 @@ const workerConfig: RunnerConfig = {
6
7
  role: "worker",
7
8
  defaultPrompt: "/start-worker",
8
9
  metadataType: "worker_metadata",
10
+ capabilities: getEnabledCapabilities(),
9
11
  };
10
12
 
11
13
  export async function runWorker(opts: WorkerOptions) {
package/src/http.ts CHANGED
@@ -10,6 +10,7 @@ import { isInitializeRequest } from "@modelcontextprotocol/sdk/types.js";
10
10
  import { createServer } from "@/server";
11
11
  import {
12
12
  closeDb,
13
+ createAgent,
13
14
  getAgentById,
14
15
  getAgentWithTasks,
15
16
  getAllAgents,
@@ -24,8 +25,11 @@ import {
24
25
  getInboxSummary,
25
26
  getLogsByAgentId,
26
27
  getLogsByTaskId,
28
+ getOfferedTasksForAgent,
29
+ getPendingTaskForAgent,
27
30
  getServicesByAgentId,
28
31
  getTaskById,
32
+ getUnassignedTasksCount,
29
33
  postMessage,
30
34
  updateAgentStatus,
31
35
  } from "./be/db";
@@ -209,6 +213,147 @@ const httpServer = createHttpServer(async (req, res) => {
209
213
  return;
210
214
  }
211
215
 
216
+ // ============================================================================
217
+ // Runner-Level Polling Endpoints
218
+ // ============================================================================
219
+
220
+ const pathSegments = getPathSegments(req.url || "");
221
+
222
+ // POST /api/agents - Register a new agent (or return existing if already registered)
223
+ if (
224
+ req.method === "POST" &&
225
+ pathSegments[0] === "api" &&
226
+ pathSegments[1] === "agents" &&
227
+ !pathSegments[2]
228
+ ) {
229
+ // Parse request body
230
+ const chunks: Buffer[] = [];
231
+ for await (const chunk of req) {
232
+ chunks.push(chunk);
233
+ }
234
+ const body = JSON.parse(Buffer.concat(chunks).toString());
235
+
236
+ // Validate required fields
237
+ if (!body.name || typeof body.name !== "string") {
238
+ res.writeHead(400, { "Content-Type": "application/json" });
239
+ res.end(JSON.stringify({ error: "Missing or invalid 'name' field" }));
240
+ return;
241
+ }
242
+
243
+ // Use X-Agent-ID header if provided, otherwise generate new UUID
244
+ const agentId = myAgentId || crypto.randomUUID();
245
+
246
+ // Use transaction to ensure atomicity of check-and-create/update
247
+ const result = getDb().transaction(() => {
248
+ // Check if agent already exists
249
+ const existingAgent = getAgentById(agentId);
250
+ if (existingAgent) {
251
+ // Update status to idle if offline
252
+ if (existingAgent.status === "offline") {
253
+ updateAgentStatus(existingAgent.id, "idle");
254
+ }
255
+ return { agent: getAgentById(agentId), created: false };
256
+ }
257
+
258
+ // Create new agent
259
+ const agent = createAgent({
260
+ id: agentId,
261
+ name: body.name,
262
+ isLead: body.isLead ?? false,
263
+ status: "idle",
264
+ description: body.description,
265
+ role: body.role,
266
+ capabilities: body.capabilities,
267
+ });
268
+
269
+ return { agent, created: true };
270
+ })();
271
+
272
+ res.writeHead(result.created ? 201 : 200, { "Content-Type": "application/json" });
273
+ res.end(JSON.stringify(result.agent));
274
+ return;
275
+ }
276
+
277
+ // GET /api/poll - Poll for triggers (tasks, mentions, etc.)
278
+ if (req.method === "GET" && pathSegments[0] === "api" && pathSegments[1] === "poll") {
279
+ if (!myAgentId) {
280
+ res.writeHead(400, { "Content-Type": "application/json" });
281
+ res.end(JSON.stringify({ error: "Missing X-Agent-ID header" }));
282
+ return;
283
+ }
284
+
285
+ // Use transaction for consistent reads across all trigger checks
286
+ const result = getDb().transaction(() => {
287
+ const agent = getAgentById(myAgentId);
288
+ if (!agent) {
289
+ return { error: "Agent not found", status: 404 };
290
+ }
291
+
292
+ // Check for offered tasks first (highest priority)
293
+ const offeredTasks = getOfferedTasksForAgent(myAgentId);
294
+ const firstOfferedTask = offeredTasks[0];
295
+ if (firstOfferedTask) {
296
+ return {
297
+ trigger: {
298
+ type: "task_offered",
299
+ taskId: firstOfferedTask.id,
300
+ task: firstOfferedTask,
301
+ },
302
+ };
303
+ }
304
+
305
+ // Check for pending tasks (assigned directly to this agent)
306
+ const pendingTask = getPendingTaskForAgent(myAgentId);
307
+ if (pendingTask) {
308
+ return {
309
+ trigger: {
310
+ type: "task_assigned",
311
+ taskId: pendingTask.id,
312
+ task: pendingTask,
313
+ },
314
+ };
315
+ }
316
+
317
+ // For lead agents, check for unread mentions
318
+ if (agent.isLead) {
319
+ const inbox = getInboxSummary(myAgentId);
320
+ if (inbox.mentionsCount > 0) {
321
+ return {
322
+ trigger: {
323
+ type: "unread_mentions",
324
+ mentionsCount: inbox.mentionsCount,
325
+ },
326
+ };
327
+ }
328
+
329
+ // Check for tasks needing assignment (unassigned tasks in pool)
330
+ const unassignedCount = getUnassignedTasksCount();
331
+ if (unassignedCount > 0) {
332
+ return {
333
+ trigger: {
334
+ type: "pool_tasks_available",
335
+ count: unassignedCount,
336
+ },
337
+ };
338
+ }
339
+ }
340
+
341
+ // No trigger found
342
+ return { trigger: null };
343
+ })();
344
+
345
+ // Handle error case
346
+ if ("error" in result) {
347
+ res.writeHead(result.status ?? 500, { "Content-Type": "application/json" });
348
+ res.end(JSON.stringify({ error: result.error }));
349
+ return;
350
+ }
351
+
352
+ res.writeHead(200, { "Content-Type": "application/json" });
353
+ res.end(JSON.stringify(result));
354
+ return;
355
+ }
356
+
212
357
  // GET /ecosystem - Generate PM2 ecosystem config for agent's services
213
358
  if (req.method === "GET" && req.url === "/ecosystem") {
214
359
  if (!myAgentId) {
@@ -249,7 +394,6 @@ const httpServer = createHttpServer(async (req, res) => {
249
394
  // REST API Endpoints (for frontend dashboard)
250
395
  // ============================================================================
251
396
 
252
- const pathSegments = getPathSegments(req.url || "");
253
397
  const queryParams = parseQueryParams(req.url || "");
254
398
 
255
399
  // GET /api/agents - List all agents (optionally with tasks)
@@ -38,9 +38,9 @@ As a worker agent of the swarm, you are responsible for executing tasks assigned
38
38
  #### Useful tools for workers
39
39
 
40
40
  - poll-task: Automatically waits for new tasks assigned by the lead or claimed from the unassigned pool.
41
- - read-messages: To read messages sent to you by the lead or other workers, by default when a task is found, it will auto-assign it to you.
42
41
  - store-progress: Critical tool to save your work and progress on tasks!
43
42
  - task-action: Manage tasks with different actions like claim, release, accept, reject, and complete.
43
+ - read-messages: If communications enabled, use it to read messages sent to you by the lead or other workers, by default when a task is found, it will auto-assign it to you.
44
44
  `;
45
45
 
46
46
  const BASE_PROMPT_FILESYSTEM = `
@@ -57,8 +57,7 @@ const BASE_PROMPT_FILESYSTEM = `
57
57
  const BASE_PROMPT_GUIDELINES = `
58
58
  ### Agent Swarm Operational Guidelines
59
59
 
60
- - Communication: Use the /swarm-chat command to communicate with other agents and the human operator. Keep everyone informed of your progress and any issues you encounter. It will allow you to see the internal Slack-like communication platform to: create and view channels (or DMs), read messages, and post messages.
61
- -
60
+ - Follow the communicationes ettiquette and protocols established for the swarm. If not stated, do not use the chat features, focus on your tasks.
62
61
  `;
63
62
 
64
63
  const BASE_PROMPT_SYSTEM = `
@@ -67,7 +66,9 @@ const BASE_PROMPT_SYSTEM = `
67
66
  You have a full Ubuntu environment with some packages pre-installed: node, bun, python3, curl, wget, git, gh, jq, etc.
68
67
 
69
68
  If you need to install additional packages, use "sudo apt-get install {package_name}".
69
+ `;
70
70
 
71
+ const BASE_PROMPT_SERVICES = `
71
72
  ### External Swarm Access & Service Registry
72
73
 
73
74
  Port 3000 is exposed for web apps or APIs. Use PM2 for robust process management:
@@ -108,6 +109,7 @@ export type BasePromptArgs = {
108
109
  role: string;
109
110
  agentId: string;
110
111
  swarmUrl: string;
112
+ capabilities?: string[];
111
113
  };
112
114
 
113
115
  export const getBasePrompt = (args: BasePromptArgs): string => {
@@ -127,5 +129,17 @@ export const getBasePrompt = (args: BasePromptArgs): string => {
127
129
  prompt += BASE_PROMPT_GUIDELINES;
128
130
  prompt += BASE_PROMPT_SYSTEM.replace("{swarmUrl}", swarmUrl);
129
131
 
132
+ if (!args.capabilities || args.capabilities.includes("services")) {
133
+ prompt += BASE_PROMPT_SERVICES;
134
+ }
135
+
136
+ if (args.capabilities) {
137
+ prompt += `
138
+ ### Capabilities enabled for this agent:
139
+
140
+ - ${args.capabilities.join("\n- ")}
141
+ `;
142
+ }
143
+
130
144
  return prompt;
131
145
  };