OverviewFilesCommitsIssuesDocsReleasesPeers

atomspace-gpu commits / 4af34af

M3: causal lineage over the log — GOLD #3 (all 3 ActiveGraph props)
Completes Nakajima's third property. For a high-level atom X = Evaluation(lives-in, List(whale, water)), the harness traces X's causal history through the Hypercore log via a new `seqof` verb: X @seq 7 <- whale@3 water@4 lives-in@5 List@6 Gold-checks: every dependency has a real log position, all entered BEFORE X (append-only + outgoing-first), the causally-unrelated cat@0/animal@1/Inheritance@2 are EXCLUDED, and the seq-ordered chain matches exactly. Split of concerns: the daemon answers seqof (log position); the C++ side composes the causal walk over the AtomSpace — structure is the projection's job, the log's job is order. daemon.js: `seqof <sexpr>` -> birth seq (linear scan; a seq index is an M4 optimisation). m0_roundtrip.cc: `lineage` mode + transitive-outgoing closure DFS. run.sh: Phase 4. All four phases green: M0-M3 ALL PASS. Deterministic replay + cheap fork + complete lineage now all hold over the append-only log. Deferred: guile-loadable HyperStorageNode (packaging), Values, Autobase multi-writer (M4). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4af34afa019579bbbe2ec2b5821095d8f6785e38
by plankatron <fleeky@gmail.com> · 2026-07-05T14:44:25.000Z

Changed files (4) +112 -10

experiments/m0-uds-bridge/README.md +24 -9
……@@ -1,4 +1,4 @@
1# hyper-atomspace bridge — M0 + M1
1# hyper-atomspace bridge — M0 · M1 · M3
22
33Milestones of the HyperStorageNode (spec: `../../spec/hyperstoragenode.md`;
44`atomspace` org issue #4). A Bare daemon holds the op-log; a C++ client speaks the
……@@ -14,10 +14,14 @@ real opencog s-expr codec over a UNIX-domain socket. An AtomSpace is a
1414 (barrier) ──barrier──▶ ok
1515 (replay) ──load─────▶ ( get(0..len) )
1616 (checkout @K) ──loadat K─▶ ( get(0..K) ) ← cheap fork
17 (provenance) ──seqof X──▶ <birth seq of X> ← causal lineage
1718 Sexpr::decode_atom ◀────────────
1819 gold check: src AtomSpace ≡ dst AtomSpace
1920```
2021
22All three ActiveGraph properties (arXiv:2605.21997) now hold over the log:
23**deterministic replay**, **cheap forking**, **complete lineage**.
24
2125## Milestones proven here (`./run.sh` runs all three)
2226- **M0 — bridge + codec round-trip.** C++ `AF_UNIX` client round-trips atoms
2327 through the Bare daemon; payload is the real `libsexpr` codec (encode is a
……@@ -28,6 +32,13 @@ real opencog s-expr codec over a UNIX-domain socket. An AtomSpace is a
2832- **M1 #2 — cheap fork.** `loadat K` is a read-only **checkout at seq K**: read
2933 fewer blocks, get an earlier, strictly-smaller graph, no reprocessing. `[fork]`
3034 phase → `GOLD #2` (full=8, checkout@3=3, subset ✓, strict ✓).
35- **M3 #3 — complete lineage.** For a high-level atom `X =
36 Evaluation(lives-in, List(whale, water))`, trace its causal history through the
37 log via `seqof`: the exact ops that produced it — `whale@3 water@4 lives-in@5
38 List@6 Eval@7` — every dependency logged **before** X (append-only, outgoing-first),
39 and the causally-unrelated `cat@0 animal@1 Inheritance@2` correctly **excluded**.
40 `[lineage]` phase → `GOLD #3`. The daemon answers `seqof` (log position); the C++
41 side composes the causal walk — structure is the projection's job, not the log's.
3142
3243## Files
3344| File | Role |
……@@ -42,7 +53,8 @@ real opencog s-expr codec over a UNIX-domain socket. An AtomSpace is a
4253# [full] live round-trip src=8 dst=8 PASS
4354# [verify] durable replay src=8 dst=8 GOLD #1 PASS
4455# [fork] full=8 checkout@3=3 subset=1 strict=1 GOLD #2 PASS
45# M1 ALL PASS
56# [lineage] X=(EvaluationLink …) @seq 7 … found=1 deps<X=1 excl-unrelated=1 chain=1 GOLD #3 PASS
57# M0-M3 ALL PASS
4658```
4759Requires: `bare` (v1.26+), `g++` (**C++20** — opencog headers use `std::atomic::wait`),
4860opencog under `/usr/local` (`libatomspace`/`libsexpr`/`libatombase`/`libatom_types`).
……@@ -55,16 +67,19 @@ opencog under `/usr/local` (`libatomspace`/`libsexpr`/`libatombase`/`libatom_typ
5567| `store <sexpr>` | — | `core.append(sexpr)` (fire-and-forget) |
5668| `load` | `(<sexpr>…)\n` | replay whole log |
5769| `loadat <K>` | `(<sexpr>…)\n` | replay prefix 0..K (checkout / cheap fork) |
70| `seqof <sexpr>` | `<seq>\n` | log position this op entered at, or `-1` (provenance) |
5871| `clear` | `ok\n` | `core.truncate(0)` |
5972| `barrier` | `ok\n` | sync point (all prior stores applied) |
6073| `count` | `<len>\n` | `core.length` |
6174
6275## Deferred (next)
63- **Values** — structure only; per *event-source the graph, checkpoint the Values*
64 (`../../spec/activegraph-synthesis.md`).
6576- **`HyperStorageNode : public StorageNode` + guile module** — packaging, not new
66 capability: the data path is proven and `HyperChannel` is the transplant unit.
67 Needs atom_types codegen + PersistSCM + CMake (a loadable `.so`).
68- **Autobase multi-writer** (M4) — two daemons, one linearized log = two agents.
69- **Branch-fork with divergent writes** — M1 `loadat` is a read-only checkout;
70 true forking-with-writes rides Autobase.
77 capability: all three properties are proven and `HyperChannel` is the transplant
78 unit. Needs atom_types codegen + PersistSCM + CMake (a loadable `.so`) so opencog
79 can `(cog-open (HyperStorageNode "hyper://…"))` directly.
80- **Values** — structure only; per *event-source the graph, checkpoint the Values*
81 (`../../spec/activegraph-synthesis.md`). The GPU slice-table (Linas Step 4).
82- **Autobase multi-writer** (M4) — two daemons, one linearized log = two agents;
83 the device-as-agent step. `seqof`'s linear scan also wants a real seq index here.
84- **Branch-fork with divergent writes** — `loadat` is a read-only checkout; true
85 forking-with-writes rides Autobase.
experiments/m0-uds-bridge/daemon.js +13 -0
……@@ -11,6 +11,8 @@
1111// store <sexpr>\n append <sexpr> to the log. NO reply (fire-and-forget).
1212// load\n reply: (<sexpr0>…<sexpr_{len-1}>)\n — whole log.
1313// loadat <K>\n reply: (<sexpr0>…<sexpr_{K-1}>)\n — prefix/checkout @K.
14// seqof <sexpr>\n reply: <seq>\n — log position this op entered at, or -1.
15// (log-level provenance; the C++ side composes causal lineage.)
1416// clear\n truncate the log to 0. reply: ok\n
1517// barrier\n sync point. reply: ok\n
1618// count\n reply: <len>\n
……@@ -61,6 +63,17 @@ async function handleLine (line, sock) {
6163 case 'loadat':
6264 sock.write(await replay(parseInt(rest, 10) || 0))
6365 return
66 case 'seqof': {
67 // Exact-match scan for the op that entered the log as `rest`. The log is
68 // append-only so the first (only) match is the birth position. A seq index
69 // is a later optimisation; M3's log is small.
70 let found = -1
71 for (let i = 0; i < core.length; i++) {
72 if (await core.get(i) === rest) { found = i; break }
73 }
74 sock.write(String(found) + '\n')
75 return
76 }
6477 case 'clear':
6578 await core.truncate(0)
6679 sock.write('ok\n')
experiments/m0-uds-bridge/m0_roundtrip.cc +69 -0
……@@ -15,12 +15,18 @@
1515 * same Hypercore dir -> proves durable deterministic replay, M1 #1)
1616 * fork clear + store + load(full) + loadat(K) + prefix check
1717 * (checkout at seq K = cheap fork, M1 #2)
18 * lineage clear + store; then for a high-level atom X, trace its
19 * causal history through the log via seqof and check it is the
20 * exact set of ops that produced X, ordered, excluding
21 * causally-unrelated atoms (complete lineage, M3 #3)
1822 *
1923 * SPDX-License-Identifier: AGPL-3.0-or-later
2024 */
2125
2226#include <algorithm>
27#include <functional>
2328#include <iostream>
29#include <set>
2430#include <string>
2531#include <vector>
2632
……@@ -103,6 +109,11 @@ public:
103109 send_line("loadat " + std::to_string(k) + "\n");
104110 return recv_line();
105111 }
112 int seqof(const std::string& sexpr)
113 {
114 send_line("seqof " + sexpr + "\n");
115 return std::stoi(recv_line());
116 }
106117};
107118
108119// ---------------------------------------------------------------------------
……@@ -211,6 +222,64 @@ int main(int argc, char** argv)
211222 return ok ? 0 : 1;
212223 }
213224
225 if (mode == "lineage")
226 {
227 ch.clear();
228 for (const Handle& h : order) ch.store(Sexpr::encode_atom(h)); // creation order
229 ch.barrier();
230
231 // X = the high-level fact: Evaluation(lives-in, List(whale, water)).
232 const Handle& X = order[7];
233
234 // Causal closure: X + its transitive outgoing atoms (its dependencies).
235 std::set<Handle> closure;
236 std::function<void(const Handle&)> dfs = [&](const Handle& h) {
237 if (closure.count(h)) return;
238 if (h->is_link())
239 for (const Handle& o : h->getOutgoingSet()) dfs(o);
240 closure.insert(h);
241 };
242 dfs(X);
243
244 int seqX = ch.seqof(Sexpr::encode_atom(X));
245
246 // Each dependency has a real log position, entered BEFORE X (append-only,
247 // outgoing-first). Order the lineage by seq to reconstruct the chain.
248 std::vector<std::pair<int, std::string>> chain;
249 bool all_found = true, deps_before_X = true;
250 for (const Handle& h : closure)
251 {
252 int s = ch.seqof(Sexpr::encode_atom(h));
253 if (s < 0) all_found = false;
254 if (h != X && !(s < seqX)) deps_before_X = false;
255 chain.push_back({s, Sexpr::encode_atom(h)});
256 }
257 std::sort(chain.begin(), chain.end());
258
259 // Causally-unrelated atoms (cat, animal, Inheritance) must NOT appear.
260 bool excludes_unrelated =
261 !closure.count(order[0]) && !closure.count(order[1]) &&
262 !closure.count(order[2]);
263
264 // Expected lineage, by seq: whale@3 water@4 lives-in@5 List@6 Eval@7.
265 std::vector<std::pair<int, std::string>> expect;
266 for (int i : {3, 4, 5, 6, 7})
267 expect.push_back({i, Sexpr::encode_atom(order[i])});
268 bool chain_ok = (chain == expect);
269
270 std::cout << "[lineage] X=" << Sexpr::encode_atom(X) << " @seq " << seqX << "\n";
271 for (const auto& pr : chain)
272 std::cout << " seq " << pr.first << " " << pr.second << "\n";
273
274 bool ok = all_found && deps_before_X && excludes_unrelated && chain_ok &&
275 (int) closure.size() == 5 && seqX == 7;
276 std::cout << " found=" << all_found << " deps<X=" << deps_before_X
277 << " excl-unrelated=" << excludes_unrelated
278 << " chain=" << chain_ok
279 << (ok ? " GOLD #3 PASS\n" : " GOLD #3 FAIL\n");
280 return ok ? 0 : 1;
281 }
282
214283 // mode == full : live append + replay round-trip.
215284 ch.clear();
216285 for (const Handle& h : order) ch.store(Sexpr::encode_atom(h));
experiments/m0-uds-bridge/run.sh +6 -1
……@@ -62,4 +62,9 @@ start_daemon "$STORE2"
6262./m0_roundtrip "$SOCK" fork
6363stop_daemon
6464
65echo "M1 ALL PASS"
65# Phase 4 — fresh store, causal lineage of a high-level atom (GOLD #3)
66start_daemon "$WORK/store3"
67./m0_roundtrip "$SOCK" lineage
68stop_daemon
69
70echo "M0-M3 ALL PASS"