@toolpack-sdk/agents 2.0.0-alpha.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.
package/README.md ADDED
@@ -0,0 +1,827 @@
1
+ # @toolpack-sdk/agents
2
+
3
+ Build production-ready AI agents with channels, workflows, and event-driven architecture.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/@toolpack-sdk/agents/alpha.svg?label=alpha)](https://www.npmjs.com/package/@toolpack-sdk/agents)
6
+ [![License: Apache 2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
7
+
8
+ > **Pre-release:** This package is currently in alpha (`2.0.0-alpha.1`). APIs may change before the stable `2.0.0` release.
9
+
10
+ ## Features
11
+
12
+ - **4 Built-in Agents** — Research, Coding, Data, Browser
13
+ - **7 Channel Types** — Slack, Telegram, Discord, Email, SMS, Webhook, Scheduled
14
+ - **Event-Driven** — Full lifecycle hooks and events
15
+ - **Human-in-the-Loop** — `ask()` support for two-way channels
16
+ - **Knowledge Integration** — Built-in RAG support with knowledge bases
17
+ - **Type-Safe** — Full TypeScript support
18
+ - **Production-Ready** — 573 tests passing
19
+
20
+ ## Installation
21
+
22
+ ```bash
23
+ npm install @toolpack-sdk/agents@alpha
24
+ ```
25
+
26
+ ## Stable API (Phase 4)
27
+
28
+ The following APIs are stable and follow semantic versioning. Breaking changes will require a major version bump:
29
+
30
+ - `BaseAgent` — Abstract base class for all agents
31
+ - `BaseChannel` — Abstract base class for all channels
32
+ - `AgentRegistry` — Registry for agents and channels
33
+ - `AgentInput`, `AgentResult`, `AgentOutput` — Core data structures
34
+ - `AgentTransport`, `LocalTransport`, `JsonRpcTransport` — Transport layer
35
+ - `AgentJsonRpcServer` — JSON-RPC server for hosting agents
36
+ - `AgentError` — Error class for agent failures
37
+
38
+ ### Version Policy
39
+
40
+ - **Major (X.y.z)** — Breaking API changes
41
+ - **Minor (x.Y.z)** — New features, backward compatible
42
+ - **Patch (x.y.Z)** — Bug fixes, backward compatible
43
+
44
+ ## Quick Start
45
+
46
+ ```typescript
47
+ import { BaseAgent, AgentRegistry, SlackChannel } from '@toolpack-sdk/agents';
48
+
49
+ // 1. Create a channel
50
+ const slack = new SlackChannel({
51
+ name: 'slack',
52
+ token: process.env.SLACK_BOT_TOKEN,
53
+ signingSecret: process.env.SLACK_SIGNING_SECRET,
54
+ channel: '#support',
55
+ });
56
+
57
+ // 2. Create an agent (channels live on the agent)
58
+ class SupportAgent extends BaseAgent {
59
+ name = 'support-agent';
60
+ description = 'Customer support agent';
61
+ mode = 'chat';
62
+ channels = [slack];
63
+
64
+ async invokeAgent(input) {
65
+ const result = await this.run(input.message);
66
+ return result;
67
+ }
68
+ }
69
+
70
+ // 3. Single-agent: start directly
71
+ const agent = new SupportAgent({ apiKey: process.env.ANTHROPIC_API_KEY });
72
+ await agent.start();
73
+
74
+ // OR multi-agent: use AgentRegistry
75
+ // const registry = new AgentRegistry([agent]);
76
+ // await registry.start();
77
+ ```
78
+
79
+ ## Built-in Agents
80
+
81
+ ### ResearchAgent
82
+ Web research for summarization, fact-finding, and trend monitoring.
83
+
84
+ ```typescript
85
+ import { ResearchAgent } from '@toolpack-sdk/agents';
86
+
87
+ const agent = new ResearchAgent({ apiKey: process.env.ANTHROPIC_API_KEY });
88
+ const result = await agent.invokeAgent({
89
+ message: 'Summarize recent AI developments',
90
+ });
91
+ ```
92
+
93
+ **Mode:** `agent` | **Tools:** `web.search`, `web.fetch`, `web.scrape`
94
+
95
+ ### CodingAgent
96
+ Code generation, refactoring, debugging, and test writing.
97
+
98
+ ```typescript
99
+ import { CodingAgent } from '@toolpack-sdk/agents';
100
+
101
+ const agent = new CodingAgent({ apiKey: process.env.ANTHROPIC_API_KEY });
102
+ const result = await agent.invokeAgent({
103
+ message: 'Refactor the auth module',
104
+ });
105
+ ```
106
+
107
+ **Mode:** `coding` | **Tools:** `fs.*`, `coding.*`, `git.*`, `exec.*`
108
+
109
+ ### DataAgent
110
+ Database queries, reporting, data analysis, and CSV generation.
111
+
112
+ ```typescript
113
+ import { DataAgent } from '@toolpack-sdk/agents';
114
+
115
+ const agent = new DataAgent({ apiKey: process.env.ANTHROPIC_API_KEY });
116
+ const result = await agent.invokeAgent({
117
+ message: 'Generate weekly signups report',
118
+ });
119
+ ```
120
+
121
+ **Mode:** `agent` | **Tools:** `db.*`, `fs.*`, `http.*`
122
+
123
+ ### BrowserAgent
124
+ Web browsing, form interaction, and content extraction.
125
+
126
+ ```typescript
127
+ import { BrowserAgent } from '@toolpack-sdk/agents';
128
+
129
+ const agent = new BrowserAgent({ apiKey: process.env.ANTHROPIC_API_KEY });
130
+ const result = await agent.invokeAgent({
131
+ message: 'Extract prices from acme.com/products',
132
+ });
133
+ ```
134
+
135
+ **Mode:** `chat` | **Tools:** `web.fetch`, `web.screenshot`, `web.extract_links`
136
+
137
+ ## Channels
138
+
139
+ Channels connect agents to external services. They can be **two-way** (receive messages, support `ask()`) or **trigger-only** (send only, no `ask()` support).
140
+
141
+ ### SlackChannel (Two-way)
142
+
143
+ ```typescript
144
+ const slack = new SlackChannel({
145
+ name: 'slack-support',
146
+ token: process.env.SLACK_BOT_TOKEN,
147
+ signingSecret: process.env.SLACK_SIGNING_SECRET,
148
+ channel: '#support',
149
+ port: 3000,
150
+ });
151
+ ```
152
+
153
+ ### TelegramChannel (Two-way)
154
+
155
+ ```typescript
156
+ const telegram = new TelegramChannel({
157
+ name: 'telegram-bot',
158
+ token: process.env.TELEGRAM_BOT_TOKEN,
159
+ });
160
+ ```
161
+
162
+ ### WebhookChannel (Two-way)
163
+
164
+ ```typescript
165
+ const webhook = new WebhookChannel({
166
+ name: 'github-webhook',
167
+ path: '/webhook/github',
168
+ port: 3000,
169
+ });
170
+ ```
171
+
172
+ ### ScheduledChannel (Trigger-only)
173
+
174
+ Runs agents on cron schedules. Supports full cron expressions.
175
+
176
+ ```typescript
177
+ const scheduler = new ScheduledChannel({
178
+ name: 'daily-report',
179
+ cron: '0 9 * * 1-5', // 9am weekdays
180
+ notify: 'webhook:https://hooks.example.com/daily-report',
181
+ message: 'Generate daily report',
182
+ });
183
+ // For Slack delivery, attach a named SlackChannel to the same agent and
184
+ // call `this.sendTo('<slackChannelName>', output)` from within `run()`.
185
+ ```
186
+
187
+ ### DiscordChannel (Two-way)
188
+
189
+ ```typescript
190
+ const discord = new DiscordChannel({
191
+ name: 'discord-bot',
192
+ token: process.env.DISCORD_BOT_TOKEN,
193
+ guildId: 'your-guild-id',
194
+ channelId: 'your-channel-id',
195
+ });
196
+ ```
197
+
198
+ ### EmailChannel (Outbound-only)
199
+
200
+ ```typescript
201
+ const email = new EmailChannel({
202
+ name: 'email-alerts',
203
+ from: 'bot@acme.com',
204
+ to: 'team@acme.com',
205
+ smtp: {
206
+ host: 'smtp.gmail.com',
207
+ port: 587,
208
+ auth: { user: 'bot@acme.com', pass: process.env.SMTP_PASSWORD },
209
+ },
210
+ });
211
+ ```
212
+
213
+ ### SMSChannel (Configurable)
214
+
215
+ Two-way when `webhookPath` is set, outbound-only otherwise.
216
+
217
+ ```typescript
218
+ // Two-way
219
+ const sms = new SMSChannel({
220
+ name: 'sms-alerts',
221
+ accountSid: process.env.TWILIO_ACCOUNT_SID,
222
+ authToken: process.env.TWILIO_AUTH_TOKEN,
223
+ from: '+1234567890',
224
+ webhookPath: '/sms/webhook',
225
+ port: 3000,
226
+ });
227
+
228
+ // Outbound-only
229
+ const smsOutbound = new SMSChannel({
230
+ name: 'sms-notifications',
231
+ accountSid: process.env.TWILIO_ACCOUNT_SID,
232
+ authToken: process.env.TWILIO_AUTH_TOKEN,
233
+ from: '+1234567890',
234
+ to: '+0987654321',
235
+ });
236
+ ```
237
+
238
+ ## Creating Custom Agents
239
+
240
+ Extend `BaseAgent` to create custom agents:
241
+
242
+ ```typescript
243
+ import { BaseAgent } from '@toolpack-sdk/agents';
244
+
245
+ class MyAgent extends BaseAgent {
246
+ name = 'my-agent';
247
+ description = 'My custom agent';
248
+ mode = 'agent';
249
+
250
+ async invokeAgent(input) {
251
+ // Process the message
252
+ const result = await this.run(input.message);
253
+
254
+ // Send to a channel
255
+ await this.sendTo('slack', result.output);
256
+
257
+ return result;
258
+ }
259
+ }
260
+ ```
261
+
262
+ ## Human-in-the-Loop
263
+
264
+ Use `ask()` to pause execution and request human input (two-way channels only). `ask()` sends the question and returns immediately — the user's answer arrives on the **next** invocation, where you check `getPendingAsk()`.
265
+
266
+ ```typescript
267
+ class ApprovalAgent extends BaseAgent {
268
+ name = 'approval-agent';
269
+ mode = 'agent';
270
+
271
+ async invokeAgent(input) {
272
+ // Turn 2: check if we are waiting for an answer
273
+ const pending = this.getPendingAsk(input.conversationId);
274
+ if (pending && input.message) {
275
+ return this.handlePendingAsk(
276
+ pending,
277
+ input.message,
278
+ async (answer) => {
279
+ if (answer.toLowerCase() === 'yes') {
280
+ await this.sendTo('slack', 'Draft approved!');
281
+ return { output: 'Draft approved and sent.' };
282
+ }
283
+ return { output: 'Draft discarded.' };
284
+ },
285
+ );
286
+ }
287
+
288
+ // Turn 1: do some work, then ask for approval
289
+ const draft = await this.run(`Draft a response to: ${input.message}`);
290
+ return this.ask(`Here is my draft:\n\n${draft.output}\n\nApprove? (yes/no)`);
291
+ }
292
+ }
293
+ ```
294
+
295
+ **Note:** `ask()` throws if called from trigger-only channels (ScheduledChannel, EmailChannel). It requires a registry — use `AgentRegistry`, not standalone `agent.start()`.
296
+
297
+ ## Conversation History
298
+
299
+ Store conversation history separately from domain knowledge:
300
+
301
+ ```typescript
302
+ import { InMemoryConversationStore } from '@toolpack-sdk/agents';
303
+
304
+ class SupportAgent extends BaseAgent {
305
+ // In-memory store (development/single-process)
306
+ conversationHistory = new InMemoryConversationStore();
307
+
308
+ async invokeAgent(input) {
309
+ // History is automatically loaded before AI call
310
+ // and stored after response
311
+ const result = await this.run(input.message);
312
+ return result;
313
+ }
314
+ }
315
+ ```
316
+
317
+ **Features:**
318
+ - Auto-assembles conversation history before each AI call (up to 3 000-token budget by default)
319
+ - Auto-stores user and assistant messages via the capture interceptor
320
+ - Auto-trims to `maxMessagesPerConversation` limit (default: 500)
321
+ - Zero-config in-memory mode for development
322
+ - `conversation_search` tool is automatically provided as a request-scoped tool whenever a `conversationId` is active
323
+
324
+ **Memory model:**
325
+ Agent memory is per-conversation by default. The `conversation_search` tool is bound at invocation time to the current conversation — the LLM cannot override this scope, and turns from other conversations are structurally unreachable. Use `knowledge_add` to promote durable facts that should persist across conversations; knowledge is the only cross-conversation bridge.
326
+
327
+ ## Knowledge Integration
328
+
329
+ Integrate knowledge bases for RAG (domain knowledge, not conversation history).
330
+ Knowledge is configured at the SDK level and automatically available to all agents:
331
+
332
+ ```typescript
333
+ import { Toolpack } from 'toolpack-sdk';
334
+ import { Knowledge, MemoryProvider } from '@toolpack-sdk/knowledge';
335
+
336
+ // Configure knowledge at SDK level
337
+ const knowledge = await Knowledge.create({
338
+ provider: new MemoryProvider(),
339
+ });
340
+
341
+ const toolpack = await Toolpack.init({
342
+ provider: 'openai',
343
+ knowledge, // Available to all agents using this toolpack
344
+ });
345
+
346
+ class SmartAgent extends BaseAgent {
347
+ async invokeAgent(input) {
348
+ // Both `knowledge_search` and `knowledge_add` tools are
349
+ // automatically available as request-scoped tools.
350
+ // The AI can use them to retrieve or store information.
351
+ const result = await this.run(input.message);
352
+ return result;
353
+ }
354
+ }
355
+ ```
356
+
357
+ **Available Tools:**
358
+ - `knowledge_search` — Search the knowledge base for relevant information
359
+ - `knowledge_add` — Add new information to the knowledge base at runtime
360
+
361
+ The SDK automatically injects usage guidance into the system prompt when these tools are available.
362
+
363
+ **Knowledge as the cross-conversation bridge:**
364
+
365
+ `knowledge_add` is the *only* path by which information crosses conversation boundaries. Conversation history is scoped to the current conversation and inaccessible elsewhere; anything promoted via `knowledge_add` becomes available in all future conversations for that agent.
366
+
367
+ Promote when:
368
+ - A task surfaces a fact useful beyond the current conversation
369
+ - A user states a durable preference
370
+ - A decision is made that future conversations should respect
371
+
372
+ Do **not** promote:
373
+ - Routine task outputs (e.g., "answered a weather question")
374
+ - Context that is specific to this conversation only
375
+ - Confidential information whose visibility should remain inside the current conversation
376
+
377
+ Because every promotion is an explicit agent action visible in traces, the knowledge base stays auditable and intentional. If you need per-entry visibility controls (e.g., scoping a knowledge entry to a subset of channels), that is a future extension — for now, apply developer discipline: only promote what every future conversation is permitted to see.
378
+
379
+ ## Multi-Channel Routing
380
+
381
+ Send output to multiple channels:
382
+
383
+ ```typescript
384
+ class MultiChannelAgent extends BaseAgent {
385
+ async invokeAgent(input) {
386
+ const result = await this.run(input.message);
387
+
388
+ await this.sendTo('slack', result.output);
389
+ await this.sendTo('email-team', result.output);
390
+ await this.sendTo('sms-alerts', 'Task done!');
391
+
392
+ return result;
393
+ }
394
+ }
395
+ ```
396
+
397
+ ## Agent Events
398
+
399
+ Listen to agent lifecycle events:
400
+
401
+ ```typescript
402
+ const agent = new MyAgent(sdk);
403
+
404
+ agent.on('agent:start', (input) => {
405
+ console.log('Agent started:', input.message);
406
+ });
407
+
408
+ agent.on('agent:complete', (result) => {
409
+ console.log('Agent completed:', result.output);
410
+ });
411
+
412
+ agent.on('agent:error', (error) => {
413
+ console.error('Agent error:', error);
414
+ });
415
+ ```
416
+
417
+ ## Extending Built-in Agents
418
+
419
+ Customize built-in agents with your own prompts and logic:
420
+
421
+ ```typescript
422
+ import { ResearchAgent } from '@toolpack-sdk/agents';
423
+ import { AGENT_MODE } from 'toolpack-sdk';
424
+
425
+ class FintechResearchAgent extends ResearchAgent {
426
+ mode = {
427
+ ...AGENT_MODE,
428
+ systemPrompt: 'You are a fintech research specialist. Always cite sources and flag regulatory implications.',
429
+ };
430
+
431
+ async onComplete(result) {
432
+ // Notify team
433
+ await this.sendTo('slack-research', result.output);
434
+ }
435
+ }
436
+
437
+ // Knowledge is configured at SDK level, not on the agent.
438
+ // The AI can use `knowledge_add` to store information during execution.
439
+ const toolpack = await Toolpack.init({
440
+ provider: 'openai',
441
+ knowledge: await Knowledge.create({ provider: new MemoryProvider() }),
442
+ });
443
+ ```
444
+
445
+ ## Peer Dependencies
446
+
447
+ The following are optional peer dependencies. Install only what you need:
448
+
449
+ ```bash
450
+ # For DiscordChannel
451
+ npm install discord.js
452
+
453
+ # For EmailChannel
454
+ npm install nodemailer
455
+
456
+ # For SMSChannel
457
+ npm install twilio
458
+ ```
459
+
460
+ ## API Reference
461
+
462
+ ### BaseAgent
463
+
464
+ ```typescript
465
+ abstract class BaseAgent {
466
+ abstract name: string;
467
+ abstract description: string;
468
+ abstract mode: ModeConfig | string;
469
+
470
+ // Core method to implement
471
+ abstract invokeAgent(input: AgentInput): Promise<AgentResult>;
472
+
473
+ // Built-in methods
474
+ protected run(message: string, options?: AgentRunOptions, context?: { conversationId?: string }): Promise<AgentResult>;
475
+ protected sendTo(channelName: string, message: string): Promise<void>;
476
+ protected ask(question: string, options?: { context?: Record<string, unknown>; maxRetries?: number; expiresIn?: number }): Promise<AgentResult>;
477
+ protected getPendingAsk(conversationId?: string): PendingAsk | null;
478
+ }
479
+ ```
480
+
481
+ ### AgentRegistry
482
+
483
+ ```typescript
484
+ class AgentRegistry {
485
+ constructor(agents: BaseAgent[]);
486
+ start(): Promise<void>;
487
+ stop(): Promise<void>;
488
+ sendTo(channelName: string, output: AgentOutput): Promise<void>;
489
+ getAgent(name: string): AgentInstance | undefined;
490
+ getChannel(name: string): ChannelInterface | undefined;
491
+ invoke(agentName: string, input: AgentInput): Promise<AgentResult>;
492
+ }
493
+ ```
494
+
495
+ ### Channels
496
+
497
+ All channels extend `BaseChannel`:
498
+
499
+ ```typescript
500
+ abstract class BaseChannel {
501
+ abstract readonly isTriggerChannel: boolean;
502
+ name?: string;
503
+
504
+ abstract listen(): void;
505
+ abstract send(output: AgentOutput): Promise<void>;
506
+ abstract normalize(incoming: unknown): AgentInput;
507
+ onMessage(handler: (input: AgentInput) => Promise<void>): void;
508
+ }
509
+ ```
510
+
511
+ ## Agent-to-Agent Messaging
512
+
513
+ Agents can delegate tasks to other agents without tight coupling.
514
+
515
+ ### Local Delegation (Same Process)
516
+
517
+ ```typescript
518
+ import { AgentRegistry, BaseAgent } from '@toolpack-sdk/agents';
519
+ import type { AgentInput, AgentResult } from '@toolpack-sdk/agents';
520
+
521
+ class EmailAgent extends BaseAgent {
522
+ name = 'email-agent';
523
+ description = 'Sends email reports';
524
+ mode = 'chat';
525
+ channels = [slack]; // channels are class properties, not constructor args
526
+
527
+ async invokeAgent(input: AgentInput): Promise<AgentResult> {
528
+ // Delegate to DataAgent and wait for result
529
+ const report = await this.delegateAndWait('data-agent', {
530
+ message: 'Generate weekly leads report',
531
+ intent: 'generate_report',
532
+ });
533
+
534
+ return {
535
+ output: `Email sent with report: ${report.output}`,
536
+ };
537
+ }
538
+ }
539
+
540
+ const emailAgent = new EmailAgent({ apiKey: process.env.ANTHROPIC_API_KEY! });
541
+ const dataAgent = new DataAgent({ apiKey: process.env.ANTHROPIC_API_KEY! });
542
+ const registry = new AgentRegistry([emailAgent, dataAgent]);
543
+ await registry.start();
544
+ ```
545
+
546
+ ### Cross-Process Delegation (JSON-RPC)
547
+
548
+ **Server (Host Agents):**
549
+ ```typescript
550
+ import { AgentJsonRpcServer } from '@toolpack-sdk/agents';
551
+
552
+ const server = new AgentJsonRpcServer({ port: 3000 });
553
+ server.registerAgent('data-agent', new DataAgent({ apiKey: process.env.ANTHROPIC_API_KEY! }));
554
+ server.registerAgent('research-agent', new ResearchAgent({ apiKey: process.env.ANTHROPIC_API_KEY! }));
555
+ server.listen();
556
+ ```
557
+
558
+ **Client (Call Remote Agents):**
559
+ ```typescript
560
+ import { AgentRegistry, JsonRpcTransport, BaseAgent } from '@toolpack-sdk/agents';
561
+ import type { AgentInput, AgentResult } from '@toolpack-sdk/agents';
562
+
563
+ const emailAgent = new EmailAgent({ apiKey: process.env.ANTHROPIC_API_KEY! });
564
+ const registry = new AgentRegistry([emailAgent], {
565
+ transport: new JsonRpcTransport({
566
+ agents: {
567
+ 'data-agent': 'http://localhost:3000',
568
+ 'research-agent': 'http://remote-server:3000',
569
+ }
570
+ })
571
+ });
572
+
573
+ // Inside EmailAgent
574
+ class EmailAgent extends BaseAgent {
575
+ async invokeAgent(input: AgentInput): Promise<AgentResult> {
576
+ // Can now delegate to remote agents
577
+ const report = await this.delegateAndWait('data-agent', {
578
+ message: 'Generate report'
579
+ });
580
+ return { output: `Email sent with: ${report.output}` };
581
+ }
582
+ }
583
+ ```
584
+
585
+ ### Delegation Methods
586
+
587
+ - **`delegate(agentName, input)`** - Fire-and-forget, returns immediately
588
+ - **`delegateAndWait(agentName, input)`** - Waits for result, returns `AgentResult`
589
+
590
+ ## Registry
591
+
592
+ Discover and publish community-built agents.
593
+
594
+ ### Finding Agents
595
+
596
+ ```typescript
597
+ import { searchRegistry } from '@toolpack-sdk/agents/registry';
598
+
599
+ // Search all agents
600
+ const results = await searchRegistry();
601
+
602
+ // Search by keyword
603
+ const results = await searchRegistry({ keyword: 'fintech' });
604
+
605
+ // Filter by category
606
+ const results = await searchRegistry({ category: 'research' });
607
+
608
+ // Display results
609
+ for (const agent of results.agents) {
610
+ console.log(`${agent.name}: ${agent.toolpack?.description || agent.description}`);
611
+ console.log(` Install: npm install ${agent.name}`);
612
+ }
613
+ ```
614
+
615
+ ### Publishing an Agent
616
+
617
+ Add the `toolpack` metadata to your `package.json`:
618
+
619
+ ```json
620
+ {
621
+ "name": "toolpack-agent-fintech-research",
622
+ "version": "1.0.0",
623
+ "keywords": ["toolpack-agent"],
624
+ "toolpack": {
625
+ "agent": true,
626
+ "category": "research",
627
+ "description": "Research agent focused on fintech news and regulatory updates",
628
+ "tags": ["fintech", "news", "research"]
629
+ }
630
+ }
631
+ ```
632
+
633
+ Requirements:
634
+ - Must include `"toolpack-agent"` in `keywords`
635
+ - Must have `"toolpack": { "agent": true }` in package.json
636
+ - Agent class must extend `BaseAgent`
637
+
638
+ ## Error Handling
639
+
640
+ ### Error Types
641
+
642
+ | Error | Cause | Resolution |
643
+ |-------|-------|------------|
644
+ | `AgentError` | Generic agent failure | Check error message for details |
645
+ | `AgentError` (delegate) | Agent not registered | Ensure agent is registered with `AgentRegistry` |
646
+ | `AgentError` (transport) | Transport misconfiguration | Verify transport config and agent URLs |
647
+ | `RegistryError` | NPM registry failure | Check network connection and registry URL |
648
+
649
+ ### Handling Errors
650
+
651
+ ```typescript
652
+ import { AgentError } from '@toolpack-sdk/agents';
653
+
654
+ try {
655
+ const result = await agent.invokeAgent({ message: 'Hello' });
656
+ } catch (error) {
657
+ if (error instanceof AgentError) {
658
+ // Agent-specific error
659
+ console.error('Agent failed:', error.message);
660
+ } else {
661
+ // Unknown error
662
+ console.error('Unexpected error:', error);
663
+ }
664
+ }
665
+ ```
666
+
667
+ ### Common Issues
668
+
669
+ **Agent not found during delegation**
670
+ ```
671
+ Agent "data-agent" not found in registry. Available agents: email-agent, browser-agent
672
+ ```
673
+ → Ensure the target agent is registered in `AgentRegistry`.
674
+
675
+ **Transport configuration error**
676
+ ```
677
+ No transport configured for delegation
678
+ ```
679
+ → Use `AgentRegistry` with `LocalTransport` (default) or configure `JsonRpcTransport` for cross-process communication.
680
+
681
+ **JSON-RPC connection failure**
682
+ ```
683
+ Failed to invoke agent "data-agent" at http://localhost:3000: fetch failed
684
+ ```
685
+ → Verify the JSON-RPC server is running and the URL/port is correct.
686
+
687
+ ## Interceptors
688
+
689
+ Interceptors are composable middleware that run before `invokeAgent`. They can filter, enrich, classify, or short-circuit incoming messages. All built-ins are opt-in — none run unless you explicitly list them.
690
+
691
+ Import from the dedicated subpath:
692
+
693
+ ```typescript
694
+ import {
695
+ createNoiseFilterInterceptor,
696
+ createRateLimitInterceptor,
697
+ createSelfFilterInterceptor,
698
+ // ...
699
+ } from '@toolpack-sdk/agents/interceptors';
700
+ ```
701
+
702
+ ### Writing a Custom Interceptor
703
+
704
+ ```typescript
705
+ import type { Interceptor } from '@toolpack-sdk/agents/interceptors';
706
+
707
+ const myInterceptor: Interceptor = async (input, ctx, next) => {
708
+ if (shouldIgnore(input)) {
709
+ return ctx.skip(); // End the chain silently — no reply sent
710
+ }
711
+ const result = await next(); // Continue to next interceptor or agent
712
+ return result;
713
+ };
714
+
715
+ class MyAgent extends BaseAgent {
716
+ interceptors = [myInterceptor];
717
+ }
718
+ ```
719
+
720
+ ### Registering Interceptors
721
+
722
+ ```typescript
723
+ import {
724
+ createNoiseFilterInterceptor,
725
+ createRateLimitInterceptor,
726
+ } from '@toolpack-sdk/agents/interceptors';
727
+
728
+ class MyAgent extends BaseAgent {
729
+ name = 'my-agent';
730
+ description = 'My agent';
731
+ mode = 'chat';
732
+
733
+ interceptors = [
734
+ createNoiseFilterInterceptor({ denySubtypes: ['message_changed', 'message_deleted'] }),
735
+ createRateLimitInterceptor({
736
+ getKey: (input) => input.participant?.id ?? 'anon',
737
+ tokensPerInterval: 5,
738
+ interval: 60000, // 5 messages per minute per user
739
+ }),
740
+ ];
741
+
742
+ async invokeAgent(input) {
743
+ return this.run(input.message);
744
+ }
745
+ }
746
+ ```
747
+
748
+ ### Built-in Interceptors
749
+
750
+ | Interceptor | Purpose |
751
+ |---|---|
752
+ | `createNoiseFilterInterceptor` | Drop messages by subtype (edits, deletes, bot messages) |
753
+ | `createEventDedupInterceptor` | Drop duplicate events (Slack retries, webhook redeliveries) |
754
+ | `createSelfFilterInterceptor` | Drop the agent's own messages (infinite loop guard) |
755
+ | `createRateLimitInterceptor` | Token-bucket rate limiting per user or conversation |
756
+ | `createAddressCheckInterceptor` | Rule-based address detection (@mention, vocative, direct message) |
757
+ | `createIntentClassifierInterceptor` | LLM-based intent classification for ambiguous address checks |
758
+ | `createParticipantResolverInterceptor` | Resolve participant identity from platform user ID |
759
+ | `createCaptureInterceptor` | Persist inbound and outbound messages to conversation history (auto-registered) |
760
+ | `createDepthGuardInterceptor` | Reject delegation chains that exceed a configured depth |
761
+ | `createTracerInterceptor` | Structured logging of each chain hop for debugging |
762
+
763
+ ## Capabilities
764
+
765
+ Capability agents are headless agents with no channels. They are invoked by interceptors or other agents for specific cross-cutting concerns.
766
+
767
+ Import from the dedicated subpath:
768
+
769
+ ```typescript
770
+ import { IntentClassifierAgent, SummarizerAgent } from '@toolpack-sdk/agents/capabilities';
771
+ ```
772
+
773
+ ### IntentClassifierAgent
774
+
775
+ Classifies whether a message is directly addressing the target agent. Used by `createIntentClassifierInterceptor` to resolve ambiguous cases that rules alone cannot determine.
776
+
777
+ ```typescript
778
+ import { IntentClassifierAgent } from '@toolpack-sdk/agents/capabilities';
779
+ import type { IntentClassifierInput } from '@toolpack-sdk/agents/capabilities';
780
+
781
+ const classifier = new IntentClassifierAgent({ apiKey: process.env.ANTHROPIC_API_KEY });
782
+ const result = await classifier.invokeAgent({
783
+ message: 'classify',
784
+ data: {
785
+ message: 'Hey @assistant can you help?',
786
+ agentName: 'assistant',
787
+ agentId: 'U123',
788
+ senderName: 'alice',
789
+ channelName: 'general',
790
+ } as IntentClassifierInput,
791
+ });
792
+ // result.output === 'direct' | 'indirect' | 'passive' | 'ignore'
793
+ ```
794
+
795
+ ### SummarizerAgent
796
+
797
+ Compresses older conversation history turns into a compact summary. Used by the prompt assembler when conversation history exceeds the token budget.
798
+
799
+ ```typescript
800
+ import { SummarizerAgent } from '@toolpack-sdk/agents/capabilities';
801
+ import type { SummarizerInput, SummarizerOutput } from '@toolpack-sdk/agents/capabilities';
802
+
803
+ const summarizer = new SummarizerAgent({ apiKey: process.env.ANTHROPIC_API_KEY });
804
+ const result = await summarizer.invokeAgent({
805
+ message: 'summarize',
806
+ data: {
807
+ turns: olderTurns,
808
+ agentName: 'support-agent',
809
+ agentId: 'U123',
810
+ maxTokens: 500,
811
+ extractDecisions: true,
812
+ } as SummarizerInput,
813
+ });
814
+ const summary = JSON.parse(result.output) as SummarizerOutput;
815
+ ```
816
+
817
+ ## Testing
818
+
819
+ ```bash
820
+ npm test
821
+ ```
822
+
823
+ **Test Coverage:** 573 tests passing across 29 test files.
824
+
825
+ ## License
826
+
827
+ Apache 2.0 © Toolpack SDK