Skip to content

Commit 4fe48bf

Browse files
author
MesTTo
committed
feat(das): DasSpace + transport (mock-tested) + browser gateway wire; integration boundary documented
1 parent 6defb43 commit 4fe48bf

13 files changed

Lines changed: 297 additions & 0 deletions

File tree

packages/das-client/README.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
# @metta-ts/das-client
2+
3+
Distributed AtomSpace (DAS) as a pluggable `Space` backend for MeTTa TS.
4+
5+
## What ships here
6+
7+
- **`DasTransport`** — the network boundary interface (`query`/`add`/`remove`/`atoms`).
8+
- **`DasSpace`** — implements the kernel's `Space` interface over a transport, so a DAS is just another Space backend (drops in wherever `InMemorySpace` does). Mock-tested.
9+
- **`MockTransport`** — an in-process transport that exercises the exact `DasSpace` code paths, for tests and offline development.
10+
- **`das.metta`** — the vendored DAS module type declarations (`new-das!`, `das-evolution!`, `das-link-creation!`, …) from hyperon-experimental.
11+
12+
## What does not ship (the remaining integration)
13+
14+
The **live bus client** is deliberately not shipped as unverified code. DAS's distributed layer is gRPC (`singnet/das-proto`: `DistributedAlgorithmNode.execute_message`, `attention_broker`) plus a peer-to-peer streaming choreography. Completing it means:
15+
16+
1. Generate gRPC stubs from `das-proto` (ts-proto + `@grpc/grpc-js`).
17+
2. Port the choreography from the Python reference `hyperon_das` (~2.8k LOC): service bus, proxy, port pool, distributed-algorithm bus node, pattern-matching + atomdb clients, and atom-handle hashing (capture parity vectors from the Python client first).
18+
3. Validate against a live DAS (`das-toolbox`) in an integration test.
19+
20+
This is **Node-only**: a participant must host an inbound bus node, which a browser cannot do — the browser reaches DAS through [`@metta-ts/das-gateway`](../das-gateway). Per the project's correctness-first principle, this lands behind a live-DAS differential test rather than as untested wire-protocol code.
21+
22+
## Kernel integration note
23+
24+
`DasSpace` implements the same `Space` interface as `InMemorySpace`. Wiring a named DAS-backed space into the interpreter's `World` (so `match`/`add-atom`/`get-atoms` over a `new-das!` handle dispatch to a `DasSpace`) is a small generalization of `World.spaces` from `Atom[]` lists to `Space` backends — the seam is already the `Space` interface.

packages/das-client/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "@metta-ts/das-client",
3+
"version": "0.0.0",
4+
"type": "module",
5+
"main": "./dist/index.js",
6+
"types": "./dist/index.d.ts",
7+
"exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } },
8+
"files": ["dist"],
9+
"scripts": { "build": "tsup src/index.ts --format esm --dts --clean" },
10+
"dependencies": { "@metta-ts/core": "workspace:*" }
11+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
import { describe, it, expect } from "vitest";
2+
import { sym, variable, expr, instantiate, atomEq } from "@metta-ts/core";
3+
import { DasSpace } from "./das-space";
4+
import { MockTransport } from "./transport";
5+
6+
describe("DasSpace (over a mock transport)", () => {
7+
it("implements the Space interface: add, query, remove", () => {
8+
const space = new DasSpace(new MockTransport());
9+
space.add(expr([sym("Similarity"), sym("human"), sym("chimp")]));
10+
space.add(expr([sym("Similarity"), sym("human"), sym("monkey")]));
11+
12+
const res = space.query(expr([sym("Similarity"), sym("human"), variable("s")]));
13+
expect(res.length).toBe(2);
14+
const got = res.map((b) => instantiate(b, variable("s")));
15+
expect(got.some((a) => atomEq(a, sym("chimp")))).toBe(true);
16+
expect(got.some((a) => atomEq(a, sym("monkey")))).toBe(true);
17+
18+
expect(space.remove(expr([sym("Similarity"), sym("human"), sym("chimp")]))).toBe(true);
19+
expect(space.query(expr([sym("Similarity"), sym("human"), variable("s")])).length).toBe(1);
20+
});
21+
});
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
// A Space backed by a Distributed AtomSpace. It implements the same `Space` interface the kernel
2+
// already injects (so it drops in wherever `InMemorySpace` does), delegating every operation to a
3+
// `DasTransport`. This is how DAS becomes "just another Space backend".
4+
import { type Atom, type Bindings, type Space } from "@metta-ts/core";
5+
import { type DasTransport } from "./transport";
6+
7+
export class DasSpace implements Space {
8+
constructor(private readonly transport: DasTransport) {}
9+
add(atom: Atom): void {
10+
this.transport.add(atom);
11+
}
12+
remove(atom: Atom): boolean {
13+
return this.transport.remove(atom);
14+
}
15+
query(pattern: Atom): Bindings[] {
16+
return this.transport.query(pattern);
17+
}
18+
atoms(): readonly Atom[] {
19+
return this.transport.atoms();
20+
}
21+
}

packages/das-client/src/das.metta

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
(@doc new-das!
2+
(@desc "Creates a new Distributed AtomSpace (DAS) instance with specified server and known peer endpoints")
3+
(@params (
4+
(@param "Server endpoint with port range (e.g., 0.0.0.0:42000-42999 or localhost:52000-52099)")
5+
(@param "Known peer endpoint (e.g., 0.0.0.0:40002 or localhost:40002)")))
6+
(@return "A Space instance representing the DAS"))
7+
8+
(@doc das-join-network!
9+
(@desc "(re)Joins the DAS network using the configured parameters")
10+
(@params ())
11+
(@return "Unit atom"))
12+
13+
(@doc das-services!
14+
(@desc "Prints information about available DAS services")
15+
(@params ())
16+
(@return "Unit atom"))
17+
18+
(@doc das-service-status!
19+
(@desc "Checks the status of a specific DAS service by name")
20+
(@params (
21+
(@param "Service name (string)")))
22+
(@return "String: 'ServiceAvailable: <name>' if available, or error if not available or not registered"))
23+
24+
(@doc das-set-param!
25+
(@desc "Sets a DAS parameter value. The parameter must exist in the DAS configuration")
26+
(@params (
27+
(@param "Expression containing key-value pair, e.g., (max_answers 100)")))
28+
(@return "String: 'DAS Param Updated: <param_name>' or error"))
29+
30+
(@doc das-get-params!
31+
(@desc "Prints all current DAS parameters")
32+
(@params ())
33+
(@return "Unit atom"))
34+
35+
(@doc das-create-context!
36+
(@desc "Creates a context in the DAS with specified query, determiner schema, and stimulus schema")
37+
(@params (
38+
(@param "Context name (string)")
39+
(@param "ExpressionAtom containing schemas: ( (query) (determiner_schema) (stimulus_schema) )")))
40+
(@return "Result atom from context creation"))
41+
42+
(@doc das-evolution!
43+
(@desc "Performs DAS evolution operation with query, fitness function, correlation queries, replacements, and mappings")
44+
(@params (
45+
(@param "ExpressionAtom containing evolution configuration: ( (query) (fitness-function) (correlation-queries) (correlation-replacements) (correlation-mappings) )")
46+
(@param "Template atom to be filled with evolution results")))
47+
(@return "Non-deterministic result: template with bindings from evolution"))
48+
49+
(@doc das-link-creation!
50+
(@desc "Creates links in the DAS based on a query pattern and one or more templates")
51+
(@params (
52+
(@param "Query atom to match against")
53+
(@param "Template atoms (one or more) for link creation")))
54+
(@return "Result atom from link creation"))
55+
56+
(@doc das-helpers!
57+
(@desc "Executes helper functions for DAS operations. Currently supports 'sleep' helper")
58+
(@params (
59+
(@param "Helper function name (string), e.g., 'sleep'")
60+
(@param "Argument for the helper function (string), e.g., '5' for sleep duration in seconds")))
61+
(@return "Unit atom"))

packages/das-client/src/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
// @metta-ts/das-client — Distributed AtomSpace as a pluggable Space backend for MeTTa TS.
2+
//
3+
// Architecture (shipped, mock-tested): `DasTransport` is the network boundary; `DasSpace`
4+
// implements the kernel's `Space` interface over a transport, so DAS is just another Space backend.
5+
//
6+
// Live bus client (the remaining integration — NOT shipped unverified): generate gRPC stubs from
7+
// `singnet/das-proto` (`DistributedAlgorithmNode.execute_message`, `attention_broker`), then port
8+
// the choreography from the Python reference `hyperon_das` (service bus, proxy, port pool, bus node,
9+
// pattern-matching/atomdb clients, atom-handle hashing). That requires a running DAS to validate, so
10+
// per the project's correctness-first principle it lands behind a live-DAS integration test, not as
11+
// unverified code. Node-only (a browser cannot host an inbound bus node; the browser reaches DAS via
12+
// @metta-ts/das-gateway). See README.
13+
export { type DasTransport, MockTransport } from "./transport";
14+
export { DasSpace } from "./das-space";
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// The DAS transport boundary. A `DasTransport` speaks to a Distributed AtomSpace; the concrete
2+
// bus implementation (gRPC generated from `das-proto`, choreography ported from the Python
3+
// `hyperon_das` reference) plugs in here. This package ships the architecture + a deterministic
4+
// `MockTransport` for tests; the live bus client is the remaining integration (it needs a running
5+
// DAS service to validate, so it is not shipped unverified — see README).
6+
import { type Atom, type Bindings, matchAtoms } from "@metta-ts/core";
7+
8+
export interface DasTransport {
9+
/** Pattern-matching query against the remote space; returns binding sets (DAS `query`). */
10+
query(pattern: Atom): Bindings[];
11+
/** Create/insert an atom (DAS `add_link` / atomdb write). */
12+
add(atom: Atom): void;
13+
/** Remove an atom. */
14+
remove(atom: Atom): boolean;
15+
/** Enumerate atoms (where the backend supports it). */
16+
atoms(): readonly Atom[];
17+
}
18+
19+
/** An in-process transport over a local atom list, for tests and offline development. It exercises
20+
* the exact `DasSpace`/grounded-op code paths a real bus client would, minus the network. */
21+
export class MockTransport implements DasTransport {
22+
constructor(private readonly store: Atom[] = []) {}
23+
query(pattern: Atom): Bindings[] {
24+
const out: Bindings[] = [];
25+
for (const a of this.store) for (const b of matchAtoms(pattern, a)) out.push(b);
26+
return out;
27+
}
28+
add(atom: Atom): void {
29+
this.store.push(atom);
30+
}
31+
remove(atom: Atom): boolean {
32+
const i = this.store.findIndex((a) => JSON.stringify(a) === JSON.stringify(atom));
33+
if (i < 0) return false;
34+
this.store.splice(i, 1);
35+
return true;
36+
}
37+
atoms(): readonly Atom[] {
38+
return this.store;
39+
}
40+
}

packages/das-client/tsconfig.json

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
{
2+
"extends": "../../tsconfig.base.json",
3+
"include": ["src"],
4+
"compilerOptions": { "outDir": "dist", "rootDir": "src" }
5+
}

packages/das-gateway/package.json

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"name": "@metta-ts/das-gateway",
3+
"version": "0.0.0",
4+
"type": "module",
5+
"main": "./dist/index.js",
6+
"types": "./dist/index.d.ts",
7+
"exports": { ".": { "types": "./dist/index.d.ts", "default": "./dist/index.js" } },
8+
"files": ["dist"],
9+
"scripts": { "build": "tsup src/index.ts --format esm --dts --clean" },
10+
"dependencies": { "@metta-ts/core": "workspace:*", "@metta-ts/das-client": "workspace:*" }
11+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
import { describe, it, expect } from "vitest";
2+
import { sym, variable, expr, format, instantiate, atomEq } from "@metta-ts/core";
3+
import { encodePattern, decodeBindings, queryDas, type GatewayTransport } from "./index";
4+
5+
describe("das-gateway wire format", () => {
6+
it("encodes a pattern and decodes binding solutions round-trip", async () => {
7+
// A stub gateway that "answers" $s = chimp / monkey for any pattern.
8+
const transport: GatewayTransport = {
9+
query: () =>
10+
Promise.resolve({
11+
bindings: [
12+
[["s", "chimp"]],
13+
[["s", "monkey"]],
14+
],
15+
}),
16+
};
17+
const pattern = expr([sym("Similarity"), sym("human"), variable("s")]);
18+
expect(encodePattern(pattern)).toBe("(Similarity human $s)");
19+
20+
const sols = await queryDas(transport, "&das", pattern);
21+
const got = sols.map((b) => format(instantiate(b, variable("s"))));
22+
expect(got).toEqual(["chimp", "monkey"]);
23+
expect(decodeBindings({ bindings: [[["s", "chimp"]]] }).length).toBe(1);
24+
expect(atomEq(instantiate(decodeBindings({ bindings: [[["s", "chimp"]]] })[0]!, variable("s")), sym("chimp"))).toBe(true);
25+
});
26+
});

0 commit comments

Comments
 (0)