skynet-graph 1.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.
Files changed (213) hide show
  1. package/CONTRIBUTING.md +22 -0
  2. package/LICENSE +661 -0
  3. package/README.md +245 -0
  4. package/bin/sg +20 -0
  5. package/concepts/README.md +23 -0
  6. package/concepts/_substrate/Claim/Confidence/Trusted.json +6 -0
  7. package/concepts/_substrate/Claim/Confidence.json +10 -0
  8. package/concepts/_substrate/Claim/Freshness/Stale.json +7 -0
  9. package/concepts/_substrate/Claim/Freshness.json +5 -0
  10. package/concepts/_substrate/Claim/Verification/Refuted.json +6 -0
  11. package/concepts/_substrate/Claim/Verification.json +6 -0
  12. package/concepts/_substrate/Claim.json +4 -0
  13. package/concepts/_substrate/Frontier/InBeam.json +6 -0
  14. package/concepts/_substrate/Frontier/Stuck.json +6 -0
  15. package/concepts/_substrate/Frontier.json +5 -0
  16. package/concepts/_substrate/Intake/ToTask.json +22 -0
  17. package/concepts/_substrate/Intake/Typed.json +10 -0
  18. package/concepts/_substrate/Intake.json +27 -0
  19. package/concepts/_substrate/Task/Answer.json +12 -0
  20. package/concepts/_substrate/Task/Complexity/Atomic.json +5 -0
  21. package/concepts/_substrate/Task/Complexity/Compound/Expansion.json +7 -0
  22. package/concepts/_substrate/Task/Complexity/Compound.json +6 -0
  23. package/concepts/_substrate/Task/Complexity.json +6 -0
  24. package/concepts/_substrate/Task/EvalComplexity.json +13 -0
  25. package/concepts/_substrate/Task/ReportUp.json +6 -0
  26. package/concepts/_substrate/Task/Rollup.json +10 -0
  27. package/concepts/_substrate/Task.json +5 -0
  28. package/concepts/clinical/Diagnosis/Medication.json +6 -0
  29. package/concepts/clinical/Diagnosis.json +15 -0
  30. package/concepts/clinical/Observation/LabValue/OutOfRange.json +6 -0
  31. package/concepts/clinical/Observation/LabValue.json +5 -0
  32. package/concepts/clinical/Observation.json +6 -0
  33. package/concepts/common/Document/pathBasket.json +6 -0
  34. package/concepts/common/Document.json +3 -0
  35. package/concepts/common/Edge/Distance.json +13 -0
  36. package/concepts/common/Edge/Stay/LongStay.json +8 -0
  37. package/concepts/common/Edge/Stay.json +8 -0
  38. package/concepts/common/Edge/Travel/LongTravel.json +16 -0
  39. package/concepts/common/Edge/Travel/ShortTravel.json +11 -0
  40. package/concepts/common/Edge/Travel/targetNode.json +7 -0
  41. package/concepts/common/Edge/Travel.json +7 -0
  42. package/concepts/common/Edge.json +3 -0
  43. package/concepts/common/Vertice.json +3 -0
  44. package/concepts/supply/Fulfillment.json +10 -0
  45. package/concepts/supply/Inventory/Reorder.json +7 -0
  46. package/concepts/supply/Inventory.json +5 -0
  47. package/concepts/supply/Procurement/SupplierConfirm.json +6 -0
  48. package/concepts/supply/Procurement.json +8 -0
  49. package/concepts/supply/Transport.json +5 -0
  50. package/doc/API.md +556 -0
  51. package/doc/CAPABILITIES.md +373 -0
  52. package/doc/MODELISATION.md +511 -0
  53. package/doc/architecture.md +267 -0
  54. package/doc/concept-as-graph.md +379 -0
  55. package/doc/concept-learning.md +168 -0
  56. package/doc/creative-loop-map.md +202 -0
  57. package/doc/original-2016-doc.md +148 -0
  58. package/doc/usage.md +501 -0
  59. package/lib/authoring/abstract.js +401 -0
  60. package/lib/authoring/abstraction.js +190 -0
  61. package/lib/authoring/adapt.js +393 -0
  62. package/lib/authoring/ancestry.js +216 -0
  63. package/lib/authoring/author.js +147 -0
  64. package/lib/authoring/bounded-merge.js +57 -0
  65. package/lib/authoring/canon.js +158 -0
  66. package/lib/authoring/clock.js +123 -0
  67. package/lib/authoring/combinator.js +92 -0
  68. package/lib/authoring/compete.js +76 -0
  69. package/lib/authoring/compose-hotspot.js +314 -0
  70. package/lib/authoring/compress.js +279 -0
  71. package/lib/authoring/concept-net.js +254 -0
  72. package/lib/authoring/concepts.js +66 -0
  73. package/lib/authoring/context-project.js +277 -0
  74. package/lib/authoring/contract.js +476 -0
  75. package/lib/authoring/corpus-pack.js +125 -0
  76. package/lib/authoring/cost-probe.js +86 -0
  77. package/lib/authoring/crystallize.js +555 -0
  78. package/lib/authoring/dag-decompose.js +256 -0
  79. package/lib/authoring/dataset-adapter.js +253 -0
  80. package/lib/authoring/debug-provider.js +92 -0
  81. package/lib/authoring/decompose.js +343 -0
  82. package/lib/authoring/emittability.js +236 -0
  83. package/lib/authoring/equilibrium.js +160 -0
  84. package/lib/authoring/extract.js +153 -0
  85. package/lib/authoring/forest.js +55 -0
  86. package/lib/authoring/forge-fallback.js +116 -0
  87. package/lib/authoring/givens.js +121 -0
  88. package/lib/authoring/glossary.js +177 -0
  89. package/lib/authoring/grammar-graph.js +191 -0
  90. package/lib/authoring/granularity.js +80 -0
  91. package/lib/authoring/graph-net.js +108 -0
  92. package/lib/authoring/ground.js +148 -0
  93. package/lib/authoring/higher-order.js +85 -0
  94. package/lib/authoring/hotspot.js +132 -0
  95. package/lib/authoring/hysteresis.js +85 -0
  96. package/lib/authoring/index.js +106 -0
  97. package/lib/authoring/lattice-morphism.js +61 -0
  98. package/lib/authoring/lattice-pack.js +148 -0
  99. package/lib/authoring/leaf-io.js +77 -0
  100. package/lib/authoring/library.js +156 -0
  101. package/lib/authoring/lifecycle.js +92 -0
  102. package/lib/authoring/loop.js +193 -0
  103. package/lib/authoring/master-loop.js +132 -0
  104. package/lib/authoring/mdl.js +209 -0
  105. package/lib/authoring/memo-stability.js +122 -0
  106. package/lib/authoring/method-explorer.js +138 -0
  107. package/lib/authoring/method-pack.js +181 -0
  108. package/lib/authoring/method.js +202 -0
  109. package/lib/authoring/mine.js +510 -0
  110. package/lib/authoring/mount.js +119 -0
  111. package/lib/authoring/negotiate.js +100 -0
  112. package/lib/authoring/parametric.js +139 -0
  113. package/lib/authoring/reaggregate.js +75 -0
  114. package/lib/authoring/rebalance.js +157 -0
  115. package/lib/authoring/recall.js +135 -0
  116. package/lib/authoring/registry.js +350 -0
  117. package/lib/authoring/relearn.js +147 -0
  118. package/lib/authoring/retention.js +89 -0
  119. package/lib/authoring/segment-proxy.js +114 -0
  120. package/lib/authoring/serve-leaf.js +64 -0
  121. package/lib/authoring/slot-aware-serve.js +77 -0
  122. package/lib/authoring/sound-invoke.js +59 -0
  123. package/lib/authoring/split-serve.js +74 -0
  124. package/lib/authoring/ste.js +239 -0
  125. package/lib/authoring/stock.js +101 -0
  126. package/lib/authoring/store.js +94 -0
  127. package/lib/authoring/supervise.js +138 -0
  128. package/lib/authoring/support.js +131 -0
  129. package/lib/authoring/task-mirror.js +51 -0
  130. package/lib/authoring/typed-loop.js +160 -0
  131. package/lib/authoring/validate.js +569 -0
  132. package/lib/authoring/widen.js +88 -0
  133. package/lib/combos/appliance.js +189 -0
  134. package/lib/combos/critique.js +377 -0
  135. package/lib/combos/defaults.js +86 -0
  136. package/lib/combos/durable-runner.js +115 -0
  137. package/lib/combos/forge.js +209 -0
  138. package/lib/combos/index.js +86 -0
  139. package/lib/combos/learning-library.js +253 -0
  140. package/lib/combos/mixture-serve.js +193 -0
  141. package/lib/combos/plan-loop.js +192 -0
  142. package/lib/combos/proxy-cache.js +272 -0
  143. package/lib/combos/self-mod.js +94 -0
  144. package/lib/durable/audit.js +102 -0
  145. package/lib/durable/checkpoint-store.js +550 -0
  146. package/lib/durable/fold.js +62 -0
  147. package/lib/durable/interpreter.js +242 -0
  148. package/lib/durable/xlate.js +185 -0
  149. package/lib/graph/Graph.js +2486 -0
  150. package/lib/graph/expr.js +307 -0
  151. package/lib/graph/index.js +26 -0
  152. package/lib/graph/log.js +134 -0
  153. package/lib/graph/objects/Concept.js +382 -0
  154. package/lib/graph/objects/Entity.js +503 -0
  155. package/lib/graph/objects/Node.js +57 -0
  156. package/lib/graph/objects/PathMap.js +557 -0
  157. package/lib/graph/objects/Segment.js +100 -0
  158. package/lib/graph/tasks/stabilize.js +59 -0
  159. package/lib/graph/tasks/taskflow.js +247 -0
  160. package/lib/index.js +122 -0
  161. package/lib/load.js +132 -0
  162. package/lib/providers/backends.js +117 -0
  163. package/lib/providers/borderline.js +129 -0
  164. package/lib/providers/cache.js +226 -0
  165. package/lib/providers/canonicalize.js +198 -0
  166. package/lib/providers/constat.js +107 -0
  167. package/lib/providers/geo.js +53 -0
  168. package/lib/providers/index.js +116 -0
  169. package/lib/providers/intake.js +263 -0
  170. package/lib/providers/llm-local.js +88 -0
  171. package/lib/providers/llm.js +236 -0
  172. package/lib/providers/local-host.js +197 -0
  173. package/lib/providers/merge-consistency.js +162 -0
  174. package/lib/providers/nogood.js +149 -0
  175. package/lib/providers/reason-loop.js +223 -0
  176. package/lib/providers/semiring.js +285 -0
  177. package/lib/providers/solver-fork.js +163 -0
  178. package/lib/providers/stats.js +228 -0
  179. package/lib/providers/verify.js +120 -0
  180. package/lib/runtime/index.js +135 -0
  181. package/lib/runtime/invoke-pool.js +92 -0
  182. package/lib/runtime/protocol.js +210 -0
  183. package/lib/runtime/transport-socket.js +77 -0
  184. package/lib/runtime/worker-entry.js +31 -0
  185. package/lib/sg/cli.js +601 -0
  186. package/lib/sg/log-sinks.js +214 -0
  187. package/lib/sg/mcp.js +395 -0
  188. package/lib/sg/proxy-run.js +66 -0
  189. package/lib/sg/serve-demo.js +185 -0
  190. package/lib/sg/serve.js +205 -0
  191. package/lib/sg/trace.js +118 -0
  192. package/lib/studio/protocol.js +45 -0
  193. package/lib/studio/public/app.js +310 -0
  194. package/lib/studio/public/components/ConceptEditor.js +43 -0
  195. package/lib/studio/public/components/ConceptTree.js +43 -0
  196. package/lib/studio/public/components/CorpusPanel.js +68 -0
  197. package/lib/studio/public/components/ForkTree.js +36 -0
  198. package/lib/studio/public/components/GrammarGraph.js +99 -0
  199. package/lib/studio/public/components/GraphCanvas.js +156 -0
  200. package/lib/studio/public/components/Inspector.js +48 -0
  201. package/lib/studio/public/components/LearningPanel.js +85 -0
  202. package/lib/studio/public/components/PromptConsole.js +46 -0
  203. package/lib/studio/public/components/ProviderTrace.js +39 -0
  204. package/lib/studio/public/components/SessionSplit.js +47 -0
  205. package/lib/studio/public/components/TilingOverlay.js +40 -0
  206. package/lib/studio/public/components/Timeline.js +57 -0
  207. package/lib/studio/public/index.html +26 -0
  208. package/lib/studio/public/styles.css +185 -0
  209. package/lib/studio/public/ws.js +58 -0
  210. package/lib/studio/server.js +130 -0
  211. package/lib/studio/session.js +397 -0
  212. package/lib/studio/studio.js +149 -0
  213. package/package.json +42 -0
@@ -0,0 +1,92 @@
1
+ /*
2
+ * Copyright 2026 Nathanael Braun
3
+ * @author : Nathanael BRAUN <pp9ping@gmail.com>
4
+ *
5
+ * AGPL-3.0-or-later. See <https://www.gnu.org/licenses/>.
6
+ */
7
+ 'use strict';
8
+ /**
9
+ * invoke-pool — THE SHARED INSTANCE KEYED BY CONTRACT (roadmap 2026-07-09 P3; the scale point). P2 delegates via the
10
+ * P1 invoke, but `invokeGraph` SPAWNS + TEARS DOWN a worker per cast (the proven fil). P3 keeps workers WARM in a pool
11
+ * keyed by `libraryKey`/contract, so **N cases → 1 instance** (never one sub-graph per concept-method instance) — the
12
+ * conceptMap + providers (+ a load-once model, via local-host, on the worker) are paid ONCE, reused across invokes.
13
+ *
14
+ * STATELESS-PER-INVOKE (the guardrail that keeps the constat): the warm worker is only warm in its CONFIG — each
15
+ * `invoke` builds a FRESH graph from its seed (`Graph.fromDirs({seed})` in protocol.js#invoke) and stabilizes it
16
+ * independently, so a reused worker carries NO cross-invoke graph state. Reuse buys the load cost, not shared mutable
17
+ * state; determinism per invoke is unchanged. The pool is ZERO-CORE host orchestration over `createGraphWorker`.
18
+ *
19
+ * const pool = createInvokePool();
20
+ * const r = await pool.invoke('methodKey', { conceptMap, providers, seed, boundedFrom, boundedKeys });
21
+ * // ... N more invokes of 'methodKey' reuse the SAME warm worker (pool.size() === 1) ...
22
+ * await pool.close();
23
+ */
24
+ /**
25
+ * createInvokePool(opts) — a warm-worker pool keyed by contract.
26
+ * @param opts.workerDefaults base createGraphWorker opts merged under every keyed worker (logger, conf, …).
27
+ * @param opts.max optional soft cap on distinct warm instances (LRU-evict the least-recently-used).
28
+ * @returns { invoke(key, iopts), has(key), keys(), size(), stats(), evict(key), close() }
29
+ */
30
+ function createInvokePool( opts ) {
31
+ opts = opts || {};
32
+ const workers = new Map(); // key -> { worker, uses, last }
33
+ let tick = 0;
34
+
35
+ // the method CONFIG the key's worker is built from (conceptMap/providers) — stored so P5 `refine` can swap the BODY
36
+ // behind the fixed key without the caller re-supplying it.
37
+ const configOf = ( iopts ) => ({ conceptMap: iopts.conceptMap, concepts: iopts.concepts, providers: iopts.providers,
38
+ geo: iopts.geo, llm: iopts.llm, ask: iopts.ask, conf: iopts.conf, logger: iopts.logger || opts.logger });
39
+
40
+ function keyWorker( key, iopts ) {
41
+ let e = workers.get(key);
42
+ if ( !e ) { e = { worker: null, uses: 0, config: configOf(iopts) }; workers.set(key, e); } // first use registers the config
43
+ if ( !e.worker ) { // create (first use) or RE-create (post-refine, body swapped)
44
+ const createGraphWorker = require('./index.js').createGraphWorker; // lazy — avoids the load-order circular
45
+ e.worker = createGraphWorker(Object.assign({}, opts.workerDefaults, e.config)); // the STORED config (refine wins over iopts)
46
+ e.last = ++tick; // stamp BEFORE the cap check so a fresh worker isn't its own LRU victim
47
+ if ( opts.max && liveCount() > opts.max ) evictLRU(key);
48
+ }
49
+ e.uses++; e.last = ++tick;
50
+ return e.worker;
51
+ }
52
+ const liveCount = () => { let n = 0; for ( const e of workers.values() ) if ( e.worker ) n++; return n; };
53
+ function evictLRU( keep ) { // evict the LRU among LIVE workers (never `keep`)
54
+ let lruKey = null, lruTick = Infinity;
55
+ for ( const [k, e] of workers ) if ( e.worker && k !== keep && e.last < lruTick ) { lruTick = e.last; lruKey = k; }
56
+ if ( lruKey != null ) evict(lruKey);
57
+ }
58
+ function evict( key ) {
59
+ const e = workers.get(key);
60
+ if ( !e ) return Promise.resolve(false);
61
+ workers.delete(key);
62
+ return e.worker ? Promise.resolve(e.worker.terminate()).then(() => true) : Promise.resolve(true);
63
+ }
64
+
65
+ return {
66
+ // invoke the method keyed by `key` on its WARM instance (created on first use, reused after). Stateless-per-invoke.
67
+ invoke: function ( key, iopts ) {
68
+ iopts = iopts || {};
69
+ const w = keyWorker(key, iopts);
70
+ return w.invoke({ seed: iopts.seed, boundedFrom: iopts.boundedFrom, boundedKeys: iopts.boundedKeys, settleTimeout: iopts.settleTimeout });
71
+ },
72
+ // P5 — AFFINER sans casser le contrat: swap the BODY behind the fixed key (new conceptMap/providers) and tear
73
+ // down the warm worker; the NEXT invoke re-creates from the refined config. The key/contract/Σ_sep — the
74
+ // interface the caller casts on — is untouched. "Le délégué évolue, l'interface reste."
75
+ refine: function ( key, newConfig ) {
76
+ let e = workers.get(key);
77
+ if ( !e ) { workers.set(key, { worker: null, uses: 0, config: Object.assign({}, newConfig) }); return Promise.resolve(true); }
78
+ const old = e.worker;
79
+ e.config = Object.assign({}, e.config, newConfig); // the refined body
80
+ e.worker = null; // force re-create on next invoke
81
+ return old ? Promise.resolve(old.terminate()).then(() => true) : Promise.resolve(true);
82
+ },
83
+ has: ( key ) => workers.has(key),
84
+ keys: () => [...workers.keys()],
85
+ size: () => [...workers.values()].filter(( e ) => e.worker ).length, // LIVE warm instances (a refined-but-not-yet-reinvoked key has no worker)
86
+ stats: () => ({ instances: [...workers.values()].filter(( e ) => e.worker ).length, keys: [...workers.keys()], uses: [...workers.values()].reduce(( a, e ) => a + e.uses, 0) }),
87
+ evict: evict,
88
+ close: function () { return Promise.all([...workers.values()].map(( e ) => Promise.resolve(e.worker.terminate()))).then(() => { workers.clear(); }); }
89
+ };
90
+ }
91
+
92
+ module.exports = { createInvokePool };
@@ -0,0 +1,210 @@
1
+ /*
2
+ * Copyright 2026 Nathanael Braun
3
+ * @author : Nathanael BRAUN <pp9ping@gmail.com>
4
+ *
5
+ * AGPL-3.0-or-later. See <https://www.gnu.org/licenses/>.
6
+ */
7
+ 'use strict';
8
+ /**
9
+ * Runtime PROTOCOL — the transport-agnostic halves of the distributed sub-graph runtime (roadmap P5).
10
+ *
11
+ * The message shape {init, dispatch, ask, askReply, result, error, log, ready, terminate} was plain-JSON by
12
+ * design; this module lifts the worker- and client-side handling OFF worker_threads so the SAME logic runs over
13
+ * ANY duplex `channel` — a worker_threads port (transport-thread) OR a TCP/unix socket (transport-socket). A
14
+ * channel is the minimal duplex seam:
15
+ * { send(msg), onMessage(fn), close(), onError?(fn) }
16
+ * `send`/`onMessage` carry already-parsed JSON objects (a socket transport frames them; a thread transport passes
17
+ * them through structured-clone). Behaviour is byte-for-byte the worker_threads original — only the seam changed.
18
+ */
19
+
20
+ /**
21
+ * attachWorker(channel) — the WORKER/SERVER side. Loads a concept set + providers on `init` (wiring any LLM `ask`
22
+ * to a proxy that round-trips the model call back over the channel), builds+stabilizes a fresh sub-graph per
23
+ * `dispatch`, and ships the serialized snapshot back. One config per process (engine providers are process-global),
24
+ * exactly like the worker_thread model.
25
+ */
26
+ function attachWorker( channel ) {
27
+ var Graph = require('../index.js');
28
+ var providers = require('../providers');
29
+ var createLogger = require('../graph/log.js').createLogger;
30
+ var boundedProject = require('../authoring/bounded-merge.js').boundedProject;
31
+
32
+ var ctx = null, askCounter = 0;
33
+ var askPending = {};
34
+
35
+ // the proxied model backend: forward the prompt over the channel, await the peer's reply.
36
+ function proxiedAsk( prompt, opts ) {
37
+ return new Promise(function ( resolve, reject ) {
38
+ var callId = ++askCounter;
39
+ askPending[callId] = { resolve: resolve, reject: reject };
40
+ channel.send({ type: 'ask', callId: callId, prompt: prompt, opts: opts });
41
+ });
42
+ }
43
+
44
+ function init( msg ) {
45
+ var conceptMap = msg.conceptMap || (msg.concepts ? Graph.loadConceptMap(msg.concepts) : {});
46
+ if ( msg.providers ) providers.register(Graph, Graph.loadProviders(msg.providers, { ask: proxiedAsk, env: process.env }));
47
+ if ( msg.geo ) providers.register(Graph, [{ CommonGeo: providers.CommonGeo }]);
48
+ if ( msg.llm ) providers.register(Graph, [providers.createLLMProvider({ ask: proxiedAsk })]);
49
+ var logger = createLogger({ label: 'worker', level: msg.logLevel || 'verbose', console: false });
50
+ logger.addSink(function ( record ) { channel.send({ type: 'log', record: record }); });
51
+ ctx = { conceptMap: conceptMap, conf: msg.conf || {}, logger: logger };
52
+ channel.send({ type: 'ready' });
53
+ }
54
+
55
+ function dispatch( msg ) {
56
+ var dispatchId = msg.dispatchId, settled = false;
57
+ var finish = function ( graph ) {
58
+ if ( settled ) return; settled = true;
59
+ channel.send({ type: 'result', dispatchId: dispatchId, snapshot: graph.serialize() });
60
+ };
61
+ try {
62
+ var g = Graph.fromDirs({
63
+ conceptMap: ctx.conceptMap,
64
+ seed : msg.seed,
65
+ conf : Object.assign({}, ctx.conf, { autoMount: true, logger: ctx.logger, onStabilize: function ( graph ) { finish(graph); } })
66
+ });
67
+ var t = setTimeout(function () { finish(g); }, msg.settleTimeout || 10000); // bound the empty-seed / no-write case
68
+ if ( t.unref ) t.unref();
69
+ } catch ( e ) {
70
+ channel.send({ type: 'error', dispatchId: dispatchId, message: e.message });
71
+ }
72
+ }
73
+
74
+ // invoke (roadmap P1) — the BOUNDED cross-instance invoke. Same settle as dispatch, but the worker crosses back
75
+ // ONLY the declared frontier alphabet (Σ_sep) off the `boundedFrom` object — `boundedProject(summaryFacts)` + the
76
+ // write-footprint (the keys the object carries) — NOT the whole serialized snapshot. This is the KG-PROXY fil
77
+ // promoted into the runtime: the constat holds while merge traffic is O(|frontier|), independent of the child size.
78
+ // facts of an object as seeded (across the seed's conceptMaps/nodes/segments shapes) — the "before" for the delta.
79
+ function seedFactsOf( seed, id ) {
80
+ var lists = [].concat((seed && seed.conceptMaps) || [], (seed && seed.nodes) || [], (seed && seed.segments) || []);
81
+ for ( var i = 0; i < lists.length; i++ ) if ( lists[i] && lists[i]._id === id ) return lists[i];
82
+ return {};
83
+ }
84
+ function invoke( msg ) {
85
+ var invokeId = msg.invokeId, settled = false;
86
+ var finish = function ( graph ) {
87
+ if ( settled ) return; settled = true;
88
+ var summary = boundedProject({ from: msg.boundedFrom, targetId: msg.boundedFrom, keys: msg.boundedKeys || [] })(graph);
89
+ delete summary.$$_id; // the wire summary is a plain facts object
90
+ var e = graph.getEtty ? graph.getEtty(msg.boundedFrom) : null, f = (e && e._) || {};
91
+ // write-footprint = the DELTA the body actually wrote (added/changed vs the seed) — what G1 (frame-completeness)
92
+ // diffs against the declared write; NOT the pre-existing structural facts (Segment/originNode/…).
93
+ var orig = seedFactsOf(msg.seed, msg.boundedFrom);
94
+ var footprint = Object.keys(f).filter(function ( k ) {
95
+ return k !== '_id' && k !== '_rev' && JSON.stringify(f[k]) !== JSON.stringify(orig[k]);
96
+ });
97
+ channel.send({ type: 'invokeResult', invokeId: invokeId, summary: summary, writeFootprint: footprint });
98
+ };
99
+ try {
100
+ var g = Graph.fromDirs({
101
+ conceptMap: ctx.conceptMap,
102
+ seed : msg.seed,
103
+ conf : Object.assign({}, ctx.conf, { autoMount: true, logger: ctx.logger, onStabilize: function ( graph ) { finish(graph); } })
104
+ });
105
+ var t = setTimeout(function () { finish(g); }, msg.settleTimeout || 10000);
106
+ if ( t.unref ) t.unref();
107
+ } catch ( e ) {
108
+ channel.send({ type: 'error', invokeId: invokeId, message: e.message });
109
+ }
110
+ }
111
+
112
+ channel.onMessage(function ( msg ) {
113
+ switch ( msg.type ) {
114
+ case 'init': init(msg); break;
115
+ case 'dispatch': dispatch(msg); break;
116
+ case 'invoke': invoke(msg); break;
117
+ case 'askReply': {
118
+ var p = askPending[msg.callId];
119
+ if ( p ) { delete askPending[msg.callId]; msg.error ? p.reject(new Error(msg.error)) : p.resolve(msg.result); }
120
+ break;
121
+ }
122
+ case 'terminate': channel.close(); break;
123
+ }
124
+ });
125
+ }
126
+
127
+ /**
128
+ * attachClient(channel, opts) — the PARENT/CLIENT side. Sends `init`, answers proxied `ask` calls with the
129
+ * parent-bound `opts.ask`, re-emits forwarded `log` records into `opts.logger` (tagged {worker:true}), and
130
+ * resolves each `dispatch` with the peer's stabilized snapshot. Returns { ready, dispatch, terminate }.
131
+ */
132
+ function attachClient( channel, opts ) {
133
+ opts = opts || {};
134
+ var ask = opts.ask || function () { return Promise.reject(new Error('worker requested a model call but no `ask` backend was wired')); };
135
+ var dispatches = {}, dispatchCounter = 0, invokes = {}, invokeCounter = 0, readyResolve;
136
+ var ready = new Promise(function ( res ) { readyResolve = res; });
137
+
138
+ channel.onMessage(async function ( msg ) {
139
+ switch ( msg.type ) {
140
+ case 'ready': readyResolve(); break;
141
+ case 'ask': {
142
+ try { channel.send({ type: 'askReply', callId: msg.callId, result: await ask(msg.prompt, msg.opts) }); }
143
+ catch ( e ) { channel.send({ type: 'askReply', callId: msg.callId, error: e.message }); }
144
+ break;
145
+ }
146
+ case 'result': {
147
+ var dr = dispatches[msg.dispatchId];
148
+ if ( dr ) { delete dispatches[msg.dispatchId]; dr.resolve(msg.snapshot); }
149
+ break;
150
+ }
151
+ case 'invokeResult': {
152
+ var ir = invokes[msg.invokeId];
153
+ if ( ir ) { delete invokes[msg.invokeId]; ir.resolve({ summary: msg.summary, writeFootprint: msg.writeFootprint }); }
154
+ break;
155
+ }
156
+ case 'error': {
157
+ if ( msg.invokeId != null ) { var ie = invokes[msg.invokeId]; if ( ie ) { delete invokes[msg.invokeId]; ie.reject(new Error(msg.message)); } break; }
158
+ var de = dispatches[msg.dispatchId];
159
+ if ( de ) { delete dispatches[msg.dispatchId]; de.reject(new Error(msg.message)); }
160
+ break;
161
+ }
162
+ case 'log': {
163
+ var r = msg.record;
164
+ if ( opts.logger && r && typeof opts.logger[r.level] === 'function' ) {
165
+ var child = opts.logger.child(Object.assign({ worker: true }, r.ctx || {}));
166
+ child[r.level].apply(child, [r.msg].concat(r.args || []));
167
+ }
168
+ break;
169
+ }
170
+ }
171
+ });
172
+ if ( channel.onError ) channel.onError(function ( e ) {
173
+ Object.keys(dispatches).forEach(function ( id ) { dispatches[id].reject(e); delete dispatches[id]; });
174
+ Object.keys(invokes).forEach(function ( id ) { invokes[id].reject(e); delete invokes[id]; });
175
+ });
176
+
177
+ channel.send({
178
+ type: 'init', conceptMap: opts.conceptMap, concepts: opts.concepts, providers: opts.providers,
179
+ geo: opts.geo, llm: opts.llm, logLevel: opts.logLevel, conf: opts.conf
180
+ });
181
+
182
+ return {
183
+ ready: function () { return ready; },
184
+ dispatch: function ( seed, dopts ) {
185
+ dopts = dopts || {};
186
+ return ready.then(function () {
187
+ return new Promise(function ( resolve, reject ) {
188
+ var dispatchId = ++dispatchCounter;
189
+ dispatches[dispatchId] = { resolve: resolve, reject: reject };
190
+ channel.send({ type: 'dispatch', dispatchId: dispatchId, seed: seed, settleTimeout: dopts.settleTimeout });
191
+ });
192
+ });
193
+ },
194
+ // invoke (P1) — resolve with the BOUNDED summary only: { summary:{<Σ_sep facts>}, writeFootprint:[keys] }.
195
+ invoke: function ( iopts ) {
196
+ iopts = iopts || {};
197
+ return ready.then(function () {
198
+ return new Promise(function ( resolve, reject ) {
199
+ var invokeId = ++invokeCounter;
200
+ invokes[invokeId] = { resolve: resolve, reject: reject };
201
+ channel.send({ type: 'invoke', invokeId: invokeId, seed: iopts.seed,
202
+ boundedFrom: iopts.boundedFrom, boundedKeys: iopts.boundedKeys, settleTimeout: iopts.settleTimeout });
203
+ });
204
+ });
205
+ },
206
+ terminate: function () { return channel.close(); }
207
+ };
208
+ }
209
+
210
+ module.exports = { attachWorker: attachWorker, attachClient: attachClient };
@@ -0,0 +1,77 @@
1
+ /*
2
+ * Copyright 2026 Nathanael Braun
3
+ * @author : Nathanael BRAUN <pp9ping@gmail.com>
4
+ *
5
+ * AGPL-3.0-or-later. See <https://www.gnu.org/licenses/>.
6
+ */
7
+ 'use strict';
8
+ /**
9
+ * Socket transport (roadmap P5) — the cross-INSTANCE channel: the same {init, dispatch, ask, …} protocol as
10
+ * worker_threads, carried over a TCP or unix-domain socket, so a sub-graph can be dispatched to a graph runtime
11
+ * on ANOTHER process or machine. Framing = NDJSON (one `JSON.stringify(msg)` per line; JSON never emits a raw
12
+ * newline, so a line is a whole message). The parent-bound model `ask` round-trips back over the SAME socket,
13
+ * exactly as in-thread.
14
+ *
15
+ * // server (the remote runtime instance):
16
+ * const { serve } = require('skynet-graph/lib/runtime/transport-socket');
17
+ * const server = await serve({ path: '/tmp/sg.sock' }); // each connection = one attachWorker
18
+ * // client (elsewhere): createGraphWorker({ address: { path: '/tmp/sg.sock' }, conceptMap, geo:true })
19
+ */
20
+ var net = require('net');
21
+
22
+ /**
23
+ * socketChannel(socket) — wrap a connected duplex socket as a protocol channel { send, onMessage, close, onError }.
24
+ * NDJSON: buffer incoming bytes, split on '\n', JSON.parse each complete line → the message handler.
25
+ */
26
+ function socketChannel( socket ) {
27
+ socket.setEncoding('utf8');
28
+ var buf = '';
29
+ var handlers = [];
30
+ socket.on('data', function ( chunk ) {
31
+ buf += chunk;
32
+ var i;
33
+ while ( (i = buf.indexOf('\n')) >= 0 ) {
34
+ var line = buf.slice(0, i);
35
+ buf = buf.slice(i + 1);
36
+ if ( !line ) continue;
37
+ var msg;
38
+ try { msg = JSON.parse(line); } catch ( e ) { continue; } // skip a corrupt frame rather than crash the peer
39
+ for ( var h = 0; h < handlers.length; h++ ) handlers[h](msg);
40
+ }
41
+ });
42
+ return {
43
+ socket: socket,
44
+ send: function ( m ) { try { socket.write(JSON.stringify(m) + '\n'); } catch ( e ) { /* peer gone */ } },
45
+ onMessage: function ( fn ) { handlers.push(fn); },
46
+ onError: function ( fn ) { socket.on('error', fn); },
47
+ close: function () { try { socket.end(); } catch ( e ) {} }
48
+ };
49
+ }
50
+
51
+ /**
52
+ * connect(address) → Promise<channel>. address = { port[, host] } (TCP) | { path } (unix socket) — anything
53
+ * net.connect accepts. Resolves once the connection is open; rejects on a connect error.
54
+ */
55
+ function connect( address ) {
56
+ return new Promise(function ( resolve, reject ) {
57
+ var socket = net.connect(address, function () { socket.removeListener('error', reject); resolve(socketChannel(socket)); });
58
+ socket.once('error', reject);
59
+ });
60
+ }
61
+
62
+ /**
63
+ * serve(listenOpts, workerOpts?) → Promise<server>. Listens (TCP { port } or unix { path }); for EACH connection,
64
+ * runs a worker-side protocol handler (attachWorker) over that connection's channel — the connecting client drives
65
+ * `init`/`dispatch` and answers the proxied `ask`. `server.close()` stops accepting. (One config per process, like
66
+ * the thread worker — engine providers are process-global.)
67
+ */
68
+ function serve( listenOpts, workerOpts ) {
69
+ var attachWorker = require('./protocol.js').attachWorker;
70
+ var server = net.createServer(function ( conn ) { attachWorker(socketChannel(conn), workerOpts || {}); });
71
+ return new Promise(function ( resolve, reject ) {
72
+ server.once('error', reject);
73
+ server.listen(listenOpts, function () { server.removeListener('error', reject); resolve(server); });
74
+ });
75
+ }
76
+
77
+ module.exports = { socketChannel: socketChannel, connect: connect, serve: serve };
@@ -0,0 +1,31 @@
1
+ /*
2
+ * Copyright 2026 Nathanael Braun
3
+ * @author : Nathanael BRAUN <pp9ping@gmail.com>
4
+ *
5
+ * This program is free software: you can redistribute it and/or modify
6
+ * it under the terms of the GNU Affero General Public License as published by
7
+ * the Free Software Foundation, either version 3 of the License, or
8
+ * (at your option) any later version.
9
+ *
10
+ * This program is distributed in the hope that it will be useful,
11
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
+ * GNU Affero General Public License for more details.
14
+ *
15
+ * You should have received a copy of the GNU Affero General Public License
16
+ * along with this program. If not, see <https://www.gnu.org/licenses/>.
17
+ */
18
+ 'use strict';
19
+ /**
20
+ * Worker-thread entry — the worker_threads adaptor for the transport-agnostic worker protocol (protocol.js).
21
+ * It wraps `parentPort` as a channel and hands it to `attachWorker`; all the init/dispatch/ask logic lives in
22
+ * protocol.js so the socket transport (transport-socket.js) runs the identical handler over a socket.
23
+ */
24
+ var parentPort = require('worker_threads').parentPort;
25
+ var attachWorker = require('./protocol.js').attachWorker;
26
+
27
+ attachWorker({
28
+ send: function ( m ) { parentPort.postMessage(m); },
29
+ onMessage: function ( fn ) { parentPort.on('message', fn); },
30
+ close: function () { process.exit(0); }
31
+ });