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,253 @@
|
|
|
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
|
+
* C3 — the LEARNING METHOD LIBRARY (roadmap P2 SKELETON → P3 complete). A thin assembly over the
|
|
21
|
+
* always-on master loop (the cost-ladder MATCH→RETRIEVE→FORGE→ESCALATE), a persistent method store,
|
|
22
|
+
* and the O(1) dispatch catalog — so a recurrent typed workload:
|
|
23
|
+
* • AMORTIZES — a repeat problem elides the forge (MATCH → 0 model calls) once the library warms;
|
|
24
|
+
* • SURVIVES a restart — the library is disk-backed (a fresh process replays at 0 calls);
|
|
25
|
+
* • SHIPS — `pack()` → a portable `.sgc` a different deployment `load()`s and replays (version-gated);
|
|
26
|
+
* `packAll()`/`loadAll()` ship the EXERCISED library = methods AND the grown typed lattice (the
|
|
27
|
+
* registry: isa vocab + admitted synonym rings), each a version-gated single-kind `.sgc` bundle;
|
|
28
|
+
* • RE-DERIVES on drift — `drift(problem)` invalidates the method (cache + recall) and deopts its
|
|
29
|
+
* mount rank toward the ESCALATE floor, so the next solve re-forges only the violated entry.
|
|
30
|
+
*
|
|
31
|
+
* P2 laid the SKELETON: master-loop + store + dispatch catalog + `.sgc` portability, the §4 posture
|
|
32
|
+
* (durable store ON, validator/audit available; effectful forge opt-in). P3 COMPLETES it (all additive,
|
|
33
|
+
* `learning:false` default = the skeleton unchanged):
|
|
34
|
+
* • `crystallizeFrom(records, ctx)` — distill methods from captured trace records
|
|
35
|
+
* (crystallizeFromRecords pass-through; admitted candidates are indexed into the catalog);
|
|
36
|
+
* • `learning:true` — the FORGE arm becomes the creative loop (adaptOrForgeAsync:
|
|
37
|
+
* dispatch→hit(0 call)→antiUnify-adapt(1 call)→forge, verifier-gated, auto-indexed); the host
|
|
38
|
+
* supplies the projections `target(problem)`/`dispatchFacts(problem)` and a forge that returns
|
|
39
|
+
* `{candidate, calls}` (the method IS the amortized result). A `reject` THROWS a typed error
|
|
40
|
+
* (fail-closed — a rejection must never be cached as a 0-call result). `drift()` then ALSO
|
|
41
|
+
* invalidates the catalog-side template (library.invalidateTemplate) so the stale method cannot
|
|
42
|
+
* re-`hit` at 0 calls right after the exact cache was evicted (correct-on-drift, both layers);
|
|
43
|
+
* • `dispatch`/`blame`/`credit` — pure pass-throughs (library.dispatch, attributeSlot{Blame,Credit})
|
|
44
|
+
* exposing the O(1) catalog lookup and the LOCALIZED blame/credit rules on mounted frames. The
|
|
45
|
+
* AUTONOMOUS blame→revise loop is C5 (`relearn.js`), deliberately NOT here.
|
|
46
|
+
* The bricks stay usable "à nu" — this is an optional convenience over `Graph.authoring.*`.
|
|
47
|
+
*
|
|
48
|
+
* @param opts.signature (problem) => {structure, content} the K1 signature; STRUCTURE = method class
|
|
49
|
+
* (mount/deopt key), CONTENT = what's derived. Default: identity on the problem.
|
|
50
|
+
* @param opts.forge async (problem, ctx) => {result, cost, signals?} the expensive path (REQUIRED).
|
|
51
|
+
* With `learning:true`: async (problem, ctx) => {candidate, calls} — ctx carries
|
|
52
|
+
* {scopeFacts, neighbours, donors} from the dispatch (reuse them = adapt).
|
|
53
|
+
* @param opts.reForge optional partial re-forge of the differing content on a recalled skeleton.
|
|
54
|
+
* @param opts.store a file PATH (→ disk-backed, cross-restart) or a Map-like store; default in-memory.
|
|
55
|
+
* NOTE with `learning:true` the exact cache holds METHOD candidates (they carry a
|
|
56
|
+
* provider fn) — use `pack()`/`load()` for cross-restart method portability; a
|
|
57
|
+
* JSON file store is for opaque (JSON-safe) results.
|
|
58
|
+
* @param opts.mount a mount controller (default createMountController; opts.maxDeopt → its threshold).
|
|
59
|
+
* @param opts.recallK fuzzy-recall breadth (default 3).
|
|
60
|
+
* @param opts.learning OPT-IN (§4 default OFF). true → REQUIRES opts.target(problem) => {frontier,
|
|
61
|
+
* signatureKeys} and opts.dispatchFacts(problem) => scopeFacts (host projections;
|
|
62
|
+
* the combo never computes a signature/target — watch-point §11.1). Optional:
|
|
63
|
+
* opts.adaptContent(contentVars, scopeFacts) => {path:value} (the content-forger,
|
|
64
|
+
* may be async), opts.verify(candidate, scopeFacts) => bool (may be async),
|
|
65
|
+
* opts.interfaceRecall (§6.2 donor skeletons).
|
|
66
|
+
* @param opts.registry optional grown typed lattice (a registry: isa vocab + synonym rings) the combo HOLDS
|
|
67
|
+
* so pack/load ship it with the methods. Host-supplied + host-grown (the combo never
|
|
68
|
+
* derives/grows it — loadLattice grows it through the registry's own admission gate).
|
|
69
|
+
* @param opts.* the §4 knobs via resolveComboDefaults.
|
|
70
|
+
* @returns {{ solve, drift, stats, pack, load, packLattice, loadLattice, packAll, loadAll, registry,
|
|
71
|
+
* library, loop, store, crystallizeFrom, dispatch, blame, credit }}
|
|
72
|
+
*/
|
|
73
|
+
|
|
74
|
+
var defaults = require('./defaults.js');
|
|
75
|
+
var master = require('../authoring/master-loop.js');
|
|
76
|
+
var mountMod = require('../authoring/mount.js');
|
|
77
|
+
var libMod = require('../authoring/library.js');
|
|
78
|
+
var storeMod = require('../authoring/store.js');
|
|
79
|
+
var packMod = require('../authoring/method-pack.js');
|
|
80
|
+
var latticeMod = require('../authoring/lattice-pack.js');
|
|
81
|
+
var adaptMod = require('../authoring/adapt.js');
|
|
82
|
+
var crystalMod = require('../authoring/crystallize.js');
|
|
83
|
+
var parametric = require('../authoring/parametric.js');
|
|
84
|
+
|
|
85
|
+
function createLearningLibrary( opts ) {
|
|
86
|
+
opts = opts || {};
|
|
87
|
+
var d = defaults.resolveComboDefaults(opts);
|
|
88
|
+
if ( typeof opts.forge !== 'function' ) throw new Error('createLearningLibrary needs opts.forge (async (problem, ctx) -> {result, cost})');
|
|
89
|
+
if ( d.learning && (typeof opts.target !== 'function' || typeof opts.dispatchFacts !== 'function') )
|
|
90
|
+
throw new Error('createLearningLibrary learning:true needs opts.target(problem) -> {frontier, signatureKeys} and opts.dispatchFacts(problem) -> scopeFacts');
|
|
91
|
+
|
|
92
|
+
// ── the persistent method store (the "library"): a file path → disk-backed (survives restart),
|
|
93
|
+
// a Map-like → used as-is, else in-memory. This is the master-loop's exact-match cache. ────────
|
|
94
|
+
var store = (typeof opts.store === 'string') ? storeMod.createFileStore(opts.store)
|
|
95
|
+
: (opts.store || new Map());
|
|
96
|
+
|
|
97
|
+
// ── the O(1) dispatch catalog (for the P3 adapt-or-forge creative loop; exposed now for wiring) ──
|
|
98
|
+
var library = opts.library || libMod.makeLibrary();
|
|
99
|
+
|
|
100
|
+
// ── the grown typed LATTICE (the registry: isa vocab + synonym rings). HOST-supplied + host-grown
|
|
101
|
+
// (like target/dispatchFacts — the combo never derives/grows it; it only HOLDS the ref so pack/load
|
|
102
|
+
// can ship it alongside the methods). null until a host passes one or loadLattice adopts one. ──────
|
|
103
|
+
var registry = opts.registry || null;
|
|
104
|
+
|
|
105
|
+
// ── the mount controller (regime + deopt-to-ESCALATE ladder) ────────────────────────────────────
|
|
106
|
+
var mount = opts.mount || mountMod.createMountController(opts.maxDeopt != null ? { thresholds: { maxDeopt: opts.maxDeopt } } : undefined);
|
|
107
|
+
|
|
108
|
+
// ── the FORGE arm. Default: the host's forge, raw (the skeleton). With `learning:true`: the
|
|
109
|
+
// creative loop — adaptOrForgeAsync CHAINS dispatch(0-call hit)→antiUnify-adapt(1 call)→host
|
|
110
|
+
// forge, gated + auto-indexed by the brick; the combo only CALLS the host projections and
|
|
111
|
+
// renames candidate→result (the method IS the amortized unit). No invented signals (a policy
|
|
112
|
+
// verdict would be combo logic — mount already gets hitRate from the loop's own bookkeeping);
|
|
113
|
+
// the host keeps the opts.signals seam. A `reject` THROWS (fail-closed): the master loop would
|
|
114
|
+
// otherwise cache it as a permanent 0-call result. ─────────────────────────────────────────────
|
|
115
|
+
var masterForge = opts.forge;
|
|
116
|
+
if ( d.learning ) masterForge = async function ( problem, ctx ) {
|
|
117
|
+
var r = await adaptMod.adaptOrForgeAsync({
|
|
118
|
+
lib : library,
|
|
119
|
+
target : opts.target(problem),
|
|
120
|
+
scopeFacts : opts.dispatchFacts(problem),
|
|
121
|
+
adaptContent : opts.adaptContent,
|
|
122
|
+
verify : opts.verify,
|
|
123
|
+
interfaceRecall: opts.interfaceRecall,
|
|
124
|
+
recallK : opts.recallK,
|
|
125
|
+
requireContract: d.failClosed,
|
|
126
|
+
blend : opts.blend, // B.4 — opt-in compositional-reuse rung (graft a donor before a fresh forge)
|
|
127
|
+
donor : opts.donor,
|
|
128
|
+
combine : opts.combine, // the 2nd composition operator (set-op) — opt-in, sibling to blend; the host
|
|
129
|
+
// resolves {left, right, op, outerClass?} for a set-op task (0 calls). The
|
|
130
|
+
// rung's outerClass defaults to the `target` above (re-keys the combined method).
|
|
131
|
+
forge : function ( sf, neighbours, donors ) {
|
|
132
|
+
return opts.forge(problem, Object.assign({}, ctx, { scopeFacts: sf, neighbours: neighbours, donors: donors }));
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
if ( r.outcome === 'reject' ) {
|
|
136
|
+
var err = new Error('learning forge rejected: ' + r.reason);
|
|
137
|
+
err.outcome = 'reject'; err.reason = r.reason; err.calls = r.calls;
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
return { result: r.candidate, cost: r.calls, outcome: r.outcome };
|
|
141
|
+
};
|
|
142
|
+
|
|
143
|
+
// ── the always-on master loop: MATCH→RETRIEVE→FORGE→ESCALATE ────────────────────────────────────
|
|
144
|
+
var loop = master.createMasterLoop({
|
|
145
|
+
signature: opts.signature,
|
|
146
|
+
forge : masterForge,
|
|
147
|
+
reForge : opts.reForge,
|
|
148
|
+
cache : store,
|
|
149
|
+
mount : mount,
|
|
150
|
+
recallK : opts.recallK,
|
|
151
|
+
signals : opts.signals
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
return {
|
|
155
|
+
loop : loop,
|
|
156
|
+
store : store,
|
|
157
|
+
library: library,
|
|
158
|
+
|
|
159
|
+
/** solve a problem through the cost ladder → {result, arm, regime, cost}. */
|
|
160
|
+
solve: function ( problem ) { return loop.solve(problem); },
|
|
161
|
+
|
|
162
|
+
/** a premise drifted → invalidate the method (cache + recall) + deopt its mount rank. With
|
|
163
|
+
* `learning:true` ALSO drop the catalog-side template for this site (invalidateTemplate) —
|
|
164
|
+
* else the next FORGE would re-`hit` the stale method at 0 calls (correct-on-drift). */
|
|
165
|
+
drift: function ( problem ) {
|
|
166
|
+
var r = loop.drift(problem);
|
|
167
|
+
if ( d.learning ) r.invalidated = libMod.invalidateTemplate(library, opts.target(problem), opts.dispatchFacts(problem)).invalidated;
|
|
168
|
+
return r;
|
|
169
|
+
},
|
|
170
|
+
|
|
171
|
+
/** P3 — distill methods from captured trace records (`methodTrace().records` off a live run):
|
|
172
|
+
* a PURE pass-through to crystallizeFromRecords (`ctx` carries {episodeTree, schemaGraph,
|
|
173
|
+
* declaredFrontier, equivKeys, proseKeys, minCount, all, idFor} — the combo is NOT tree-bound;
|
|
174
|
+
* traces from different trees may feed the same library). Every ADMITTED candidate is indexed
|
|
175
|
+
* into the dispatch catalog (in `all` mode, from res.candidates — indexMethod does not dedup). */
|
|
176
|
+
crystallizeFrom: function ( records, ctx ) {
|
|
177
|
+
var res = crystalMod.crystallizeFromRecords(Object.assign({ records: records }, ctx || {}));
|
|
178
|
+
var all = res.candidates || [res];
|
|
179
|
+
for ( var i = 0; i < all.length; i++ )
|
|
180
|
+
if ( all[i].admitted && all[i].candidate ) libMod.indexMethod(library, all[i].candidate);
|
|
181
|
+
return res;
|
|
182
|
+
},
|
|
183
|
+
|
|
184
|
+
/** P3 — the O(1) catalog lookup, exposed: → {key, candidates, scanned, total}. */
|
|
185
|
+
dispatch: function ( target, scopeFacts ) { return libMod.dispatch(library, target, scopeFacts); },
|
|
186
|
+
|
|
187
|
+
/** P3 — the LOCALIZED blame rule (per-slot): → {perAtom, role, admissible}. Admissible iff
|
|
188
|
+
* every failed atom maps to ONE role (else discard — the blame-gate). Pass-through. */
|
|
189
|
+
blame: function ( o ) { return parametric.attributeSlotBlame(o); },
|
|
190
|
+
|
|
191
|
+
/** P3 — the localized credit rule (blame's dual): → {perAtom, roles}. Pass-through. */
|
|
192
|
+
credit: function ( o ) { return parametric.attributeSlotCredit(o); },
|
|
193
|
+
|
|
194
|
+
/** the ladder counters {match, recallFull, recallPartial, forge, escalate, cost, calls}. */
|
|
195
|
+
stats: function () { return loop.stats; },
|
|
196
|
+
|
|
197
|
+
/** pack the warm library as a portable, version-stamped `.sgc` bundle (ship it). */
|
|
198
|
+
pack: function ( packOpts ) { return packMod.packMethods(loop, packOpts || {}); },
|
|
199
|
+
|
|
200
|
+
/** load a `.sgc` bundle into this library (version-gated — a stale package never silently
|
|
201
|
+
* replays). With `learning:true` (and the version gate passed) every loaded method that IS a
|
|
202
|
+
* candidate (schema + templatesBySig — the catalog's own unit, a shape read) is ALSO re-indexed
|
|
203
|
+
* into the dispatch catalog, so a fresh deployment 0-call `hit`s on dispatch (G-P3-5). The pack
|
|
204
|
+
* ships the EXERCISED library (recall-index entries); a crystallized-but-never-solved method
|
|
205
|
+
* re-crystallizes from its records (the durable source) — the honest portability boundary. */
|
|
206
|
+
load: function ( bundle, loadOpts ) {
|
|
207
|
+
var r = packMod.loadMethods(bundle, loop, loadOpts || {});
|
|
208
|
+
if ( d.learning && r.exactReplaySafe ) {
|
|
209
|
+
var ms = packMod.unpackMethods(bundle).methods, n = 0;
|
|
210
|
+
for ( var i = 0; i < ms.length; i++ )
|
|
211
|
+
if ( ms[i].method && ms[i].method.schema && ms[i].method.templatesBySig ) { libMod.indexMethod(library, ms[i].method); n++; }
|
|
212
|
+
r.catalogued = n;
|
|
213
|
+
}
|
|
214
|
+
return r;
|
|
215
|
+
},
|
|
216
|
+
|
|
217
|
+
/** the current grown typed lattice (the registry: isa vocab + synonym rings), or null. */
|
|
218
|
+
registry: function () { return registry; },
|
|
219
|
+
|
|
220
|
+
/** pack the grown typed LATTICE as a portable, version-stamped `.sgc kind:'lattice'` bundle — the
|
|
221
|
+
* runtime-grown canon (isa vocab + admitted synonym rings) ships alongside the methods. Packs the
|
|
222
|
+
* held registry (empty if the host never supplied one). Pass-through to lattice-pack. */
|
|
223
|
+
packLattice: function ( packOpts ) { return latticeMod.packLattice(registry, packOpts || {}); },
|
|
224
|
+
|
|
225
|
+
/** load a `.sgc kind:'lattice'` bundle into the held canon (version-gated). With a held registry it
|
|
226
|
+
* GROWS it through the SAME admission gate (mergeRingProposals — confluence-checked, conflicting
|
|
227
|
+
* aliases rejected); with none it ADOPTS the packaged canon. The grown registry becomes the held
|
|
228
|
+
* one (so a later packLattice ships it). Pass-through to lattice-pack. */
|
|
229
|
+
loadLattice: function ( bundle, loadOpts ) {
|
|
230
|
+
var r = latticeMod.loadLattice(bundle, registry, loadOpts || {});
|
|
231
|
+
if ( r.loadSafe && r.registry ) registry = r.registry;
|
|
232
|
+
return r;
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
/** SHIP BOTH in one call: the exercised library = the methods AND the grown lattice. Returns a plain
|
|
236
|
+
* `{ methods, lattice }` envelope of two single-kind `.sgc` bundles (NOT a new `.sgc` kind — each
|
|
237
|
+
* part stays a valid corpus/method/lattice bundle). `lattice` is null when no registry is held. */
|
|
238
|
+
packAll: function ( packOpts ) {
|
|
239
|
+
return { methods: packMod.packMethods(loop, packOpts || {}), lattice: registry ? latticeMod.packLattice(registry, packOpts || {}) : null };
|
|
240
|
+
},
|
|
241
|
+
|
|
242
|
+
/** load a `{ methods, lattice }` envelope (the packAll output) — methods then lattice, each
|
|
243
|
+
* version-gated by its own pack. Returns `{ methods, lattice }` load results (lattice null if absent). */
|
|
244
|
+
loadAll: function ( bundle, loadOpts ) {
|
|
245
|
+
bundle = bundle || {};
|
|
246
|
+
var m = bundle.methods ? this.load(bundle.methods, loadOpts) : null;
|
|
247
|
+
var l = bundle.lattice ? this.loadLattice(bundle.lattice, loadOpts) : null;
|
|
248
|
+
return { methods: m, lattice: l };
|
|
249
|
+
}
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
module.exports = { createLearningLibrary: createLearningLibrary };
|
|
@@ -0,0 +1,193 @@
|
|
|
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
|
+
* C8 — the MIXTURE-RUNTIME server (owner "cerveau gauche × LLM intuitif", 2026-07-09). A cheap local model,
|
|
10
|
+
* ORIENTED by a forged certified-method stock, serves what a trust-gate can vouch for at 0 big-model cost, and
|
|
11
|
+
* ESCALATES the rest to a bigger/frontier tier (the tiered corrector, proven 10/16). A thin assembly over the
|
|
12
|
+
* proven arc — orientation (exact 5→8/36 gold-free, `WIP/experiments/2026-07-09-orientation` + `…-mixture-runtime`)
|
|
13
|
+
* + tiered correction (`…-tiered-correction`) — adding only the runtime trust-gate.
|
|
14
|
+
*
|
|
15
|
+
* ⟐ THE HONEST GUARANTEE (kill-gated live on Spider, Ministral-8B; the confront de-leaked the numbers):
|
|
16
|
+
* 1. 0-FALSE lives on the LEARNED CERTIFIED LAYER, never on the decomposition PICK. The stock's shapes are
|
|
17
|
+
* gold-certified at forge time (`stock.js#goldGate`); at runtime a shape is only ever SERVED-AS-CERTIFIED if
|
|
18
|
+
* it is in that vocabulary. We NEVER claim the small's chosen shape is correct — that is "pas gagné" (owner).
|
|
19
|
+
* 2. ORIENTATION improves the SCORE, not the soundness: the certified menu lifts the small's exact-match
|
|
20
|
+
* (+3/36 gold-free, measured) — a quality lever, cost-free, no guarantee attached.
|
|
21
|
+
* 3. The TRUST-GATE is a CONFIDENCE gate, not a truth oracle. The only gate that reached 0-false live is
|
|
22
|
+
* CROSS-AGREEMENT — the small's oriented shape EQUALS an INDEPENDENT predictor's shape (surface k-NN). Two
|
|
23
|
+
* independent signals concurring = trustable (100% precision live, ~38% recall). Self-consistency and a
|
|
24
|
+
* self-fit check did NOT separate correct from a confidently-wrong misread (a systematic misread is stable).
|
|
25
|
+
* ⚠ SCALE ERRATUM (2026-07-10, `WIP/experiments/2026-07-10-mixture-scale`, N=201 stratified over the FULL
|
|
26
|
+
* Spider dev distribution, 12% in-stock coverage): the "100%" was the n≤8 small-sample bound coming home —
|
|
27
|
+
* at scale surface cross-agreement admits 6/8 WRONG (25% precision): on an out-of-stock query surface-CLOSE
|
|
28
|
+
* to a certified class, the menu-steered small and the surface k-NN agree on the same wrong certified shape
|
|
29
|
+
* (independence of signals does not make the QUERY separable). CONFIRMED AT HIGH COVERAGE TOO (FinQA N=120,
|
|
30
|
+
* 85% traffic-covered stock: precision 42% — `2026-07-10-finqa-mixture-scale`): the failure is not the
|
|
31
|
+
* uncovered mass, surface agreement is simply not a truth signal. DO NOT ship cross-agreement as a 0-false
|
|
32
|
+
* trust tier; the fail-closed default (nothing auto-trusted) stays the safe wiring, and forge-time admission
|
|
33
|
+
* (oracle-gated) is a DIFFERENT, untouched layer. A trustable runtime tier needs a typed/grounded signal
|
|
34
|
+
* (per-query executable verification, deterministic slot-checker) — open work, tracked in the ledger.
|
|
35
|
+
* ⚠ INDEPENDENCE IS LOAD-BEARING (live finding): the cross-agreement precision COLLAPSES (100%→60% live) if
|
|
36
|
+
* `proposeMenu` and `predict` share the SAME surface signal — the menu then STEERS the small toward the very
|
|
37
|
+
* shape the predictor will name, so "agreement" is circular. Wire the FLAT certified menu (default) for
|
|
38
|
+
* orientation and keep `predict` as the independent signal; do NOT surface-narrow the menu AND predict from
|
|
39
|
+
* the same k-NN (narrowing is a large-stock optimization, but then use a DIFFERENT independent predictor).
|
|
40
|
+
* 4. ESCALATION carries the residual: whatever the trust-gate does not vouch for goes to the big tier (no false
|
|
41
|
+
* negative — the caller always gets an answer). The big model, not the gate, is what corrects a misread.
|
|
42
|
+
* 5. PRE-ROUTING is where the amortization actually lives (Fable-review correction, verified on the memoized
|
|
43
|
+
* traces `WIP/experiments/2026-07-09-amortization-e2e/prerouting.js`): gate-AFTER-small makes every query pay
|
|
44
|
+
* the small pass, so a high escalation rate costs MORE than big-alone (+28% live). A TEXT-ONLY router that
|
|
45
|
+
* sends predicted-uncovered queries STRAIGHT to the big tier (skipping the wasted small pass) turns the same
|
|
46
|
+
* k-NN predictor into +2%, and a perfect covered/uncovered router into −18%. `preRoute` wires that router
|
|
47
|
+
* BEFORE the small call; it never touches the trust decision (a pre-routed serve is a plain escalation).
|
|
48
|
+
* So: closed-vocabulary soundness + a small MEASURED 0-false trusted tier + a raw-score lift + escalation of the
|
|
49
|
+
* rest. The amortization is `1 − escalation-rate`; grow it by growing the stock / a tighter independent predictor,
|
|
50
|
+
* and CAPTURE it by pre-routing (route-then-gate, never pay-small-first).
|
|
51
|
+
*
|
|
52
|
+
* Bricks stay usable "à nu": `small`/`big`/`proposeMenu`/`predict` are all INJECTED (the combo owns no model and
|
|
53
|
+
* no corpus). `makeSurfaceDispatch` is an optional helper that builds `proposeMenu`+`predict` from a labelled
|
|
54
|
+
* anchor corpus (TF-IDF k-NN) — the surface signal is real (+20 pts over permuted-labels) but modest over the
|
|
55
|
+
* frequency prior, so it is an OPTIMIZATION (narrow a large stock + feed the cross-agreement gate), never load-bearing.
|
|
56
|
+
*
|
|
57
|
+
* const sd = makeSurfaceDispatch({ anchors: [{text, shape}, …], k: 5 });
|
|
58
|
+
* const mx = createMixtureServe({ certifiedShapes, small, big, proposeMenu: sd.proposeMenu, predict: sd.predict });
|
|
59
|
+
* const r = await mx.serve(query); // { shape, tier:'local-trusted'|'escalated'|'local-untrusted', certified, … }
|
|
60
|
+
*
|
|
61
|
+
* @param opts.certifiedShapes REQUIRED array<string> — the learned 0-false vocabulary (from the forged `.sgc` stock).
|
|
62
|
+
* @param opts.small REQUIRED async (query, menu) => shape — the cheap tier-1 model, oriented by `menu`.
|
|
63
|
+
* @param opts.big optional async (query, menu) => shape — the tier-2 escalation (frontier corrector). Absent
|
|
64
|
+
* → an untrusted result is returned tagged `local-untrusted` (the host decides), never forced.
|
|
65
|
+
* @param opts.proposeMenu optional (query) => array<string> — the oriented menu (surface-narrowed); default = the
|
|
66
|
+
* flat certified vocabulary (already gold-free; narrowing is the optional optimization).
|
|
67
|
+
* @param opts.predict optional (query) => shape — an INDEPENDENT shape predictor (surface k-NN). Enables the
|
|
68
|
+
* default CROSS-AGREEMENT trust-gate. Without it (and without `trust`) nothing is auto-trusted
|
|
69
|
+
* (fail-closed: everything escalates) — we never auto-trust a bare certified shape (47% live).
|
|
70
|
+
* @param opts.trust optional (query, shape, ctx{menu,predicted}) => bool — override the trust predicate.
|
|
71
|
+
* @param opts.preRoute optional true | (query, ctx{predicted,menu}) => bool — route-then-gate (the amortization
|
|
72
|
+
* capture): decided BEFORE the small call; `true` routes a query whose predicted shape is
|
|
73
|
+
* OUTSIDE the certified vocabulary straight to `big` (needs `predict`); a function is a
|
|
74
|
+
* custom router (return true = go direct). Inert without a `big` tier (fail-safe: the small
|
|
75
|
+
* path still serves). A pre-routed record is `tier:'escalated', preRouted:true`.
|
|
76
|
+
* @param opts.onServe optional (record) => void — a per-serve hook (telemetry).
|
|
77
|
+
* @returns {{ serve, stats, certifiedShapes }}
|
|
78
|
+
*/
|
|
79
|
+
function createMixtureServe( opts ) {
|
|
80
|
+
opts = opts || {};
|
|
81
|
+
var shapes = opts.certifiedShapes;
|
|
82
|
+
if ( !Array.isArray(shapes) || !shapes.length ) throw new Error('createMixtureServe needs opts.certifiedShapes (the learned certified vocabulary from the forged stock)');
|
|
83
|
+
if ( typeof opts.small !== 'function' ) throw new Error('createMixtureServe needs opts.small (async (query, menu) -> shape) — the cheap tier-1 model');
|
|
84
|
+
var certified = Object.create(null); shapes.forEach(function ( s ) { certified[s] = true; });
|
|
85
|
+
var big = (typeof opts.big === 'function') ? opts.big : null;
|
|
86
|
+
var proposeMenu = (typeof opts.proposeMenu === 'function') ? opts.proposeMenu : function () { return shapes; };
|
|
87
|
+
var predict = (typeof opts.predict === 'function') ? opts.predict : null;
|
|
88
|
+
// default trust = CROSS-AGREEMENT when an independent predictor is given; else fail-closed (never auto-trust).
|
|
89
|
+
var trust = (typeof opts.trust === 'function') ? opts.trust
|
|
90
|
+
: (predict ? function ( q, shape, ctx ) { return shape === ctx.predicted; } : function () { return false; });
|
|
91
|
+
var onServe = (typeof opts.onServe === 'function') ? opts.onServe : null;
|
|
92
|
+
// pre-router (route-then-gate): default = predicted shape outside the certified vocabulary → go direct to big.
|
|
93
|
+
var router = (typeof opts.preRoute === 'function') ? opts.preRoute
|
|
94
|
+
: (opts.preRoute ? function ( q, ctx ) { return !certified[ctx.predicted]; } : null);
|
|
95
|
+
if ( router && !predict && typeof opts.preRoute !== 'function' ) throw new Error('createMixtureServe preRoute:true needs opts.predict (the text-only covered/uncovered signal) — or pass a custom router function');
|
|
96
|
+
var stats = { served: 0, localTrusted: 0, escalated: 0, localUntrusted: 0, preRouted: 0 };
|
|
97
|
+
|
|
98
|
+
async function serve( query ) {
|
|
99
|
+
stats.served++;
|
|
100
|
+
var menu = proposeMenu(query) || shapes;
|
|
101
|
+
var predicted = predict ? predict(query) : undefined; // ONE predict call: shared by the router and the gate
|
|
102
|
+
if ( router && big && router(query, { predicted: predicted, menu: menu }) ) {
|
|
103
|
+
var dshape = await big(query, menu);
|
|
104
|
+
stats.preRouted++; stats.escalated++;
|
|
105
|
+
var drec = { shape: dshape, tier: 'escalated', certified: !!certified[dshape], trusted: false, preRouted: true, predicted: predicted };
|
|
106
|
+
if ( onServe ) onServe(Object.assign({ query: query }, drec));
|
|
107
|
+
return drec;
|
|
108
|
+
}
|
|
109
|
+
var shape = await opts.small(query, menu);
|
|
110
|
+
var isCert = !!certified[shape];
|
|
111
|
+
var trusted = isCert && !!trust(query, shape, { menu: menu, predicted: predicted });
|
|
112
|
+
var rec;
|
|
113
|
+
if ( trusted ) {
|
|
114
|
+
stats.localTrusted++;
|
|
115
|
+
rec = { shape: shape, tier: 'local-trusted', certified: true, trusted: true, predicted: predicted };
|
|
116
|
+
} else if ( big ) {
|
|
117
|
+
var bshape = await big(query, menu);
|
|
118
|
+
stats.escalated++;
|
|
119
|
+
rec = { shape: bshape, tier: 'escalated', certified: !!certified[bshape], trusted: false, local: shape, predicted: predicted };
|
|
120
|
+
} else {
|
|
121
|
+
stats.localUntrusted++;
|
|
122
|
+
rec = { shape: shape, tier: 'local-untrusted', certified: isCert, trusted: false, predicted: predicted };
|
|
123
|
+
}
|
|
124
|
+
if ( onServe ) onServe(Object.assign({ query: query }, rec));
|
|
125
|
+
return rec;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return { serve: serve, stats: stats, certifiedShapes: shapes.slice() };
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// --- makeSurfaceDispatch: the KG-1 surface signal promoted to a reusable brick (TF-IDF k-NN over labelled anchors).
|
|
132
|
+
|
|
133
|
+
var STOP = { 'the': 1, 'a': 1, 'an': 1, 'of': 1, 'to': 1, 'in': 1, 'on': 1, 'for': 1, 'and': 1, 'or': 1, 'is': 1, 'are': 1, 'was': 1, 'were': 1, 'be': 1, 'with': 1, 'by': 1, 'at': 1, 'as': 1, 'from': 1, 'that': 1, 'this': 1, 'these': 1, 'those': 1, 'what': 1, 'which': 1, 'who': 1, 'how': 1, 'many': 1, 'much': 1, 'list': 1, 'show': 1, 'give': 1, 'find': 1, 'name': 1, 'number': 1, 'count': 1, 'all': 1, 'each': 1, 'every': 1, 'their': 1, 'its': 1, 'there': 1 };
|
|
134
|
+
function tokenize( s ) { return String(s || '').toLowerCase().split(/[^a-z0-9]+/).filter(function ( w ) { return w && w.length > 1 && !STOP[w]; }); }
|
|
135
|
+
function vectorize( toks, idf ) {
|
|
136
|
+
var tf = {}, i; for ( i = 0; i < toks.length; i++ ) tf[toks[i]] = (tf[toks[i]] || 0) + 1;
|
|
137
|
+
var v = {}, n = 0, w; for ( w in tf ) { var x = tf[w] * (idf[w] || 0); v[w] = x; n += x * x; }
|
|
138
|
+
n = Math.sqrt(n) || 1; for ( w in v ) v[w] /= n; return v;
|
|
139
|
+
}
|
|
140
|
+
function cosine( a, b ) { var s = 0, x = a, y = b, w; if ( Object.keys(a).length > Object.keys(b).length ) { x = b; y = a; } for ( w in x ) if ( y[w] ) s += x[w] * y[w]; return s; }
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Build the surface-dispatch pair (`proposeMenu` + `predict`) from a labelled anchor corpus — the same signal the
|
|
144
|
+
* KG-1 kill-gate measured (real geometry, +20 pts over permuted-labels; modest over the frequency prior, so an
|
|
145
|
+
* optimization). Deterministic (pure TF-IDF, index-stable tie-breaks). The anchors are TRAINING queries of the
|
|
146
|
+
* certified classes (their `shape` is the training label) — no runtime gold.
|
|
147
|
+
* @param opts.anchors REQUIRED array<{ text, shape }> — labelled training queries.
|
|
148
|
+
* @param opts.k proposal menu size (default 5).
|
|
149
|
+
* @returns {{ proposeMenu, predict }} — proposeMenu(query) => top-k distinct shapes (cosine-vote); predict(query)
|
|
150
|
+
* => the single top-1 shape (for the cross-agreement trust-gate).
|
|
151
|
+
*/
|
|
152
|
+
function makeSurfaceDispatch( opts ) {
|
|
153
|
+
opts = opts || {};
|
|
154
|
+
var anchors = opts.anchors;
|
|
155
|
+
if ( !Array.isArray(anchors) || !anchors.length ) throw new Error('makeSurfaceDispatch needs opts.anchors ([{text, shape}])');
|
|
156
|
+
var K = opts.k == null ? 5 : opts.k;
|
|
157
|
+
var toks = anchors.map(function ( a ) { return tokenize(a.text); });
|
|
158
|
+
var df = {}; toks.forEach(function ( t ) { var seen = {}, i; for ( i = 0; i < t.length; i++ ) if ( !seen[t[i]] ) { seen[t[i]] = 1; df[t[i]] = (df[t[i]] || 0) + 1; } });
|
|
159
|
+
var idf = {}, w; for ( w in df ) idf[w] = Math.log((anchors.length + 1) / (df[w] + 1)) + 1;
|
|
160
|
+
var vecs = toks.map(function ( t ) { return vectorize(t, idf); });
|
|
161
|
+
|
|
162
|
+
// ranked (shape -> summed cosine) over ALL anchors, ties broken by lexical shape then first-seen index.
|
|
163
|
+
function rankShapes( query ) {
|
|
164
|
+
var qv = vectorize(tokenize(query), idf), byShape = {}, i;
|
|
165
|
+
for ( i = 0; i < anchors.length; i++ ) { var sh = anchors[i].shape, s = cosine(qv, vecs[i]); byShape[sh] = (byShape[sh] || 0) + s; }
|
|
166
|
+
return Object.keys(byShape).sort(function ( a, b ) { return (byShape[b] - byShape[a]) || (a < b ? -1 : 1); });
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
proposeMenu: function ( query ) { return rankShapes(query).slice(0, K); },
|
|
170
|
+
predict: function ( query ) { return rankShapes(query)[0] || null; }
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
/**
|
|
175
|
+
* qualifyMenu — present the certified shapes with their SLOT QUALIFICATIONS so the small conforms to the reference
|
|
176
|
+
* vocabulary instead of deviating off-menu (the owner's "meilleure qualification des slots"). Kill-gated live: a
|
|
177
|
+
* slot-glossed menu lifted in-stock exact-match +3/18 (8→11) over the opaque shape strings, because the loss is
|
|
178
|
+
* CONFORMANCE (the small emits an off-reference shape), not selection-among-options (KG-3). Pure/deterministic; the
|
|
179
|
+
* host supplies the domain gloss (a step→meaning map — the "role" the lattice types each slot with).
|
|
180
|
+
* @param shapes array<string> — the certified shapes (e.g. "join>filter>select").
|
|
181
|
+
* @param glossMap { <stepKind>: "human meaning" } — the per-slot role. Missing kinds echo the kind.
|
|
182
|
+
* @param sep the step separator inside a shape (default '>').
|
|
183
|
+
* @returns array<string> — each shape annotated "shape (kind=meaning, …)" for the orientation prompt.
|
|
184
|
+
*/
|
|
185
|
+
function qualifyMenu( shapes, glossMap, sep ) {
|
|
186
|
+
var s = sep || '>', g = glossMap || {};
|
|
187
|
+
return (shapes || []).map(function ( shape ) {
|
|
188
|
+
var parts = String(shape).split(s).map(function ( k ) { return k + '=' + (g[k] || k); });
|
|
189
|
+
return shape + ' (' + parts.join(', ') + ')';
|
|
190
|
+
});
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
module.exports = { createMixtureServe: createMixtureServe, makeSurfaceDispatch: makeSurfaceDispatch, qualifyMenu: qualifyMenu };
|
|
@@ -0,0 +1,192 @@
|
|
|
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
|
+
* plan-loop — C7, the HIERARCHICAL PLAN LOOP (design `WIP/2026-07-07-design-r1-plan-loop.md`; kill-gated R1:
|
|
10
|
+
* KG-R1a the channel + KG-R1b the fixpoint). A task LONGER THAN THE CONTEXT is decomposed into typed leaves,
|
|
11
|
+
* each leaf sees only a PROJECTED digest (never the whole task), leaves are served by the cost ladder, the plan
|
|
12
|
+
* is driven to a BALANCED fixpoint (rebalance brick), and reassembly is VERIFIED — a leaf whose required facts
|
|
13
|
+
* are missing is REFUSED (never a silent wrong), and reassembly reads nothing uncovered (never a claim-of-absence).
|
|
14
|
+
*
|
|
15
|
+
* THIN assembly (the combo doctrine — no new logic; a missing piece goes into a brick):
|
|
16
|
+
* decompose (injected: typed-loop) → project + leaf pre-contract → serveLeaf (injected: the C6 ladder,
|
|
17
|
+
* match→retrieve→forge→ESCALADE frontier — kill-gate finding: escalation is LOAD-BEARING on hard/novel leaves,
|
|
18
|
+
* C-local alone is model-capability-bound) → rebalancePlan (E2∘E1∘E3∘E4) → checkReassembly (checkCompose).
|
|
19
|
+
*
|
|
20
|
+
* Both heavy stages are INJECTED so the combo stays usable "à nu" and testable without a model: production wires
|
|
21
|
+
* decompose = typed-loop's recursive typed decompose, serveLeaf = createProxyCache(...).solve (the C6 ladder).
|
|
22
|
+
*
|
|
23
|
+
* const { createPlanLoop } = require('skynet-graph').combos; // or Graph.combos.createPlanLoop
|
|
24
|
+
* const loop = createPlanLoop({ decompose, serveLeaf });
|
|
25
|
+
* const { answer, converged, refused, reassembly } = await loop.run(task, ctx);
|
|
26
|
+
* // ctx.givens = { key: value } (givens.js#seedOf): the task's BASE FACTS, seeded into the projection pool as
|
|
27
|
+
* // val_<key> — a leaf citing a given key in readsExtra gets the value injected into its bounded inputs (gap i).
|
|
28
|
+
*/
|
|
29
|
+
const { rebalancePlan, checkReassembly } = require('../authoring/rebalance.js');
|
|
30
|
+
const { createContextProjection } = require('../authoring/context-project.js');
|
|
31
|
+
const { stableStringify } = require('../providers/cache.js');
|
|
32
|
+
|
|
33
|
+
const tok = ( s ) => Math.ceil(String(s == null ? '' : s).length / 4);
|
|
34
|
+
|
|
35
|
+
// leaf pre-contract (default): a typed request is COMPLETE iff no required field is null/'' (a severed digest
|
|
36
|
+
// amputates one → refused AT PROJECTION, before any serve). Shallow + one level into a `filters` array. ──
|
|
37
|
+
function defaultComplete( request ) {
|
|
38
|
+
if ( !request || typeof request !== 'object' ) return false;
|
|
39
|
+
for ( const k of Object.keys(request) ) {
|
|
40
|
+
const v = request[k];
|
|
41
|
+
if ( v === null || v === '' ) return false;
|
|
42
|
+
if ( Array.isArray(v) ) for ( const f of v ) if ( f && typeof f === 'object' )
|
|
43
|
+
for ( const kk of Object.keys(f) ) if ( f[kk] === null || f[kk] === '' ) return false;
|
|
44
|
+
}
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
// default answer fold — canonical {id: value | REFUSED}, sorted (deterministic; E4/refold semantics). ──
|
|
49
|
+
function defaultFold( leaves ) {
|
|
50
|
+
const parts = [];
|
|
51
|
+
for ( const lf of leaves ) for ( const id of leafWrites(lf) )
|
|
52
|
+
parts.push([id, lf.refused ? 'REFUSED' : (lf.severed ? 'REFUSED' : String(lf.value == null ? '' : lf.value))]);
|
|
53
|
+
parts.sort(( a, b ) => a[0] < b[0] ? -1 : a[0] > b[0] ? 1 : 0);
|
|
54
|
+
return parts.map(( [id, v] ) => id + '=' + v ).join(';');
|
|
55
|
+
}
|
|
56
|
+
const leafWrites = ( n ) => n.bundle ? n.bundle.map(( r ) => r.id ) : [n.request && n.request.id];
|
|
57
|
+
|
|
58
|
+
// sink fold — the answer is the LAST SINK's value (a leaf nobody reads; "the LAST part yields the final answer"
|
|
59
|
+
// plans). Promoted from the KG-ZOOM harness (P1/P1.5, measured on 560 tasks). Wire as opts.fold: sinkFold. ──
|
|
60
|
+
function sinkFold( leaves ) {
|
|
61
|
+
const needed = new Set(leaves.flatMap(( l ) => l.readsExtra || [] ));
|
|
62
|
+
const sinks = leaves.filter(( l ) => l.request && !needed.has(l.request.id) && !l.refused );
|
|
63
|
+
const last = sinks.length ? sinks[sinks.length - 1] : leaves[leaves.length - 1];
|
|
64
|
+
return last ? String(last.value == null ? '' : last.value) : '';
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// degenerate decompose — the split FAILED (no leaves, or a single untyped task-echo leaf). With opts.fallback
|
|
68
|
+
// the product answer is "don't split then": serve the WHOLE task, FLAGGED (never a silent wrong/undefined). ──
|
|
69
|
+
function defaultIsDegenerate( leaves ) {
|
|
70
|
+
return leaves.length === 0 || (leaves.length === 1 && !(leaves[0] && leaves[0].request && leaves[0].request.kind));
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* createPlanLoop(opts)
|
|
75
|
+
* @param opts.decompose REQUIRED async (task, ctx) => [{ id, request, nl }] — the typed leaves (typed-loop in
|
|
76
|
+
* production). May be DEGENERATE (redundant / disordered / an over-budget `{ bundle:[request,…] }`) — the
|
|
77
|
+
* fixpoint repairs it. `request` is the leaf's typed request (the fusion key + contract are derived from it).
|
|
78
|
+
* @param opts.serveLeaf REQUIRED async (leaf, ctx) => value — the cost ladder (createProxyCache(...).solve).
|
|
79
|
+
* @param opts.isComplete (request) => bool — the leaf pre-contract (default: no null/'' required field).
|
|
80
|
+
* @param opts.complete (leaf) => promptString — the projection's prompt renderer, passed through to
|
|
81
|
+
* createContextProjection (e.g. context-project's `stratComplete`). Caveat: the projected roadmap keys
|
|
82
|
+
* are internal step ids (L0, L1…) — a host wanting figure-keyed prompts renders in serveLeaf instead.
|
|
83
|
+
* @param opts.fold (servedLeaves) => answer — the report fold (default: canonical {id:value|REFUSED};
|
|
84
|
+
* `sinkFold` exported for "the LAST part yields the answer" plans).
|
|
85
|
+
* @param opts.fallback async (task, ctx) => value — OPT-IN whole-task fallback when the decompose is DEGENERATE
|
|
86
|
+
* (opts.isDegenerate, default: no leaves / single untyped echo): a failed split means "don't split", the
|
|
87
|
+
* answer is served whole and FLAGGED `fallback:true` in the result (report it apart from pure-decomposed).
|
|
88
|
+
* @param opts.budget node token budget (default 512) — a served digest over budget is an E2 scission target.
|
|
89
|
+
* @param opts.cap fixpoint round cap (default from the brick).
|
|
90
|
+
* ctx.givens = base facts seeded as val_<key> (givens.js#seedOf) · ctx.labels = provenance labels
|
|
91
|
+
* (givens.js#labelsOf, the cells rule) — both ride into the context projection.
|
|
92
|
+
*/
|
|
93
|
+
function createPlanLoop( opts ) {
|
|
94
|
+
opts = opts || {};
|
|
95
|
+
if ( typeof opts.decompose !== 'function' ) throw new Error('createPlanLoop needs opts.decompose(task,ctx) -> [{id,request,nl}]');
|
|
96
|
+
if ( typeof opts.serveLeaf !== 'function' ) throw new Error('createPlanLoop needs opts.serveLeaf(leaf,ctx) -> value');
|
|
97
|
+
const budget = opts.budget || 512;
|
|
98
|
+
const isComplete = opts.isComplete || defaultComplete;
|
|
99
|
+
const fold = opts.fold || defaultFold;
|
|
100
|
+
const isDegenerate = opts.isDegenerate || defaultIsDegenerate;
|
|
101
|
+
|
|
102
|
+
// the rebalance spec — derived entirely from the leaves' TYPED requests (no new logic) ──
|
|
103
|
+
const spec = {
|
|
104
|
+
isLeaf: ( n ) => n.kind === 'leaf',
|
|
105
|
+
fusionKey: ( n ) => stableStringify(n.bundle ? n.bundle : n.request),
|
|
106
|
+
overBudget: ( n ) => n.kind === 'leaf' && (!!n.bundle || tok(n.digest) > budget),
|
|
107
|
+
split: ( n ) => (n.bundle || []).map(( r, i ) => ({ id: n.id + '_' + i, kind: 'leaf', request: r, nl: r.nl, refused: !isComplete(r), value: n.served && n.served[i] }) ),
|
|
108
|
+
writes: leafWrites,
|
|
109
|
+
reads: ( n ) => n.kind === 'root' ? (n.reads || []) : (n.readsExtra || []),
|
|
110
|
+
refold: ( root, leaves ) => fold(leaves),
|
|
111
|
+
contractOf: ( n ) => n.kind === 'root'
|
|
112
|
+
? { name: n.id, contract: { read: (n.reads || []), write: ['__answer'], effect: 'pure' } }
|
|
113
|
+
: { name: n.id, contract: { read: (n.readsExtra || []), write: leafWrites(n), effect: n.retract ? 'effect' : 'pure' } },
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
// serve the atomic leaves THROUGH the graph-native projection: build a roadmap from the leaves' footprint
|
|
117
|
+
// (a leaf's produces = a unique per-leaf key; its needs = the readsExtra it shares with a producing sibling),
|
|
118
|
+
// serve each leaf WITH its completed bounded context (upstream values relabelled to the figure key), in the
|
|
119
|
+
// emergent dependency order. A cyclic footprint → the projection's `guardPlan` returns a TYPED refusal (the
|
|
120
|
+
// intra-plan cycle detector) which run() surfaces — never a silent wedge, never a serve-despite-impossible-deps.
|
|
121
|
+
// Mutates lf.value / lf.projectedPrompt in place; returns the refusal string or null. ──
|
|
122
|
+
async function projectServe( atomic, producedKeys, task, ctx ) {
|
|
123
|
+
const givens = (ctx && ctx.givens) || {}; // the task's base facts (givens.js) — ride as pre-satisfied needs
|
|
124
|
+
const writeKeyOf = ( l ) => leafWrites(l)[0];
|
|
125
|
+
const firstIdxOf = {}; atomic.forEach(( l, i ) => { const k = writeKeyOf(l); if ( k != null && !(k in firstIdxOf) ) firstIdxOf[k] = i; });
|
|
126
|
+
const stepId = ( i ) => 'L' + i, byStep = {}, writeKeyByStep = {};
|
|
127
|
+
atomic.forEach(( l, i ) => { byStep[stepId(i)] = l; writeKeyByStep[stepId(i)] = writeKeyOf(l); });
|
|
128
|
+
const roadmap = atomic.map(( l, i ) => ({ id: stepId(i), produces: stepId(i),
|
|
129
|
+
needs: (l.readsExtra || []).filter(( k ) => producedKeys.has(k) && firstIdxOf[k] !== i ).map(( k ) => stepId(firstIdxOf[k]) )
|
|
130
|
+
.concat((l.readsExtra || []).filter(( k ) => !producedKeys.has(k) && k in givens )) }) );
|
|
131
|
+
const serve = async ( s, c ) => {
|
|
132
|
+
const inputs = {}; for ( const k of Object.keys(s.inputs) ) inputs[writeKeyByStep[k] || k] = s.inputs[k]; // relabel producer-step id → figure key
|
|
133
|
+
// the projected prompt + provenance labels ride along (a host serveLeaf may use leaf.prompt as-is, or
|
|
134
|
+
// render its own from inputs + labels — the cells rule, givens.js#labelsOf).
|
|
135
|
+
return await opts.serveLeaf(Object.assign({}, byStep[s.id], { inputs: inputs, prompt: s.prompt, labels: s.labels }), c);
|
|
136
|
+
};
|
|
137
|
+
const statement = typeof task === 'string' ? task : (( ctx && ctx.statement ) || JSON.stringify(task));
|
|
138
|
+
const pr = await createContextProjection({ serve: serve, complete: opts.complete }).run(roadmap, Object.assign({ statement: statement }, ctx));
|
|
139
|
+
if ( pr.refusal ) return pr.refusal; // unresolvable structure (cycle) → surfaced, leaves unserved
|
|
140
|
+
for ( const s of roadmap ) { const l = byStep[s.id]; l.value = pr.results[s.id].value; l.projectedPrompt = pr.results[s.id].prompt; }
|
|
141
|
+
return null;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
async function run( task, ctx ) {
|
|
145
|
+
// 1. DECOMPOSE (typed) → leaves (possibly degenerate)
|
|
146
|
+
const raw = await opts.decompose(task, ctx) || [];
|
|
147
|
+
// 1b. DEGENERATE + opts.fallback → the whole-task answer, FLAGGED (a failed split is "don't split",
|
|
148
|
+
// never a silent String(undefined) nor an unexplained refusal when the host opted into the fallback).
|
|
149
|
+
if ( opts.fallback && isDegenerate(raw) ) {
|
|
150
|
+
const v = await opts.fallback(task, ctx);
|
|
151
|
+
return { answer: v, fallback: true, converged: true, refusal: null, refused: [],
|
|
152
|
+
reassembly: null, rounds: 0, monotone: true, trace: [], leaves: raw.length, projected: false };
|
|
153
|
+
}
|
|
154
|
+
// 2. PROJECT + leaf PRE-CONTRACT (a severed/incomplete request → refused AT PROJECTION, never served-then-guessed)
|
|
155
|
+
const leaves = raw.map(( n ) => Object.assign({ kind: 'leaf' }, n, { refused: !!n.bundle ? false : !isComplete(n.request) }) );
|
|
156
|
+
// 3. SERVE. Bundles are pre-served per atom (a scission concern). Admissible ATOMIC leaves are served through
|
|
157
|
+
// the REAL context projection (lib/authoring/context-project): a leaf that reads another leaf's write is
|
|
158
|
+
// served AFTER it, with the upstream value completed into its bounded context (the R1 §2 "contexte par
|
|
159
|
+
// nœud" — no leaf sees the whole task). A decompose with NO intra-plan dependency degenerates to the
|
|
160
|
+
// direct serve (all leaves are independent roots) — that IS the no-dependency case of the same projection,
|
|
161
|
+
// kept as a fast path (redundant serves are cheap — the C6 cache dedupes).
|
|
162
|
+
for ( const lf of leaves ) if ( lf.bundle ) {
|
|
163
|
+
lf.served = []; for ( const r of lf.bundle ) lf.served.push(await opts.serveLeaf({ kind: 'leaf', request: r, nl: r.nl }, ctx));
|
|
164
|
+
}
|
|
165
|
+
const atomic = leaves.filter(( l ) => !l.bundle && !l.refused );
|
|
166
|
+
const producedKeys = new Set(atomic.map(( l ) => leafWrites(l)[0] ).filter(( k ) => k != null ));
|
|
167
|
+
const givens = (ctx && ctx.givens) || {};
|
|
168
|
+
const projected = atomic.some(( l ) => (l.readsExtra || []).some(( k ) => producedKeys.has(k) || k in givens ) );
|
|
169
|
+
let projRefusal = null;
|
|
170
|
+
if ( !projected ) { for ( const lf of atomic ) lf.value = await opts.serveLeaf(lf, ctx); }
|
|
171
|
+
else projRefusal = await projectServe(atomic, producedKeys, task, ctx);
|
|
172
|
+
// 4. BUILD the plan + REBALANCE to the fixpoint (E2 split → E1 dedupe → E3 reorder → E4 refold)
|
|
173
|
+
const producedIds = leaves.filter(( l ) => !l.refused ).flatMap(spec.writes).filter(Boolean);
|
|
174
|
+
const root = { id: '__root', kind: 'root', reads: producedIds, value: null };
|
|
175
|
+
const plan = { order: leaves.concat([root]) };
|
|
176
|
+
const rb = rebalancePlan(plan, spec, { cap: opts.cap });
|
|
177
|
+
// 5. VERIFIED REASSEMBLY (checkCompose sound + nothing uncovered)
|
|
178
|
+
const producers = rb.plan.order.filter(spec.isLeaf);
|
|
179
|
+
const reassembly = checkReassembly(root, producers, spec);
|
|
180
|
+
return {
|
|
181
|
+
answer: root.value, converged: rb.converged, refusal: projRefusal || rb.refusal,
|
|
182
|
+
refused: producers.filter(( p ) => p.refused ).flatMap(spec.writes).filter(Boolean),
|
|
183
|
+
reassembly, rounds: rb.rounds, monotone: rb.monotone, trace: rb.trace,
|
|
184
|
+
leaves: producers.length, projected: projected,
|
|
185
|
+
};
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return { run: run, rebalance: rebalancePlan, spec: spec };
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
module.exports = { createPlanLoop: createPlanLoop, defaultComplete: defaultComplete, defaultFold: defaultFold,
|
|
192
|
+
sinkFold: sinkFold, defaultIsDegenerate: defaultIsDegenerate };
|