skynet-graph 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (213) hide show
  1. package/CONTRIBUTING.md +22 -0
  2. package/LICENSE +661 -0
  3. package/README.md +245 -0
  4. package/bin/sg +20 -0
  5. package/concepts/README.md +23 -0
  6. package/concepts/_substrate/Claim/Confidence/Trusted.json +6 -0
  7. package/concepts/_substrate/Claim/Confidence.json +10 -0
  8. package/concepts/_substrate/Claim/Freshness/Stale.json +7 -0
  9. package/concepts/_substrate/Claim/Freshness.json +5 -0
  10. package/concepts/_substrate/Claim/Verification/Refuted.json +6 -0
  11. package/concepts/_substrate/Claim/Verification.json +6 -0
  12. package/concepts/_substrate/Claim.json +4 -0
  13. package/concepts/_substrate/Frontier/InBeam.json +6 -0
  14. package/concepts/_substrate/Frontier/Stuck.json +6 -0
  15. package/concepts/_substrate/Frontier.json +5 -0
  16. package/concepts/_substrate/Intake/ToTask.json +22 -0
  17. package/concepts/_substrate/Intake/Typed.json +10 -0
  18. package/concepts/_substrate/Intake.json +27 -0
  19. package/concepts/_substrate/Task/Answer.json +12 -0
  20. package/concepts/_substrate/Task/Complexity/Atomic.json +5 -0
  21. package/concepts/_substrate/Task/Complexity/Compound/Expansion.json +7 -0
  22. package/concepts/_substrate/Task/Complexity/Compound.json +6 -0
  23. package/concepts/_substrate/Task/Complexity.json +6 -0
  24. package/concepts/_substrate/Task/EvalComplexity.json +13 -0
  25. package/concepts/_substrate/Task/ReportUp.json +6 -0
  26. package/concepts/_substrate/Task/Rollup.json +10 -0
  27. package/concepts/_substrate/Task.json +5 -0
  28. package/concepts/clinical/Diagnosis/Medication.json +6 -0
  29. package/concepts/clinical/Diagnosis.json +15 -0
  30. package/concepts/clinical/Observation/LabValue/OutOfRange.json +6 -0
  31. package/concepts/clinical/Observation/LabValue.json +5 -0
  32. package/concepts/clinical/Observation.json +6 -0
  33. package/concepts/common/Document/pathBasket.json +6 -0
  34. package/concepts/common/Document.json +3 -0
  35. package/concepts/common/Edge/Distance.json +13 -0
  36. package/concepts/common/Edge/Stay/LongStay.json +8 -0
  37. package/concepts/common/Edge/Stay.json +8 -0
  38. package/concepts/common/Edge/Travel/LongTravel.json +16 -0
  39. package/concepts/common/Edge/Travel/ShortTravel.json +11 -0
  40. package/concepts/common/Edge/Travel/targetNode.json +7 -0
  41. package/concepts/common/Edge/Travel.json +7 -0
  42. package/concepts/common/Edge.json +3 -0
  43. package/concepts/common/Vertice.json +3 -0
  44. package/concepts/supply/Fulfillment.json +10 -0
  45. package/concepts/supply/Inventory/Reorder.json +7 -0
  46. package/concepts/supply/Inventory.json +5 -0
  47. package/concepts/supply/Procurement/SupplierConfirm.json +6 -0
  48. package/concepts/supply/Procurement.json +8 -0
  49. package/concepts/supply/Transport.json +5 -0
  50. package/doc/API.md +556 -0
  51. package/doc/CAPABILITIES.md +373 -0
  52. package/doc/MODELISATION.md +511 -0
  53. package/doc/architecture.md +267 -0
  54. package/doc/concept-as-graph.md +379 -0
  55. package/doc/concept-learning.md +168 -0
  56. package/doc/creative-loop-map.md +202 -0
  57. package/doc/original-2016-doc.md +148 -0
  58. package/doc/usage.md +501 -0
  59. package/lib/authoring/abstract.js +401 -0
  60. package/lib/authoring/abstraction.js +190 -0
  61. package/lib/authoring/adapt.js +393 -0
  62. package/lib/authoring/ancestry.js +216 -0
  63. package/lib/authoring/author.js +147 -0
  64. package/lib/authoring/bounded-merge.js +57 -0
  65. package/lib/authoring/canon.js +158 -0
  66. package/lib/authoring/clock.js +123 -0
  67. package/lib/authoring/combinator.js +92 -0
  68. package/lib/authoring/compete.js +76 -0
  69. package/lib/authoring/compose-hotspot.js +314 -0
  70. package/lib/authoring/compress.js +279 -0
  71. package/lib/authoring/concept-net.js +254 -0
  72. package/lib/authoring/concepts.js +66 -0
  73. package/lib/authoring/context-project.js +277 -0
  74. package/lib/authoring/contract.js +476 -0
  75. package/lib/authoring/corpus-pack.js +125 -0
  76. package/lib/authoring/cost-probe.js +86 -0
  77. package/lib/authoring/crystallize.js +555 -0
  78. package/lib/authoring/dag-decompose.js +256 -0
  79. package/lib/authoring/dataset-adapter.js +253 -0
  80. package/lib/authoring/debug-provider.js +92 -0
  81. package/lib/authoring/decompose.js +343 -0
  82. package/lib/authoring/emittability.js +236 -0
  83. package/lib/authoring/equilibrium.js +160 -0
  84. package/lib/authoring/extract.js +153 -0
  85. package/lib/authoring/forest.js +55 -0
  86. package/lib/authoring/forge-fallback.js +116 -0
  87. package/lib/authoring/givens.js +121 -0
  88. package/lib/authoring/glossary.js +177 -0
  89. package/lib/authoring/grammar-graph.js +191 -0
  90. package/lib/authoring/granularity.js +80 -0
  91. package/lib/authoring/graph-net.js +108 -0
  92. package/lib/authoring/ground.js +148 -0
  93. package/lib/authoring/higher-order.js +85 -0
  94. package/lib/authoring/hotspot.js +132 -0
  95. package/lib/authoring/hysteresis.js +85 -0
  96. package/lib/authoring/index.js +106 -0
  97. package/lib/authoring/lattice-morphism.js +61 -0
  98. package/lib/authoring/lattice-pack.js +148 -0
  99. package/lib/authoring/leaf-io.js +77 -0
  100. package/lib/authoring/library.js +156 -0
  101. package/lib/authoring/lifecycle.js +92 -0
  102. package/lib/authoring/loop.js +193 -0
  103. package/lib/authoring/master-loop.js +132 -0
  104. package/lib/authoring/mdl.js +209 -0
  105. package/lib/authoring/memo-stability.js +122 -0
  106. package/lib/authoring/method-explorer.js +138 -0
  107. package/lib/authoring/method-pack.js +181 -0
  108. package/lib/authoring/method.js +202 -0
  109. package/lib/authoring/mine.js +510 -0
  110. package/lib/authoring/mount.js +119 -0
  111. package/lib/authoring/negotiate.js +100 -0
  112. package/lib/authoring/parametric.js +139 -0
  113. package/lib/authoring/reaggregate.js +75 -0
  114. package/lib/authoring/rebalance.js +157 -0
  115. package/lib/authoring/recall.js +135 -0
  116. package/lib/authoring/registry.js +350 -0
  117. package/lib/authoring/relearn.js +147 -0
  118. package/lib/authoring/retention.js +89 -0
  119. package/lib/authoring/segment-proxy.js +114 -0
  120. package/lib/authoring/serve-leaf.js +64 -0
  121. package/lib/authoring/slot-aware-serve.js +77 -0
  122. package/lib/authoring/sound-invoke.js +59 -0
  123. package/lib/authoring/split-serve.js +74 -0
  124. package/lib/authoring/ste.js +239 -0
  125. package/lib/authoring/stock.js +101 -0
  126. package/lib/authoring/store.js +94 -0
  127. package/lib/authoring/supervise.js +138 -0
  128. package/lib/authoring/support.js +131 -0
  129. package/lib/authoring/task-mirror.js +51 -0
  130. package/lib/authoring/typed-loop.js +160 -0
  131. package/lib/authoring/validate.js +569 -0
  132. package/lib/authoring/widen.js +88 -0
  133. package/lib/combos/appliance.js +189 -0
  134. package/lib/combos/critique.js +377 -0
  135. package/lib/combos/defaults.js +86 -0
  136. package/lib/combos/durable-runner.js +115 -0
  137. package/lib/combos/forge.js +209 -0
  138. package/lib/combos/index.js +86 -0
  139. package/lib/combos/learning-library.js +253 -0
  140. package/lib/combos/mixture-serve.js +193 -0
  141. package/lib/combos/plan-loop.js +192 -0
  142. package/lib/combos/proxy-cache.js +272 -0
  143. package/lib/combos/self-mod.js +94 -0
  144. package/lib/durable/audit.js +102 -0
  145. package/lib/durable/checkpoint-store.js +550 -0
  146. package/lib/durable/fold.js +62 -0
  147. package/lib/durable/interpreter.js +242 -0
  148. package/lib/durable/xlate.js +185 -0
  149. package/lib/graph/Graph.js +2486 -0
  150. package/lib/graph/expr.js +307 -0
  151. package/lib/graph/index.js +26 -0
  152. package/lib/graph/log.js +134 -0
  153. package/lib/graph/objects/Concept.js +382 -0
  154. package/lib/graph/objects/Entity.js +503 -0
  155. package/lib/graph/objects/Node.js +57 -0
  156. package/lib/graph/objects/PathMap.js +557 -0
  157. package/lib/graph/objects/Segment.js +100 -0
  158. package/lib/graph/tasks/stabilize.js +59 -0
  159. package/lib/graph/tasks/taskflow.js +247 -0
  160. package/lib/index.js +122 -0
  161. package/lib/load.js +132 -0
  162. package/lib/providers/backends.js +117 -0
  163. package/lib/providers/borderline.js +129 -0
  164. package/lib/providers/cache.js +226 -0
  165. package/lib/providers/canonicalize.js +198 -0
  166. package/lib/providers/constat.js +107 -0
  167. package/lib/providers/geo.js +53 -0
  168. package/lib/providers/index.js +116 -0
  169. package/lib/providers/intake.js +263 -0
  170. package/lib/providers/llm-local.js +88 -0
  171. package/lib/providers/llm.js +236 -0
  172. package/lib/providers/local-host.js +197 -0
  173. package/lib/providers/merge-consistency.js +162 -0
  174. package/lib/providers/nogood.js +149 -0
  175. package/lib/providers/reason-loop.js +223 -0
  176. package/lib/providers/semiring.js +285 -0
  177. package/lib/providers/solver-fork.js +163 -0
  178. package/lib/providers/stats.js +228 -0
  179. package/lib/providers/verify.js +120 -0
  180. package/lib/runtime/index.js +135 -0
  181. package/lib/runtime/invoke-pool.js +92 -0
  182. package/lib/runtime/protocol.js +210 -0
  183. package/lib/runtime/transport-socket.js +77 -0
  184. package/lib/runtime/worker-entry.js +31 -0
  185. package/lib/sg/cli.js +601 -0
  186. package/lib/sg/log-sinks.js +214 -0
  187. package/lib/sg/mcp.js +395 -0
  188. package/lib/sg/proxy-run.js +66 -0
  189. package/lib/sg/serve-demo.js +185 -0
  190. package/lib/sg/serve.js +205 -0
  191. package/lib/sg/trace.js +118 -0
  192. package/lib/studio/protocol.js +45 -0
  193. package/lib/studio/public/app.js +310 -0
  194. package/lib/studio/public/components/ConceptEditor.js +43 -0
  195. package/lib/studio/public/components/ConceptTree.js +43 -0
  196. package/lib/studio/public/components/CorpusPanel.js +68 -0
  197. package/lib/studio/public/components/ForkTree.js +36 -0
  198. package/lib/studio/public/components/GrammarGraph.js +99 -0
  199. package/lib/studio/public/components/GraphCanvas.js +156 -0
  200. package/lib/studio/public/components/Inspector.js +48 -0
  201. package/lib/studio/public/components/LearningPanel.js +85 -0
  202. package/lib/studio/public/components/PromptConsole.js +46 -0
  203. package/lib/studio/public/components/ProviderTrace.js +39 -0
  204. package/lib/studio/public/components/SessionSplit.js +47 -0
  205. package/lib/studio/public/components/TilingOverlay.js +40 -0
  206. package/lib/studio/public/components/Timeline.js +57 -0
  207. package/lib/studio/public/index.html +26 -0
  208. package/lib/studio/public/styles.css +185 -0
  209. package/lib/studio/public/ws.js +58 -0
  210. package/lib/studio/server.js +130 -0
  211. package/lib/studio/session.js +397 -0
  212. package/lib/studio/studio.js +149 -0
  213. package/package.json +42 -0
package/doc/API.md ADDED
@@ -0,0 +1,556 @@
1
+ # Skynet-Graph — Public API
2
+
3
+ The stable surface of the engine as of the V1 "Neurosymbolic Reasoning Graph" (formerly "MOE Graph") Phase-0 work. Skynet-graph is a
4
+ **library**: a host app embeds the engine, supplies *concept definitions* (the experts) and
5
+ *provider functions* (the effectful work), and drives the graph through mutations.
6
+
7
+ Mental model: data objects (nodes, segments) carry **typed facts**. **Concepts** are declarative
8
+ rules that become applicable when their `require`/`assert` conditions hold against the facts; when
9
+ they cast, they add more facts and/or new child segments, which makes other concepts applicable —
10
+ a forward-chaining cascade that runs to a fixpoint (**stabilization**). Mutations destabilize;
11
+ stabilization re-casts/uncasts; repeat until nothing more fires. See `CLAUDE.md` and `doc/original-2016-doc.md`
12
+ for the concept-schema and the embedded reference/template DSLs.
13
+
14
+ ---
15
+
16
+ ## Construction
17
+
18
+ ```js
19
+ const Graph = require('skynet-graph'); // native CommonJS — no build step
20
+ const graph = new Graph(record, conf, conceptMap);
21
+ ```
22
+
23
+ - **`record`** — the initial graph. Either a serialized snapshot `{ graph: "<json string>", lastRev }`
24
+ or a plain `{ lastRev, nodes:[…], segments:[…], freeNodes:[…] }` (or `{ conceptMaps:[…] }`, each
25
+ tagged with `Node:true` / `Segment:true`). Nodes/segments are `{ _id, … }`; segments also need
26
+ `originNode` / `targetNode`. Arbitrary typed facts (e.g. `Position`, `Distance`) live right on the
27
+ object record.
28
+ - **`conf`** — overrides merged onto `Graph.prototype.cfg`. Common fields:
29
+ | field | meaning |
30
+ |---|---|
31
+ | `label` | name for logs |
32
+ | `autoMount` | start stabilizing immediately (default `true`) |
33
+ | `isMaster` | master vs client (client forwards mutations via `pushToMaster`) |
34
+ | `conceptSets` | which keys of `conceptMap` to `deepmerge` into the active tree (default `["common"]`) |
35
+ | `onStabilize(graph, tokens)` | called once the graph settles (the main hook) |
36
+ | `bagRefManagers` | external-data ref managers (default `caipi` matches `/^db:(.+)$/`) |
37
+ - **`conceptMap`** — host-supplied, keyed by concept-set name (`{ common: <tree> }`). The tree is a
38
+ nested `childConcepts` hierarchy. `lib/authoring/concepts.js#buildConceptTree(dir)` assembles one
39
+ from the `concepts/<set>/` directory.
40
+
41
+ ---
42
+
43
+ ## Lifecycle & stabilization
44
+
45
+ ```js
46
+ new Graph(seed, {
47
+ autoMount: true, isMaster: true, conceptSets: ['common'], bagRefManagers: {},
48
+ onStabilize(g) { /* graph has reached a fixpoint */ }
49
+ }, { common: tree });
50
+ ```
51
+
52
+ - **`onStabilize`** fires every time the graph reaches a coherent fixpoint — after the initial mount
53
+ and after every mutation/rollback/patch settles. It is the primary place to read results and to
54
+ drive the next step.
55
+ - **`graph.stabilize(cb?)`** — ensure the task loop is running; `cb` fires once on the next settle.
56
+ - A snapshot is captured on every settle (see History).
57
+
58
+ ---
59
+
60
+ ## Mutations
61
+
62
+ ```js
63
+ graph.pushMutation(template, targetId, force?, atomId?, initialRefBag?, cb?);
64
+ ```
65
+
66
+ Apply a mutation template that creates/updates objects and marks them unstable. The template uses the
67
+ `$`-prefixed reference/template DSL (`$_id`, `$$_id`, `$key` ref, `$$key` bagRef, `_incoming`/`_outgoing`
68
+ nesting). Example (grow the graph):
69
+
70
+ ```js
71
+ graph.pushMutation([
72
+ { _id: 'tokyo', Node: true, Position: { lat: 35.6762, lng: 139.6503 } },
73
+ { _id: 'long2', Segment: true, originNode: 'paris', targetNode: 'tokyo' }
74
+ ]);
75
+ ```
76
+
77
+ Providers emit the same templates from their callback (see Providers). After mutating outside an
78
+ `onStabilize` turn, call `graph.stabilize()` if the loop isn't already running.
79
+
80
+ ---
81
+
82
+ ## State & history
83
+
84
+ | method | returns |
85
+ |---|---|
86
+ | `graph.serialize()` | `{ lastRev, graph: "<json>" }` snapshot of the whole graph |
87
+ | `graph.getCurrentRevision()` | current revision number |
88
+ | `graph.getRevisions()` | ascending list of revisions with captured snapshots |
89
+ | `graph.getSnapshot(rev)` / `graph.diffRevisions(a, b)` | snapshot at `rev` / added·removed·changed between revs |
90
+ | `graph.rollbackTo(rev)` | re-mount that snapshot, drop later ones (linear undo), re-stabilize — restores **rules too** |
91
+ | `graph.exportConcepts()` | the LIVE concept tree as a serializable record (reflects `addConcept`/`patchConcept`) — feed to `corpus-pack` / `exportConceptsToDir` |
92
+
93
+ ```js
94
+ const revA = g.getCurrentRevision();
95
+ g.pushMutation(/* … grow … */); // -> settles at revB
96
+ // later:
97
+ g.rollbackTo(revA); // the growth is undone, onStabilize re-fires at revA
98
+ ```
99
+
100
+ Snapshots are full `serialize()` copies captured on each settle (delta-replay is a later optimization).
101
+
102
+ ---
103
+
104
+ ## Sub-agents — fork / merge
105
+
106
+ Spin up an independent child graph (a sandboxed sub-agent, optionally with a *different* concept set =
107
+ different capabilities), let it work a sub-problem, and reintegrate its result.
108
+
109
+ ```js
110
+ const child = graph.fork(subSeed, {
111
+ label: 'child', conceptSets: ['worker'], // child-only capabilities
112
+ reintegrateInto: 'root', // target object in the PARENT
113
+ project: (child) => ({ $$_id: 'root', mergedWork: child._objById['sub']._etty._.work })
114
+ });
115
+ ```
116
+
117
+ - **`fork(seed?, conf?)`** → a new child `Graph`. `seed` omitted ⇒ forks this graph's current snapshot.
118
+ Reuses this graph's concept library unless `conf.conceptMap` overrides it. With `conf.reintegrateInto`
119
+ (+ optional `conf.project`), the child auto-`merge`s back on its own stabilize.
120
+ - **`merge(child, targetId, project?)`** — apply `project(child)` (a mutation template) onto `targetId`
121
+ in this graph, then `child.destroy()`. Default `project` attaches the child's serialized graph.
122
+
123
+ ---
124
+
125
+ ## Hot-patching experts — patchConcept
126
+
127
+ Change a live concept (an "expert") and re-evaluate the whole graph against it, both directions, with
128
+ no restart or rebuild.
129
+
130
+ ```js
131
+ graph.patchConcept('Far', { assert: ['$Distance.inKm > 500'] });
132
+ ```
133
+
134
+ - **`patchConcept(nameOrId, updates, cb?)`** — deep-merges `updates` into the concept's schema
135
+ (**arrays REPLACE, not concatenate** — so `{assert:[…]}` overrides), recompiles its applicability
136
+ test, then for every live object: newly-applicable + not cast ⇒ cast; cast + no-longer-applicable ⇒
137
+ **uncast (cascading to child concepts)**; then re-stabilizes.
138
+ - **`getConceptByName(nameOrId)`** — resolve a concept by its library id, else by `_name`.
139
+
140
+ Tightening an assert retracts the concept where it no longer holds *and its dependent children*;
141
+ loosening one casts it onto newly-qualifying objects.
142
+
143
+ ---
144
+
145
+ ## Manual concept control & accessors
146
+
147
+ | method | meaning |
148
+ |---|---|
149
+ | `graph.castConcept(objId, conceptId, cb?)` | force-cast a concept onto an object, re-stabilize |
150
+ | `graph.unCastConcept(objId, conceptId, cb?)` | force-uncast, re-stabilize |
151
+ | `graph.getConcept(id)` | concept instance by library id |
152
+ | `graph.getRef(exp, scope?, follow?, unref?)` | resolve a reference path (`a:b:c`, `$key`) from a scope |
153
+ | `graph.getEtty(id)` | the Entity wrapper for an object id |
154
+ | `graph.getPaths(fromId, toId, skip?)` / `graph.getOpenPathOf(id)` | path discovery / a `PathMap` |
155
+ | `graph.on(evt, cb)` / `graph.un(evt, cb)` | subscribe/unsubscribe (`"stabilize"`, `"destroy"`) |
156
+ | `graph.destroy()` | tear down the graph and its objects |
157
+
158
+ Object internals: `graph._objById[id]._etty` is the Entity; `…._etty._` is the raw typed facts record
159
+ (e.g. `graph._objById['long']._etty._.Distance.inKm`).
160
+
161
+ ---
162
+
163
+ ## Providers
164
+
165
+ A provider does the effectful work a concept needs (geo math, an API/DB call, an LLM call) and emits
166
+ facts. The engine looks them up in `Graph._providers` (host-wired — the engine does **not** auto-load
167
+ them). A concept references one as `"Namespace::fn"` (optionally `["ns::fn", …args]`).
168
+
169
+ **Contract:**
170
+
171
+ ```js
172
+ Graph._providers = {
173
+ Namespace: {
174
+ fn(graph, concept, scope, argz, cb) {
175
+ // …compute…
176
+ cb(err, mutationTemplate); // template applied onto the scope object (`$_id:'_parent'`)
177
+ }
178
+ }
179
+ };
180
+ ```
181
+
182
+ `scope` is the object's Entity; read its context with `graph.getRef('originNode:Position', scope)`.
183
+ Return `cb(null, null)` to no-op (e.g. wait for a dependency).
184
+
185
+ ### Packaged base providers (host opt-in)
186
+
187
+ ```js
188
+ const Graph = require('skynet-graph');
189
+ const { register, CommonGeo, createLLMProvider } = require('skynet-graph/lib/providers');
190
+
191
+ register(Graph, [ { CommonGeo }, createLLMProvider({ ask: myBackend }) ]);
192
+ // register(Graph) with no selection wires the defaults (Geo + a default LLM client)
193
+ ```
194
+
195
+ - **`CommonGeo`** → `CommonGeo::Distance` — haversine great-circle distance between two node
196
+ `Position`s, emitting `{ Distance: { inKm } }`. Drives `concepts/common/Edge/Distance.json`.
197
+ (`haversineKm(a, b)` is also exported as a pure function.)
198
+ - **`createLLMProvider({ ask, parseJSON?, namespace? })`** → `{ LLM: { complete } }`, a generic
199
+ concept↔prompt runner. The concept supplies a `prompt` block:
200
+
201
+ ```json
202
+ { "provider": ["LLM::complete"],
203
+ "prompt": { "system": "You judge.", "user": "Step: ${label}",
204
+ "maxTokens": 500, "json": true, "as": "Classification" } }
205
+ ```
206
+
207
+ `system`/`user` interpolate `${ref}` tokens (resolved against the scope via `graph.getRef`);
208
+ `json:true` salvages the reply (robust to "thinking" preambles — returns the last balanced JSON);
209
+ the result is written back as facts (merged if a plain object, or stored under `as`). Backend errors
210
+ are captured as an `llmError` fact so the graph still settles. The backend is **pluggable** — pass
211
+ any `ask({system,user,maxTokens})`; the bundled `makeAsk(opts)` **dispatches on `opts.api` / env
212
+ `LLM_API`** — `anthropic` (default, `/v1/messages`) or `openai` (`/v1/chat/completions`, for
213
+ vLLM / llama.cpp / LM-Studio; reads `choices[0].message.content` and falls back to
214
+ `reasoning_content` for reasoning models). `makeOpenAIAsk` / `makeAnthropicAsk` are exported too.
215
+ All configurable via `LLM_BASE` / `LLM_MODEL` / `LLM_KEY` or `{ base, model, key }`.
216
+
217
+ #### Canonicalization barrier — the `facts` / `prose` contract
218
+
219
+ An `LLM::complete` expert whose output **feeds downstream experts** must not let raw prose onto a
220
+ dependency edge: two semantically-equal replies differ textually, so a `require`/`ensure` keyed on prose
221
+ re-keys every run and the memo never hits (risk **K1**, `doc/MODELISATION.md` §4.2). Declare a `facts`
222
+ schema and the provider writes **only** those discrete, canonicalized keys as *tracked* facts; the free
223
+ text lands on an *untracked* prose key; a stable `<name>FactsDigest` is emitted as an explicit memo key.
224
+
225
+ ```json
226
+ { "provider": ["LLM::complete"],
227
+ "prompt": {
228
+ "system": "Classify the risk.", "user": "Step: ${label}",
229
+ "facts": { // the tracked, discrete spine
230
+ "severity": { "enum": ["low","medium","high"] }, // snapped to a closed vocabulary
231
+ "priceK": { "grain": 100, "from": "price" }, // numeric rounded to a grain; read raw `price`
232
+ "count": { "type": "int" } // int | number | bool | id | string
233
+ },
234
+ "prose": "summary" // free text -> UNTRACKED key (default: `<name>Prose`)
235
+ } }
236
+ ```
237
+
238
+ Snapping is **deterministic only** (never embedding similarity — a fuzzy false-hit graves a wrong fact
239
+ that *propagates*). An out-of-vocabulary enum **fails closed**: the fact is `null` and the key is listed
240
+ in `<name>CanonMiss` (visible, never a silent wrong snap). Helpers are exported for direct use:
241
+ `canonFacts(raw, schema) -> { facts, misses }`, `canonValue(raw, spec)`, `digest(facts)`.
242
+ With no `facts` schema declared, the provider keeps its legacy merge/`as` behavior (back-compatible).
243
+ - **`register(Graph, fragments?)`** merges provider-map fragments onto `Graph._providers`, preserving
244
+ any already set.
245
+
246
+ ### Verification — verdict facts + `ensure` defeasance
247
+
248
+ The engine maintains **coherence**, never **truth** (K3): a hallucinated-but-valid fact propagates and
249
+ retracts cleanly. Verification makes unreliability *visible and non-propagating* by emitting discrete
250
+ **verdict facts** that downstream concepts gate on via `ensure` — a refuted fact auto-retracts its
251
+ dependents (refutation *is* defeasance; no new engine path). Verdicts are discrete (the typed-fact spine),
252
+ never prose, and **never overwrite** the checked fact. `createVerifier()` (from `skynet-graph/lib/providers`)
253
+ returns `{ Verify: { check }, Vote: { tally } }`; `checks` and `majority` are exported too.
254
+
255
+ Three patterns (all engine-verified; pick by reactivity need):
256
+
257
+ 1. **Deterministic verifier = a concept whose `ensure` IS the invariant** — full `expr.js` grammar (uncapped):
258
+ `{ "_name":"BudgetOK", "require":["cost","cap"], "ensure":["$cost <= $cap"] }`. Its self-flag is the verdict;
259
+ a target change re-tests it and auto-retracts. A consumer **nested** under it (`childConcepts`) cascade-
260
+ retracts on refutation. Prefer this — deterministic checkers ≫ LLM-refuters, and it is reactive.
261
+ 2. **Independent verdict provider** `Verify::check` — for a check that runs as an effect (external lookup /
262
+ LLM-refuter): `{ "provider":["Verify::check"], "verify":{ "target":"$x", "check":"range", "params":{"min":0,"max":100}, "as":"x" } }`
263
+ writes `xVerdict`/`xVerified`/`xVerifiedAgainst` (provenance), never `$x`. Downstream gates `ensure:["$xVerified == true"]`.
264
+ (A provider verifier is **cast-once** — it re-runs only on uncast/recast; use pattern 1 for reactive re-checking.)
265
+ 3. **k-of-n voting** `Vote::tally` — self-consistency: n strategies `{__push}` a vote into a grow-only array;
266
+ a `Vote` concept gated `ensure:["$votes.length == $expected"]` emits `consensus` + `confidence = agree/n`;
267
+ downstream gates `ensure:["$confidence >= 0.7"]`. Treat `confidence` as a heuristic, never proof (a biased
268
+ model votes confidently wrong). Independence discipline: the refuter must not be the call that produced the fact.
269
+
270
+ Deterministic checkers: `range`, `oneOf`, `equals`, `approx`, `nonEmpty` — `(value, params) -> { pass, reason }`,
271
+ extend via `createVerifier({ checks: { … } })`.
272
+
273
+ ### Freshness / TTL — time as a fact
274
+
275
+ The engine has no internal wall-clock (replay stays hermetic). Time enters as an ordinary fact on a
276
+ `clock` free-node; a time-bound concept gates freshness in an `ensure`. Advancing the clock re-tests
277
+ exactly the concepts that follow it, so a fact that has gone **stale auto-retracts** (and its dependents
278
+ cascade) — the cache-poisoning fix (an LLM/API fact otherwise lives forever). The whole pattern is plain
279
+ seed + mutation, no dedicated API:
280
+
281
+ ```js
282
+ // seed: { freeNodes: [ { _id:'clock', tick:0 } ], nodes: [ { _id:'n', source:'db', sensedAt:0 } ] }
283
+ // concept: { "require":["source"], "ensure":["$$clock:tick - $sensedAt < 2"], "provider":["AI::sense"] }
284
+ // ($$clock — DOUBLE-$, a GLOBAL free-node ref; a single $clock is a key on the current scope)
285
+ g.pushMutation('clock', { $$_id: 'clock', tick: 3 }); // tick 0 -> 3: the fact is now stale -> retracts + cascades
286
+ ```
287
+
288
+ - **Invalidation is automatic and reliable.** **Refetch is host-triggered** — a provider is cast-once, so a
289
+ stale provider-fact re-derives only on uncast→recast (retract the cast flag, restabilize). A provider
290
+ stamps its fetch time from the clock node.
291
+ - Pitfall: an `ensure` with `||` (`"$x==null || $$clock:tick-$x<t"`) **short-circuits watcher registration** —
292
+ seed the stamp so the freshness operand always evaluates, or split the fetch from the freshness gate.
293
+
294
+ ---
295
+
296
+ ## Author-time concept validation
297
+
298
+ A host-side validator enforces the typed-fact discipline **before** a concept tree reaches the engine —
299
+ the safety gate for hand- or AI-authored concepts. It validates *structure*, never the expression grammar.
300
+
301
+ ```js
302
+ const { validateConceptTree, validateOrThrow } = require('skynet-graph/lib/authoring/validate');
303
+ const { errors, warnings } = validateConceptTree(tree, { palette: ['LLM::complete', 'CommonGeo::Distance'] });
304
+ ```
305
+
306
+ Checks: every concept has a `_name` (the self-flag — without it the engine re-fires it forever);
307
+ `assert`/`ensure` parse under the real evaluator (`lib/graph/expr.js`) and don't touch `constructor`/`__proto__`;
308
+ `provider` ∈ a vetted `palette` (advisory warning, or an error under `{ strict: true }`); and the valuable
309
+ one — **ref soundness**: a `require`/`ensure`/`assert` that keys on a **prose** fact (a declared `prose`
310
+ key, or the `<name>Prose`/`<name>CanonMiss` defaults) is **rejected** (it would fragment the memo, K1),
311
+ and a bare dependency on a child-set (`expandedInto`/`answeredBy`/…) without `.length` is **warned** (the
312
+ "all-children-answered" footgun — `getRef` has no quantifier). `validateOrThrow` throws on the first error.
313
+
314
+ ### Mixture-of-Reasoners regime providers (host opt-in, additive)
315
+
316
+ All are `require('skynet-graph/lib/providers')` factories; pair each with its ready-made concept-tree
317
+ fragment. The deterministic core is untouched.
318
+
319
+ | factory | wires | concept-tree helper |
320
+ |---|---|---|
321
+ | `createSemiring()` | `Semiring::reduce` — fold `{__push}`ed contributions under `boolean`/`logodds`/`maxplus`/`probor` | `semiringConceptTree({ semiring, contribKey, bands? })` |
322
+ | `createSemiring()` (pareto family) | multi-criteria **skyline SELECT** → `selectedId`/`frontIds`/`frontSize` | `selectConceptTree({ criteria, lex })` — the Candidate→Selected cluster |
323
+ | `createStats()` | `Stats::{report,grandMean,shrink}` — hierarchical Beta-Binomial shrinkage | `shrinkageConceptTree(...)` |
324
+ | `createNogood()` | `Nogood::guard` — learned sound-skip of dead-ends | `nogoodGuardConcept()` / `guardTrial(schema)` |
325
+ | `createVerifier()` | `Verify::check` (verdict facts) + `Vote::tally` (k-of-n) | — |
326
+ | `createConsistency()` | `Merge::combine` — sheaf-style agree/borderline/conflict bands | `consistencyConceptTree()` |
327
+ | `createSolver({ solve })` | `Solve::run` — a C-regime **fork** that searches; crosses only the snapped model | `solverConceptTree()` |
328
+ | `createConstat()` | `Constat::record` — typed lesson-on-retraction `{claim,retractedBecause,certaintyBand,atRev}` | — |
329
+
330
+ Pure helpers: `paretoFront` / `paretoSelect` / `makePareto` / `dominates` / `reduceSemiring`
331
+ (`lib/providers/semiring.js`).
332
+
333
+ ### Tiling, grammar graph & corpus exchange (`lib/authoring/`)
334
+
335
+ - **`treeDecomposition(tree)` / `forkPlan(tree)`** (`decompose.js`) — derive, off the concept-dependency
336
+ graph, the separator interface + the candidate forks + each fork's **frontier alphabet** + the treewidth
337
+ cost bound (`partitionPays`). Feeds `fork`/`merge` and `validateMergeProjection`.
338
+ - **`conceptFactGraph(conceptMap)`** (`grammar-graph.js`) — the concept↔fact flux graph: produced /
339
+ consumed facts **with polarity**, cross-corpus links, writer-collisions, entry points, tiling overlay.
340
+ - **`.sgc` corpus exchange** (`corpus-pack.js`): `deriveManifest` (produces/consumes alphabet, required
341
+ providers), `packCorpus` / `unpackCorpus` (a portable bundle of the *authored grammar*). Disk round-trip:
342
+ `Graph.loadConceptMap(dir, { validate })` ↔ `exportConceptsToDir(tree, dir)` (`lib/load.js`).
343
+
344
+ ### The support grammar (`lib/authoring/support.js`)
345
+
346
+ `supportConceptTree({ criteria, lex })` + `makeSupportProviders({ evalFn, expandFn, proposeFn,
347
+ escalateFn, escalateBar, rollupFn })` compose the decompose loop with the per-segment
348
+ **Propose → Pareto-SELECT → Adopt** alternative-search trio + escalation on `Stuck`. Inject the content
349
+ functions (deterministic in tests, an LLM in production).
350
+
351
+ ### Concept-as-graph: the method toolkit (`lib/authoring/`)
352
+
353
+ The LLM-driven **Use 2** mechanism — forge / crystallize / reuse typed methods on top of the substrate.
354
+ Host-side, ZERO-CORE, additive (the base hand-authoring use needs none of it). This is the mechanism the
355
+ two preprints measure; their replay artifacts ship under `artifact/`.
356
+
357
+ - **`abstract.js`** — F6 abstractivation: `relativize`/`instantiate` (id/frontier holes), `antiUnify` (Plotkin
358
+ LGG), `methodTransform` (the cache `{onStore,onReplay}`), `emitMethodAsSubgraph` (re-mountable parameterized
359
+ method via `Graph#getMutationFromPath`). The cross-problem structural-transfer keystone.
360
+ - **`crystallize.js`** (+ `mine.js`, `abstraction.js`, `memo-stability.js`) — `crystallize`/`adopt`/`consolidate`:
361
+ mine a producer→consumer chain → compose → MDL/utility gate (`abstraction.evaluate` scores model calls) →
362
+ install fail-closed (memo-stability). `crystallizeStructural` distils a recurrent **structural** cast into a
363
+ re-mountable defeasible method with a **declared** frontier (`mine.js#declaredCtx`) reified as
364
+ `schema.frontier` (a `FrontierSignature` — `{params:[{name,sort,field,role}], summaryFacts, appConditions}`,
365
+ serializes with the tree) + a `libraryKey` (the O(1) dispatch index) + `lintFrontier`; a soundness gate refuses a
366
+ method that would leak a learning id at replay (un-holed / base-prefix-phantom / collapsed endpoints).
367
+ - **`library.js` · `adapt.js`** — the method-library index + the adapt-or-forge controller. `library.js`:
368
+ `makeLibrary`/`indexMethod`/`dispatch(lib, target, scopeFacts)` — an **O(1) bucket lookup on `libraryKey`** →
369
+ refine by application-conditions → ranked candidates (a lookup, never a corpus search). `adapt.js`:
370
+ `adaptOrForge({lib,target,scopeFacts,forge,verify})` — retrieve(hit, 0 calls) / forge-or-adapt(reuse
371
+ neighbours) / verifier-gate(contract) / index-back(amortise). Structure-mapping (Gentner) over learned methods.
372
+ - **`method.js`** — the concept-as-graph host toolkit (the middle spine): `applySubgraphArg` / `mapTemplate` /
373
+ `mapSubgraph` (a method receives + applies a sub-graph param; `Map` fans a body per element with fresh ids),
374
+ `lintMethod(def)` (the decidability invariants + the footprint/frame check), `selectCluster` (case-parameterized
375
+ selection by mutually-exclusive typed gates).
376
+
377
+ ### C-contract — composition soundness & the un-learn loop (`lib/authoring/contract.js`)
378
+
379
+ The defeasible separation-triple checker (Use 2's soundness). Exposed on the facade as deep-path; ZERO-CORE.
380
+
381
+ - **`checkCompose(m1, m2, opts)`** → `{ verdict:'sound'|'unsound'|'escalate', shared, perKey, reasons, needsOracle }`
382
+ — `post(m1) ⊨ pre(m2)` over `write(m1)∩read(m2)`, by per-key **abstract-domain** entailment (`normalize` /
383
+ `entailsKey`); never false-accepts (out-of-fragment / under-determined → `escalate`). `opts.oracle` for an
384
+ effecting m1 (G2).
385
+ - **`assertPost(contract, factsAfter, touchedKeys, opts)`** → `{ ok, violations, blame }` — the runtime monitor:
386
+ G1 frame-completeness (touched ⊆ declared write), the post must hold, G2 the effect-tag oracle.
387
+ - **`footprintCycles(methods)`** → cycles of retractable methods (Tarjan-SCC; G3, reject before they oscillate).
388
+ - **`reviseOnBlame(contract, {key,value})`** → a NEW contract with the pre specialized (CEGIS — un-learn, not
389
+ removal). **`satisfies(atoms, facts)`** — is a method applicable to a case (selection after a revision).
390
+ **`acceptRate(verdicts)`** — the measured typed-coverage fraction.
391
+
392
+ ### Durable executor — run methods as workflow-nets (`lib/durable/`)
393
+
394
+ The "execute" half of the build/execute separation: a thin durable substrate that runs case records through a
395
+ compiled method-net (the belief / durable boundary). ZERO-CORE; on the facade as `Graph.durable` +
396
+ `Graph.createCheckpointStore({file})`.
397
+
398
+ - **`checkpoint-store.js`** — `createMemoryCheckpointStore()` / `createSqliteCheckpointStore({file})` (one
399
+ contract). The durable **marking** (`ensureRun`/`inject`/`claim`/`move`/`fail`/`joinArrive`/`failGroup`/
400
+ `track`/`rollbackInflight`/`marking`/`stats`), the content-addressed **memo** (`memoGet`/`memoSet`), the
401
+ createdRefs rollback. Crash-safety = lease-expiry + `rollbackInflight` + a **fencing token** (a monotonic
402
+ persisted leaseId; a re-claimed lease fences out a zombie worker).
403
+ - **`xlate.js`** — `compileMethod(spec) → net` (a select+task+map+reduce spec → a workflow-net), `validateNet`
404
+ (structural lint), `indexByFrom`.
405
+ - **`interpreter.js`** — `runFlow(store, runId, net, { runTask, keyOf?, foldKeyOf?, oracle?, assertStep?, lease?,
406
+ batch?, maxSteps? })` → measured counters. Drains records: typed `select`, content-memoized `task`, `map`
407
+ fan-out, the fold-back `join`/`fold`. A per-step `contract` is asserted before commit (a violation quarantines
408
+ + blames). Async (real LLM micro-tasks). Resumable (call again after `rollbackInflight`).
409
+ - **`fold.js`** — `foldSiblings(siblings, reduce)` (the JOIN's monoid algebra; commutative = order-independent),
410
+ `monoids()`, `isCommutative(name)`.
411
+ - **`audit.js`** — `auditRun(store, runId)` → `{ records: { <id>: {status, terminal, result, blame, lineage…} },
412
+ totals }` (the derivation forest + verdict + blame), `auditSummary(audit)` (one line per record).
413
+
414
+ ### Learned concepts — population training, plasticity & serving (`lib/authoring/`)
415
+
416
+ Train concepts as neural-net populations instead of hand-authoring them, then bake the frozen result
417
+ back into the engine. Host-side, ZERO-CORE, shelved (kept for the curious).
418
+
419
+ - **`equilibrium.js`** — gradient through a fixpoint (Deep Equilibrium Models / implicit diff):
420
+ `solveFixpoint(F, z0, {maxIter,tol})` (Picard to `z*`), `implicitGrad(Jz, Jtheta, gradL, {mode})`
421
+ (`mode:'direct'` dense adjoint solve or `{neumann:K}`), `spectralRadius(Jz)` (the `ρ` regime
422
+ instrument), `numJac(fn, point)` (finite-difference Jacobian of one sweep).
423
+ - **`concept-net.js`** — a population of concept-units (gate-NN × update-NN):
424
+ `ringPopulation(K)` / `chainPopulation(K)` / `widePopulation(K)` (builders) →
425
+ `train(pop, {X,T,steps,lr,hard})` (learn at the fixpoint, returns `{theta,loss0,loss,rho}`),
426
+ `grad` / `loss`, `evolve({makePop,X,T,maxK,margin})` (grow the form by success, utility-gated),
427
+ `bakePopulation(pop, theta)` → `{conceptTree, providers}` (serve a frozen population as real engine
428
+ concepts), `unrollPopulation(pop, N)` → `{pop, tieTheta, readout}` (serve a *cyclic* population by
429
+ unrolling its fixpoint to depth N; a direct cyclic bake deadlocks).
430
+ - **`lifecycle.js`** — the plasticity ledger: `createLifecycle()` → `register` / `record(name, ok)` /
431
+ `plasticity(name)` (the unified knob `p∈[0,1]`) / `regime(name)` / `reputation(name)`. Thread
432
+ `plasticity` into `createLLMProvider({ ask, plasticity })` (→ temperature) or `createNet(net,
433
+ { plasticity })` (→ STE exploration noise) so `p=1` explores / `p=0` serves deterministically.
434
+
435
+ ### Combos (`Graph.combos.*`) — thin, delivered assemblies
436
+
437
+ Each combo composes existing bricks with the product posture ON by default (fail-closed, memo/store
438
+ ON, validator ON); none is a required path — the bricks stay usable "à nu". Full guide:
439
+ `doc/usage.md` §9; per-capability maturity + numbers: [CAPABILITIES.md](CAPABILITIES.md).
440
+
441
+ | combo | role |
442
+ |---|---|
443
+ | `createAppliance` (C1) | typed-QA appliance: intake → reason loop → typed refusal → memo |
444
+ | `createDurableRunner` (C2) | durable workflow runner (compile / run / resume / audit) |
445
+ | `createLearningLibrary` (C3) | learning method library: cost ladder + crystallize + blame/credit + `.sgc` |
446
+ | `reactiveKG` (C4) | the engine's original Use-1 preset over `fromDirs` (builtins ON) |
447
+ | `createSelfMod` (C5) | supervised self-modification (opt-in, guarded; `rollbackTo` = the guarantee) |
448
+ | `createProxyCache` (C6) | local-first proxy cache / distiller (covered → local at 0 frontier calls) |
449
+ | `createPlanLoop` (C7) | the hierarchical plan loop (the piece-by-piece zoom); `decompose`/`serveLeaf` injected |
450
+ | `createMixtureServe` (C8) | orientation menu + optional `preRoute` over a low-quant target (the runtime cross-agreement trust tier is REFUTED at scale — fail-closed default) |
451
+ | `createCriticalMind` (C9) | the external critical mind: `run({topic, statements?, viewpoints?})` → typed ledger + verdict or honest UNDECIDED (mechanical only at the measured margin bound) |
452
+
453
+ The C7 bricks are standalone `lib/authoring/` modules: `dag-decompose`, `context-project` (with the
454
+ `stratComplete` stratified rendering), `givens` (`numberGivens` / `cellGivens` / `seedOf` / `labelsOf` —
455
+ `labelsOf` implements the measured CELLS rule: label an input iff its provenance is a structured table
456
+ cell, and pass it as `run(task, { givens, labels })`), `leaf-io`.
457
+
458
+ ### Studio (embeddable web workbench)
459
+
460
+ `const server = Graph.createStudioServer({ Graph, root, ask, logger })` — an http+ws server over a
461
+ registry of live `Graph` sessions, driving a no-build React UI. Wire ops (`lib/studio/protocol.js`):
462
+ `grammarGraph` · `corpusManifest` · `exportCorpus` / `importCorpus` (`.sgc`) · `providerTrace` ·
463
+ `mergePreview` · `forkPlan` · `fork`/`merge`/`selectSession` · `mutate`/`run`/`state` ·
464
+ `revisions`/`snapshot`/`rollback`/`diff` · `validateConcept`/`patchConcept`/`addConcept` · `prompt`.
465
+ Events include a Session-derived `retract` (the red-flash signal). Also via `bin/sg studio`.
466
+
467
+ ## Logging & diagnostics
468
+
469
+ One logger per graph (`graph._log`, exposed as `graph.logger`). Levels, severity descending:
470
+ `error > warn > log > info > verbose` — a record reaches sinks iff `rank(level) <= rank(threshold)`
471
+ (default `warn`, or env `SG_LOG_LEVEL`; the `sg run` CLI overrides its own session to `info`). A `LogRecord` is `{ level, ts, label, ctx, msg, args }` and is
472
+ JSON-serializable (Errors in `args` are reduced to `{name,message,stack}`).
473
+
474
+ **Configure** (in `cfg`, e.g. via `new Graph(seed, conf, conceptMap)` or `Graph.fromDirs({conf})`):
475
+
476
+ | key | meaning |
477
+ |---|---|
478
+ | `cfg.logger` | inject a logger instance (`Graph.createLogger(...)`); overrides the rest |
479
+ | `cfg.logLevel` | threshold name for the auto-built logger |
480
+ | `cfg.onLog(record)` | convenience sink fn |
481
+
482
+ **`graph.logger` interface** (the debugger/host hook):
483
+
484
+ | method | returns |
485
+ |---|---|
486
+ | `addSink(fn)` / `removeSink(fn)` | register/unregister `fn(record)` |
487
+ | `tail(n, filter?)` | last `n` records; `filter` = `{concept?, target?, applyId?, level?}` |
488
+ | `records` | the bounded ring buffer (default 500) |
489
+ | `setLevel(name)` / `level` | get/set the threshold |
490
+ | `child(ctx)` | a logger that merges `ctx` into every record |
491
+
492
+ **Provider logging contract** (no provider-signature change): inside a provider, reach a context logger via
493
+ `scope.log` (ctx `{target,type}`) or `concept.log(scope)` (ctx `{concept,target,type,applyId}`):
494
+
495
+ ```js
496
+ function work ( graph, concept, scope, argz, cb ) {
497
+ const log = concept.log(scope); // capture early (freezes applyId for async)
498
+ log.verbose('prompt', prompt);
499
+ log.warn('input missing on %s', scope._._id);
500
+ cb(null, { $_id: '_parent', Worked: true });
501
+ }
502
+ ```
503
+
504
+ Each concept-apply mints `graph._applyId`, stamped into **both** the contextual logger and the
505
+ `cfg.onConceptApply` trace record. So the logs a concept produced *while applying* are retrievable with
506
+ `graph.logger.tail(n, { concept })` or `{ applyId }`, and join the trace by `applyId` — all **without
507
+ storing anything on the graph objects** (logs live in the logger, never as facts). For full history beyond
508
+ the bounded buffer, attach a sink (file/studio).
509
+
510
+ **CLI:** `sg run … [--log-level <lvl>] [--log-mode dashboard|plain] [--log-plain] [--log-file <path>]
511
+ [--log-file-level <lvl>]`. A styled boot banner prints at startup. `dashboard` = TTY mode where colored
512
+ logs scroll normally under a live **status bar pinned at the bottom** (graph state, unstable node/segment
513
+ counts, main-loop queue size, rev, applies, provider time, elapsed); degrades to plain off a TTY. `plain` =
514
+ line output (logs → stderr, run summary → stdout). `--log-file` writes `.jsonl` (machine-readable) or
515
+ formatted text. **Workers:** pass `logger` to `createGraphWorker`/`spawnGraph` and a dispatched graph's
516
+ log records re-emit into it, tagged `{worker:true}`.
517
+
518
+ `Graph.createLogger({ label, level, onRecord, capacity, console })` builds a logger standalone.
519
+
520
+ ## Serving surfaces (`lib/sg/serve.js`, `lib/sg/mcp.js`)
521
+
522
+ Two zero-dep, zero-integration fronts over the combos (full guide: `doc/usage.md` §10):
523
+
524
+ - **`sg serve`** — an OpenAI-compatible endpoint (`POST /v1/chat/completions`, `GET /v1/models`) over the
525
+ C6 proxy cache. `createServeHandler({proxy, model, onAnswer})` is the PURE request handler (stub-testable);
526
+ `startServeServer({handler, port, host})` is the node:http wrapper. Provenance on every completion:
527
+ headers `x-sg-served-from|arm|cost|coverage|saved|sgc-version` + `usage.sg_*` mirror; `stream:true` is simulated SSE.
528
+ The v1 wire contract: the query = the LAST user turn (a QA cache, not a dialog engine).
529
+ - **`sg mcp`** — an MCP tools server (stdio JSON-RPC). `createMcpServer({tools, serverInfo})` is the pure
530
+ dispatcher; `defaultTools(wiring)` wires the base tools — `ask` (answer OR a STRUCTURED typed refusal),
531
+ `drift`, `metrics`, `lattice_load` (growth through `loadLattice` — the only registry write path),
532
+ `methods_describe`, `lattice_rings`, `trace_tail` — plus the ASSISTANT lanes: SOFT `hint` /
533
+ `state_recall` / `state_note` / `plan_sync` (the typed task delta, JTMS `reopen` included), HARD
534
+ `propose` (gate-tested; `force` → recorded-untrusted, never admission), INSTANCES `graph_invoke` /
535
+ `graph_instances`, and the C9 `critique` tool (typed ledger + verdict or honest UNDECIDED; OPEN
536
+ points + UNDECIDED = a typed data request — re-call with `statements`). `stockWiring(sgc)` /
537
+ `--stock <f.sgc>` wires `hint`/`propose` from a forged stock; `startMcpStdio` is the line-framed
538
+ transport.
539
+ - **`sg flow run <module.js>`** — the C2 durable runner as a CLI; the module exports
540
+ `{ spec, runTask | makeRunTask(), keyOf?, STREAM? }`.
541
+ - **Intake depth back-check** — `require('lib/providers/intake.js').makeProseBackCheck({ask, proseOf?,
542
+ onBlame?})` → a ready-made independent verifier for `createIntake({backCheck})` (a 'fail' downgrades the
543
+ intake to `untyped`; judged-wrong keys surface via `onBlame`).
544
+
545
+ ## Running in-repo
546
+
547
+ ```bash
548
+ npm test # unit + integration (node:test, native CJS — no Babel)
549
+ node examples/integrated-demo/run.js --replay # the public verifiable: 7 checks, deterministic, no GPU
550
+ node examples/run-basic.js # non-LLM end-to-end stabilization over the real `common` set
551
+ node examples/run-problem.js # LLM-driven plan decomposition (needs an endpoint; see lib/providers/llm.js)
552
+ node bin/sg run --concepts ./concepts --builtins # standalone CLI boot
553
+ ```
554
+
555
+ The engine is native CommonJS and runs directly under Node (no build step; `tests/_boot.js` just sets
556
+ `__SERVER__`).