@cosmonapse/sdk 0.1.2

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.
@@ -0,0 +1,1915 @@
1
+ /**
2
+ * @cosmonapse/sdk - envelope
3
+ *
4
+ * Signal envelope types and codec. The TypeScript surface mirrors the Python
5
+ * `cosmonapse.envelope` module 1:1 (see ENVELOPE_SPEC.md §7). Every message
6
+ * crossing the Synapse is a Signal - a JSON object conforming to this schema.
7
+ *
8
+ * Producer tags (who emits each type):
9
+ * [A] Axon (skill/connector)
10
+ * [C] Cortex (developer-built orchestrating component)
11
+ */
12
+ /** Return a prefixed event ULID: `evt_<26-char ULID>`. */
13
+ declare function newEventId(): string;
14
+ /** Return a prefixed trace ULID: `trc_<26-char ULID>`. */
15
+ declare function newTraceId(): string;
16
+ /** Return a prefixed Engram entry ULID: `eng_<26-char ULID>`. */
17
+ declare function newEngramId(): string;
18
+ declare const SignalType: {
19
+ readonly TASK: "TASK";
20
+ readonly AGENT_OUTPUT: "AGENT_OUTPUT";
21
+ readonly FINAL: "FINAL";
22
+ readonly ERROR: "ERROR";
23
+ readonly TASK_OFFER: "TASK_OFFER";
24
+ readonly BID: "BID";
25
+ readonly TASK_AWARDED: "TASK_AWARDED";
26
+ readonly TASK_DECLINED: "TASK_DECLINED";
27
+ readonly THOUGHT_DELTA: "THOUGHT_DELTA";
28
+ readonly PLAN: "PLAN";
29
+ readonly TOOL_CALL: "TOOL_CALL";
30
+ readonly TOOL_RESULT: "TOOL_RESULT";
31
+ readonly MEMORY_APPEND: "MEMORY_APPEND";
32
+ readonly ESCALATION: "ESCALATION";
33
+ readonly CONSENSUS: "CONSENSUS";
34
+ readonly CONTEXT_SYNC: "CONTEXT_SYNC";
35
+ readonly CRITIQUE: "CRITIQUE";
36
+ readonly CLARIFICATION: "CLARIFICATION";
37
+ readonly PERMISSION: "PERMISSION";
38
+ readonly PERMISSION_DECISION: "PERMISSION_DECISION";
39
+ readonly CLARIFICATION_ANSWER: "CLARIFICATION_ANSWER";
40
+ readonly REGISTER: "REGISTER";
41
+ readonly DEREGISTER: "DEREGISTER";
42
+ readonly HEARTBEAT: "HEARTBEAT";
43
+ readonly RECALL: "RECALL";
44
+ readonly RECALLED: "RECALLED";
45
+ readonly IMPRINT: "IMPRINT";
46
+ readonly IMPRINTED: "IMPRINTED";
47
+ readonly DISCOVER: "DISCOVER";
48
+ };
49
+ type SignalType = (typeof SignalType)[keyof typeof SignalType];
50
+ /** Types the Axon (skill) is allowed to produce. */
51
+ declare const AXON_TYPES: ReadonlySet<SignalType>;
52
+ /** Types the Cortex (orchestrator) is allowed to produce. */
53
+ declare const SYNAPSE_TYPES: ReadonlySet<SignalType>;
54
+ /**
55
+ * Unified addressing for a Signal. Mirrors Python `cosmonapse.envelope.Directed`
56
+ * 1:1 so the wire shape is identical across SDKs.
57
+ *
58
+ * A Signal may be addressed three ways, in precedence order:
59
+ * - `id` Direct address. A `neuron_id` for TASK-family routing, or an
60
+ * `engram_id` for RECALL/IMPRINT.
61
+ * - `type` Type-based routing. A neuron type, or an `engram_kind`.
62
+ * - `capabilities` Capability-based routing.
63
+ *
64
+ * `id` wins over `type`, which wins over `capabilities` on the receiving side.
65
+ */
66
+ interface Directed {
67
+ id: string | null;
68
+ type: string | null;
69
+ capabilities: string[];
70
+ }
71
+ /** A partial Directed accepted at call sites; missing fields default to null/[]. */
72
+ type DirectedInput = Partial<Directed> | null;
73
+ /** Normalise a partial Directed (or null) into a full Directed (or null). */
74
+ declare function normalizeDirected(d: DirectedInput | undefined): Directed | null;
75
+ /** Small helper for building a {@link Directed} at call sites. */
76
+ declare function directedTo(id?: string | null, opts?: {
77
+ type?: string | null;
78
+ capabilities?: string[];
79
+ }): Directed;
80
+ type Json = Record<string, unknown>;
81
+ /**
82
+ * The universal envelope for every message crossing the Synapse.
83
+ *
84
+ * - `v` Protocol version. Always `"1"` for this release.
85
+ * - `id` Unique event ID. Format: `evt_<26-char ULID>`.
86
+ * - `trace_id` Groups all Signals belonging to one logical workflow (`trc_<ULID>`).
87
+ * - `parent_id` The id of the Signal that caused this one. Optional.
88
+ * - `type` One of the {@link SignalType} values.
89
+ * - `directed` Unified addressing (id / type / capabilities). Null when unset.
90
+ * - `ts` RFC 3339 UTC timestamp of emission.
91
+ * - `payload` Type-specific content.
92
+ * - `meta` Non-semantic annotations: model name, token counts, cost, etc.
93
+ */
94
+ interface Signal {
95
+ v: string;
96
+ id: string;
97
+ trace_id: string;
98
+ parent_id: string | null;
99
+ type: SignalType;
100
+ directed: Directed | null;
101
+ ts: string;
102
+ payload: Json;
103
+ meta: Json;
104
+ }
105
+ interface NewSignalInput {
106
+ type: SignalType;
107
+ trace_id?: string;
108
+ parent_id?: string | null;
109
+ directed?: DirectedInput;
110
+ payload?: Json;
111
+ meta?: Json;
112
+ v?: string;
113
+ id?: string;
114
+ ts?: string;
115
+ }
116
+ /** Construct a fully-populated, validated Signal, filling protocol defaults. */
117
+ declare function createSignal(input: NewSignalInput): Signal;
118
+ /** Throw if the envelope's identifier fields violate the spec. */
119
+ declare function validateSignal(signal: Signal): void;
120
+ /** Serialise to UTF-8 JSON bytes for wire transmission. */
121
+ declare function encode(signal: Signal): Uint8Array;
122
+ /** Deserialise from JSON bytes or string into a validated Signal. */
123
+ declare function decode(data: Uint8Array | string): Signal;
124
+ /**
125
+ * Construct a reply Signal that shares `source`'s trace_id and sets
126
+ * `parent_id` to `source`'s id.
127
+ */
128
+ declare function reply(source: Signal, opts: {
129
+ type: SignalType;
130
+ payload?: Json;
131
+ directed?: DirectedInput;
132
+ meta?: Json;
133
+ }): Signal;
134
+
135
+ /**
136
+ * @cosmonapse/sdk - typed signal builders
137
+ *
138
+ * Convenience constructors for the common Signal types. These are NOT required
139
+ * by the protocol - the protocol only requires a valid Signal with the correct
140
+ * `type` and `payload`. They mirror the helpers in `cosmonapse.envelope`.
141
+ *
142
+ * Addressing is carried by the envelope `directed` field (see {@link Directed}).
143
+ * Builders accept a `directed` (full or partial) rather than a bare `neuron`
144
+ * string; higher-level helpers (e.g. `Dendrite.dispatchTask`) keep a `neuron`
145
+ * argument for ergonomics and wrap it into `{ id: neuron }`.
146
+ */
147
+
148
+ /** [C] Dispatch a unit of work to a Neuron. */
149
+ declare function taskSignal(args: {
150
+ input: Json;
151
+ traceId?: string;
152
+ parentId?: string | null;
153
+ directed?: DirectedInput;
154
+ contextRef?: string;
155
+ capabilities?: string[];
156
+ meta?: Json;
157
+ }): Signal;
158
+ /** [A] Wrap a Neuron's raw output in a neutral AGENT_OUTPUT envelope. */
159
+ declare function agentOutputSignal(args: {
160
+ traceId: string;
161
+ parentId: string;
162
+ directed?: DirectedInput;
163
+ output: Json;
164
+ meta?: Json;
165
+ }): Signal;
166
+ /** [A] The Neuron needs more information before it can complete the task. */
167
+ declare function clarificationSignal(args: {
168
+ traceId: string;
169
+ parentId: string;
170
+ directed?: DirectedInput;
171
+ question: string;
172
+ context?: Json;
173
+ meta?: Json;
174
+ }): Signal;
175
+ /** [A] A Neuron asks permission to perform `action` before doing it. */
176
+ declare function permissionSignal(args: {
177
+ traceId: string;
178
+ parentId: string;
179
+ directed?: DirectedInput;
180
+ action: string;
181
+ scope?: Json;
182
+ reason?: string;
183
+ context?: Json;
184
+ meta?: Json;
185
+ }): Signal;
186
+ /** [C] Verdict on a PERMISSION request. `parentId` MUST be the PERMISSION's id. */
187
+ declare function permissionDecisionSignal(args: {
188
+ traceId: string;
189
+ parentId: string;
190
+ granted: boolean;
191
+ directed?: DirectedInput;
192
+ reason?: string;
193
+ ttlMs?: number;
194
+ meta?: Json;
195
+ }): Signal;
196
+ /** [C] Answer to a blocking CLARIFICATION. `parentId` MUST be the CLARIFICATION's id. */
197
+ declare function clarificationAnswerSignal(args: {
198
+ traceId: string;
199
+ parentId: string;
200
+ answer: unknown;
201
+ directed?: DirectedInput;
202
+ meta?: Json;
203
+ }): Signal;
204
+ /** [C] Workflow concluded. `result` carries the terminal output. */
205
+ declare function finalSignal(args: {
206
+ traceId: string;
207
+ parentId: string;
208
+ result: Json;
209
+ directed?: DirectedInput;
210
+ cost?: Json;
211
+ meta?: Json;
212
+ }): Signal;
213
+ /** [A][C] Something went wrong. `recoverable` lets the Cortex retry or reroute. */
214
+ declare function errorSignal(args: {
215
+ traceId: string;
216
+ code: string;
217
+ message: string;
218
+ parentId?: string | null;
219
+ directed?: DirectedInput;
220
+ recoverable?: boolean;
221
+ meta?: Json;
222
+ }): Signal;
223
+ /**
224
+ * [A] A participant connecting to the Synapse and declaring its capabilities.
225
+ *
226
+ * Both Neurons and Engrams register the same way: `directed.id` is the
227
+ * participant id, `directed.type` its type (neuron type or engram_kind), and
228
+ * `directed.capabilities` its capability list. Pass `engram: true` for an
229
+ * Engram so receivers record it as an Engram registration. `capabilities` is
230
+ * mirrored into the payload for registry stores; when omitted it falls back to
231
+ * `directed.capabilities`.
232
+ */
233
+ declare function registerSignal(args: {
234
+ directed: DirectedInput;
235
+ capabilities?: string[];
236
+ version?: string;
237
+ engram?: boolean;
238
+ meta?: Json;
239
+ }): Signal;
240
+ /** [A] A participant disconnecting from the Synapse. */
241
+ declare function deregisterSignal(args: {
242
+ directed: DirectedInput;
243
+ reason?: string;
244
+ meta?: Json;
245
+ }): Signal;
246
+ /** [A] Periodic liveness signal from a participant. */
247
+ declare function heartbeatSignal(args: {
248
+ directed: DirectedInput;
249
+ status?: string;
250
+ meta?: Json;
251
+ }): Signal;
252
+ /** [C] Write a value to the shared Engram under the given key. */
253
+ declare function memoryAppendSignal(args: {
254
+ traceId: string;
255
+ parentId: string;
256
+ key: string;
257
+ value: unknown;
258
+ directed?: DirectedInput;
259
+ meta?: Json;
260
+ }): Signal;
261
+ /** [C] Broadcast a task to candidate Neurons for competitive bidding. */
262
+ declare function taskOfferSignal(args: {
263
+ traceId: string;
264
+ input: Json;
265
+ parentId?: string | null;
266
+ capabilities?: string[];
267
+ deadlineMs?: number;
268
+ meta?: Json;
269
+ }): Signal;
270
+ /** [C] Cortex bids on behalf of a Neuron in response to a TASK_OFFER. */
271
+ declare function bidSignal(args: {
272
+ traceId: string;
273
+ parentId: string;
274
+ directed?: DirectedInput;
275
+ cost: number;
276
+ etaMs?: number;
277
+ confidence?: number;
278
+ meta?: Json;
279
+ }): Signal;
280
+ /** [C] Peer review of another Neuron's output. `verdict`: 'pass' | 'fail' | 'revise'. */
281
+ declare function critiqueSignal(args: {
282
+ traceId: string;
283
+ parentId: string;
284
+ targetEventId: string;
285
+ issues: Json[];
286
+ verdict: "pass" | "fail" | "revise";
287
+ directed?: DirectedInput;
288
+ meta?: Json;
289
+ }): Signal;
290
+ /** [C] Structured plan emitted before execution. */
291
+ declare function planSignal(args: {
292
+ traceId: string;
293
+ parentId: string;
294
+ steps: Json[];
295
+ rationale?: string;
296
+ directed?: DirectedInput;
297
+ meta?: Json;
298
+ }): Signal;
299
+ /** [C] Streaming reasoning chunk. */
300
+ declare function thoughtDeltaSignal(args: {
301
+ traceId: string;
302
+ parentId: string;
303
+ delta: string;
304
+ seq?: number;
305
+ directed?: DirectedInput;
306
+ meta?: Json;
307
+ }): Signal;
308
+ /** [C] Neuron invoking an external tool. */
309
+ declare function toolCallSignal(args: {
310
+ traceId: string;
311
+ parentId: string;
312
+ tool: string;
313
+ args: Json;
314
+ callId?: string;
315
+ directed?: DirectedInput;
316
+ meta?: Json;
317
+ }): Signal;
318
+ /** [C] Result returned from a tool. Set exactly one of `result` / `error`. */
319
+ declare function toolResultSignal(args: {
320
+ traceId: string;
321
+ parentId: string;
322
+ tool: string;
323
+ result?: unknown;
324
+ error?: string;
325
+ callId?: string;
326
+ directed?: DirectedInput;
327
+ meta?: Json;
328
+ }): Signal;
329
+ /** [C] Escalate a task or sub-decision to a higher-authority Neuron. */
330
+ declare function escalationSignal(args: {
331
+ traceId: string;
332
+ parentId: string;
333
+ reason: string;
334
+ target?: string;
335
+ context?: Json;
336
+ directed?: DirectedInput;
337
+ meta?: Json;
338
+ }): Signal;
339
+ /** [C] Record a consensus outcome among multiple Neurons. */
340
+ declare function consensusSignal(args: {
341
+ traceId: string;
342
+ parentId: string;
343
+ members: string[];
344
+ verdict: string;
345
+ votes?: Json;
346
+ directed?: DirectedInput;
347
+ meta?: Json;
348
+ }): Signal;
349
+ /** [C] Share/synchronise context across Neurons. */
350
+ declare function contextSyncSignal(args: {
351
+ traceId: string;
352
+ parentId: string;
353
+ snapshot: Json;
354
+ version?: string;
355
+ directed?: DirectedInput;
356
+ meta?: Json;
357
+ }): Signal;
358
+ /**
359
+ * [C] Solicit a REGISTER snapshot from peers on a namespace. `neuron` /
360
+ * `capabilities` are payload filter fields (which participants to discover),
361
+ * not envelope addressing.
362
+ */
363
+ declare function discoverSignal(args?: {
364
+ neuron?: string;
365
+ capabilities?: string[];
366
+ traceId?: string;
367
+ parentId?: string | null;
368
+ meta?: Json;
369
+ }): Signal;
370
+ /**
371
+ * [D] Memory-recall request. Inherits `traceId` from the containing TASK and
372
+ * MUST be addressed: `directed` needs at least one of `id` (engram_id) or
373
+ * `type` (engram_kind). `recallMode` controls fan-out: `"first"` (one winner),
374
+ * `"merge"` (caller merges all by deadline), `"all"` (stream each).
375
+ */
376
+ declare function recallSignal(args: {
377
+ traceId: string;
378
+ parentId: string;
379
+ directed: DirectedInput;
380
+ query: Json;
381
+ filters?: Json;
382
+ contextRef?: string;
383
+ deadlineMs?: number;
384
+ minConfidence?: number;
385
+ recallMode?: "first" | "merge" | "all";
386
+ meta?: Json;
387
+ }): Signal;
388
+ /** [D] Response from one Engram to a RECALL. `parentId` MUST be the RECALL's id. */
389
+ declare function recalledSignal(args: {
390
+ traceId: string;
391
+ parentId: string;
392
+ engramId: string;
393
+ hits: Json[];
394
+ truncated?: boolean;
395
+ tookMs?: number;
396
+ directed?: DirectedInput;
397
+ meta?: Json;
398
+ }): Signal;
399
+ /**
400
+ * [D] Memory-write request. `op` is one of add | append | merge | upsert |
401
+ * delete; `mergeKey` is required for merge/upsert. MUST be addressed.
402
+ */
403
+ declare function imprintSignal(args: {
404
+ traceId: string;
405
+ parentId: string;
406
+ directed: DirectedInput;
407
+ op: "add" | "append" | "merge" | "upsert" | "delete";
408
+ entry: Json;
409
+ mergeKey?: string;
410
+ meta?: Json;
411
+ }): Signal;
412
+ /** [D] Receipt of a completed IMPRINT. `parentId` MUST be the IMPRINT's id. */
413
+ declare function imprintedSignal(args: {
414
+ traceId: string;
415
+ parentId: string;
416
+ engramId: string;
417
+ op: string;
418
+ id?: string;
419
+ version?: number;
420
+ tookMs?: number;
421
+ error?: string;
422
+ directed?: DirectedInput;
423
+ meta?: Json;
424
+ }): Signal;
425
+
426
+ /**
427
+ * @cosmonapse/sdk - synapse
428
+ *
429
+ * The Synapse interface (five methods) and the in-process MemorySynapse,
430
+ * ported 1:1 from `cosmonapse.synapse.base` / `cosmonapse.synapse.memory`.
431
+ *
432
+ * MemorySynapse is NOT a throwaway test double - it is the adapter that backs
433
+ * the local dev experience, and any code written against it works unchanged
434
+ * against a networked adapter. NatsSynapse is ported (synapse-nats.ts); the
435
+ * Kafka adapter is still outstanding - see PORTING_STATUS.md.
436
+ *
437
+ * Subject convention (ENVELOPE_SPEC.md §10):
438
+ * cosmonapse.<namespace>.<type> e.g. cosmonapse.team_a.TASK
439
+ * cosmonapse.> subscribe to everything
440
+ *
441
+ * Wildcards (same as NATS):
442
+ * * matches exactly one token
443
+ * > matches one or more trailing tokens (must be the last token)
444
+ */
445
+
446
+ /** A subscriber callback. May be sync or async; async handlers are awaited. */
447
+ type MessageHandler = (signal: Signal) => void | Promise<void>;
448
+ /** Handle for an active subscription. Used to unsubscribe cleanly. */
449
+ interface Subscription {
450
+ unsubscribe(): Promise<void>;
451
+ }
452
+ interface SubscribeOptions {
453
+ /**
454
+ * If set, only one subscriber in the group receives each message
455
+ * (round-robin load balancing). Doppler subscribers must NOT use a group.
456
+ */
457
+ queueGroup?: string;
458
+ }
459
+ interface RequestOptions {
460
+ timeoutMs?: number;
461
+ }
462
+ /** Abstract base for all Cosmonapse synapse adapters. */
463
+ interface Synapse {
464
+ /** Establish connection / initialise in-memory state. */
465
+ connect(): Promise<void>;
466
+ /** Gracefully disconnect and release resources. */
467
+ close(): Promise<void>;
468
+ /** Publish a Signal to the given subject. */
469
+ publish(subject: string, signal: Signal): Promise<void>;
470
+ /** Subscribe to a subject pattern (exact or wildcard). */
471
+ subscribe(subject: string, handler: MessageHandler, opts?: SubscribeOptions): Promise<Subscription>;
472
+ /** Publish a Signal and wait for exactly one reply. */
473
+ request(subject: string, signal: Signal, opts?: RequestOptions): Promise<Signal>;
474
+ }
475
+ /**
476
+ * In-process synapse. Supports fan-out, queue groups (round-robin),
477
+ * wildcard subjects, and request/reply. Single-threaded by design.
478
+ */
479
+ declare class MemorySynapse implements Synapse {
480
+ private subs;
481
+ private counter;
482
+ private connected;
483
+ private rrCounters;
484
+ connect(): Promise<void>;
485
+ close(): Promise<void>;
486
+ private nextId;
487
+ /** @internal */
488
+ _removeHandler(subject: string, handlerId: number): void;
489
+ /**
490
+ * Return true if `subject` matches `pattern`.
491
+ * * matches any single token (no dots)
492
+ * > matches any sequence of trailing tokens
493
+ */
494
+ static matches(pattern: string, subject: string): boolean;
495
+ publish(subject: string, signal: Signal): Promise<void>;
496
+ private deliver;
497
+ subscribe(subject: string, handler: MessageHandler, opts?: SubscribeOptions): Promise<Subscription>;
498
+ request(subject: string, signal: Signal, opts?: RequestOptions): Promise<Signal>;
499
+ /**
500
+ * Convenience: send `reply` to the `_reply_to` subject stored in
501
+ * `original.meta`. Used by request/reply responders.
502
+ */
503
+ replyTo(original: Signal, reply: Signal): Promise<void>;
504
+ }
505
+
506
+ /**
507
+ * @cosmonapse/sdk - NATS synapse adapter
508
+ *
509
+ * NATS maps onto the Synapse contract directly:
510
+ * - subjects use the same `cosmonapse.<namespace>.<TYPE>` convention
511
+ * - `*` and `>` wildcards are native NATS - no translation needed
512
+ * - queue groups are native (`queue` on subscribe)
513
+ * - request/reply is native (`nc.request`)
514
+ *
515
+ * The `nats` package (nats.js) is **lazy-imported** inside connect(), so this
516
+ * module is safe to load even when `nats` isn't installed. It is declared as
517
+ * an optional dependency: npm i nats
518
+ *
519
+ * Ported from `cosmonapse.synapse.nats`. One enhancement over the Python
520
+ * adapter: the inbound bridge stashes the NATS reply subject into
521
+ * `signal.meta._reply_to`, and `replyTo()` publishes there - so the SAME
522
+ * request/reply responder code works against MemorySynapse and NatsSynapse.
523
+ */
524
+
525
+ interface NatsSynapseOptions {
526
+ /** NATS server URL or list of URLs. */
527
+ url?: string | string[];
528
+ }
529
+ /** NATS-backed Synapse. Interchangeable with MemorySynapse. */
530
+ declare class NatsSynapse implements Synapse {
531
+ private readonly url;
532
+ private nc;
533
+ private connected;
534
+ constructor(opts?: NatsSynapseOptions);
535
+ connect(): Promise<void>;
536
+ close(): Promise<void>;
537
+ private requireConn;
538
+ publish(subject: string, signal: Signal): Promise<void>;
539
+ subscribe(subject: string, handler: MessageHandler, opts?: SubscribeOptions): Promise<Subscription>;
540
+ request(subject: string, signal: Signal, opts?: RequestOptions): Promise<Signal>;
541
+ /**
542
+ * Send `reply` to the `_reply_to` subject the inbound bridge stashed in
543
+ * `original.meta` (the native NATS inbox). Mirrors MemorySynapse.replyTo so
544
+ * request/reply responder code is adapter-agnostic.
545
+ */
546
+ replyTo(original: Signal, reply: Signal): Promise<void>;
547
+ }
548
+
549
+ /**
550
+ * @cosmonapse/sdk - local dev synapse
551
+ *
552
+ * A tiny TCP + NDJSON broker, ported 1:1 from `cosmonapse.synapse.dev`.
553
+ *
554
+ * `cosmo synapse start memory` boots a {@link DevSynapseServer} and prints a URL
555
+ * like `cosmo://127.0.0.1:7070`. Any process can then connect to that URL with
556
+ * `const synapse = await connectSynapse("cosmo://...")` and hand the result to a
557
+ * `new Dendrite({ synapse, namespace })` to start exchanging Signals.
558
+ *
559
+ * This is **not** a production synapse. It is the equivalent of MemorySynapse
560
+ * for the case where Axons, Dendrites and Cortices live in separate processes
561
+ * on a developer's laptop. For production use NatsSynapse or KafkaSynapse.
562
+ *
563
+ * Wire protocol (NDJSON over TCP, UTF-8, `\n` framed) - identical to the Python
564
+ * server so the two interoperate:
565
+ *
566
+ * Client -> Server:
567
+ * {"op":"hello"} | {"op":"ping"}
568
+ * {"op":"pub","subject":"a.b.c","frame":"<utf8 JSON of Signal>"}
569
+ * {"op":"sub","sub_id":"s1","subject":"a.b.*","queue_group":null}
570
+ * {"op":"unsub","sub_id":"s1"}
571
+ * {"op":"ns_register","namespace":"dev","transport":"memory"}
572
+ * {"op":"mgmt_list"} | {"op":"mgmt_info","namespace":"dev"} | {"op":"mgmt_stop","namespace":"dev"}
573
+ *
574
+ * Server -> Client:
575
+ * {"op":"welcome"} | {"op":"pong"}
576
+ * {"op":"msg","sub_id":"s1","subject":"a.b.c","frame":"<utf8 JSON>"}
577
+ * {"op":"err","message":"..."}
578
+ * {"op":"ns_registered","namespace":"dev"}
579
+ * {"op":"mgmt_ns_list","namespaces":[...]} | {"op":"mgmt_ns_info",...}
580
+ * {"op":"mgmt_stop_ack","namespace":"dev"} | {"op":"ns_stopping","namespace":"dev"}
581
+ *
582
+ * Subject matching uses the same `*` / `>` wildcards as MemorySynapse and NATS.
583
+ * Queue groups load-balance round-robin within the group; subscribers with no
584
+ * queue_group (Dopplers) each receive every matching message.
585
+ */
586
+
587
+ interface DevSynapseServerOptions {
588
+ host?: string;
589
+ port?: number;
590
+ }
591
+ /**
592
+ * TCP server speaking the dev-synapse wire protocol.
593
+ *
594
+ * ```ts
595
+ * const server = new DevSynapseServer({ host: "127.0.0.1", port: 7070 });
596
+ * await server.start();
597
+ * console.log(server.url); // cosmo://127.0.0.1:7070
598
+ * // ...
599
+ * await server.stop();
600
+ * ```
601
+ */
602
+ declare class DevSynapseServer {
603
+ private _host;
604
+ private _port;
605
+ private server;
606
+ private readonly sessions;
607
+ private readonly rrCounters;
608
+ private readonly namespaces;
609
+ /**
610
+ * Optional observer hook: every published Signal is passed here as
611
+ * (subject, frame) before fan-out. Used by `cosmo synapse start` to stream
612
+ * to stdout.
613
+ */
614
+ onSignal: ((subject: string, frame: string) => void) | null;
615
+ constructor(opts?: DevSynapseServerOptions);
616
+ get host(): string;
617
+ get port(): number;
618
+ get url(): string;
619
+ get sessionCount(): number;
620
+ start(): Promise<void>;
621
+ stop(): Promise<void>;
622
+ private handleClient;
623
+ private handleOp;
624
+ private deliver;
625
+ }
626
+ interface DevSynapseOptions {
627
+ host?: string;
628
+ port?: number;
629
+ /** Convenience: pass `cosmo://host:port` instead of host/port. */
630
+ url?: string;
631
+ }
632
+ /** TCP / NDJSON client speaking to a {@link DevSynapseServer}. */
633
+ declare class DevSynapse implements Synapse {
634
+ private _host;
635
+ private _port;
636
+ private socket;
637
+ private connected;
638
+ private readonly handlers;
639
+ private subCounter;
640
+ private readonly splitter;
641
+ constructor(opts?: DevSynapseOptions);
642
+ get url(): string;
643
+ connect(): Promise<void>;
644
+ close(): Promise<void>;
645
+ private onFrame;
646
+ private send;
647
+ /** @internal - used by DevSubscription. */
648
+ _sendUnsub(subId: string): Promise<void>;
649
+ publish(subject: string, signal: Signal): Promise<void>;
650
+ subscribe(subject: string, handler: MessageHandler, opts?: SubscribeOptions): Promise<Subscription>;
651
+ request(subject: string, signal: Signal, opts?: RequestOptions): Promise<Signal>;
652
+ /** Send `reply` to the `_reply_to` subject stored in `original.meta`. */
653
+ replyTo(original: Signal, reply: Signal): Promise<void>;
654
+ }
655
+
656
+ /**
657
+ * @cosmonapse/sdk - Kafka synapse adapter
658
+ *
659
+ * Ported from `cosmonapse.synapse.kafka`.
660
+ *
661
+ * Kafka does not map onto the Synapse contract as cleanly as NATS - a few
662
+ * translations are needed:
663
+ *
664
+ * Cosmonapse Kafka
665
+ * -------------------------- ----------------------------------
666
+ * subject `a.b.TYPE` topic `a.b.TYPE` (verbatim)
667
+ * wildcard `a.b.*` / `a.>` regex consumer subscription
668
+ * (`^a\.b\.[^.]+$` / `^a\..+$`)
669
+ * queueGroup="workers" consumer `groupId="workers"`
670
+ * no queueGroup (Doppler) consumer `groupId=<unique>` so it joins its
671
+ * own group and sees every message
672
+ * request / reply per-call reply topic; the requester
673
+ * subscribes to its inbox before publishing,
674
+ * then awaits one message whose parent_id
675
+ * matches the request's signal id.
676
+ *
677
+ * The `kafkajs` library is **lazy-imported** - the module loads fine without it;
678
+ * `connect()` raises a clear error if it is missing. Install: npm i kafkajs
679
+ *
680
+ * Caveats
681
+ * -------
682
+ * - Kafka topics must exist (`auto.create.topics.enable=true` on the broker if
683
+ * you want them created on first publish).
684
+ * - High-fan-out request/reply is poorly suited to Kafka. Prefer NATS for that;
685
+ * use Kafka where you want the long-term audit log of every Signal.
686
+ */
687
+
688
+ interface KafkaSynapseOptions {
689
+ /** Kafka broker(s), e.g. "localhost:9092" or a list. */
690
+ bootstrapServers?: string | string[];
691
+ /** Optional client identifier. */
692
+ clientId?: string;
693
+ }
694
+ /** Kafka-backed Synapse. Interchangeable with MemorySynapse. */
695
+ declare class KafkaSynapse implements Synapse {
696
+ private readonly brokers;
697
+ private readonly clientId;
698
+ private kafka;
699
+ private producer;
700
+ private readonly consumers;
701
+ private connected;
702
+ constructor(opts?: KafkaSynapseOptions);
703
+ connect(): Promise<void>;
704
+ close(): Promise<void>;
705
+ publish(subject: string, signal: Signal): Promise<void>;
706
+ subscribe(subject: string, handler: MessageHandler, opts?: SubscribeOptions): Promise<Subscription>;
707
+ request(subject: string, signal: Signal, opts?: RequestOptions): Promise<Signal>;
708
+ }
709
+
710
+ /**
711
+ * @cosmonapse/sdk - synapse URL factory
712
+ *
713
+ * Synapse URL -> Synapse factory and connector. Ported 1:1 from
714
+ * `cosmonapse._url`.
715
+ *
716
+ * A Dendrite (or Cortex) does NOT own the Synapse. It uses a Synapse that the
717
+ * caller has built and connected. The caller is also responsible for closing it.
718
+ *
719
+ * cosmo://host:port -> DevSynapse (local dev / `cosmo dev synapse`)
720
+ * nats://host:port -> NatsSynapse
721
+ * kafka://host:port -> KafkaSynapse
722
+ *
723
+ * For the in-process MemorySynapse, construct it directly - a URL would be
724
+ * ambiguous across processes.
725
+ */
726
+
727
+ /** Build (but do not connect) a Synapse from a Cosmonapse synapse URL. */
728
+ declare function synapseFromUrl(url: string): Synapse;
729
+ /**
730
+ * Build a Synapse from `url` and `.connect()` it. Return the connected Synapse
731
+ * so the caller can pass it to Dendrites / Cortices and close it when finished:
732
+ *
733
+ * ```ts
734
+ * const synapse = await connectSynapse("cosmo://127.0.0.1:7070");
735
+ * try {
736
+ * const dendrite = new Dendrite({ synapse, registryStore });
737
+ * await dendrite.start();
738
+ * // ...
739
+ * } finally {
740
+ * await synapse.close();
741
+ * }
742
+ * ```
743
+ *
744
+ * Multiple Dendrites and Cortices can share the same Synapse instance. Closing
745
+ * the Synapse is the caller's responsibility - no component will close it.
746
+ */
747
+ declare function connectSynapse(url: string): Promise<Synapse>;
748
+
749
+ /**
750
+ * @cosmonapse/sdk - registry store
751
+ *
752
+ * The one mandatory persistent surface: a live view of the Neurons a namespace
753
+ * has seen. Ported from `cosmonapse.storage.base` / `cosmonapse.storage.memory`.
754
+ *
755
+ * Timestamps are RFC 3339 strings (matching the envelope's `ts`), so records
756
+ * serialise cleanly to JSON. A backend is conformant iff it behaves like
757
+ * MemoryRegistryStore (the reference implementation).
758
+ *
759
+ * NAMING - snake_case is deliberate. NeuronRecord fields (`neuron_id`,
760
+ * `last_heartbeat`, `registered_at`) intentionally use snake_case rather than
761
+ * the usual TS camelCase because a record IS the on-the-wire / on-disk shape:
762
+ * it round-trips verbatim through JSON and across the Python and TS SDKs, which
763
+ * share one registry schema. Renaming to camelCase here would force a
764
+ * translation layer at every Synapse and storage boundary and break
765
+ * cross-language parity. Treat these field names as part of the wire contract,
766
+ * not as a TS style choice. (See PORTING_STATUS.md if a camelCase view/adapter
767
+ * is ever added on top.)
768
+ *
769
+ * (Python additionally ships sqlite/postgres backends; only the in-memory
770
+ * backend is ported here - see PORTING_STATUS.md. Implement the RegistryStore
771
+ * interface for others.)
772
+ */
773
+ type NeuronStatus = "registered" | "draining" | "deregistered";
774
+ /**
775
+ * A live view of one Neuron the namespace has seen.
776
+ *
777
+ * Fields are snake_case on purpose - this is the serialised wire/registry
778
+ * shape shared with the Python SDK, not an idiomatic TS object. See the file
779
+ * header for the rationale.
780
+ */
781
+ interface NeuronRecord {
782
+ neuron_id: string;
783
+ capabilities: string[];
784
+ version: string | null;
785
+ status: NeuronStatus;
786
+ last_heartbeat: string | null;
787
+ registered_at: string;
788
+ }
789
+ interface NeuronRecordInit {
790
+ neuron_id: string;
791
+ capabilities?: string[];
792
+ version?: string | null;
793
+ status?: NeuronStatus;
794
+ last_heartbeat?: string | null;
795
+ registered_at?: string;
796
+ }
797
+ /** Build a NeuronRecord, filling defaults (status "registered", now()). */
798
+ declare function neuronRecord(init: NeuronRecordInit): NeuronRecord;
799
+ interface ListOptions {
800
+ capability?: string;
801
+ includeDeregistered?: boolean;
802
+ }
803
+ /**
804
+ * Abstract registry store. All methods are async. Implementations must be safe
805
+ * for concurrent calls within one event loop; cross-process consistency is a
806
+ * backend concern (use a shared DB when multiple Cortices share state).
807
+ */
808
+ interface RegistryStore {
809
+ connect(): Promise<void>;
810
+ close(): Promise<void>;
811
+ /** Insert or update a record by neuron_id (called on REGISTER). */
812
+ upsert(record: NeuronRecord): Promise<void>;
813
+ /** Mark an existing record deregistered. No-op if unknown. */
814
+ markDeregistered(neuronId: string): Promise<void>;
815
+ /**
816
+ * Update last_heartbeat (and optionally status). If unknown, the backend
817
+ * MAY create a thin record - tolerating heartbeats that arrive before
818
+ * REGISTER.
819
+ */
820
+ touchHeartbeat(neuronId: string, ts: string, status?: NeuronStatus): Promise<void>;
821
+ get(neuronId: string): Promise<NeuronRecord | null>;
822
+ list(opts?: ListOptions): Promise<NeuronRecord[]>;
823
+ }
824
+ /** In-process RegistryStore. Default backend; reset on process restart. */
825
+ declare class MemoryRegistryStore implements RegistryStore {
826
+ private records;
827
+ connect(): Promise<void>;
828
+ close(): Promise<void>;
829
+ upsert(record: NeuronRecord): Promise<void>;
830
+ markDeregistered(neuronId: string): Promise<void>;
831
+ touchHeartbeat(neuronId: string, ts: string, status?: NeuronStatus): Promise<void>;
832
+ get(neuronId: string): Promise<NeuronRecord | null>;
833
+ list(opts?: ListOptions): Promise<NeuronRecord[]>;
834
+ }
835
+
836
+ /**
837
+ * @cosmonapse/sdk - SQLite registry store
838
+ *
839
+ * Ported from `cosmonapse.storage.sqlite`. A single file on disk (or
840
+ * `:memory:`), backed by `better-sqlite3` (lazy-imported, optional dependency:
841
+ * `npm i better-sqlite3`). The Python port uses stdlib sqlite3; Node has no
842
+ * stable built-in equivalent, so we use the de-facto standard driver.
843
+ *
844
+ * The schema is created on `connect()` if it does not already exist. Records
845
+ * round-trip the same on-disk shape as the Python SDK: capabilities as a JSON
846
+ * array, timestamps as RFC 3339 strings.
847
+ */
848
+
849
+ /**
850
+ * SQLite-backed RegistryStore.
851
+ *
852
+ * @param path Filesystem path to the DB file. Use `:memory:` for an ephemeral
853
+ * in-process DB (useful for tests; not shared across connections).
854
+ */
855
+ declare class SqliteRegistryStore implements RegistryStore {
856
+ private readonly path;
857
+ private db;
858
+ constructor(path?: string);
859
+ connect(): Promise<void>;
860
+ close(): Promise<void>;
861
+ private require;
862
+ upsert(record: NeuronRecord): Promise<void>;
863
+ markDeregistered(neuronId: string): Promise<void>;
864
+ touchHeartbeat(neuronId: string, ts: string, status?: NeuronStatus): Promise<void>;
865
+ get(neuronId: string): Promise<NeuronRecord | null>;
866
+ list(opts?: ListOptions): Promise<NeuronRecord[]>;
867
+ }
868
+
869
+ /**
870
+ * @cosmonapse/sdk - Postgres registry store
871
+ *
872
+ * Ported from `cosmonapse.storage.postgres`. Backed by `pg` (node-postgres),
873
+ * lazy-imported as an optional dependency: `npm i pg`. Only `connect()` raises
874
+ * a clear error when the package is missing.
875
+ *
876
+ * The schema is bootstrapped on first `connect()`. Use a dedicated schema /
877
+ * database for the Cortex if you don't want it sharing a namespace with your
878
+ * application tables.
879
+ *
880
+ * Records round-trip the same shape as the Python SDK: capabilities as JSONB,
881
+ * timestamps as RFC 3339 strings on the wire.
882
+ */
883
+
884
+ interface PostgresRegistryStoreOptions {
885
+ /** Postgres DSN, e.g. "postgresql://user:pass@host:5432/db". */
886
+ dsn: string;
887
+ minSize?: number;
888
+ maxSize?: number;
889
+ }
890
+ /** Postgres-backed RegistryStore via node-postgres connection pool. */
891
+ declare class PostgresRegistryStore implements RegistryStore {
892
+ private readonly dsn;
893
+ private readonly minSize;
894
+ private readonly maxSize;
895
+ private pool;
896
+ constructor(opts: PostgresRegistryStoreOptions);
897
+ connect(): Promise<void>;
898
+ close(): Promise<void>;
899
+ private require;
900
+ upsert(record: NeuronRecord): Promise<void>;
901
+ markDeregistered(neuronId: string): Promise<void>;
902
+ touchHeartbeat(neuronId: string, ts: string, status?: NeuronStatus): Promise<void>;
903
+ get(neuronId: string): Promise<NeuronRecord | null>;
904
+ list(opts?: ListOptions): Promise<NeuronRecord[]>;
905
+ }
906
+
907
+ /**
908
+ * @cosmonapse/sdk - lifecycle hooks
909
+ *
910
+ * Shared lifecycle-hook surface for Axon and Dendrite / Cortex, ported from
911
+ * `cosmonapse._hooks`. Three hook kinds cover both the centralised
912
+ * (orchestrator-first) and decentralised (peer-to-peer) cases:
913
+ *
914
+ * onConnect fire-once after the component finishes its own connect
915
+ * handshake (Axon attached + registered, Dendrite up on the
916
+ * Synapse).
917
+ * onRefresh fired whenever the component's observable state refreshes -
918
+ * heartbeat tick, REGISTER / DEREGISTER / HEARTBEAT seen by the
919
+ * registry, or a manual `refresh()`. The handler receives a
920
+ * {@link RefreshEvent} describing what changed.
921
+ * onSchedule developer-supplied periodic task. Runs as a background loop
922
+ * every `everyMs` until the component stops.
923
+ *
924
+ * Decentralised use case - each Dendrite can announce itself on connect,
925
+ * reconcile peers on refresh, and gossip on a schedule, so peer-to-peer
926
+ * fabrics emerge without the SDK baking in an orchestration model.
927
+ *
928
+ * Python uses a mixin; TypeScript favours composition, so the host holds a
929
+ * `LifecycleHooks` instance and drives it (`_fireConnect`, `_launchSchedule`,
930
+ * `_fireRefresh`, `_stopHooks`). The `on*` decorators are re-exposed on the
931
+ * host (Axon / Dendrite) for ergonomics.
932
+ */
933
+ /** Context passed to onRefresh hooks. */
934
+ interface RefreshEvent {
935
+ /** "heartbeat" | "register" | "deregister" | "manual" | "scheduled" */
936
+ reason: string;
937
+ /** The neuron implicated in the change, if any. */
938
+ neuronId?: string | null;
939
+ /** Free-form bag for component-specific detail. */
940
+ extra: Record<string, unknown>;
941
+ }
942
+ type ConnectHook<O> = (owner: O) => void | Promise<void>;
943
+ type RefreshHook<O> = (owner: O, event: RefreshEvent) => void | Promise<void>;
944
+ type ScheduleHook<O> = (owner: O) => void | Promise<void>;
945
+ /**
946
+ * Lifecycle-hook registry + driver. Generic over the owner type so handlers
947
+ * receive a fully-typed back-reference to the component they're attached to.
948
+ */
949
+ declare class LifecycleHooks<O> {
950
+ private readonly owner;
951
+ private readonly connectHooks;
952
+ private readonly refreshHooks;
953
+ private readonly scheduleHooks;
954
+ private readonly timers;
955
+ private started;
956
+ constructor(owner: O);
957
+ /** Register a fire-once handler called after the host finishes start(). */
958
+ onConnect(fn: ConnectHook<O>): ConnectHook<O>;
959
+ /** Register a handler called whenever the host's state refreshes. */
960
+ onRefresh(fn: RefreshHook<O>): RefreshHook<O>;
961
+ /**
962
+ * Register a periodic handler. The background loop runs every `everyMs`
963
+ * for the lifetime of the host. If the host is already running, the loop
964
+ * starts immediately.
965
+ */
966
+ onSchedule(everyMs: number, fn: ScheduleHook<O>): ScheduleHook<O>;
967
+ /** @internal */
968
+ _fireConnect(): Promise<void>;
969
+ /** @internal */
970
+ _fireRefresh(event: RefreshEvent): Promise<void>;
971
+ /** @internal */
972
+ _launchSchedule(): void;
973
+ /** @internal */
974
+ _stopHooks(): void;
975
+ /**
976
+ * Manually fire a refresh event. Useful when a developer's own code knows
977
+ * internal state has changed.
978
+ */
979
+ refresh(opts?: {
980
+ reason?: string;
981
+ neuronId?: string | null;
982
+ extra?: Record<string, unknown>;
983
+ }): Promise<void>;
984
+ private spawnLoop;
985
+ }
986
+
987
+ /**
988
+ * @cosmonapse/sdk - neuron contract
989
+ *
990
+ * A Neuron is a pure function - `(input, context) => output`. It has zero
991
+ * knowledge of the protocol, envelopes, trace IDs, or workflow semantics.
992
+ * Any existing LLM-driven agent can be wrapped to satisfy this signature and
993
+ * become a protocol participant with no modification.
994
+ *
995
+ * Provider-backed Neuron factories (Ollama / HuggingFace over `fetch`) are
996
+ * available via `ollamaNeuron` / `huggingFaceNeuron` (see neuron-http.ts) or the
997
+ * unified `neuron("ollama" | "huggingface", …)` factory - or bring your own
998
+ * async function that satisfies this signature.
999
+ */
1000
+
1001
+ /**
1002
+ * The Neuron function type.
1003
+ * input - `payload.input` from the TASK envelope (arbitrary JSON).
1004
+ * context - resolved by the Axon from `payload.context_ref` (empty if none).
1005
+ * returns - a JSON-serialisable object, a {@link ClarificationOutput}, or a
1006
+ * {@link PermissionRequestOutput}.
1007
+ */
1008
+ type NeuronFn = (input: Json, context: unknown[]) => Promise<Json> | Json;
1009
+ /**
1010
+ * A NeuronFn that also exposes an async `close()` to release any resource it
1011
+ * holds (e.g. an MCP subprocess). The Axon calls `close()` automatically when
1012
+ * it deregisters.
1013
+ */
1014
+ interface CloseableNeuronFn extends NeuronFn {
1015
+ close(): Promise<void>;
1016
+ }
1017
+ /** Async fetcher the Axon uses to resolve a `context_ref` into context items. */
1018
+ type ContextFetcher = (ref: string) => Promise<unknown[]> | unknown[];
1019
+ /**
1020
+ * Marker a Neuron returns to request more information instead of producing a
1021
+ * result. The Axon converts this into a CLARIFICATION signal.
1022
+ */
1023
+ interface ClarificationOutput {
1024
+ __clarification__: true;
1025
+ question: string;
1026
+ context?: Json;
1027
+ }
1028
+ /** Build a clarification result for a Neuron to return. */
1029
+ declare function clarify(question: string, context?: Json): ClarificationOutput;
1030
+ /** Type guard: did the Neuron return a clarification marker? */
1031
+ declare function isClarification(output: unknown): output is ClarificationOutput;
1032
+ /**
1033
+ * Marker a Neuron returns to request permission to perform `action` instead of
1034
+ * producing a result. The Axon converts this into a PERMISSION signal. Same
1035
+ * return-and-resume shape as {@link ClarificationOutput}: the Neuron typically
1036
+ * tries `recall(...)` against an Engram first and only returns this on a miss.
1037
+ */
1038
+ interface PermissionRequestOutput {
1039
+ __permission__: true;
1040
+ action: string;
1041
+ scope?: Json;
1042
+ reason?: string;
1043
+ context?: Json;
1044
+ }
1045
+ /** Build a permission-request result for a Neuron to return. */
1046
+ declare function permissionRequest(action: string, opts?: {
1047
+ scope?: Json;
1048
+ reason?: string;
1049
+ context?: Json;
1050
+ }): PermissionRequestOutput;
1051
+ /** Type guard: did the Neuron return a permission-request marker? */
1052
+ declare function isPermissionRequest(output: unknown): output is PermissionRequestOutput;
1053
+ /**
1054
+ * Marker that yields an ERROR signal without throwing. A recogniser (e.g. the
1055
+ * MCP one on `is_error`) returns this so the Axon emits ERROR with a supplied
1056
+ * code/message instead of a generic NEURON_EXCEPTION.
1057
+ */
1058
+ interface ErrorOutput {
1059
+ __error__: true;
1060
+ code?: string;
1061
+ message?: string;
1062
+ recoverable?: boolean;
1063
+ }
1064
+ /** Build an error result for a Neuron / recogniser to return. */
1065
+ declare function errorResult(message: string, opts?: {
1066
+ code?: string;
1067
+ recoverable?: boolean;
1068
+ }): ErrorOutput;
1069
+ /** Type guard: did the Neuron / recogniser return an error marker? */
1070
+ declare function isErrorOutput(output: unknown): output is ErrorOutput;
1071
+
1072
+ /**
1073
+ * @cosmonapse/sdk - MCP-server Neuron
1074
+ *
1075
+ * Wrap **any stdio MCP server** as a Neuron. This is a thin client wrapper -
1076
+ * it does NOT implement an MCP server. It spawns an existing server as a
1077
+ * subprocess, speaks MCP over stdio via `@modelcontextprotocol/sdk`, and
1078
+ * exposes that server's tools behind the NeuronFn signature. A TASK becomes an
1079
+ * MCP `tools/call`; the tool result becomes the Neuron output.
1080
+ *
1081
+ * The `@modelcontextprotocol/sdk` package is an optional peer dependency and is
1082
+ * imported lazily, so projects that don't use MCP neurons don't need it.
1083
+ *
1084
+ * ```ts
1085
+ * import { Axon, mcpNeuron } from "@cosmonapse/sdk";
1086
+ *
1087
+ * const files = mcpNeuron({ server: "filesystem", args: ["/data"], tool: "read_file" });
1088
+ * const axon = new Axon({ neuronId: "files", neuronFn: files });
1089
+ * ```
1090
+ *
1091
+ * Input dict:
1092
+ * tool Tool to call (falls back to `opts.tool`, or the sole tool).
1093
+ * arguments | args Tool arguments (object). If omitted, all non-control keys
1094
+ * are used as arguments.
1095
+ * __list_tools__ When truthy, return the server's tool catalogue.
1096
+ *
1097
+ * Output (`tools/call`):
1098
+ * { response, result, is_error, content, meta:{tool,server,command} }
1099
+ * Output (`__list_tools__`):
1100
+ * { tools: [{ name, description, input_schema }] }
1101
+ */
1102
+
1103
+ interface McpNeuronOptions {
1104
+ /** Executable to spawn (e.g. "npx", "uvx"). Required unless `server` is set. */
1105
+ command?: string;
1106
+ /** Arguments. Appended after a preset's args when `server` is set. */
1107
+ args?: string[];
1108
+ /** Name of a standard server preset (see {@link standardMcpServers}). */
1109
+ server?: string;
1110
+ /** Extra environment variables for the subprocess. */
1111
+ env?: Record<string, string>;
1112
+ /** Working directory for the subprocess. */
1113
+ cwd?: string;
1114
+ /** Default tool name used when the input omits `tool`. */
1115
+ tool?: string;
1116
+ clientName?: string;
1117
+ clientVersion?: string;
1118
+ }
1119
+ /**
1120
+ * Launch specs for standard, separately-published MCP servers. We wrap them -
1121
+ * we do not ship them. Anything in `opts.args` is appended (e.g. allowed dirs
1122
+ * for filesystem, `--repository <path>` for git).
1123
+ */
1124
+ declare const standardMcpServers: Record<string, {
1125
+ command: string;
1126
+ args: string[];
1127
+ note: string;
1128
+ }>;
1129
+ declare function mcpNeuron(opts: McpNeuronOptions): CloseableNeuronFn;
1130
+
1131
+ /**
1132
+ * @cosmonapse/sdk - provider-backed Neurons (LLM over HTTP)
1133
+ *
1134
+ * Ported from `cosmonapse.neuron` (`_OllamaNeuron` / `_HuggingFaceNeuron`) and
1135
+ * `cosmonapse._neuron_base`. Wrap a running LLM server behind the `NeuronFn`
1136
+ * signature so it slots straight into an Axon.
1137
+ *
1138
+ * Uses the global `fetch` (Node 18+), so there is no extra dependency - the
1139
+ * Python port needs `httpx`; in Node the runtime ships the client.
1140
+ *
1141
+ * Input convention (shared with the Python SDK):
1142
+ * - `prompt` (string) - single-turn input, or
1143
+ * - `messages` (OpenAI-style `[{ role, content }]`) - multi-turn / system.
1144
+ * (`text` / `query` / `content` are also accepted as a prompt alias.)
1145
+ *
1146
+ * Output: `{ response: "<text>", meta: <raw provider payload> }`.
1147
+ */
1148
+
1149
+ interface OllamaNeuronOptions {
1150
+ /** Ollama model tag, e.g. "llama3", "mistral", "phi3". */
1151
+ model: string;
1152
+ /** Base URL of the Ollama daemon. Default "http://localhost:11434". */
1153
+ endpoint?: string;
1154
+ /** Optional system prompt injected before any user message. */
1155
+ system?: string;
1156
+ temperature?: number;
1157
+ /** Maximum tokens to generate (`num_predict` in Ollama). */
1158
+ maxTokens?: number;
1159
+ /** HTTP timeout in ms. Default 120_000. */
1160
+ timeoutMs?: number;
1161
+ }
1162
+ /** Wrap a running Ollama daemon as a NeuronFn. */
1163
+ declare function ollamaNeuron(opts: OllamaNeuronOptions): NeuronFn;
1164
+ interface HuggingFaceNeuronOptions {
1165
+ /** Base URL of the inference server, e.g. "http://localhost:8080". */
1166
+ endpoint: string;
1167
+ /** Model name forwarded in the chat-completions body (required for vLLM). */
1168
+ model?: string;
1169
+ /** Force the `/v1/chat/completions` path even for plain prompts. */
1170
+ useChatApi?: boolean;
1171
+ temperature?: number;
1172
+ /** Maximum tokens to generate. Default 512. */
1173
+ maxNewTokens?: number;
1174
+ /** Bearer token - use your HF Hub token for hosted endpoints. */
1175
+ apiKey?: string;
1176
+ /** HTTP timeout in ms. Default 120_000. */
1177
+ timeoutMs?: number;
1178
+ }
1179
+ /** Wrap a HuggingFace TGI endpoint (or any OpenAI-compatible server) as a NeuronFn. */
1180
+ declare function huggingFaceNeuron(opts: HuggingFaceNeuronOptions): NeuronFn;
1181
+
1182
+ /**
1183
+ * @cosmonapse/sdk - hosted-LLM provider Neurons (OpenAI, Anthropic)
1184
+ *
1185
+ * Ported from `cosmonapse.neuron` (`_OpenAINeuron` / `_AnthropicNeuron`). Wrap a
1186
+ * hosted LLM API behind the `NeuronFn` signature so it slots straight into an
1187
+ * Axon. Uses the global `fetch` (Node 18+), so there is no extra dependency -
1188
+ * the Python port needs `httpx`; in Node the runtime ships the client.
1189
+ *
1190
+ * Input convention (shared with the Python SDK):
1191
+ * - `prompt` (string) - single-turn input, or
1192
+ * - `messages` (OpenAI-style `[{ role, content }]`) - multi-turn / system.
1193
+ * (`text` / `query` / `content` are also accepted as a prompt alias.)
1194
+ *
1195
+ * Output: `{ response: "<text>", meta: <raw provider payload> }`.
1196
+ */
1197
+
1198
+ interface OpenAINeuronOptions {
1199
+ /** Chat model name, e.g. "gpt-4o", "gpt-4o-mini". */
1200
+ model: string;
1201
+ /** API key. If omitted, falls back to the `OPENAI_API_KEY` env var. */
1202
+ apiKey?: string;
1203
+ /** API base URL. Default "https://api.openai.com/v1" (point at Azure/proxies). */
1204
+ endpoint?: string;
1205
+ temperature?: number;
1206
+ /** Maximum tokens to generate. */
1207
+ maxTokens?: number;
1208
+ /** Optional system prompt injected as the first `system` message. */
1209
+ system?: string;
1210
+ /** HTTP timeout in ms. Default 120_000. */
1211
+ timeoutMs?: number;
1212
+ }
1213
+ /** Wrap the OpenAI Chat Completions API (or a compatible proxy) as a NeuronFn. */
1214
+ declare function openaiNeuron(opts: OpenAINeuronOptions): NeuronFn;
1215
+ interface AnthropicNeuronOptions {
1216
+ /** Claude model name, e.g. "claude-opus-4-6", "claude-sonnet-4-6". */
1217
+ model: string;
1218
+ /** API key. If omitted, falls back to the `ANTHROPIC_API_KEY` env var. */
1219
+ apiKey?: string;
1220
+ /**
1221
+ * Optional system prompt. Sent as the top-level `system` field (the Anthropic
1222
+ * API does not accept a `system` role inside `messages`).
1223
+ */
1224
+ system?: string;
1225
+ /** Maximum tokens to generate. Required by the API; defaults to 1024. */
1226
+ maxTokens?: number;
1227
+ temperature?: number;
1228
+ /** HTTP timeout in ms. Default 120_000. */
1229
+ timeoutMs?: number;
1230
+ }
1231
+ /** Wrap the Anthropic Messages API as a NeuronFn. */
1232
+ declare function anthropicNeuron(opts: AnthropicNeuronOptions): NeuronFn;
1233
+
1234
+ /**
1235
+ * @cosmonapse/sdk - unified Neuron factory
1236
+ *
1237
+ * Mirrors the Python `Neuron(source=...)` ergonomics in TypeScript: pick a
1238
+ * source, get back a NeuronFn you hand straight to an Axon. A Neuron is any
1239
+ * unit of real-world behaviour - an MCP server or an LLM provider today, with
1240
+ * room for more sources later.
1241
+ *
1242
+ * ```ts
1243
+ * import { Axon, neuron } from "@cosmonapse/sdk";
1244
+ *
1245
+ * new Axon({ neuronId: "files", neuronFn: neuron("mcp", { server: "filesystem", args: ["/data"] }) });
1246
+ * new Axon({ neuronId: "chat", neuronFn: neuron("ollama", { model: "llama3" }) });
1247
+ * new Axon({ neuronId: "gpt", neuronFn: neuron("openai", { model: "gpt-4o-mini" }) });
1248
+ * ```
1249
+ *
1250
+ * NOTE - there is no "express" / "http" / "api" source. An HTTP API is not a
1251
+ * Neuron: instead of wrapping a web app behind an Axon, keep your framework
1252
+ * (Express, Fastify, …) on the outside as an HTTP boundary and dispatch TASK
1253
+ * Signals from inside its route handlers via an orchestrator Dendrite. See the
1254
+ * `real-world-neurons` example.
1255
+ *
1256
+ * WHICH TO USE - `neuron(source, opts)` is the recommended, source-agnostic
1257
+ * entry point and the one mirrored from the Python SDK; prefer it in app code.
1258
+ * The standalone `mcpNeuron(...)` / `ollamaNeuron(...)` exports are the
1259
+ * lower-level primitives `neuron()` delegates to: reach for them only when you
1260
+ * want a single source's exact option type without the union, or to tree-shake
1261
+ * away the others. They are not a second, parallel API - same behaviour,
1262
+ * narrower surface.
1263
+ */
1264
+
1265
+ /**
1266
+ * Options for the OpenAI-compatible hosted aliases (`groq` / `openrouter` /
1267
+ * `together` / `mistral`). These are pre-configured {@link huggingFaceNeuron}s:
1268
+ * `endpoint` defaults to the provider's base URL and `apiKey` falls back to the
1269
+ * provider's env var, but both (and any other HuggingFace option) can be
1270
+ * overridden.
1271
+ */
1272
+ type OpenAICompatNeuronOptions = Omit<HuggingFaceNeuronOptions, "endpoint"> & {
1273
+ endpoint?: string;
1274
+ };
1275
+ type OpenAICompatAlias = "groq" | "openrouter" | "together" | "mistral";
1276
+ type NeuronSource = "mcp" | "ollama" | "huggingface" | "hf" | "openai" | "anthropic" | OpenAICompatAlias;
1277
+ declare function neuron(source: "mcp", opts: McpNeuronOptions): CloseableNeuronFn;
1278
+ declare function neuron(source: "ollama", opts: OllamaNeuronOptions): NeuronFn;
1279
+ declare function neuron(source: "huggingface" | "hf", opts: HuggingFaceNeuronOptions): NeuronFn;
1280
+ declare function neuron(source: "openai", opts: OpenAINeuronOptions): NeuronFn;
1281
+ declare function neuron(source: "anthropic", opts: AnthropicNeuronOptions): NeuronFn;
1282
+ declare function neuron(source: OpenAICompatAlias, opts?: OpenAICompatNeuronOptions): NeuronFn;
1283
+
1284
+ /**
1285
+ * @cosmonapse/sdk - dendrite
1286
+ *
1287
+ * The synapse-side participant, ported from `cosmonapse.dendrite`.
1288
+ *
1289
+ * Construction is minimal: only `synapse` is required. Everything else is
1290
+ * opt-in:
1291
+ * - Attach Axons -> subscribes to TASK, emits REGISTER / HEARTBEAT /
1292
+ * DEREGISTER, routes inbound TASKs to the right Axon.
1293
+ * - Register handlers -> subscribes to that AXON_TYPE and dispatches.
1294
+ * - heartbeatMs = 0 -> the heartbeat loop never starts.
1295
+ *
1296
+ * The Dendrite does NOT own the Synapse - the caller builds and closes it.
1297
+ *
1298
+ * There is no separate Cortex class: every Dendrite has dispatchTask /
1299
+ * emitFinal / emitError / emit plus the inbound-handler hooks. `Cortex` is
1300
+ * kept as a back-compat alias.
1301
+ *
1302
+ * Lifecycle: call `await dendrite.start()` / `await dendrite.stop()`, or use
1303
+ * `await using dendrite = new Dendrite({...}); await dendrite.start();` - the
1304
+ * Symbol.asyncDispose implementation calls stop() automatically when the scope
1305
+ * exits. This is the TS counterpart to Python's `async with dendrite:`.
1306
+ *
1307
+ * LifecycleHooks (onConnect / onRefresh / onSchedule) are wired in: connect
1308
+ * hooks fire and schedule loops launch at the end of start(); refresh hooks
1309
+ * fire on every heartbeat tick and whenever a REGISTER / DEREGISTER / HEARTBEAT
1310
+ * updates the registry; all loops stop in stop(). Attached Axons' hooks are
1311
+ * driven alongside the Dendrite's own.
1312
+ */
1313
+
1314
+ declare global {
1315
+ interface SymbolConstructor {
1316
+ readonly asyncDispose: unique symbol;
1317
+ }
1318
+ }
1319
+ type SignalHandler = (signal: Signal) => void | Promise<void>;
1320
+ /** Raised when an emit violates the protocol (e.g. emitting an Axon-only type). */
1321
+ declare class DendriteProtocolError extends Error {
1322
+ constructor(message: string);
1323
+ }
1324
+
1325
+ interface DendriteOptions {
1326
+ synapse: Synapse;
1327
+ /** Optional registry. When set, the Dendrite mirrors its own Axons and
1328
+ * tracks the namespace-wide Neuron view from REGISTER/DEREGISTER/HEARTBEAT. */
1329
+ registryStore?: RegistryStore;
1330
+ namespace?: string;
1331
+ dendriteId?: string;
1332
+ /** Per-attached-Axon heartbeat interval in ms. 0 disables the loop. */
1333
+ heartbeatMs?: number;
1334
+ /** Re-emit REGISTER on every heartbeat tick so late joiners catch up. */
1335
+ reregisterOnHeartbeat?: boolean;
1336
+ }
1337
+ declare class Dendrite {
1338
+ readonly synapse: Synapse;
1339
+ readonly registryStore: RegistryStore | null;
1340
+ readonly namespace: string;
1341
+ readonly dendriteId: string;
1342
+ private readonly heartbeatMs;
1343
+ private readonly reregisterOnHeartbeat;
1344
+ private readonly _axons;
1345
+ private readonly handlers;
1346
+ private taskSub;
1347
+ private readonly inboundSubs;
1348
+ private heartbeatTimer;
1349
+ private heartbeatStopped;
1350
+ private running;
1351
+ /** @internal - lifecycle hooks for this Dendrite. */
1352
+ readonly hooks: LifecycleHooks<Dendrite>;
1353
+ constructor(opts: DendriteOptions);
1354
+ get axons(): ReadonlyMap<string, Axon>;
1355
+ axon(neuronId: string): Axon | undefined;
1356
+ attachAxon(axon: Axon): void;
1357
+ private on;
1358
+ onAgentOutput(fn: SignalHandler): SignalHandler;
1359
+ onClarification(fn: SignalHandler): SignalHandler;
1360
+ /**
1361
+ * Register a handler fired on inbound PERMISSION requests - the *answering*
1362
+ * side. A central Cortex or a peer Dendrite evaluates the request (often
1363
+ * consulting an Engram of standing grants, keyed per-neuron) and replies via
1364
+ * {@link respondToPermission} (re-dispatch a TASK with the verdict) or
1365
+ * {@link grantPermission} / {@link denyPermission} (emit a discrete
1366
+ * PERMISSION_DECISION). It may also imprint the decision into an Engram so
1367
+ * future recalls hit.
1368
+ */
1369
+ onPermission(fn: SignalHandler): SignalHandler;
1370
+ onErrorSignal(fn: SignalHandler): SignalHandler;
1371
+ onRegister(fn: SignalHandler): SignalHandler;
1372
+ onDeregister(fn: SignalHandler): SignalHandler;
1373
+ onHeartbeat(fn: SignalHandler): SignalHandler;
1374
+ /** Register a fire-once handler called after start() completes. */
1375
+ onConnect(fn: ConnectHook<Dendrite>): ConnectHook<Dendrite>;
1376
+ /** Register a handler called whenever this Dendrite's state refreshes. */
1377
+ onRefresh(fn: RefreshHook<Dendrite>): RefreshHook<Dendrite>;
1378
+ /** Register a periodic handler that runs every `everyMs` until stop(). */
1379
+ onSchedule(everyMs: number, fn: ScheduleHook<Dendrite>): ScheduleHook<Dendrite>;
1380
+ /** Manually fire a refresh event (reason defaults to "manual"). */
1381
+ refresh(opts?: {
1382
+ reason?: string;
1383
+ neuronId?: string | null;
1384
+ extra?: Record<string, unknown>;
1385
+ }): Promise<void>;
1386
+ start(): Promise<void>;
1387
+ /**
1388
+ * Heartbeat as a self-scheduling async loop rather than `setInterval`.
1389
+ *
1390
+ * Why not setInterval: it fires on a fixed wall-clock cadence regardless of
1391
+ * whether the previous tick finished, so under load ticks overlap and the
1392
+ * effective interval drifts; and because the callback is sync, any rejection
1393
+ * from the async work inside is an unhandled rejection that setInterval
1394
+ * silently drops. Here each tick is fully awaited, its errors are caught, and
1395
+ * only then is the next tick scheduled - matching the Python SDK's
1396
+ * asyncio.Task semantics (structured error handling + clean cancellation).
1397
+ */
1398
+ private startHeartbeatLoop;
1399
+ stop(reason?: string): Promise<void>;
1400
+ /**
1401
+ * Explicit-resource-management hook so a Dendrite can be used with
1402
+ * `await using` - the TS equivalent of Python's `async with dendrite:`.
1403
+ *
1404
+ * ```ts
1405
+ * await using dendrite = new Dendrite({ synapse });
1406
+ * dendrite.attachAxon(axon);
1407
+ * await dendrite.start();
1408
+ * // ... stop() runs automatically when this scope exits, even on throw.
1409
+ * ```
1410
+ *
1411
+ * Idempotent: stop() is a no-op if the Dendrite was never started or already
1412
+ * stopped. As with stop(), the caller still owns the Synapse/registry store.
1413
+ */
1414
+ [Symbol.asyncDispose](): Promise<void>;
1415
+ private requireStore;
1416
+ /** All known records, optionally filtered (live records only by default). */
1417
+ registrySnapshot(opts?: ListOptions): Promise<NeuronRecord[]>;
1418
+ /** Live (non-deregistered) records, optionally filtered by capability. */
1419
+ findNeurons(opts?: {
1420
+ capability?: string;
1421
+ }): Promise<NeuronRecord[]>;
1422
+ dispatchTask(args: {
1423
+ neuron: string;
1424
+ input: Json;
1425
+ traceId?: string;
1426
+ parentId?: string | null;
1427
+ contextRef?: string;
1428
+ capabilities?: string[];
1429
+ meta?: Json;
1430
+ }): Promise<Signal>;
1431
+ emitFinal(args: {
1432
+ traceId: string;
1433
+ parentId: string;
1434
+ result: Json;
1435
+ meta?: Json;
1436
+ }): Promise<Signal>;
1437
+ emitError(args: {
1438
+ traceId: string;
1439
+ parentId?: string | null;
1440
+ code: string;
1441
+ message: string;
1442
+ recoverable?: boolean;
1443
+ meta?: Json;
1444
+ }): Promise<Signal>;
1445
+ /** Emit a synapse-side Signal. Refuses Axon-owned types. */
1446
+ /**
1447
+ * Reply to a PERMISSION by re-dispatching a TASK carrying the verdict.
1448
+ *
1449
+ * The "send it back to the axon" path: the follow-up TASK is addressed by
1450
+ * default to the Neuron that asked (`signal.neuron`), with `parentId` = the
1451
+ * PERMISSION's id and the original `traceId` carried over, so the Neuron
1452
+ * resumes on the same thread and can imprint the decision into an Engram (or
1453
+ * recall it next time). New TASK input: `{ permission: { action, granted,
1454
+ * reason?, ttlMs?, ...extra } }`.
1455
+ */
1456
+ respondToPermission(request: Signal, opts: {
1457
+ granted: boolean;
1458
+ reason?: string;
1459
+ ttlMs?: number;
1460
+ extra?: Json;
1461
+ neuron?: string;
1462
+ meta?: Json;
1463
+ }): Promise<Signal>;
1464
+ /** Approve a PERMISSION request. `ttlMs` optionally advertises how long the
1465
+ * grant is valid so the requester can cache it (e.g. in an Engram). */
1466
+ grantPermission(request: Signal, opts?: {
1467
+ reason?: string;
1468
+ ttlMs?: number;
1469
+ meta?: Json;
1470
+ }): Promise<Signal>;
1471
+ /** Reject a PERMISSION request. */
1472
+ denyPermission(request: Signal, opts?: {
1473
+ reason?: string;
1474
+ meta?: Json;
1475
+ }): Promise<Signal>;
1476
+ private decidePermission;
1477
+ /** Answer a *blocking* CLARIFICATION (the Neuron called ask(...) and is
1478
+ * awaiting). Distinct from the legacy return-marker flow. */
1479
+ answerClarification(request: Signal, answer: unknown, opts?: {
1480
+ meta?: Json;
1481
+ }): Promise<Signal>;
1482
+ emit(signal: Signal): Promise<void>;
1483
+ publish(signal: Signal): Promise<void>;
1484
+ subscribe(type: SignalType, handler: MessageHandler, opts?: {
1485
+ queueGroup?: string;
1486
+ }): Promise<Subscription>;
1487
+ private subject;
1488
+ private ensureInboundSub;
1489
+ private onTask;
1490
+ private emitRegister;
1491
+ private emitDeregister;
1492
+ private heartbeatTick;
1493
+ private mirrorToStore;
1494
+ private dispatchInbound;
1495
+ private updateRegistry;
1496
+ }
1497
+ /** Back-compat alias - a Cortex is just a Dendrite. */
1498
+ declare const Cortex: typeof Dendrite;
1499
+ type Cortex = Dendrite;
1500
+
1501
+ /**
1502
+ * @cosmonapse/sdk - axon
1503
+ *
1504
+ * Agent-side tool that turns a Neuron's raw output into a protocol-valid
1505
+ * Signal. Ported from `cosmonapse.axon`.
1506
+ *
1507
+ * The Axon does NOT touch the Synapse. It owns:
1508
+ * - the Neuron's identity (neuronId, capabilities, version)
1509
+ * - the body of the tool (the NeuronFn)
1510
+ * - response validation:
1511
+ * normal return -> AGENT_OUTPUT
1512
+ * clarification marker -> CLARIFICATION
1513
+ * thrown error -> ERROR
1514
+ *
1515
+ * Like the Python Axon, this one carries LifecycleHooks (onConnect /
1516
+ * onRefresh / onSchedule). The hosting Dendrite drives them: it fires the
1517
+ * connect hooks and launches the schedule loops once the Axon is attached and
1518
+ * registered, and stops them when the Dendrite stops.
1519
+ */
1520
+
1521
+ /**
1522
+ * Package-internal keys for the attach/detach handshake. These are deliberately
1523
+ * NOT re-exported from index.ts, so only same-package code (the Dendrite) can
1524
+ * name them and invoke the methods. This enforces "internal" at the language
1525
+ * level - which a `@internal` JSDoc tag on a `public` method does not. External
1526
+ * consumers have no way to reference these symbols, so `axon[ATTACH](...)` is
1527
+ * effectively private to the package.
1528
+ */
1529
+ declare const ATTACH: unique symbol;
1530
+ declare const DETACH: unique symbol;
1531
+ /**
1532
+ * Recognises a Neuron's *native* output (an LLM's `{ response }`, an MCP
1533
+ * server's `{ is_error }`) and normalises it into the marker dict the Axon
1534
+ * understands. The recognition the Axon applies before wrapping. May throw to
1535
+ * yield an ERROR signal.
1536
+ */
1537
+ type OutputParser = (raw: unknown) => unknown;
1538
+ /**
1539
+ * A detector registered via `axon.detects*`. Returns the intent's fields on a
1540
+ * match, or null/undefined to fall through. May be sync or async.
1541
+ */
1542
+ type Recogniser = (raw: unknown) => unknown | Promise<unknown>;
1543
+ interface AxonOptions {
1544
+ neuronId: string;
1545
+ neuronFn: NeuronFn;
1546
+ capabilities?: string[];
1547
+ version?: string;
1548
+ contextFetcher?: ContextFetcher;
1549
+ /** Recognition the Axon applies to the Neuron's raw output before wrapping. */
1550
+ outputParser?: OutputParser;
1551
+ }
1552
+ /** Axon metadata accepted by the source-paired factories. */
1553
+ interface AxonExtra {
1554
+ capabilities?: string[];
1555
+ version?: string;
1556
+ contextFetcher?: ContextFetcher;
1557
+ /** Attach the source's recogniser (default true). */
1558
+ recognize?: boolean;
1559
+ }
1560
+ declare class Axon {
1561
+ readonly neuronId: string;
1562
+ readonly capabilities: string[];
1563
+ readonly version: string | undefined;
1564
+ private readonly fn;
1565
+ private readonly contextFetcher;
1566
+ private readonly outputParser;
1567
+ private dendrite;
1568
+ /**
1569
+ * Decorator-registered recognisers, one bucket per capability (the asking
1570
+ * side; named `detects*` to stay distinct from the Dendrite's `on*` inbound
1571
+ * handlers). Applied in precedence error -> clarification -> permission ->
1572
+ * output by {@link applyRecognisers}.
1573
+ */
1574
+ private readonly recognisers;
1575
+ /** @internal - lifecycle hooks, driven by the hosting Dendrite. */
1576
+ readonly hooks: LifecycleHooks<Axon>;
1577
+ constructor(opts: AxonOptions);
1578
+ private static build;
1579
+ /** Axon paired with any registered Neuron source + its recogniser. */
1580
+ static fromSource(source: NeuronSource, neuronId: string, opts: any, extra?: AxonExtra): Axon;
1581
+ /** Axon paired with the OpenAI Chat Completions API. */
1582
+ static openai(neuronId: string, opts: OpenAINeuronOptions, extra?: AxonExtra): Axon;
1583
+ /** Axon paired with the Anthropic Messages API. */
1584
+ static anthropic(neuronId: string, opts: AnthropicNeuronOptions, extra?: AxonExtra): Axon;
1585
+ /** Axon paired with a local Ollama daemon. */
1586
+ static ollama(neuronId: string, opts: OllamaNeuronOptions, extra?: AxonExtra): Axon;
1587
+ /** Axon paired with a HuggingFace TGI / OpenAI-compatible endpoint. */
1588
+ static huggingface(neuronId: string, opts: HuggingFaceNeuronOptions, extra?: AxonExtra): Axon;
1589
+ /** Axon paired with a stdio MCP server. */
1590
+ static mcp(neuronId: string, opts: McpNeuronOptions, extra?: AxonExtra): Axon;
1591
+ /** Detector returning the AGENT_OUTPUT payload, or null to wrap verbatim. */
1592
+ detectsOutput(fn: Recogniser): Recogniser;
1593
+ /** Detector returning `{ question, context? }` to emit CLARIFICATION, or null. */
1594
+ detectsClarification(fn: Recogniser): Recogniser;
1595
+ /** Detector returning `{ action, scope?, reason?, context? }` for PERMISSION, or null. */
1596
+ detectsPermission(fn: Recogniser): Recogniser;
1597
+ /** Detector returning `{ code?, message?, recoverable? }` to emit ERROR, or null. */
1598
+ detectsError(fn: Recogniser): Recogniser;
1599
+ private applyRecognisers;
1600
+ /** Register a fire-once handler called after this Axon connects (attaches + registers). */
1601
+ onConnect(fn: ConnectHook<Axon>): ConnectHook<Axon>;
1602
+ /** Register a handler called whenever this Axon's observable state refreshes. */
1603
+ onRefresh(fn: RefreshHook<Axon>): RefreshHook<Axon>;
1604
+ /** Register a periodic handler that runs every `everyMs` while the host runs. */
1605
+ onSchedule(everyMs: number, fn: ScheduleHook<Axon>): ScheduleHook<Axon>;
1606
+ /**
1607
+ * Package-internal: invoked by Dendrite.attachAxon via the {@link ATTACH}
1608
+ * symbol. Not callable by external consumers (the symbol is not exported from
1609
+ * index.ts), so this replaces the previous `@internal`-comment-only contract
1610
+ * with real, enforced encapsulation.
1611
+ */
1612
+ [ATTACH](dendrite: Dendrite): void;
1613
+ /** Package-internal: invoked via the {@link DETACH} symbol. */
1614
+ [DETACH](): void;
1615
+ /** Run the Neuron and return AGENT_OUTPUT / CLARIFICATION / ERROR. */
1616
+ handleTask(task: Signal): Promise<Signal>;
1617
+ }
1618
+ /** Recogniser for LLM sources returning `{ response: text, meta }`. */
1619
+ declare function parseLlmIntents(raw: unknown): unknown;
1620
+ /** Recogniser for the `mcp` source: `is_error` -> ERROR, else pass through. */
1621
+ declare function parseMcpIntents(raw: unknown): unknown;
1622
+
1623
+ /**
1624
+ * @cosmonapse/sdk - Engram (shared memory)
1625
+ *
1626
+ * Ported from the Python `cosmonapse.engram` package (see ENGRAM_DESIGN.md).
1627
+ * An Engram is the synapse-side participant that services RECALL / IMPRINT
1628
+ * signals. Engrams are NOT Neurons: they never produce AGENT_OUTPUT. A hosting
1629
+ * Dendrite mounts one via `dendrite.attachEngram(engram)`.
1630
+ *
1631
+ * This module is the value layer: the data types, the `Engram` contract, and
1632
+ * the default in-process `InMemoryEngram`. The SQLite / Postgres backends live
1633
+ * in `engram-sqlite.ts` / `engram-postgres.ts`; the caller-side correlation
1634
+ * table lives in `engram-client.ts`.
1635
+ */
1636
+
1637
+ type RecallMode = "first" | "merge" | "all";
1638
+ type ImprintOp = "add" | "append" | "merge" | "upsert" | "delete";
1639
+ /** One search result. `score` is backend-dependent; relational backends use 1.0. */
1640
+ interface Hit {
1641
+ id: string;
1642
+ entry: Json;
1643
+ score: number;
1644
+ }
1645
+ /** What a recall() call returns to the caller. */
1646
+ interface RecallResult {
1647
+ hits: Hit[];
1648
+ engramIds: string[];
1649
+ truncated: boolean;
1650
+ tookMs: number | null;
1651
+ }
1652
+ /** What an imprint() call returns to the caller. */
1653
+ interface ImprintReceipt {
1654
+ engramId: string;
1655
+ op: string;
1656
+ id: string | null;
1657
+ version: number | null;
1658
+ tookMs: number | null;
1659
+ error: string | null;
1660
+ /** Convenience: true when `error` is null. */
1661
+ ok: boolean;
1662
+ }
1663
+ interface EngramBindingInit {
1664
+ name: string;
1665
+ directedId?: string;
1666
+ directedType?: string;
1667
+ defaultDeadlineMs?: number;
1668
+ defaultRecallMode?: RecallMode;
1669
+ }
1670
+ /**
1671
+ * Declarative wiring of one Engram into an Axon. The Neuron addresses an Engram
1672
+ * by the stable local `name`; `directedId` / `directedType` determine how
1673
+ * RECALL/IMPRINT are routed on the wire. At least one of them must be set.
1674
+ */
1675
+ declare class EngramBinding {
1676
+ readonly name: string;
1677
+ readonly directedId: string | null;
1678
+ readonly directedType: string | null;
1679
+ readonly defaultDeadlineMs: number | null;
1680
+ readonly defaultRecallMode: RecallMode;
1681
+ constructor(init: EngramBindingInit);
1682
+ /** Build the `Directed` addressing this Engram. */
1683
+ toDirected(): Directed;
1684
+ }
1685
+ declare class EngramError extends Error {
1686
+ constructor(message?: string);
1687
+ }
1688
+ /** Raised when a RECALL or IMPRINT deadline elapses with no response. */
1689
+ declare class EngramTimeout extends EngramError {
1690
+ }
1691
+ /** Raised when the containing TASK terminates while a call is in flight. */
1692
+ declare class EngramCancelled extends EngramError {
1693
+ }
1694
+ /** Raised when a Neuron asks for an Engram binding its Axon was not wired to. */
1695
+ declare class EngramNotBound extends EngramError {
1696
+ }
1697
+ /** Raised by a backend that must shed load (surfaces as an IMPRINTED error). */
1698
+ declare class EngramOverloaded extends EngramError {
1699
+ }
1700
+ interface RecallOptions {
1701
+ filters?: Json;
1702
+ contextRef?: string;
1703
+ deadlineMs?: number;
1704
+ minConfidence?: number;
1705
+ }
1706
+ interface ImprintOptions {
1707
+ mergeKey?: string;
1708
+ /** Originating IMPRINT signal id; backends use it for idempotency. */
1709
+ imprintId?: string;
1710
+ }
1711
+ /**
1712
+ * Storage wrapper. One backend per Engram instance. Every backend implements
1713
+ * this exact interface; the test suite runs against any conforming Engram.
1714
+ */
1715
+ declare abstract class Engram {
1716
+ abstract engramId: string;
1717
+ abstract engramKind: string;
1718
+ abstract capabilities: string[];
1719
+ version: string | null;
1720
+ /** Open backend resources (DB pool, file handle, ...). */
1721
+ abstract connect(): Promise<void>;
1722
+ /** Release backend resources. */
1723
+ abstract close(): Promise<void>;
1724
+ /** Return matching entries. Empty array on a miss; never throw on a miss. */
1725
+ abstract recall(query: Json, opts?: RecallOptions): Promise<Hit[]>;
1726
+ /** Write to the backend. `op` is one of add | append | merge | upsert | delete. */
1727
+ abstract imprint(op: ImprintOp, entry: Json, opts?: ImprintOptions): Promise<ImprintReceipt>;
1728
+ /** Return false if this Engram cannot satisfy the query. Default: serve all. */
1729
+ canServe(_query: Json): Promise<boolean>;
1730
+ }
1731
+ interface InMemoryEngramInit {
1732
+ engramId?: string;
1733
+ engramKind?: string;
1734
+ capabilities?: string[];
1735
+ version?: string | null;
1736
+ }
1737
+ /** Dict-backed Engram. The default backend for tests and local dev. */
1738
+ declare class InMemoryEngram extends Engram {
1739
+ engramId: string;
1740
+ engramKind: string;
1741
+ capabilities: string[];
1742
+ private entries;
1743
+ private byMergeKey;
1744
+ private imprintSeen;
1745
+ constructor(init?: InMemoryEngramInit);
1746
+ connect(): Promise<void>;
1747
+ close(): Promise<void>;
1748
+ recall(query: Json, opts?: RecallOptions): Promise<Hit[]>;
1749
+ imprint(op: ImprintOp, entry: Json, opts?: ImprintOptions): Promise<ImprintReceipt>;
1750
+ /** Test/debug helper - NOT part of the Engram contract. */
1751
+ snapshot(): Json[];
1752
+ private makeEntry;
1753
+ private store;
1754
+ private evict;
1755
+ }
1756
+ /** Conservative deep merge: dicts merge, lists concat-dedup, scalars overwrite. */
1757
+ declare function deepMerge(base: unknown, incoming: unknown): unknown;
1758
+
1759
+ /**
1760
+ * @cosmonapse/sdk - Engram caller-side client
1761
+ *
1762
+ * Ported from `cosmonapse.engram.client`. EngramClient is the caller-side
1763
+ * bridge: it builds RECALL / IMPRINT envelopes, publishes them, registers
1764
+ * pending promises keyed by the envelope id, and resolves them when a matching
1765
+ * RECALLED / IMPRINTED arrives (correlated by `parent_id`). It enforces
1766
+ * per-call deadlines and cancels in-flight calls when a TASK terminates.
1767
+ *
1768
+ * To avoid an import cycle with the Dendrite, the client depends only on a
1769
+ * minimal {@link EngramPublisher} (the Dendrite implements it by passing
1770
+ * itself in). The Dendrite owns the subscription to RECALLED / IMPRINTED and
1771
+ * calls `deliver(signal)` for each inbound.
1772
+ */
1773
+
1774
+ /** The slice of the Dendrite the client needs: a way to put a Signal on the wire. */
1775
+ interface EngramPublisher {
1776
+ publish(signal: Signal): Promise<void>;
1777
+ }
1778
+ interface RecallCallArgs {
1779
+ binding?: EngramBinding;
1780
+ engramId?: string;
1781
+ engramKind?: string;
1782
+ query: Json;
1783
+ filters?: Json;
1784
+ contextRef?: string;
1785
+ deadlineMs?: number;
1786
+ recallMode?: RecallMode;
1787
+ minConfidence?: number;
1788
+ traceId: string;
1789
+ parentId: string;
1790
+ meta?: Json;
1791
+ }
1792
+ interface ImprintCallArgs {
1793
+ binding?: EngramBinding;
1794
+ engramId?: string;
1795
+ engramKind?: string;
1796
+ op: ImprintOp;
1797
+ entry: Json;
1798
+ mergeKey?: string;
1799
+ awaitAck?: boolean;
1800
+ deadlineMs?: number;
1801
+ traceId: string;
1802
+ parentId: string;
1803
+ meta?: Json;
1804
+ }
1805
+ declare class EngramClient {
1806
+ private readonly publisher;
1807
+ private pendingRecalls;
1808
+ private pendingImprints;
1809
+ private byTrace;
1810
+ constructor(publisher: EngramPublisher);
1811
+ recall(args: RecallCallArgs): Promise<RecallResult>;
1812
+ imprint(args: ImprintCallArgs): Promise<ImprintReceipt | null>;
1813
+ /** Match RECALLED / IMPRINTED by parent_id and resolve pendings. */
1814
+ deliver(sig: Signal): void;
1815
+ /** Cancel every in-flight recall/imprint on a trace (FINAL/ERROR or shutdown). */
1816
+ cancelTrace(traceId: string): void;
1817
+ cancelAll(): void;
1818
+ private onRecallDeadline;
1819
+ private onImprintDeadline;
1820
+ private track;
1821
+ private cleanupRecall;
1822
+ private cleanupImprint;
1823
+ private discardTrace;
1824
+ }
1825
+
1826
+ /**
1827
+ * @cosmonapse/sdk - SQLite Engram
1828
+ *
1829
+ * Ported from `cosmonapse.engram.sqlite`. A single file on disk (or
1830
+ * `:memory:`), backed by `better-sqlite3` (lazy-imported, optional dependency:
1831
+ * `npm i better-sqlite3`). The Python port uses stdlib sqlite3 on a threadpool;
1832
+ * better-sqlite3 is synchronous, so the async `Engram` methods simply wrap
1833
+ * synchronous calls.
1834
+ *
1835
+ * Recall surface matches InMemoryEngram:
1836
+ * query = { text?, tag?, merge_key?, top_k = 50 }
1837
+ * filters = { tags?: string[], since?: iso, until?: iso }
1838
+ */
1839
+
1840
+ interface SqliteEngramInit {
1841
+ path?: string;
1842
+ engramId?: string;
1843
+ engramKind?: string;
1844
+ capabilities?: string[];
1845
+ version?: string | null;
1846
+ }
1847
+ /** SQLite-backed Engram (optional `better-sqlite3`). */
1848
+ declare class SqliteEngram extends Engram {
1849
+ engramId: string;
1850
+ engramKind: string;
1851
+ capabilities: string[];
1852
+ private readonly path;
1853
+ private db;
1854
+ constructor(init?: SqliteEngramInit);
1855
+ connect(): Promise<void>;
1856
+ close(): Promise<void>;
1857
+ private require;
1858
+ recall(query: Json, opts?: RecallOptions): Promise<Hit[]>;
1859
+ imprint(op: ImprintOp, entry: Json, opts?: ImprintOptions): Promise<ImprintReceipt>;
1860
+ }
1861
+
1862
+ /**
1863
+ * @cosmonapse/sdk - Postgres Engram
1864
+ *
1865
+ * Ported from `cosmonapse.engram.postgres`. JSONB content + GIN-indexed tags,
1866
+ * backed by `pg` (node-postgres; lazy-imported, optional dependency:
1867
+ * `npm i pg`). The Python port uses asyncpg; `pg` is the de-facto Node driver.
1868
+ *
1869
+ * Recall surface matches SqliteEngram for portability:
1870
+ * query = { text?, tag?, merge_key?, top_k = 50 }
1871
+ * filters = { tags?: string[], since?: iso, until?: iso }
1872
+ */
1873
+
1874
+ interface PostgresEngramInit {
1875
+ dsn: string;
1876
+ engramId?: string;
1877
+ engramKind?: string;
1878
+ capabilities?: string[];
1879
+ version?: string | null;
1880
+ minSize?: number;
1881
+ maxSize?: number;
1882
+ }
1883
+ /** Postgres-backed Engram (optional `pg`). */
1884
+ declare class PostgresEngram extends Engram {
1885
+ engramId: string;
1886
+ engramKind: string;
1887
+ capabilities: string[];
1888
+ private readonly dsn;
1889
+ private readonly minSize;
1890
+ private readonly maxSize;
1891
+ private pool;
1892
+ constructor(init: PostgresEngramInit);
1893
+ connect(): Promise<void>;
1894
+ close(): Promise<void>;
1895
+ private require;
1896
+ recall(query: Json, opts?: RecallOptions): Promise<Hit[]>;
1897
+ imprint(op: ImprintOp, entry: Json, opts?: ImprintOptions): Promise<ImprintReceipt>;
1898
+ }
1899
+
1900
+ /**
1901
+ * @cosmonapse/sdk
1902
+ *
1903
+ * TypeScript surface of the Cosmonapse envelope protocol. This entry point
1904
+ * re-exports the envelope types/codec and the typed signal builders. It is the
1905
+ * 1:1 counterpart to the Python `cosmonapse` package's envelope module.
1906
+ *
1907
+ * Ported and functional: envelope, builders, MemorySynapse, NatsSynapse,
1908
+ * DevSynapse, KafkaSynapse, the RegistryStore (in-memory + sqlite + postgres) +
1909
+ * Dendrite registry mirror, LifecycleHooks, connectSynapse, Neuron (MCP /
1910
+ * Ollama / HuggingFace), Axon and Dendrite. Any remaining intentional
1911
+ * differences are documented in PORTING_STATUS.md.
1912
+ */
1913
+ declare const VERSION: string;
1914
+
1915
+ export { AXON_TYPES, type AnthropicNeuronOptions, Axon, type AxonExtra, type AxonOptions, type ClarificationOutput, type CloseableNeuronFn, type ConnectHook, type ContextFetcher, Cortex, DendriteProtocolError as CortexProtocolError, Dendrite, type DendriteOptions, DendriteProtocolError, DevSynapse, type DevSynapseOptions, DevSynapseServer, type DevSynapseServerOptions, type Directed, type DirectedInput, Engram, EngramBinding, type EngramBindingInit, EngramCancelled, EngramClient, EngramError, EngramNotBound, EngramOverloaded, type EngramPublisher, EngramTimeout, type ErrorOutput, type Hit, type HuggingFaceNeuronOptions, type ImprintCallArgs, type ImprintOp, type ImprintOptions, type ImprintReceipt, InMemoryEngram, type InMemoryEngramInit, type Json, KafkaSynapse, type KafkaSynapseOptions, LifecycleHooks, type ListOptions, type McpNeuronOptions, MemoryRegistryStore, MemorySynapse, type MessageHandler, NatsSynapse, type NatsSynapseOptions, type NeuronFn, type NeuronRecord, type NeuronRecordInit, type NeuronSource, type NeuronStatus, type NewSignalInput, type OllamaNeuronOptions, type OpenAICompatNeuronOptions, type OpenAINeuronOptions, type OutputParser, type PermissionRequestOutput, PostgresEngram, type PostgresEngramInit, PostgresRegistryStore, type PostgresRegistryStoreOptions, type RecallCallArgs, type RecallMode, type RecallOptions, type RecallResult, type Recogniser, type RefreshEvent, type RefreshHook, type RegistryStore, type RequestOptions, SYNAPSE_TYPES, type ScheduleHook, type Signal, type SignalHandler, SignalType, SqliteEngram, type SqliteEngramInit, SqliteRegistryStore, type SubscribeOptions, type Subscription, type Synapse, VERSION, agentOutputSignal, anthropicNeuron, bidSignal, clarificationAnswerSignal, clarificationSignal, clarify, connectSynapse, consensusSignal, contextSyncSignal, createSignal, critiqueSignal, decode, deepMerge, deregisterSignal, directedTo, discoverSignal, encode, errorResult, errorSignal, escalationSignal, finalSignal, heartbeatSignal, huggingFaceNeuron, imprintSignal, imprintedSignal, isClarification, isErrorOutput, isPermissionRequest, mcpNeuron, memoryAppendSignal, neuron, neuronRecord, newEngramId, newEventId, newTraceId, normalizeDirected, ollamaNeuron, openaiNeuron, parseLlmIntents, parseMcpIntents, permissionDecisionSignal, permissionRequest, permissionSignal, planSignal, recallSignal, recalledSignal, registerSignal, reply, standardMcpServers, synapseFromUrl, taskOfferSignal, taskSignal, thoughtDeltaSignal, toolCallSignal, toolResultSignal, validateSignal };