@godman-protocols/signal 0.2.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.
package/LICENSE ADDED
@@ -0,0 +1,156 @@
1
+ Apache License
2
+ Version 2.0, January 2004
3
+ http://www.apache.org/licenses/
4
+
5
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6
+
7
+ 1. Definitions.
8
+
9
+ "License" shall mean the terms and conditions for use, reproduction,
10
+ and distribution as defined by Sections 1 through 9 of this document.
11
+
12
+ "Licensor" shall mean the copyright owner or entity authorized by
13
+ the copyright owner that is granting the License.
14
+
15
+ "Legal Entity" shall mean the union of the acting entity and all
16
+ other entities that control, are controlled by, or are under common
17
+ control with that entity. For the purposes of this definition,
18
+ "control" means (i) the power, direct or indirect, to cause the
19
+ direction or management of such entity, whether by contract or
20
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
21
+ outstanding shares, or (iii) beneficial ownership of such entity.
22
+
23
+ "You" (or "Your") shall mean an individual or Legal Entity
24
+ exercising permissions granted by this License.
25
+
26
+ "Source" form shall mean the preferred form for making modifications,
27
+ including but not limited to software source code, documentation
28
+ source, and configuration files.
29
+
30
+ "Object" form shall mean any form resulting from mechanical
31
+ transformation or translation of a Source form, including but
32
+ not limited to compiled object code, generated documentation,
33
+ and conversions to other media types.
34
+
35
+ "Work" shall mean the work of authorship made available under
36
+ the License, as indicated by a copyright notice that is included in
37
+ or attached to the work (an example is provided in the Appendix below).
38
+
39
+ "Derivative Works" shall mean any work, whether in Source or Object
40
+ form, that is based on (or derived from) the Work and for which the
41
+ editorial revisions, annotations, elaborations, or other transformations
42
+ represent, as a whole, an original work of authorship. For the purposes
43
+ of this License, Derivative Works shall not include works that remain
44
+ separable from, or merely link (or bind by name) to the interfaces of,
45
+ the Work and Derivative Works thereof.
46
+
47
+ "Contribution" shall mean, as submitted to the Licensor for inclusion
48
+ in the Work by the copyright owner or by an individual or Legal Entity
49
+ authorized to submit on behalf of the copyright owner.
50
+
51
+ "Contributor" shall mean Licensor and any Legal Entity on behalf of
52
+ whom a Contribution has been received by the Licensor and included
53
+ within the Work.
54
+
55
+ 2. Grant of Copyright License. Subject to the terms and conditions of
56
+ this License, each Contributor hereby grants to You a perpetual,
57
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
58
+ copyright license to reproduce, prepare Derivative Works of,
59
+ publicly display, publicly perform, sublicense, and distribute the
60
+ Work and such Derivative Works in Source or Object form.
61
+
62
+ 3. Grant of Patent License. Subject to the terms and conditions of
63
+ this License, each Contributor hereby grants to You a perpetual,
64
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
65
+ (except as stated in this section) patent license to make, have made,
66
+ use, offer to sell, sell, import, and otherwise transfer the Work,
67
+ where such license applies only to those patent claims licensable
68
+ by such Contributor that are necessarily infringed by their
69
+ Contribution(s) alone or by the combination of their Contribution(s)
70
+ with the Work to which such Contribution(s) was submitted. If You
71
+ institute patent litigation against any entity (including a cross-claim
72
+ or counterclaim in a lawsuit) alleging that the Work or any other
73
+ Contribution embodied in the Work constitutes patent or contributory
74
+ patent infringement, then any patent licenses granted to You under
75
+ this License for that Work shall terminate as of the date such
76
+ litigation is filed.
77
+
78
+ 4. Redistribution. You may reproduce and distribute copies of the
79
+ Work or Derivative Works thereof in any medium, with or without
80
+ modifications, and in Source or Object form, provided that You
81
+ meet the following conditions:
82
+
83
+ (a) You must give any other recipients of the Work or Derivative
84
+ Works a copy of this License; and
85
+
86
+ (b) You must cause any modified files to carry prominent notices
87
+ stating that You changed the files; and
88
+
89
+ (c) You must retain, in the Source form of any Derivative Works
90
+ that You distribute, all copyright, patent, trademark, and
91
+ attribution notices from the Source form of the Work,
92
+ excluding those notices that do not pertain to any part of
93
+ the Derivative Works; and
94
+
95
+ (d) If the Work includes a "NOTICE" text file, You must include a
96
+ readable copy of the attribution notices contained within such
97
+ NOTICE file, as part of the Derivative Works and at least one
98
+ of the following places: within a NOTICE text file distributed
99
+ as part of the Derivative Works; within the Source form or
100
+ documentation, if provided along with the Derivative Works; or,
101
+ within a display generated by the Derivative Works, if and
102
+ wherever such third-party notices normally appear.
103
+
104
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
105
+ any Contribution intentionally submitted for inclusion in the Work
106
+ by You to the Licensor shall be under the terms and conditions of
107
+ this License, without any additional terms or conditions.
108
+
109
+ 6. Trademarks. This License does not grant permission to use the trade
110
+ names, trademarks, service marks, or product names of the Licensor,
111
+ except as required for reasonable and customary use in describing the
112
+ origin of the Work.
113
+
114
+ 7. Disclaimer of Warranty. Unless required by applicable law or agreed
115
+ to in writing, Licensor provides the Work on an "AS IS" BASIS,
116
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
117
+ implied, including, without limitation, any warranties or conditions
118
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
119
+ PARTICULAR PURPOSE. You are solely responsible for determining the
120
+ appropriateness of using or reproducing the Work and assume any
121
+ risks associated with Your exercise of permissions under this License.
122
+
123
+ 8. Limitation of Liability. In no event and under no legal theory,
124
+ whether in tort (including negligence), contract, or otherwise,
125
+ unless required by applicable law (such as deliberate and grossly
126
+ negligent acts) or agreed to in writing, shall any Contributor be
127
+ liable to You for damages, including any direct, indirect, special,
128
+ incidental, or exemplary damages of any character arising as a
129
+ result of this License or out of the use or inability to use the
130
+ Work (including but not limited to damages for loss of goodwill,
131
+ work stoppage, computer failure or malfunction, or all other
132
+ commercial damages or losses), even if such Contributor has been
133
+ advised of the possibility of such damages.
134
+
135
+ 9. Accepting Warranty or Additional Liability. While redistributing
136
+ the Work or Derivative Works thereof, You may choose to offer,
137
+ and charge a fee for, acceptance of support, warranty, indemnity,
138
+ or other liability obligations and/or rights consistent with this
139
+ License. However, in accepting such obligations, You may offer only
140
+ conditions consistent with this License.
141
+
142
+ END OF TERMS AND CONDITIONS
143
+
144
+ Copyright 2026 Godman Protocols / skingem1
145
+
146
+ Licensed under the Apache License, Version 2.0 (the "License");
147
+ you may not use this file except in compliance with the License.
148
+ You may obtain a copy of the License at
149
+
150
+ http://www.apache.org/licenses/LICENSE-2.0
151
+
152
+ Unless required by applicable law or agreed to in writing, software
153
+ distributed under the License is distributed on an "AS IS" BASIS,
154
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
155
+ See the License for the specific language governing permissions and
156
+ limitations under the License.
package/README.md ADDED
@@ -0,0 +1,189 @@
1
+ # SIGNAL — Event Bus and Pub/Sub for Agent Swarms
2
+
3
+
4
+ [![npm version](https://img.shields.io/npm/v/@godman-protocols/signal.svg)](https://www.npmjs.com/package/@godman-protocols/signal)
5
+ [![License: Apache-2.0](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](https://opensource.org/licenses/Apache-2.0)
6
+ [![Node: >=20](https://img.shields.io/badge/node-%3E%3D20-brightgreen.svg)](https://nodejs.org)
7
+
8
+ > **v0.2.0** · Apache 2.0 · `@godman-protocols/signal` · Node 20+ / Deno 1.40+
9
+
10
+ SIGNAL is an open protocol for real-time event delivery between AI agents — with glob-based topic matching, idempotency deduplication, and delivery receipts, so your swarm stays coordinated without polling.
11
+
12
+ ```bash
13
+ npx skills add https://github.com/godman-protocols/signal
14
+ # or
15
+ npm install @godman-protocols/signal
16
+ ```
17
+
18
+ ---
19
+
20
+ ## The Problem
21
+
22
+ Multi-agent systems need to react to events: a mandate was signed, a task completed, a resource released. Without an event bus:
23
+
24
+ - **Polling everywhere** — agents waste cycles checking each other's state
25
+ - **Lost events** — no delivery guarantees means agents miss critical signals
26
+ - **Duplicate processing** — without idempotency, retries cause double execution
27
+
28
+ SIGNAL is the missing communication backbone for agent swarms.
29
+
30
+ ---
31
+
32
+ ## Core Concepts
33
+
34
+ | Concept | What it is |
35
+ |---------|-----------|
36
+ | **Event** | A signed, timestamped message on a dot-notation topic (e.g. `task.completed`) |
37
+ | **Subscription** | A registration to receive events matching a glob-style topic filter |
38
+ | **EventBus** | In-memory pub/sub engine with topic matching and delivery tracking |
39
+ | **DeliveryReceipt** | Confirmation that an event was delivered and processed (or failed) |
40
+ | **IdempotencyKey** | Deduplication key — same key = same event, skip re-delivery |
41
+
42
+ ---
43
+
44
+ ## Quickstart
45
+
46
+ ```typescript
47
+ import { EventBus, createEvent } from '@godman-protocols/signal';
48
+
49
+ const bus = new EventBus();
50
+ const SECRET = process.env.SIGNAL_SECRET!;
51
+
52
+ // 1. Subscribe to task completion events
53
+ const delivered: string[] = [];
54
+ bus.subscribe('did:kognai:sherlock', 'task.*', (event) => {
55
+ delivered.push(event.topic);
56
+ });
57
+
58
+ // 2. Publish an event
59
+ const event = createEvent(
60
+ 'did:kognai:messi', // publisher
61
+ 'task.completed', // topic
62
+ { taskId: 'sprint-973-01' }, // payload
63
+ SECRET
64
+ );
65
+
66
+ const receipts = await bus.publish(event);
67
+ // → [{ status: 'processed', ... }]
68
+
69
+ // 3. Idempotency — publishing the same event again is a no-op
70
+ const dupeReceipts = await bus.publish(event);
71
+ // → [{ status: 'duplicate-skipped', ... }]
72
+
73
+ // 4. Wildcard matching
74
+ bus.subscribe('did:kognai:guardiola', 'mandate.**', (event) => {
75
+ // Matches: mandate.signed, mandate.revoked, mandate.frame.closed
76
+ });
77
+ ```
78
+
79
+ ---
80
+
81
+ ## API Summary
82
+
83
+ ### Event Creation (`src/bus.ts`)
84
+
85
+ | Function | Description |
86
+ |----------|-------------|
87
+ | `createEvent(publisher, topic, payload, secret, options?)` | Create a signed event |
88
+
89
+ ### EventBus (`src/bus.ts`)
90
+
91
+ | Method | Description |
92
+ |--------|-------------|
93
+ | `subscribe(agent, topicFilter, handler, deliveryMode?)` | Subscribe to matching events |
94
+ | `unsubscribe(subscriptionId)` | Cancel a subscription |
95
+ | `publish(event)` | Deliver event to all matching subscribers |
96
+ | `getReceipts()` | Get all delivery receipts |
97
+ | `subscriptionCount` | Number of active subscriptions |
98
+
99
+ ### Topic Matching (`src/bus.ts`)
100
+
101
+ | Function | Description |
102
+ |----------|-------------|
103
+ | `topicMatches(filter, topic)` | Test if a topic matches a glob filter |
104
+
105
+ ### Singleton
106
+
107
+ | Export | Description |
108
+ |--------|-------------|
109
+ | `defaultBus` | Pre-created singleton EventBus for single-process use |
110
+
111
+ ---
112
+
113
+ ## Topic Matching Rules
114
+
115
+ Topics use dot notation: `task.completed`, `mandate.frame.closed`
116
+
117
+ Filters support two wildcards:
118
+ - **`*`** — matches exactly one segment: `task.*` matches `task.completed` but not `task.sub.completed`
119
+ - **`**`** — matches zero or more segments: `mandate.**` matches `mandate`, `mandate.signed`, `mandate.frame.closed`
120
+
121
+ ---
122
+
123
+ ## Delivery Modes
124
+
125
+ | Mode | Guarantee |
126
+ |------|-----------|
127
+ | `at-least-once` (default) | Event delivered at least once; handler may see retries |
128
+ | `at-most-once` | Event delivered at most once; may be lost on failure |
129
+
130
+ Idempotency deduplication applies regardless of delivery mode — duplicate events (same `idempotencyKey`) are always skipped.
131
+
132
+ ---
133
+
134
+ ## Security Model
135
+
136
+ SIGNAL v0.2 uses HMAC-SHA256 for event signing. The signature covers: event ID, topic, publisher, and timestamp.
137
+
138
+ **Production upgrade path:**
139
+ - Replace HMAC with Ed25519 for publisher identity verification
140
+ - Add Supabase Realtime or Redis Streams transport adapter
141
+ - Add WebSocket transport for cross-process delivery
142
+ - Persistent event log with replay capability
143
+
144
+ ---
145
+
146
+ ## Compatibility
147
+
148
+ | System | How it connects |
149
+ |--------|----------------|
150
+ | **Kognai** (event bus) | SIGNAL formalises the Supabase `kognai_events` table pattern |
151
+ | **PACT** (mandates) | Mandate lifecycle events: `mandate.signed`, `mandate.revoked`, `mandate.expired` |
152
+ | **SCORE** (evaluations) | Evaluation events: `score.evaluation.completed`, `score.reputation.updated` |
153
+ | **DRS** (resources) | Resource events: `resource.allocated`, `resource.released`, `resource.preempted` |
154
+
155
+ ---
156
+
157
+ ## Related Protocols
158
+
159
+ | Protocol | Purpose |
160
+ |----------|---------|
161
+ | **PACT** | Agent coordination and trust |
162
+ | **LAX** | Latency-aware execution scheduling |
163
+ | **SCORE** | Scoring and reputation for agent outputs |
164
+ | **AMF** | Agent Message Format |
165
+ | **DRS** | Dynamic Resource Scheduling |
166
+ | **SOUL** | Constitutional constraints and safety |
167
+ | **SIGNAL** (this repo) | Event bus and pub/sub for agent swarms |
168
+
169
+ ---
170
+
171
+ ## Roadmap
172
+
173
+ - [x] Signed event creation (v0.2)
174
+ - [x] In-memory EventBus with glob topic matching (v0.2)
175
+ - [x] Idempotency deduplication (v0.2)
176
+ - [x] Delivery receipts with status tracking (v0.2)
177
+ - [ ] Supabase Realtime transport adapter (v0.3)
178
+ - [ ] Redis Streams transport adapter (v0.3)
179
+ - [ ] Event replay from persistent log (v0.4)
180
+ - [ ] Python SDK (v0.5)
181
+ - [ ] Cross-process WebSocket delivery (v0.5)
182
+
183
+ ---
184
+
185
+ ## License
186
+
187
+ Apache License 2.0 — see [LICENSE](./LICENSE)
188
+
189
+ Part of the [Godman Protocols](https://github.com/godman-protocols) portfolio.
package/dist/bus.d.ts ADDED
@@ -0,0 +1,53 @@
1
+ /**
2
+ * SIGNAL — Event Bus and Pub/Sub for Agent Swarms
3
+ * In-memory EventBus implementation
4
+ * @version 0.2.0
5
+ */
6
+ import type { AgentId, DeliveryReceipt, Event, Subscription, SubscriptionId, Timestamp } from './types.js';
7
+ /**
8
+ * Match a topic against a glob-style filter.
9
+ * - `*` matches exactly one segment
10
+ * - `**` matches zero or more segments
11
+ * - Exact strings match literally
12
+ */
13
+ export declare function topicMatches(filter: string, topic: string): boolean;
14
+ /**
15
+ * Create a signed event.
16
+ */
17
+ export declare function createEvent(publisher: AgentId, topic: string, payload: unknown, secret: string, options?: {
18
+ id?: string;
19
+ idempotencyKey?: string;
20
+ publishedAt?: Timestamp;
21
+ }): Event;
22
+ type EventHandler = (event: Event) => void | Promise<void>;
23
+ export declare class EventBus {
24
+ private subs;
25
+ private seenIdempotencyKeys;
26
+ private receipts;
27
+ /**
28
+ * Subscribe to events matching a topic filter.
29
+ */
30
+ subscribe(subscriberAgent: AgentId, topicFilter: string, handler: EventHandler, deliveryMode?: 'at-least-once' | 'at-most-once'): Subscription;
31
+ /**
32
+ * Unsubscribe. Marks the subscription as cancelled.
33
+ */
34
+ unsubscribe(subscriptionId: SubscriptionId): void;
35
+ /**
36
+ * Publish an event to all matching subscribers.
37
+ * Deduplicates by idempotencyKey.
38
+ * Returns delivery receipts.
39
+ */
40
+ publish(event: Event): Promise<DeliveryReceipt[]>;
41
+ /**
42
+ * Get all delivery receipts.
43
+ */
44
+ getReceipts(): ReadonlyArray<DeliveryReceipt>;
45
+ /**
46
+ * Get active subscription count.
47
+ */
48
+ get subscriptionCount(): number;
49
+ }
50
+ /** Default singleton bus for single-process use */
51
+ export declare const defaultBus: EventBus;
52
+ export {};
53
+ //# sourceMappingURL=bus.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bus.d.ts","sourceRoot":"","sources":["../src/bus.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,OAAO,KAAK,EACV,OAAO,EACP,eAAe,EACf,KAAK,EACL,YAAY,EACZ,cAAc,EACd,SAAS,EACV,MAAM,YAAY,CAAC;AAMpB;;;;;GAKG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,OAAO,CAInE;AAuBD;;GAEG;AACH,wBAAgB,WAAW,CACzB,SAAS,EAAE,OAAO,EAClB,KAAK,EAAE,MAAM,EACb,OAAO,EAAE,OAAO,EAChB,MAAM,EAAE,MAAM,EACd,OAAO,GAAE;IAAE,EAAE,CAAC,EAAE,MAAM,CAAC;IAAC,cAAc,CAAC,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,SAAS,CAAA;CAAO,GAC9E,KAAK,CAOP;AAMD,KAAK,YAAY,GAAG,CAAC,KAAK,EAAE,KAAK,KAAK,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;AAO3D,qBAAa,QAAQ;IACnB,OAAO,CAAC,IAAI,CAA0C;IACtD,OAAO,CAAC,mBAAmB,CAAqB;IAChD,OAAO,CAAC,QAAQ,CAAyB;IAEzC;;OAEG;IACH,SAAS,CACP,eAAe,EAAE,OAAO,EACxB,WAAW,EAAE,MAAM,EACnB,OAAO,EAAE,YAAY,EACrB,YAAY,GAAE,eAAe,GAAG,cAAgC,GAC/D,YAAY;IAaf;;OAEG;IACH,WAAW,CAAC,cAAc,EAAE,cAAc,GAAG,IAAI;IAOjD;;;;OAIG;IACG,OAAO,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,eAAe,EAAE,CAAC;IAwCvD;;OAEG;IACH,WAAW,IAAI,aAAa,CAAC,eAAe,CAAC;IAI7C;;OAEG;IACH,IAAI,iBAAiB,IAAI,MAAM,CAE9B;CACF;AAED,mDAAmD;AACnD,eAAO,MAAM,UAAU,UAAiB,CAAC"}
package/dist/bus.js ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * SIGNAL — Event Bus and Pub/Sub for Agent Swarms
3
+ * In-memory EventBus implementation
4
+ * @version 0.2.0
5
+ */
6
+ import { createHmac, randomUUID } from 'node:crypto';
7
+ // ---------------------------------------------------------------------------
8
+ // Topic matching
9
+ // ---------------------------------------------------------------------------
10
+ /**
11
+ * Match a topic against a glob-style filter.
12
+ * - `*` matches exactly one segment
13
+ * - `**` matches zero or more segments
14
+ * - Exact strings match literally
15
+ */
16
+ export function topicMatches(filter, topic) {
17
+ const filterParts = filter.split('.');
18
+ const topicParts = topic.split('.');
19
+ return matchParts(filterParts, 0, topicParts, 0);
20
+ }
21
+ function matchParts(f, fi, t, ti) {
22
+ if (fi === f.length && ti === t.length)
23
+ return true;
24
+ if (fi === f.length)
25
+ return false;
26
+ if (f[fi] === '**') {
27
+ // ** can match zero or more segments
28
+ for (let i = ti; i <= t.length; i++) {
29
+ if (matchParts(f, fi + 1, t, i))
30
+ return true;
31
+ }
32
+ return false;
33
+ }
34
+ if (ti === t.length)
35
+ return false;
36
+ if (f[fi] === '*' || f[fi] === t[ti]) {
37
+ return matchParts(f, fi + 1, t, ti + 1);
38
+ }
39
+ return false;
40
+ }
41
+ // ---------------------------------------------------------------------------
42
+ // Event creation
43
+ // ---------------------------------------------------------------------------
44
+ /**
45
+ * Create a signed event.
46
+ */
47
+ export function createEvent(publisher, topic, payload, secret, options = {}) {
48
+ const id = options.id ?? randomUUID();
49
+ const publishedAt = options.publishedAt ?? new Date().toISOString();
50
+ const idempotencyKey = options.idempotencyKey ?? id;
51
+ const sigPayload = `${id}:${topic}:${publisher}:${publishedAt}`;
52
+ const signature = createHmac('sha256', secret).update(sigPayload, 'utf8').digest('hex');
53
+ return { id, topic, publisher, publishedAt, idempotencyKey, payload, signature };
54
+ }
55
+ export class EventBus {
56
+ subs = new Map();
57
+ seenIdempotencyKeys = new Set();
58
+ receipts = [];
59
+ /**
60
+ * Subscribe to events matching a topic filter.
61
+ */
62
+ subscribe(subscriberAgent, topicFilter, handler, deliveryMode = 'at-least-once') {
63
+ const sub = {
64
+ id: randomUUID(),
65
+ subscriberAgent,
66
+ topicFilter,
67
+ deliveryMode,
68
+ createdAt: new Date().toISOString(),
69
+ cancelledAt: null,
70
+ };
71
+ this.subs.set(sub.id, { subscription: sub, handler });
72
+ return sub;
73
+ }
74
+ /**
75
+ * Unsubscribe. Marks the subscription as cancelled.
76
+ */
77
+ unsubscribe(subscriptionId) {
78
+ const entry = this.subs.get(subscriptionId);
79
+ if (!entry)
80
+ throw new Error(`Subscription ${subscriptionId} not found`);
81
+ entry.subscription.cancelledAt = new Date().toISOString();
82
+ this.subs.delete(subscriptionId);
83
+ }
84
+ /**
85
+ * Publish an event to all matching subscribers.
86
+ * Deduplicates by idempotencyKey.
87
+ * Returns delivery receipts.
88
+ */
89
+ async publish(event) {
90
+ // Idempotency check
91
+ if (this.seenIdempotencyKeys.has(event.idempotencyKey)) {
92
+ const receipt = {
93
+ eventId: event.id,
94
+ subscriptionId: 'none',
95
+ receivedAt: new Date().toISOString(),
96
+ status: 'duplicate-skipped',
97
+ };
98
+ this.receipts.push(receipt);
99
+ return [receipt];
100
+ }
101
+ this.seenIdempotencyKeys.add(event.idempotencyKey);
102
+ const newReceipts = [];
103
+ for (const [, entry] of this.subs) {
104
+ if (entry.subscription.cancelledAt)
105
+ continue;
106
+ if (!topicMatches(entry.subscription.topicFilter, event.topic))
107
+ continue;
108
+ let status = 'processed';
109
+ try {
110
+ await entry.handler(event);
111
+ }
112
+ catch {
113
+ status = 'failed';
114
+ }
115
+ const receipt = {
116
+ eventId: event.id,
117
+ subscriptionId: entry.subscription.id,
118
+ receivedAt: new Date().toISOString(),
119
+ status,
120
+ };
121
+ newReceipts.push(receipt);
122
+ }
123
+ this.receipts.push(...newReceipts);
124
+ return newReceipts;
125
+ }
126
+ /**
127
+ * Get all delivery receipts.
128
+ */
129
+ getReceipts() {
130
+ return [...this.receipts];
131
+ }
132
+ /**
133
+ * Get active subscription count.
134
+ */
135
+ get subscriptionCount() {
136
+ return this.subs.size;
137
+ }
138
+ }
139
+ /** Default singleton bus for single-process use */
140
+ export const defaultBus = new EventBus();
141
+ //# sourceMappingURL=bus.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bus.js","sourceRoot":"","sources":["../src/bus.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,10 @@
1
+ /**
2
+ * SIGNAL — Event Bus and Pub/Sub for Agent Swarms
3
+ * Public API surface
4
+ * @version 0.2.0
5
+ */
6
+ export type { AgentId, Timestamp, Signature, EventId, SubscriptionId, Event, Subscription, TransportConfig, DeliveryReceipt, } from './types.js';
7
+ export { EventBus, defaultBus, createEvent, topicMatches, } from './bus.js';
8
+ /** Protocol version constant */
9
+ export declare const SIGNAL_VERSION: "0.2";
10
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAGH,YAAY,EACV,OAAO,EACP,SAAS,EACT,SAAS,EACT,OAAO,EACP,cAAc,EACd,KAAK,EACL,YAAY,EACZ,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AAGpB,OAAO,EACL,QAAQ,EACR,UAAU,EACV,WAAW,EACX,YAAY,GACb,MAAM,UAAU,CAAC;AAElB,gCAAgC;AAChC,eAAO,MAAM,cAAc,EAAG,KAAc,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,10 @@
1
+ /**
2
+ * SIGNAL — Event Bus and Pub/Sub for Agent Swarms
3
+ * Public API surface
4
+ * @version 0.2.0
5
+ */
6
+ // Event bus
7
+ export { EventBus, defaultBus, createEvent, topicMatches, } from './bus.js';
8
+ /** Protocol version constant */
9
+ export const SIGNAL_VERSION = '0.2';
10
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAeH,YAAY;AACZ,OAAO,EACL,QAAQ,EACR,UAAU,EACV,WAAW,EACX,YAAY,GACb,MAAM,UAAU,CAAC;AAElB,gCAAgC;AAChC,MAAM,CAAC,MAAM,cAAc,GAAG,KAAc,CAAC"}
@@ -0,0 +1,51 @@
1
+ /**
2
+ * SIGNAL — Event Bus and Pub/Sub for Agent Swarms
3
+ * Core type definitions (skeleton)
4
+ * @version 0.1.0-skeleton
5
+ */
6
+ export type AgentId = string;
7
+ export type Timestamp = string;
8
+ export type Signature = string;
9
+ export type EventId = string;
10
+ export type SubscriptionId = string;
11
+ /** A typed, timestamped, signed event */
12
+ export interface Event {
13
+ id: EventId;
14
+ /** Dot-notation topic path, e.g. 'task.completed', 'mandate.revoked' */
15
+ topic: string;
16
+ /** Publishing agent */
17
+ publisher: AgentId;
18
+ /** ISO 8601 */
19
+ publishedAt: Timestamp;
20
+ /** Idempotency key — deduplicate on re-delivery */
21
+ idempotencyKey: string;
22
+ payload: unknown;
23
+ signature: Signature;
24
+ }
25
+ /** A durable subscription to a topic filter */
26
+ export interface Subscription {
27
+ id: SubscriptionId;
28
+ subscriberAgent: AgentId;
29
+ /** Glob-style topic filter, e.g. 'task.*', '**' */
30
+ topicFilter: string;
31
+ /** Delivery guarantee */
32
+ deliveryMode: 'at-least-once' | 'at-most-once';
33
+ /** ISO 8601 */
34
+ createdAt: Timestamp;
35
+ /** ISO 8601 — null if still active */
36
+ cancelledAt: Timestamp | null;
37
+ }
38
+ /** Transport adapter configuration */
39
+ export interface TransportConfig {
40
+ type: 'supabase-realtime' | 'websocket' | 'redis-streams' | 'in-memory';
41
+ connectionString?: string;
42
+ channel?: string;
43
+ }
44
+ /** Delivery receipt — confirms an event was received and processed */
45
+ export interface DeliveryReceipt {
46
+ eventId: EventId;
47
+ subscriptionId: SubscriptionId;
48
+ receivedAt: Timestamp;
49
+ status: 'processed' | 'failed' | 'duplicate-skipped';
50
+ }
51
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAEH,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAC7B,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,MAAM,MAAM,SAAS,GAAG,MAAM,CAAC;AAC/B,MAAM,MAAM,OAAO,GAAG,MAAM,CAAC;AAC7B,MAAM,MAAM,cAAc,GAAG,MAAM,CAAC;AAEpC,yCAAyC;AACzC,MAAM,WAAW,KAAK;IACpB,EAAE,EAAE,OAAO,CAAC;IACZ,wEAAwE;IACxE,KAAK,EAAE,MAAM,CAAC;IACd,uBAAuB;IACvB,SAAS,EAAE,OAAO,CAAC;IACnB,eAAe;IACf,WAAW,EAAE,SAAS,CAAC;IACvB,mDAAmD;IACnD,cAAc,EAAE,MAAM,CAAC;IACvB,OAAO,EAAE,OAAO,CAAC;IACjB,SAAS,EAAE,SAAS,CAAC;CACtB;AAED,+CAA+C;AAC/C,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,cAAc,CAAC;IACnB,eAAe,EAAE,OAAO,CAAC;IACzB,mDAAmD;IACnD,WAAW,EAAE,MAAM,CAAC;IACpB,yBAAyB;IACzB,YAAY,EAAE,eAAe,GAAG,cAAc,CAAC;IAC/C,eAAe;IACf,SAAS,EAAE,SAAS,CAAC;IACrB,sCAAsC;IACtC,WAAW,EAAE,SAAS,GAAG,IAAI,CAAC;CAC/B;AAED,sCAAsC;AACtC,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,mBAAmB,GAAG,WAAW,GAAG,eAAe,GAAG,WAAW,CAAC;IACxE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,sEAAsE;AACtE,MAAM,WAAW,eAAe;IAC9B,OAAO,EAAE,OAAO,CAAC;IACjB,cAAc,EAAE,cAAc,CAAC;IAC/B,UAAU,EAAE,SAAS,CAAC;IACtB,MAAM,EAAE,WAAW,GAAG,QAAQ,GAAG,mBAAmB,CAAC;CACtD"}
package/dist/types.js ADDED
@@ -0,0 +1,7 @@
1
+ /**
2
+ * SIGNAL — Event Bus and Pub/Sub for Agent Swarms
3
+ * Core type definitions (skeleton)
4
+ * @version 0.1.0-skeleton
5
+ */
6
+ export {};
7
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;GAIG"}
package/package.json ADDED
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "@godman-protocols/signal",
3
+ "version": "0.2.0",
4
+ "description": "Event Bus and Pub/Sub for Agent Swarms \u2014 typed events, topic-glob routing, idempotent delivery, delivery receipts",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "exports": "./dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "files": [
10
+ "dist",
11
+ "src"
12
+ ],
13
+ "scripts": {
14
+ "build": "tsc",
15
+ "prepublishOnly": "npm run build",
16
+ "test": "npx tsx smoke.test.ts",
17
+ "typecheck": "tsc --noEmit"
18
+ },
19
+ "keywords": [
20
+ "event-bus",
21
+ "pub-sub",
22
+ "messaging",
23
+ "agent-communication",
24
+ "idempotent",
25
+ "godman-protocols",
26
+ "multi-agent"
27
+ ],
28
+ "author": "skingem1",
29
+ "license": "Apache-2.0",
30
+ "publishConfig": {
31
+ "access": "public",
32
+ "registry": "https://registry.npmjs.org/"
33
+ },
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "https://github.com/godman-protocols/signal.git"
37
+ },
38
+ "engines": {
39
+ "node": ">=20.0.0"
40
+ },
41
+ "devDependencies": {
42
+ "typescript": "^5.4.0",
43
+ "tsx": "^4.7.0"
44
+ },
45
+ "dependencies": {},
46
+ "homepage": "https://github.com/skingem1/godman-protocols/tree/main/signal#readme",
47
+ "bugs": {
48
+ "url": "https://github.com/skingem1/godman-protocols/issues"
49
+ }
50
+ }
package/src/bus.ts ADDED
@@ -0,0 +1,180 @@
1
+ /**
2
+ * SIGNAL — Event Bus and Pub/Sub for Agent Swarms
3
+ * In-memory EventBus implementation
4
+ * @version 0.2.0
5
+ */
6
+
7
+ import { createHmac, randomUUID } from 'node:crypto';
8
+ import type {
9
+ AgentId,
10
+ DeliveryReceipt,
11
+ Event,
12
+ Subscription,
13
+ SubscriptionId,
14
+ Timestamp,
15
+ } from './types.js';
16
+
17
+ // ---------------------------------------------------------------------------
18
+ // Topic matching
19
+ // ---------------------------------------------------------------------------
20
+
21
+ /**
22
+ * Match a topic against a glob-style filter.
23
+ * - `*` matches exactly one segment
24
+ * - `**` matches zero or more segments
25
+ * - Exact strings match literally
26
+ */
27
+ export function topicMatches(filter: string, topic: string): boolean {
28
+ const filterParts = filter.split('.');
29
+ const topicParts = topic.split('.');
30
+ return matchParts(filterParts, 0, topicParts, 0);
31
+ }
32
+
33
+ function matchParts(f: string[], fi: number, t: string[], ti: number): boolean {
34
+ if (fi === f.length && ti === t.length) return true;
35
+ if (fi === f.length) return false;
36
+ if (f[fi] === '**') {
37
+ // ** can match zero or more segments
38
+ for (let i = ti; i <= t.length; i++) {
39
+ if (matchParts(f, fi + 1, t, i)) return true;
40
+ }
41
+ return false;
42
+ }
43
+ if (ti === t.length) return false;
44
+ if (f[fi] === '*' || f[fi] === t[ti]) {
45
+ return matchParts(f, fi + 1, t, ti + 1);
46
+ }
47
+ return false;
48
+ }
49
+
50
+ // ---------------------------------------------------------------------------
51
+ // Event creation
52
+ // ---------------------------------------------------------------------------
53
+
54
+ /**
55
+ * Create a signed event.
56
+ */
57
+ export function createEvent(
58
+ publisher: AgentId,
59
+ topic: string,
60
+ payload: unknown,
61
+ secret: string,
62
+ options: { id?: string; idempotencyKey?: string; publishedAt?: Timestamp } = {}
63
+ ): Event {
64
+ const id = options.id ?? randomUUID();
65
+ const publishedAt = options.publishedAt ?? new Date().toISOString();
66
+ const idempotencyKey = options.idempotencyKey ?? id;
67
+ const sigPayload = `${id}:${topic}:${publisher}:${publishedAt}`;
68
+ const signature = createHmac('sha256', secret).update(sigPayload, 'utf8').digest('hex');
69
+ return { id, topic, publisher, publishedAt, idempotencyKey, payload, signature };
70
+ }
71
+
72
+ // ---------------------------------------------------------------------------
73
+ // EventBus
74
+ // ---------------------------------------------------------------------------
75
+
76
+ type EventHandler = (event: Event) => void | Promise<void>;
77
+
78
+ interface InternalSub {
79
+ subscription: Subscription;
80
+ handler: EventHandler;
81
+ }
82
+
83
+ export class EventBus {
84
+ private subs = new Map<SubscriptionId, InternalSub>();
85
+ private seenIdempotencyKeys = new Set<string>();
86
+ private receipts: DeliveryReceipt[] = [];
87
+
88
+ /**
89
+ * Subscribe to events matching a topic filter.
90
+ */
91
+ subscribe(
92
+ subscriberAgent: AgentId,
93
+ topicFilter: string,
94
+ handler: EventHandler,
95
+ deliveryMode: 'at-least-once' | 'at-most-once' = 'at-least-once'
96
+ ): Subscription {
97
+ const sub: Subscription = {
98
+ id: randomUUID(),
99
+ subscriberAgent,
100
+ topicFilter,
101
+ deliveryMode,
102
+ createdAt: new Date().toISOString(),
103
+ cancelledAt: null,
104
+ };
105
+ this.subs.set(sub.id, { subscription: sub, handler });
106
+ return sub;
107
+ }
108
+
109
+ /**
110
+ * Unsubscribe. Marks the subscription as cancelled.
111
+ */
112
+ unsubscribe(subscriptionId: SubscriptionId): void {
113
+ const entry = this.subs.get(subscriptionId);
114
+ if (!entry) throw new Error(`Subscription ${subscriptionId} not found`);
115
+ entry.subscription.cancelledAt = new Date().toISOString();
116
+ this.subs.delete(subscriptionId);
117
+ }
118
+
119
+ /**
120
+ * Publish an event to all matching subscribers.
121
+ * Deduplicates by idempotencyKey.
122
+ * Returns delivery receipts.
123
+ */
124
+ async publish(event: Event): Promise<DeliveryReceipt[]> {
125
+ // Idempotency check
126
+ if (this.seenIdempotencyKeys.has(event.idempotencyKey)) {
127
+ const receipt: DeliveryReceipt = {
128
+ eventId: event.id,
129
+ subscriptionId: 'none',
130
+ receivedAt: new Date().toISOString(),
131
+ status: 'duplicate-skipped',
132
+ };
133
+ this.receipts.push(receipt);
134
+ return [receipt];
135
+ }
136
+ this.seenIdempotencyKeys.add(event.idempotencyKey);
137
+
138
+ const newReceipts: DeliveryReceipt[] = [];
139
+
140
+ for (const [, entry] of this.subs) {
141
+ if (entry.subscription.cancelledAt) continue;
142
+ if (!topicMatches(entry.subscription.topicFilter, event.topic)) continue;
143
+
144
+ let status: DeliveryReceipt['status'] = 'processed';
145
+ try {
146
+ await entry.handler(event);
147
+ } catch {
148
+ status = 'failed';
149
+ }
150
+
151
+ const receipt: DeliveryReceipt = {
152
+ eventId: event.id,
153
+ subscriptionId: entry.subscription.id,
154
+ receivedAt: new Date().toISOString(),
155
+ status,
156
+ };
157
+ newReceipts.push(receipt);
158
+ }
159
+
160
+ this.receipts.push(...newReceipts);
161
+ return newReceipts;
162
+ }
163
+
164
+ /**
165
+ * Get all delivery receipts.
166
+ */
167
+ getReceipts(): ReadonlyArray<DeliveryReceipt> {
168
+ return [...this.receipts];
169
+ }
170
+
171
+ /**
172
+ * Get active subscription count.
173
+ */
174
+ get subscriptionCount(): number {
175
+ return this.subs.size;
176
+ }
177
+ }
178
+
179
+ /** Default singleton bus for single-process use */
180
+ export const defaultBus = new EventBus();
package/src/index.ts ADDED
@@ -0,0 +1,29 @@
1
+ /**
2
+ * SIGNAL — Event Bus and Pub/Sub for Agent Swarms
3
+ * Public API surface
4
+ * @version 0.2.0
5
+ */
6
+
7
+ // Types
8
+ export type {
9
+ AgentId,
10
+ Timestamp,
11
+ Signature,
12
+ EventId,
13
+ SubscriptionId,
14
+ Event,
15
+ Subscription,
16
+ TransportConfig,
17
+ DeliveryReceipt,
18
+ } from './types.js';
19
+
20
+ // Event bus
21
+ export {
22
+ EventBus,
23
+ defaultBus,
24
+ createEvent,
25
+ topicMatches,
26
+ } from './bus.js';
27
+
28
+ /** Protocol version constant */
29
+ export const SIGNAL_VERSION = '0.2' as const;
package/src/types.ts ADDED
@@ -0,0 +1,55 @@
1
+ /**
2
+ * SIGNAL — Event Bus and Pub/Sub for Agent Swarms
3
+ * Core type definitions (skeleton)
4
+ * @version 0.1.0-skeleton
5
+ */
6
+
7
+ export type AgentId = string;
8
+ export type Timestamp = string;
9
+ export type Signature = string;
10
+ export type EventId = string;
11
+ export type SubscriptionId = string;
12
+
13
+ /** A typed, timestamped, signed event */
14
+ export interface Event {
15
+ id: EventId;
16
+ /** Dot-notation topic path, e.g. 'task.completed', 'mandate.revoked' */
17
+ topic: string;
18
+ /** Publishing agent */
19
+ publisher: AgentId;
20
+ /** ISO 8601 */
21
+ publishedAt: Timestamp;
22
+ /** Idempotency key — deduplicate on re-delivery */
23
+ idempotencyKey: string;
24
+ payload: unknown;
25
+ signature: Signature;
26
+ }
27
+
28
+ /** A durable subscription to a topic filter */
29
+ export interface Subscription {
30
+ id: SubscriptionId;
31
+ subscriberAgent: AgentId;
32
+ /** Glob-style topic filter, e.g. 'task.*', '**' */
33
+ topicFilter: string;
34
+ /** Delivery guarantee */
35
+ deliveryMode: 'at-least-once' | 'at-most-once';
36
+ /** ISO 8601 */
37
+ createdAt: Timestamp;
38
+ /** ISO 8601 — null if still active */
39
+ cancelledAt: Timestamp | null;
40
+ }
41
+
42
+ /** Transport adapter configuration */
43
+ export interface TransportConfig {
44
+ type: 'supabase-realtime' | 'websocket' | 'redis-streams' | 'in-memory';
45
+ connectionString?: string;
46
+ channel?: string;
47
+ }
48
+
49
+ /** Delivery receipt — confirms an event was received and processed */
50
+ export interface DeliveryReceipt {
51
+ eventId: EventId;
52
+ subscriptionId: SubscriptionId;
53
+ receivedAt: Timestamp;
54
+ status: 'processed' | 'failed' | 'duplicate-skipped';
55
+ }