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,247 @@
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
+ /**
19
+ * Promise / semaphore manager & task sequencer.
20
+ *
21
+ * Vendored, zero-dependency drop-in for the `taskflows` package (originally by
22
+ * Nathanael Braun). Behaviour-preserving: the engine's stabilize loop relies on
23
+ * the exact lock/release + followers semantics (see lib/graph/Graph.js `_loopTF`,
24
+ * `pushMutation`, and the #11.a re-entrancy guard). The only change vs the
25
+ * published lib is that the unused `require()`s (isfunction/isnumber/merge/
26
+ * slice/splice) are dropped and `isArray`/`isString` are inlined.
27
+ *
28
+ * Mental model: `locks` is a semaphore. Each task run takes a lock; release() drops
29
+ * one. While locks > 0 new tasks queue (doAfter/todo); when locks hit 0 the flow is
30
+ * COMPLETE and its one-shot `_followers` fire — that follower is how stabilize.js
31
+ * hands control back to Graph._loopTF once a whole stabilization pass has drained.
32
+ *
33
+ * @class TaskFlow
34
+ */
35
+ "use strict";
36
+
37
+ var isArray = Array.isArray,
38
+ isString = function ( s ) { return typeof s === 'string'; };
39
+
40
+ /**
41
+ * TaskFlow
42
+ * @param todo {fn|fn[]} task(s) to run (string => method name on scope)
43
+ * @param scope {object} execution context passed to tasks
44
+ * @param followers {fn|fn[]} one-shot callback(s) fired when locks reach 0
45
+ * @param name {string}
46
+ * @constructor
47
+ */
48
+ var TaskFlow = function ( todo, scope, followers, name ) {
49
+ this.scope = scope || {};
50
+ this.todo = isArray(todo) ? todo : todo && [todo] || [];
51
+ this.doAfter = [];
52
+ this.locks = 0;
53
+ this.fails = 0;
54
+ this._complete = false;
55
+ this._followers = followers instanceof Array ? followers : [followers];
56
+ this._onfail = null;
57
+ this.displayName = name;
58
+
59
+ this.release = this.success = this.release.bind(this);
60
+ this.asyncFail = this.fail.bind(this);
61
+ };
62
+
63
+ module.exports = TaskFlow;
64
+
65
+ TaskFlow.prototype = {
66
+ kill : function () {
67
+ this.doAfter = this.scope = this._onfail = this._followers = 0;
68
+ this.dead = true;
69
+ },
70
+ reset : function () {
71
+ if ( this.dead ) return;
72
+ this._complete = false;
73
+ this._pos = 0;
74
+ },
75
+ /**
76
+ * Add one-shot success callback / TaskFlow.
77
+ * A passed TaskFlow is released (so it starts running if it holds no other locks).
78
+ * @param cb {function|TaskFlow}
79
+ * @returns {TaskFlow}
80
+ */
81
+ then : function () {
82
+ if ( this.dead ) return;
83
+ this._followers = (this._followers instanceof Array) && this._followers || [];
84
+ for ( var i = 0, ln = arguments.length ; i < ln ; i++ ) {
85
+ if ( arguments[i] instanceof Array ) this.then.apply(this, arguments[i]);
86
+ else {
87
+ this._followers.push(arguments[i]);
88
+ if ( arguments[i] instanceof TaskFlow )
89
+ arguments[i].locks++;
90
+ }
91
+ }
92
+ if ( this._complete ) {
93
+ this.locks++;
94
+ this.release();
95
+ }
96
+ return this;
97
+ },
98
+ /**
99
+ * Add one-shot fail callback.
100
+ * @param cb {function|TaskFlow|array}
101
+ * @returns {TaskFlow}
102
+ */
103
+ catchFail : function () {
104
+ if ( this.dead ) return;
105
+ var done = this.fails && this._complete;
106
+ this._onfail = this._onfail || [];
107
+ for ( var i = 0, ln = arguments.length ; i < ln ; i++ ) {
108
+ if ( arguments[i] instanceof Array ) this.catchFail.apply(this, arguments[i]);
109
+ else {
110
+ if ( !done ) this._onfail.push(arguments[i]);
111
+ else arguments[i]();
112
+ }
113
+ }
114
+ return this;
115
+ },
116
+ /**
117
+ * Make this flow fall into failure, triggering one-shot callbacks.
118
+ * @param cause
119
+ */
120
+ fail : function ( cause ) {
121
+ if ( this.dead ) return;
122
+ var tmp, i = 0;
123
+ this._fail = cause;
124
+ if ( this._onfail )
125
+ while ( i < this._onfail.length ) {
126
+ tmp = this._onfail[i++];
127
+ if ( tmp instanceof Function ) tmp(this.scope, cause, this);
128
+ else if ( tmp instanceof TaskFlow ) tmp.release();
129
+ }
130
+ },
131
+ /**
132
+ * Push a task & (re)start running the flow if no remaining locks.
133
+ * @returns {TaskFlow}
134
+ */
135
+ push : function () {
136
+ if ( this.dead ) return;
137
+ this.locks++;
138
+ this._complete = false;
139
+ this.todo.push.apply(this.todo, arguments);
140
+ this.release();
141
+ return this;
142
+ },
143
+ /**
144
+ * Add a lock (optionally waiting on a previous flow).
145
+ * @param previous {TaskFlow|TaskFlow[]} optional flow(s) to wait on
146
+ * @returns {TaskFlow}
147
+ */
148
+ wait : function ( previous ) {
149
+ if ( this.dead ) return;
150
+ if ( isArray(previous) )
151
+ return previous.map(this.wait.bind(this));
152
+ if ( previous ) previous.then(this);
153
+ else this.locks++;
154
+ return this;
155
+ },
156
+ /**
157
+ * Decrease locks; at 0, run pending tasks else fire `then` followers.
158
+ * @param desync
159
+ * @returns {*}
160
+ */
161
+ release : function ( desync ) {
162
+ if ( this.dead ) return;
163
+ if ( desync && this.locks > 0 ) return setTimeout(this.success) && this;
164
+ var tmp;
165
+
166
+ if ( !--this.locks ) {
167
+ if ( this.doAfter.length || this.todo.length > this._pos ) {
168
+ this.run(tmp);
169
+ return;
170
+ }
171
+ this._complete = true;
172
+ this.running = false;
173
+
174
+ if ( this._followers instanceof Array ) {
175
+ while ( this._followers.length ) {
176
+ tmp = this._followers.shift();
177
+ if ( tmp instanceof Function ) tmp(this.scope, this);
178
+ else if ( tmp instanceof TaskFlow ) tmp.release();
179
+ }
180
+ } else {
181
+ tmp = this._followers;
182
+ if ( tmp instanceof Function ) tmp(this.scope, this);
183
+ else if ( tmp instanceof TaskFlow ) tmp.release();
184
+ }
185
+ }
186
+ return this;
187
+ },
188
+ _pos : 0,
189
+ _nextTask : 0,
190
+ pushSubTask : function ( task ) {
191
+ if ( this.dead ) return;
192
+ this.doAfter.push(task);
193
+ },
194
+ /**
195
+ * Execute one task, then trampoline to the next. `run` calls itself (line ~end)
196
+ * rather than looping so a long task chain never grows the call stack. A task may
197
+ * be: a fn (called with (scope, this) — if it RETURNS a value, that becomes the
198
+ * next step, chaining work); a method name (resolved on scope); a nested TaskFlow
199
+ * (this flow waits on it via then); or an ARRAY (a parallel pool — every entry is
200
+ * scheduled via setTimeout and gets its own lock, so they run concurrently and the
201
+ * flow only completes once all have released). Each step takes a lock; the trailing
202
+ * release() balances it so `_followers` fire exactly at quiescence.
203
+ * @returns {*}
204
+ */
205
+ run : function ( step, force, releaseAfter ) {
206
+ if ( this.dead ) return;
207
+ if ( !step && !this.locks && !this.doAfter.length && (this._pos >= this.todo.length) ) {
208
+ this.locks++;
209
+ this.running = false;
210
+ this.release();
211
+ return this;
212
+ }
213
+ if ( !force && this.locks ) return step && this.pushSubTask(step);
214
+
215
+ this.running = true;
216
+
217
+ step = step || this.doAfter.length && this.doAfter.shift() || this.todo[this._pos++];
218
+
219
+ this.locks++;
220
+ if ( isString(step) ) {
221
+ if ( this.scope[step] instanceof Function )
222
+ step = this.scope[step](null, this.scope, this);
223
+ else step = this.scope[step];
224
+ } else if ( step instanceof Function ) {
225
+ this._succesfull = true;
226
+ step = step(this.scope, this);
227
+ this._succesfull = false;
228
+ } else if ( step instanceof TaskFlow ) { // sync wf
229
+ step.then(this);
230
+ step = null;
231
+ } else if ( step instanceof Array ) { // async pool
232
+ this.locks++;
233
+ for ( var i = 0 ; i < step.length ; i++, this.locks++ )
234
+ setTimeout(this.run.bind(this, step[i], true, true));
235
+ setTimeout(this.success);
236
+ step = null;
237
+ } else {
238
+ step = null;
239
+ }
240
+
241
+ !step && releaseAfter && this.release();
242
+ this.run(step, step && force, step && releaseAfter);
243
+ this.release();
244
+
245
+ return this;
246
+ }
247
+ };
package/lib/index.js ADDED
@@ -0,0 +1,122 @@
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
+ * Package facade. `require('skynet-graph')` returns the Graph constructor, with
21
+ * standalone-boot helpers attached as statics so a host can spin a graph up from
22
+ * plain directories without hand-wiring the concept tree + providers:
23
+ *
24
+ * const Graph = require('skynet-graph');
25
+ * const g = Graph.fromDirs({ concepts: './concepts', providers: './providers' });
26
+ *
27
+ * The engine core (lib/graph) stays filesystem-free; fromDirs composes the edge
28
+ * loaders (lib/load.js) with the provider registry (lib/providers).
29
+ */
30
+ const Graph = require('./graph');
31
+ const providers = require('./providers');
32
+ const { loadConceptMap, loadProviders } = require('./load.js');
33
+
34
+ /**
35
+ * Boot a Graph from on-disk concept sets + provider modules.
36
+ *
37
+ * @param {object} opts
38
+ * @param {string|string[]} [opts.concepts] concept-set dir(s) (see loadConceptMap)
39
+ * @param {string|object|Array} [opts.providers] provider module dir(s)/file(s) or fragment(s)
40
+ * @param {object} [opts.providerCtx] ctx passed to provider factory modules
41
+ * @param {boolean|Array} [opts.builtins] register packaged providers (true = geo + default llm)
42
+ * @param {object|string} [opts.seed] serialized record, or a path to a JSON snapshot
43
+ * @param {object} [opts.conf] Graph cfg overrides (conceptSets defaults to all loaded)
44
+ * @param {object} [opts.conceptMap] pre-built conceptMap (skips loadConceptMap)
45
+ * @returns {Graph}
46
+ */
47
+ function fromDirs( opts = {} ) {
48
+ const conceptMap = opts.conceptMap
49
+ || (opts.concepts ? loadConceptMap(opts.concepts) : {});
50
+
51
+ const fragments = [];
52
+ if ( opts.providers ) fragments.push(...loadProviders(opts.providers, opts.providerCtx || {}));
53
+ if ( fragments.length ) providers.register(Graph, fragments);
54
+ // builtins: true -> packaged defaults (geo + default llm client); array -> explicit fragments
55
+ if ( opts.builtins ) providers.register(Graph, opts.builtins === true ? undefined : opts.builtins);
56
+
57
+ let seed = opts.seed;
58
+ if ( typeof seed === 'string' ) seed = JSON.parse(require('fs').readFileSync(require('path').resolve(seed), 'utf8'));
59
+
60
+ const conf = { ...(opts.conf || {}) };
61
+ if ( !conf.conceptSets ) conf.conceptSets = Object.keys(conceptMap);
62
+
63
+ return new Graph(seed || {}, conf, conceptMap);
64
+ }
65
+
66
+ // Attach standalone helpers as statics (keeps `require('skynet-graph')` === Graph).
67
+ Graph.fromDirs = fromDirs;
68
+ Graph.loadConceptMap = loadConceptMap;
69
+ Graph.loadProviders = loadProviders;
70
+ Graph.register = function ( fragments ) { return providers.register(Graph, fragments); };
71
+ Graph.providers = providers;
72
+
73
+ // R0 backends — the preset/config layer over the ask-makers (a provider NAME + key -> a chat `ask`). The
74
+ // shared foundation the serving surfaces / Studio backend panel / client routing consume; every resolved
75
+ // backend carries an `egress` flag (local=false) — the seed a no-egress policy reads.
76
+ Graph.backends = require('./providers/backends');
77
+
78
+ // Authoring & R&D toolkit, namespaced (concepts/validate/author/contract/abstract/method/mount/crystallize/…).
79
+ // Exposed as a static so the substrate tools are usable without deep-path requires. Lazy-required (like
80
+ // createStudioServer): an engine-only / providers-only host pays no load cost, and the shelved numerical
81
+ // modules (concept-net/equilibrium/ste) load only when authoring is actually touched.
82
+ Object.defineProperty(Graph, 'authoring', { get: function () { return require('./authoring'); } });
83
+
84
+ // Combos — thin, delivered assemblies over the bricks (createAppliance/…). Lazy like `authoring`:
85
+ // an engine-only host pays nothing; the substrate/providers load only when a combo is touched.
86
+ Object.defineProperty(Graph, 'combos', { get: function () { return require('./combos'); } });
87
+
88
+ // The first-class SETTLE verb (Controller-P0): a promise that resolves on the next stabilization (or
89
+ // immediately if already quiescent). Lifts the de-facto `supervise.js#nextStable` so a host can
90
+ // `await Graph.settle(g)` after a mutation instead of wiring an onStabilize callback. Lazy-required.
91
+ Graph.settle = function ( graph ) { return require('./authoring/supervise.js').nextStable(graph); };
92
+
93
+ // Distributed runtime: dispatch sub-graphs to a worker (worker_threads) or a cross-instance socket runtime.
94
+ const runtime = require('./runtime');
95
+ Graph.createGraphWorker = runtime.createGraphWorker;
96
+ Graph.spawnGraph = runtime.spawnGraph;
97
+ Graph.invokeGraph = runtime.invokeGraph; // P1: one-shot BOUNDED invoke (only Σ_sep crosses back)
98
+ Graph.createInvokePool = runtime.createInvokePool; // P3: warm-worker pool keyed by contract (N cases → 1 instance)
99
+ Graph.serveGraphWorker = runtime.serveGraphWorker; // P5: stand up a socket runtime remote clients dispatch to
100
+
101
+ // Logger factory (for a host that wants to build/inject its own logger via cfg.logger).
102
+ Graph.createLogger = require('./graph/log').createLogger;
103
+
104
+ // Durable executor — Layer A (the CheckpointStore: durable marking + content-memo + lease queue). Eager-require
105
+ // is safe: the module loads `node:sqlite` ONLY inside the sqlite factory (so engine-only hosts never touch it).
106
+ // `createCheckpointStore({file})` = the convenience default — a file → the durable SQLite backend, none → memory.
107
+ const durable = require('./durable/checkpoint-store.js');
108
+ Graph.createCheckpointStore = function ( opts ) { opts = opts || {}; return opts.file ? durable.createSqliteCheckpointStore(opts) : durable.createMemoryCheckpointStore(opts); };
109
+ Graph.createMemoryCheckpointStore = durable.createMemoryCheckpointStore;
110
+ Graph.createSqliteCheckpointStore = durable.createSqliteCheckpointStore;
111
+ // the full durable executor namespace: the CheckpointStore + C-xlate (compileMethod) + Layer B (runFlow) +
112
+ // the fold-back JOIN's monoid algebra (foldSiblings).
113
+ Graph.durable = Object.assign({}, durable, require('./durable/xlate.js'), require('./durable/interpreter.js'),
114
+ require('./durable/fold.js'), require('./durable/audit.js'));
115
+
116
+ // Studio (the web inspector/console): a host can embed the server as a library instead of
117
+ // only via `bin/sg studio`. Lazy-required so the fs/ws/http deps don't load for engine-only use.
118
+ Object.defineProperty(Graph, 'createStudioServer', {
119
+ get: function () { return require('./studio/server.js').createServer; }
120
+ });
121
+
122
+ module.exports = Graph;
package/lib/load.js ADDED
@@ -0,0 +1,132 @@
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
+ * Directory loaders — turn on-disk concept sets and provider modules into the
21
+ * shapes the engine expects, so a host (or the `sg` CLI) can boot a graph from
22
+ * plain folders passed by path. Kept OUT of lib/graph/* on purpose: the engine
23
+ * core stays filesystem-free (hermetic/portable); all fs lives here at the edge.
24
+ */
25
+ const fs = require('fs');
26
+ const path = require('path');
27
+ const { buildConceptTree } = require('./authoring/concepts.js');
28
+
29
+ /**
30
+ * Build a conceptMap ({ <setName>: tree }) from one or more directories.
31
+ *
32
+ * Each argument dir is auto-classified:
33
+ * - if it directly contains top-level *.json -> it IS a single set (name = its
34
+ * basename), e.g. loadConceptMap('./concepts/common') -> { common: tree };
35
+ * - else each immediate sub-directory holding *.json is a set, e.g.
36
+ * loadConceptMap('./concepts') -> { common: tree, ... }.
37
+ *
38
+ * @param {string|string[]} concepts dir path(s)
39
+ * @param {object} [opts] { validate: true } runs validateConceptTree per set and THROWS an
40
+ * aggregated error if any set has errors (a strict validating load).
41
+ * @returns {Object} conceptMap keyed by set name
42
+ */
43
+ function loadConceptMap( concepts, opts = {} ) {
44
+ const dirs = Array.isArray(concepts) ? concepts : [concepts];
45
+ const map = {};
46
+ for ( const d of dirs ) {
47
+ const abs = path.resolve(d);
48
+ const entries = fs.readdirSync(abs, { withFileTypes: true });
49
+ const hasTopJson = entries.some(e => e.isFile() && e.name.endsWith('.json'));
50
+ if ( hasTopJson ) {
51
+ map[path.basename(abs)] = buildConceptTree(abs);
52
+ } else {
53
+ for ( const e of entries ) {
54
+ if ( !e.isDirectory() ) continue;
55
+ const setDir = path.join(abs, e.name);
56
+ if ( fs.readdirSync(setDir).some(f => f.endsWith('.json')) )
57
+ map[e.name] = buildConceptTree(setDir);
58
+ }
59
+ }
60
+ }
61
+ if ( opts.validate ) {
62
+ const { validateConceptTree } = require('./authoring/validate.js');
63
+ const fails = [];
64
+ for ( const set of Object.keys(map) )
65
+ for ( const err of validateConceptTree(map[set], opts.validateOpts || {}).errors )
66
+ fails.push(`[${set}] ${err.concept} — ${err.kind}: ${err.message}`);
67
+ if ( fails.length ) throw new Error('concept validation failed:\n' + fails.join('\n'));
68
+ }
69
+ return map;
70
+ }
71
+
72
+ // Keys re-derived from the file path on load (concepts.js#buildConceptTree), so they are NOT
73
+ // written back out — and childConcepts becomes a sibling directory, not a key.
74
+ const NON_FILE_KEYS = new Set(['_id', '_name', 'childConcepts']);
75
+
76
+ /**
77
+ * Write a serialized concept tree back to the on-disk JSONC layout (the inverse of
78
+ * buildConceptTree / loadConceptMap): one `<Name>.json` per concept, a `<Name>/` sub-directory
79
+ * for its children. Comments are NOT preserved (prose belongs in a typed `_description`/`note`
80
+ * field); the result reloads to the identical tree. fs lives here at the edge, not in the core.
81
+ * @param tree a `{ childConcepts: {...} }` tree (e.g. graph.exportConcepts())
82
+ * @param dir target directory (created if missing)
83
+ * @returns dir
84
+ */
85
+ function exportConceptsToDir( tree, dir ) {
86
+ fs.mkdirSync(dir, { recursive: true });
87
+ const kids = (tree && tree.childConcepts) || {};
88
+ for ( const name of Object.keys(kids) ) {
89
+ const node = kids[name];
90
+ const schema = {};
91
+ for ( const k of Object.keys(node) ) if ( !NON_FILE_KEYS.has(k) ) schema[k] = node[k];
92
+ fs.writeFileSync(path.join(dir, name + '.json'), JSON.stringify(schema, null, 2) + '\n');
93
+ if ( node.childConcepts && Object.keys(node.childConcepts).length )
94
+ exportConceptsToDir(node, path.join(dir, name));
95
+ }
96
+ return dir;
97
+ }
98
+
99
+ /**
100
+ * Collect provider-map fragments from module file(s)/dir(s) (or pass-through
101
+ * fragment objects). A provider module exports either:
102
+ * - a fragment map `{ Namespace: { fn } }`,
103
+ * - `{ default: fragment }`, or
104
+ * - a factory `(ctx) => fragment` (so it can self-configure from `ctx`, e.g. an
105
+ * LLM `ask` backend or env). `ctx` is whatever the caller passes.
106
+ *
107
+ * Returns an array suitable for `register(Graph, fragments)`.
108
+ *
109
+ * @param {string|object|Array} providers dir/file path(s) and/or fragment object(s)
110
+ * @param {object} [ctx] passed to any factory module
111
+ * @returns {object[]} fragments
112
+ */
113
+ function loadProviders( providers, ctx = {} ) {
114
+ const items = Array.isArray(providers) ? providers : [providers];
115
+ const fragments = [];
116
+ for ( const item of items ) {
117
+ if ( item && typeof item === 'object' ) { fragments.push(item); continue; } // already a fragment
118
+ const abs = path.resolve(item);
119
+ const files = fs.statSync(abs).isDirectory()
120
+ ? fs.readdirSync(abs).filter(f => f.endsWith('.js')).sort().map(f => path.join(abs, f))
121
+ : [abs];
122
+ for ( const f of files ) {
123
+ let mod = require(f);
124
+ if ( typeof mod === 'function' ) mod = mod(ctx); // factory(ctx)
125
+ else if ( mod && typeof mod.default !== 'undefined' ) mod = mod.default; // esm-interop default
126
+ if ( mod && typeof mod === 'object' ) fragments.push(mod);
127
+ }
128
+ }
129
+ return fragments;
130
+ }
131
+
132
+ module.exports = { loadConceptMap, loadProviders, exportConceptsToDir };
@@ -0,0 +1,117 @@
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
+ * backends — the PRESET / CONFIG layer over the ask-makers (`llm.js`, `llm-local.js`). One thin,
21
+ * data-driven place to answer "given a provider NAME + a key, hand me a chat `ask`". It invents no
22
+ * protocol and no router: every hosted provider worth targeting is OpenAI-compatible except Anthropic
23
+ * (its own `/v1/messages`) and the embedded local model (`node-llama-cpp`) — so the table is small and
24
+ * a new provider is one row (base + flavour + key-env).
25
+ *
26
+ * It is the SHARED foundation the serving surfaces, the Studio backend panel, and the client's N-tier
27
+ * routing all consume: the public preset table here = the free config brick; the client wraps it with a
28
+ * policy (order + `dataPolicy:'no-egress'`); the pro adds per-task routing. The `egress` flag carried on
29
+ * every resolved backend (local=false, remote=true) is the SEED that a no-egress policy reads to decide
30
+ * what a query is allowed to leave the machine for.
31
+ *
32
+ * const ask = makeBackend({ preset: 'deepseek' }); // key from $DEEPSEEK_API_KEY
33
+ * const ask = makeBackend({ preset: 'custom', base: 'http://localhost:8000', key: 'sk-x' }); // any vLLM
34
+ * const ask = makeBackend({ preset: 'local', modelPath: '/models/qwen.gguf' }); // embedded
35
+ *
36
+ * The returned `ask` is the standard chat seam: `async ({system,user,maxTokens,temperature}) -> text`.
37
+ */
38
+ const llm = require('./llm');
39
+
40
+ // The preset table. `base` is the API ROOT (the OpenAI maker appends `/v1/chat/completions`, the
41
+ // Anthropic maker `/v1/messages`) — so bases here carry NO `/v1`. `defaultModel` is only a convenience
42
+ // default (always overridable via spec.model or $LLM_MODEL), kept minimal so the table does not rot.
43
+ const BACKENDS = {
44
+ local: { api: 'local', label: 'Local (embedded GGUF)', egress: false, needsKey: false },
45
+ openai: { api: 'openai', label: 'OpenAI', base: 'https://api.openai.com', egress: true, needsKey: true, keyEnv: 'OPENAI_API_KEY', defaultModel: 'gpt-4o-mini' },
46
+ anthropic: { api: 'anthropic', label: 'Anthropic (Claude)', base: 'https://api.anthropic.com', egress: true, needsKey: true, keyEnv: 'ANTHROPIC_API_KEY', defaultModel: 'claude-3-5-sonnet-latest' },
47
+ huggingface: { api: 'openai', label: 'Hugging Face', base: 'https://router.huggingface.co', egress: true, needsKey: true, keyEnv: 'HF_TOKEN' },
48
+ deepseek: { api: 'openai', label: 'DeepSeek', base: 'https://api.deepseek.com', egress: true, needsKey: true, keyEnv: 'DEEPSEEK_API_KEY', defaultModel: 'deepseek-chat' },
49
+ openrouter: { api: 'openai', label: 'OpenRouter', base: 'https://openrouter.ai/api', egress: true, needsKey: true, keyEnv: 'OPENROUTER_API_KEY' },
50
+ // `custom` = any OpenAI-compatible endpoint you point at (a local vLLM / llama-server / LM Studio, a
51
+ // LAN model, a self-host). No key required by default; set `api:'anthropic'` for a Claude-shaped one.
52
+ custom: { api: 'openai', label: 'Custom (bring your own base)', egress: true, needsKey: false }
53
+ };
54
+
55
+ const ALIASES = { claude: 'anthropic', hf: 'huggingface', 'gguf': 'local' };
56
+
57
+ function err( message, code ) { return Object.assign(new Error(message), { code: code }); }
58
+
59
+ /**
60
+ * Normalize a backend spec into a descriptor (PURE — no maker call, no secret logged). Precedence per
61
+ * field: `spec > environment > preset default`. Default preset is `custom` (a bare OpenAI-compatible
62
+ * endpoint), so `resolveBackend({ base, key })` just works for a self-hosted model.
63
+ * @param spec { preset?, base?, model?, key?, modelPath?, api?, egress? }
64
+ * @returns { name, api, base, model, key, modelPath, egress, needsKey, keyEnv, label }
65
+ */
66
+ function resolveBackend( spec ) {
67
+ spec = spec || {};
68
+ const asked = spec.preset || spec.backend || spec.name || 'custom';
69
+ const name = ALIASES[asked] || asked;
70
+ const preset = BACKENDS[name];
71
+ if ( !preset ) throw err('unknown backend preset "' + asked + '" — known: ' + Object.keys(BACKENDS).join(', '), 'UNKNOWN_BACKEND');
72
+
73
+ const api = spec.api || preset.api;
74
+ const base = spec.base || preset.base || process.env.LLM_BASE || null;
75
+ const model = spec.model || process.env.LLM_MODEL || preset.defaultModel || null;
76
+ const key = spec.key || (preset.keyEnv && process.env[preset.keyEnv]) || process.env.LLM_KEY || null;
77
+ const modelPath = api === 'local' ? (spec.modelPath || process.env.LOCAL_MODEL || null) : null;
78
+ const egress = spec.egress != null ? !!spec.egress : !!preset.egress;
79
+
80
+ return { name: name, api: api, base: base, model: model, key: key, modelPath: modelPath,
81
+ egress: egress, needsKey: !!preset.needsKey, keyEnv: preset.keyEnv || null, label: preset.label };
82
+ }
83
+
84
+ /**
85
+ * Build a chat `ask` for a backend spec. Dispatches on the resolved flavour; a needs-key preset with no
86
+ * resolvable key is a typed `NO_API_KEY` error UP FRONT (a clear message beats an opaque 401 mid-call).
87
+ * @returns async ({system,user,maxTokens,temperature}) -> text
88
+ */
89
+ function makeBackend( spec ) {
90
+ spec = spec || {};
91
+ const d = resolveBackend(spec);
92
+ if ( d.needsKey && !d.key ) throw err('backend "' + d.name + '" needs an API key — set ' + (d.keyEnv || 'a key') + ' or pass { key }', 'NO_API_KEY');
93
+
94
+ if ( d.api === 'local' ) {
95
+ // lazy: only a host that actually uses the embedded model pays for the optional native dep.
96
+ const makeLocalAsk = require('./llm-local').makeLocalAsk;
97
+ return makeLocalAsk(Object.assign({}, spec.localOpts, { modelPath: d.modelPath, reasoningBudget: spec.reasoningBudget }));
98
+ }
99
+ const opts = { api: d.api, base: d.base, model: d.model, key: d.key };
100
+ if ( spec.extraBody ) opts.extraBody = spec.extraBody;
101
+ if ( spec.assistantPrefill != null ) opts.assistantPrefill = spec.assistantPrefill;
102
+ return llm.makeAsk(opts); // dispatches openai/anthropic on opts.api
103
+ }
104
+
105
+ /**
106
+ * The presets as a SECRET-FREE list (for a Studio dropdown / `sg` help): name, label, flavour, whether a
107
+ * key is needed and from which env, and the egress class. Never returns a resolved key.
108
+ */
109
+ function listBackends() {
110
+ return Object.keys(BACKENDS).map(function ( name ) {
111
+ const p = BACKENDS[name];
112
+ return { name: name, label: p.label, api: p.api, base: p.base || null,
113
+ egress: !!p.egress, needsKey: !!p.needsKey, keyEnv: p.keyEnv || null };
114
+ });
115
+ }
116
+
117
+ module.exports = { BACKENDS: BACKENDS, ALIASES: ALIASES, resolveBackend: resolveBackend, makeBackend: makeBackend, listBackends: listBackends };