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.
- package/CONTRIBUTING.md +22 -0
- package/LICENSE +661 -0
- package/README.md +245 -0
- package/bin/sg +20 -0
- package/concepts/README.md +23 -0
- package/concepts/_substrate/Claim/Confidence/Trusted.json +6 -0
- package/concepts/_substrate/Claim/Confidence.json +10 -0
- package/concepts/_substrate/Claim/Freshness/Stale.json +7 -0
- package/concepts/_substrate/Claim/Freshness.json +5 -0
- package/concepts/_substrate/Claim/Verification/Refuted.json +6 -0
- package/concepts/_substrate/Claim/Verification.json +6 -0
- package/concepts/_substrate/Claim.json +4 -0
- package/concepts/_substrate/Frontier/InBeam.json +6 -0
- package/concepts/_substrate/Frontier/Stuck.json +6 -0
- package/concepts/_substrate/Frontier.json +5 -0
- package/concepts/_substrate/Intake/ToTask.json +22 -0
- package/concepts/_substrate/Intake/Typed.json +10 -0
- package/concepts/_substrate/Intake.json +27 -0
- package/concepts/_substrate/Task/Answer.json +12 -0
- package/concepts/_substrate/Task/Complexity/Atomic.json +5 -0
- package/concepts/_substrate/Task/Complexity/Compound/Expansion.json +7 -0
- package/concepts/_substrate/Task/Complexity/Compound.json +6 -0
- package/concepts/_substrate/Task/Complexity.json +6 -0
- package/concepts/_substrate/Task/EvalComplexity.json +13 -0
- package/concepts/_substrate/Task/ReportUp.json +6 -0
- package/concepts/_substrate/Task/Rollup.json +10 -0
- package/concepts/_substrate/Task.json +5 -0
- package/concepts/clinical/Diagnosis/Medication.json +6 -0
- package/concepts/clinical/Diagnosis.json +15 -0
- package/concepts/clinical/Observation/LabValue/OutOfRange.json +6 -0
- package/concepts/clinical/Observation/LabValue.json +5 -0
- package/concepts/clinical/Observation.json +6 -0
- package/concepts/common/Document/pathBasket.json +6 -0
- package/concepts/common/Document.json +3 -0
- package/concepts/common/Edge/Distance.json +13 -0
- package/concepts/common/Edge/Stay/LongStay.json +8 -0
- package/concepts/common/Edge/Stay.json +8 -0
- package/concepts/common/Edge/Travel/LongTravel.json +16 -0
- package/concepts/common/Edge/Travel/ShortTravel.json +11 -0
- package/concepts/common/Edge/Travel/targetNode.json +7 -0
- package/concepts/common/Edge/Travel.json +7 -0
- package/concepts/common/Edge.json +3 -0
- package/concepts/common/Vertice.json +3 -0
- package/concepts/supply/Fulfillment.json +10 -0
- package/concepts/supply/Inventory/Reorder.json +7 -0
- package/concepts/supply/Inventory.json +5 -0
- package/concepts/supply/Procurement/SupplierConfirm.json +6 -0
- package/concepts/supply/Procurement.json +8 -0
- package/concepts/supply/Transport.json +5 -0
- package/doc/API.md +556 -0
- package/doc/CAPABILITIES.md +373 -0
- package/doc/MODELISATION.md +511 -0
- package/doc/architecture.md +267 -0
- package/doc/concept-as-graph.md +379 -0
- package/doc/concept-learning.md +168 -0
- package/doc/creative-loop-map.md +202 -0
- package/doc/original-2016-doc.md +148 -0
- package/doc/usage.md +501 -0
- package/lib/authoring/abstract.js +401 -0
- package/lib/authoring/abstraction.js +190 -0
- package/lib/authoring/adapt.js +393 -0
- package/lib/authoring/ancestry.js +216 -0
- package/lib/authoring/author.js +147 -0
- package/lib/authoring/bounded-merge.js +57 -0
- package/lib/authoring/canon.js +158 -0
- package/lib/authoring/clock.js +123 -0
- package/lib/authoring/combinator.js +92 -0
- package/lib/authoring/compete.js +76 -0
- package/lib/authoring/compose-hotspot.js +314 -0
- package/lib/authoring/compress.js +279 -0
- package/lib/authoring/concept-net.js +254 -0
- package/lib/authoring/concepts.js +66 -0
- package/lib/authoring/context-project.js +277 -0
- package/lib/authoring/contract.js +476 -0
- package/lib/authoring/corpus-pack.js +125 -0
- package/lib/authoring/cost-probe.js +86 -0
- package/lib/authoring/crystallize.js +555 -0
- package/lib/authoring/dag-decompose.js +256 -0
- package/lib/authoring/dataset-adapter.js +253 -0
- package/lib/authoring/debug-provider.js +92 -0
- package/lib/authoring/decompose.js +343 -0
- package/lib/authoring/emittability.js +236 -0
- package/lib/authoring/equilibrium.js +160 -0
- package/lib/authoring/extract.js +153 -0
- package/lib/authoring/forest.js +55 -0
- package/lib/authoring/forge-fallback.js +116 -0
- package/lib/authoring/givens.js +121 -0
- package/lib/authoring/glossary.js +177 -0
- package/lib/authoring/grammar-graph.js +191 -0
- package/lib/authoring/granularity.js +80 -0
- package/lib/authoring/graph-net.js +108 -0
- package/lib/authoring/ground.js +148 -0
- package/lib/authoring/higher-order.js +85 -0
- package/lib/authoring/hotspot.js +132 -0
- package/lib/authoring/hysteresis.js +85 -0
- package/lib/authoring/index.js +106 -0
- package/lib/authoring/lattice-morphism.js +61 -0
- package/lib/authoring/lattice-pack.js +148 -0
- package/lib/authoring/leaf-io.js +77 -0
- package/lib/authoring/library.js +156 -0
- package/lib/authoring/lifecycle.js +92 -0
- package/lib/authoring/loop.js +193 -0
- package/lib/authoring/master-loop.js +132 -0
- package/lib/authoring/mdl.js +209 -0
- package/lib/authoring/memo-stability.js +122 -0
- package/lib/authoring/method-explorer.js +138 -0
- package/lib/authoring/method-pack.js +181 -0
- package/lib/authoring/method.js +202 -0
- package/lib/authoring/mine.js +510 -0
- package/lib/authoring/mount.js +119 -0
- package/lib/authoring/negotiate.js +100 -0
- package/lib/authoring/parametric.js +139 -0
- package/lib/authoring/reaggregate.js +75 -0
- package/lib/authoring/rebalance.js +157 -0
- package/lib/authoring/recall.js +135 -0
- package/lib/authoring/registry.js +350 -0
- package/lib/authoring/relearn.js +147 -0
- package/lib/authoring/retention.js +89 -0
- package/lib/authoring/segment-proxy.js +114 -0
- package/lib/authoring/serve-leaf.js +64 -0
- package/lib/authoring/slot-aware-serve.js +77 -0
- package/lib/authoring/sound-invoke.js +59 -0
- package/lib/authoring/split-serve.js +74 -0
- package/lib/authoring/ste.js +239 -0
- package/lib/authoring/stock.js +101 -0
- package/lib/authoring/store.js +94 -0
- package/lib/authoring/supervise.js +138 -0
- package/lib/authoring/support.js +131 -0
- package/lib/authoring/task-mirror.js +51 -0
- package/lib/authoring/typed-loop.js +160 -0
- package/lib/authoring/validate.js +569 -0
- package/lib/authoring/widen.js +88 -0
- package/lib/combos/appliance.js +189 -0
- package/lib/combos/critique.js +377 -0
- package/lib/combos/defaults.js +86 -0
- package/lib/combos/durable-runner.js +115 -0
- package/lib/combos/forge.js +209 -0
- package/lib/combos/index.js +86 -0
- package/lib/combos/learning-library.js +253 -0
- package/lib/combos/mixture-serve.js +193 -0
- package/lib/combos/plan-loop.js +192 -0
- package/lib/combos/proxy-cache.js +272 -0
- package/lib/combos/self-mod.js +94 -0
- package/lib/durable/audit.js +102 -0
- package/lib/durable/checkpoint-store.js +550 -0
- package/lib/durable/fold.js +62 -0
- package/lib/durable/interpreter.js +242 -0
- package/lib/durable/xlate.js +185 -0
- package/lib/graph/Graph.js +2486 -0
- package/lib/graph/expr.js +307 -0
- package/lib/graph/index.js +26 -0
- package/lib/graph/log.js +134 -0
- package/lib/graph/objects/Concept.js +382 -0
- package/lib/graph/objects/Entity.js +503 -0
- package/lib/graph/objects/Node.js +57 -0
- package/lib/graph/objects/PathMap.js +557 -0
- package/lib/graph/objects/Segment.js +100 -0
- package/lib/graph/tasks/stabilize.js +59 -0
- package/lib/graph/tasks/taskflow.js +247 -0
- package/lib/index.js +122 -0
- package/lib/load.js +132 -0
- package/lib/providers/backends.js +117 -0
- package/lib/providers/borderline.js +129 -0
- package/lib/providers/cache.js +226 -0
- package/lib/providers/canonicalize.js +198 -0
- package/lib/providers/constat.js +107 -0
- package/lib/providers/geo.js +53 -0
- package/lib/providers/index.js +116 -0
- package/lib/providers/intake.js +263 -0
- package/lib/providers/llm-local.js +88 -0
- package/lib/providers/llm.js +236 -0
- package/lib/providers/local-host.js +197 -0
- package/lib/providers/merge-consistency.js +162 -0
- package/lib/providers/nogood.js +149 -0
- package/lib/providers/reason-loop.js +223 -0
- package/lib/providers/semiring.js +285 -0
- package/lib/providers/solver-fork.js +163 -0
- package/lib/providers/stats.js +228 -0
- package/lib/providers/verify.js +120 -0
- package/lib/runtime/index.js +135 -0
- package/lib/runtime/invoke-pool.js +92 -0
- package/lib/runtime/protocol.js +210 -0
- package/lib/runtime/transport-socket.js +77 -0
- package/lib/runtime/worker-entry.js +31 -0
- package/lib/sg/cli.js +601 -0
- package/lib/sg/log-sinks.js +214 -0
- package/lib/sg/mcp.js +395 -0
- package/lib/sg/proxy-run.js +66 -0
- package/lib/sg/serve-demo.js +185 -0
- package/lib/sg/serve.js +205 -0
- package/lib/sg/trace.js +118 -0
- package/lib/studio/protocol.js +45 -0
- package/lib/studio/public/app.js +310 -0
- package/lib/studio/public/components/ConceptEditor.js +43 -0
- package/lib/studio/public/components/ConceptTree.js +43 -0
- package/lib/studio/public/components/CorpusPanel.js +68 -0
- package/lib/studio/public/components/ForkTree.js +36 -0
- package/lib/studio/public/components/GrammarGraph.js +99 -0
- package/lib/studio/public/components/GraphCanvas.js +156 -0
- package/lib/studio/public/components/Inspector.js +48 -0
- package/lib/studio/public/components/LearningPanel.js +85 -0
- package/lib/studio/public/components/PromptConsole.js +46 -0
- package/lib/studio/public/components/ProviderTrace.js +39 -0
- package/lib/studio/public/components/SessionSplit.js +47 -0
- package/lib/studio/public/components/TilingOverlay.js +40 -0
- package/lib/studio/public/components/Timeline.js +57 -0
- package/lib/studio/public/index.html +26 -0
- package/lib/studio/public/styles.css +185 -0
- package/lib/studio/public/ws.js +58 -0
- package/lib/studio/server.js +130 -0
- package/lib/studio/session.js +397 -0
- package/lib/studio/studio.js +149 -0
- 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
|
+
});
|