Gensyn 백서

Gensyn

원문

bitpro가 원문에서 글자만 모은 내용이에요. 표·그림·수식은 빠지거나 깨질 수 있어요. 전체 16쪽 중 5쪽

ee-based data aggregation using the network's spanning tree #### Get Started Ready to get started? Find documentation [here](/tech/agent-exchange-layer/get-started) on cloning the repo, building the node, creating an identity key, configuring it, and making sure everything works. [^1]: we should link out to libraries that we have used # Get Started Instructions on how to get started with AXL. ## Overview **\[1]** Clone the [repo](https://github.com/gensyn-ai/axl), **\[2]** build the node, **\[3]** run it, and **\[4]** verify everything works. {% embed url="<https://github.com/gensyn-ai/axl>" fullWidth="false" %} ### Prerequisites | Tool | Required | Install | | -------------- | ------------ | ------------------------------------------------------------------------------------------- | | *Go 1.25.x* | Yes | `brew install go` (macOS) or [click here](https://go.dev/dl/) | | *Python 3.9+* | For examples | Usually pre-installed on macOS/Linux, or [download here](https://www.python.org/downloads/) | | *pip packages* | For examples | `pip install textual requests` | {% hint style="warning" %} **Go 1.26 compatibility:** The `gvisor.dev/gvisor` dependency has build tag conflicts with Go 1.26. The `toolchain go1.25.5` directive in `go.mod` handles this automatically if you have Go 1.25 installed.&#x20; If you only have Go 1.26+, prefix build commands with `GOTOOLCHAIN=go1.25.5`. {% endhint %} ### Clone and Build This command sequence produces a single binary called `node` in the current directory: ```bash git clone https://github.com/gensyn-ai/axl.git cd axl go build -o node ./cmd/node/ ``` ### Generate a Key The node needs an `ed25519` private key for its identity.&#x20; You have two options: **\[1]** persisting your identity or **\[2]** generating a new key automatically on startup. #### Option A: Persistent identity (recommended) Generate a key file so your node keeps the same public key across restarts: ```bash openssl genpkey -algorithm ed25519 -out private.pem ``` *** **For macOS users specifically:** the default `openssl` on macOS is LibreSSL, which does not support `ed25519`. To get around this, use Homebrew's OpenSLL instead: ```bash brew install openssl /opt/homebrew/opt/openssl/bin/openssl genpkey -algorithm ed25519 -out private.pem ``` *** #### Option B: Ephemeral identity Skip key generation entirely. If you omit `PrivateKeyPath` from your config, the node generates a new identity in memory each time it starts. Fine for quick testing but your public key changes every restart. ### Configure Create a `node-config.json` in the repo root. If someone gave you a peer address to connect to, add it to the `Peers` array: ```json { "PrivateKeyPath": "private.pem", "Peers": ["tls://THEIR_IP:9001"] } ``` If you don't have a peer address yet (just testing locally), leave `Peers` empty: ```json { "PrivateKeyPath": "private.pem", "Peers": [] } ``` That's the minimal config. See the [Configuration](broken://pages/qZp9xfZ7QDsw7gBLPfky) section below for all available settings. ### Start the Node Run this command: ```bash ./node -config node-config.json ``` You'll see output including the following: ``` Your IPv6 address is 200:abcd:... Your public key is 1ee862344fb283395143ac9775150d2e5936efd6e78ed0db83e3f290d3d539ef ``` If you get this output, it means your node is now running. Leave this terminal open. ### Verify Then, run this command in a separate terminal: ```bash curl -s http://127.0.0.1:9002/topology | python3 -c "import sys,json; d=json.load(sys.stdin); print('Public key:', d['our_public_key']); print('IPv6:', d['our_ipv6'])" ``` If you see your public key and IPv6 address, the node is up and the local interface is reachable. ### Connect with Another Person Your public key is your address on the network.&#x20; To 'communicate' with someone, you need to exchange keys (like trading phone numbers): 1. **Find your key:** It's printed on node startup, or run the curl command above. 2. **Share it:** Send your 64-character hex key to the other person via Slack, Discord, email, whatever. 3. **Get theirs:** They do the same. {% hint style="danger" %} Remember, this is the 64-character public key, *not* a private key in the `.pem` file. Please do not share that key! {% endhint %} Now you can send each other messages or call each other's MCP services. {% hint style="info" %} There is no way to look up another node's key from the network. The `/topology` endpoint shows keys of nodes in the spanning tree, but it doesn't tell you who owns them. Keys *must* be exchanged directly between people. {% endhint %} ### Quick Two-Node Test The fastest way to verify everything works end-to-end is running two nodes locally. To test this out, follow the list of commands below, running them sequentially in the *proper* terminals.&#x20; First, generate a second key: ```bash /opt/homebrew/opt/openssl/bin/openssl genpkey -algorithm ed25519 -out private-2.pem ``` Create a second config (`node-config-2.json`): ```json { "PrivateKeyPath": "private-2.pem", "Peers": [], "Listen": [], "api_port": 9012, "tcp_port": 7001 } ``` Start the second node in a new terminal: ```bash ./node -config node-config-2.json ``` Finally, send a message between the two nodes (in different terminals) and check the output: ```bash NODE_A_KEY=$(curl -s http://127.0.0.1:9002/topology | python3 -c "import sys,json; print(json.load(sys.stdin)['our_public_key'])") NODE_B_KEY=$(curl -s http://127.0.0.1:9012/topology | python3 -c "import sys,json; print(json.load(sys.stdin)['our_public_key'])") # Send from B → A curl -X POST http://127.0.0.1:9012/send \ -H "X-Destination-Peer-Id: $NODE_A_KEY" \ -d "hello from node B" # Receive on A sleep 1 curl -v http://127.0.0.1:9002/recv ``` The response body should contain `hello from node B`, and the `X-From-Peer-Id` header should match Node B's public key. This is the exact same flow two people on different machines would use, with the only difference being that they wouldn't need different port numbers. #### Configuration Reference Check out [this documentation](/tech/agent-exchange-layer/configuration) for the full list of API flags, configuration settings, and some example set-ups.&#x20; # Configuration CLI flags, node settings, and example configurations. ## Overview The node reads a single JSON file (default: `node-config.json`) at startup. Every field is optional, so if you omit a field, its default value is used. You only need to include settings you want to change. ### CLI Flags You can run `./node [flags]` once your node is up and running. | Flag | Description | Default | | --------- | ---------------------------------------------------- | ------------------ | | `-config` | Path to config file | `node-config.json` | | `-listen` | Listen address for incoming peers (overrides config) | *(none)* | The `-listen` flag is for hosting a public node that accepts inbound peer connections, making your node a bootstrap/relay point for others to connect to.&#x20; This requires exposing a port on the public internet. If you're just connecting outbound to existing peers (the normal case), you don't need this flag. #### node-config.json All of these fields are optional. For any fields that are left blank, default values are applied. #### Network Identity & Peering The config file is shared between two systems: **\[1]** Yggdrasil (the network layer) and the **\[2]** AXL node (the application layer).&#x20; This is why the casing differs: `PrivateKeyPath` and `Peers` are Yggdrasil settings (PascalCase), while `api_port` and `tcp_port` are AXL node settings (snake\_case). Both live in the same file. | Field | Type | Description | Example | | ---------------- | --------- | ---------------------------------------------------------- | ------------------------ | | `PrivateKeyPath` | string | Path to ed25519 PEM key file. Omit for ephemeral identity. | `"private.pem"` | | `Peers` | string\[] | Bootstrap peer URIs to connect to on startup. | `["tls://1.2.3.4:9001"]` | | `Listen` | string\[] | Addresses to listen for incoming peer connections. | `["tls://0.0.0.0:9001"]` | ### Node Settings | Field | Type | Default | Description | | ------------------------ | ------ | ----------- | ----------------------------------------- | | `api_port` | int | `9002` | HTTP interface port | | `bridge_addr` | string | `127.0.0.1` | HTTP interface bind address | | `tcp_port` | int | `7000` | Internal TCP listener port (gVisor) | | `router_addr` | string | *(empty)* | MCP Router host. Empty = MCP disabled. | | `router_port` | int | `9003` | MCP Router port | | `a2a_addr` | string | *(empty)* | A2A Server host. Empty = A2A disabled. | | `a2a_port` | int | `9004` | A2A Server port | | `max_message_size` | int | `16777216` | Max message size in bytes (default 16 MB) | | `max_concurrent_conns` | int | `128` | Max concurrent inbound TCP connections | | `conn_read_timeout_secs` | int | `60` | Read timeout per connection (seconds) | | `conn_idle_timeout_secs` | int | `300` | Idle timeout per connection (seconds) | {% hint style="warning" %} A note on `bridge_addr`: The default `127.0.0.1` means only your local machine can reach the HTTP API. If you change this to `0.0.0.0`, the API is exposed to your entire network. Anyone who can reach that port can send messages as your node.&#x20; *Do not change this unless you understand the implications.* {% endhint %} #### Resource & Connection Limits These protect the node against resource exhaustion from misbehaving or flooding peers.&#x20; The defaults are appropriate for most setups. You only need to tune these if you're running a high-traffic public node or operating in a constrained environment. | Field | Type | Default | Description | | ------------------------ | ---- | ---------- | --------------------------------------------- | | `max_message_size` | int | `16777216` | Max TCP message size in bytes (default 16 MB) | | `max_concurrent_conns` | int | `128` | Max simultaneous inbound TCP connections | | `conn_read_timeout_secs` | int | `60` | Read timeout per connection (seconds) | | `conn_idle_timeout_secs` | int | `300` | Idle timeout per connection (seconds) | #### Enabling MCP & A2A Setting `router_addr` or `a2a_addr` in the config tells the node to route matching inbound messages to those services.&#x20; But the config alone doesn't start the services: you also need the Python processes running. Spin up those processes like this: ```bash # MCP Router (must be running at router_addr:router_port) cd integrations pip install -e . python -m mcp_routing.mcp_router --port 9003 # A2A Server (must be running at a2a_addr:a2a_port) python -m a2a_serving.a2a_server --port 9004 --router http://127.0.0.1:9003 ``` If `router_addr` is empty, MCP messages arriving at your node are silently ignored. Same for `a2a_addr` and A2A messages. See [Building Applications & Examples](/tech/agent-exchange-layer/examples-and-building) for full setup walkthroughs. ### Example Configurations Standard (persistent identity, one bootstrap peer): ```json { "PrivateKeyPath": "private.pem", "Peers": ["tls://1.2.3.4:9001"] } ``` Public node (accepting inbound peers): ```json { "PrivateKeyPath": "private.pem", "Peers": [], "Listen": ["tls://0.0.0.0:9001"] } ``` With MCP and A2A enabled: ```json { "PrivateKeyPath": "private.pem", "Peers": ["tls://1.2.3.4:9001"], "router_addr": "http://127.0.0.1", "router_port": 9003, "a2a_addr": "http://127.0.0.1", "a2a_port": 9004 } ``` Here is what two nodes looks like on the same machine. *Node A* uses defaults, but *Node B* needs different ports: ```json { "PrivateKeyPath": "private-2.pem", "Peers": [], "api_port": 9012, "tcp_port": 7001 } ``` #### LAN "Hub-and-spoke" **\[1]** Hub (listening): ```json { "PrivateKeyPath": "private.pem", "Listen": ["tls://0.0.0.0:9001"] } ``` **\[2]** Spoke (connecting to hub): ```json { "PrivateKeyPath": "private.pem", "Peers": ["tls://192.168.0.22:9001"] } ``` **\[3]** Custom resource limits (for high-traffic public nodes): ```json { "PrivateKeyPath": "private.pem", "Listen": ["tls://0.0.0.0:9001"], "max_concurrent_conns": 512, "max_message_size": 33554432, "conn_read_timeout_secs": 30, "conn_idle_timeout_secs": 120 } ``` See [How It Works](/tech/agent-exchange-layer/how-it-works) for the full picture of what's happening under the hood. # How it Works In-depth information on AXL's architecture, encryption, peering, privacy model, wire format, and node internals. ## The Mental Model This is the single most important thing to understand about AXL, and it's counterintuitive if you're coming from traditional client-server architecture: *Your application code never touches the network.*&#x20; It runs on your machine and only ever talks to `http://127.0.0.1:9002` which is the local HTTP interface exposed by your AXL node. Your node handles all peer-to-peer transport, encryption, and routing behind the scenes. Both sides run the full stack independently. If two people want to communicate, each person runs: 1. Their own AXL node (the `Go` binary) 2. Their own copy of the application Nobody "connects to" the other person's application. Each application talks only to its own local node. The nodes talk to each other over the encrypted mesh. ``` Your Machine Their Machine ┌──────────────────────┐ ┌──────────────────────┐ │ [Your App] │ │ [Their App] │ │ ↕ HTTP │ │ ↕ HTTP │ │ [AXL node :9002] │ ◄── mesh ──► │ [AXL node :9002] │ └──────────────────────┘ └──────────────────────┘ ``` "Exposing a service" doesn't mean what it usually means. In traditional web development, exposing a service means binding to a port and accepting remote connections. But in AXL, it means: **\[1]** your node is running, **\[2]** your application is running locally, and **\[3]** remote nodes send messages to your public key.&#x20; The Yggdrasil network routes those messages to your node, which queues them for your application. Your application is never directly reachable from the outside. The node is the *only thing* with a network presence. Therefore, there is nothing to deploy; you run your application on your laptop, and as long as your node is up, other nodes can reach you by your public key, so long as you share it. ### Architecture The Go binary (`node`) contains four layers: ``` localhost ┌──────────────────┐ │ │ Your App ◄───┤ HTTP API (:9002) │ │ │ │ Multiplexer ─┼──► MCP Router (:9003) │ │ │ │ gVisor TCP ├──► A2A Server (:9004) │ │ │ │ Yggdrasil Core │ │ │ │ └────────┼─────────┘ │ TLS/TCP ▼ Network Peers ``` | **Layer** | **What It Does** | | ---------------- | -------------------------------------------------------------------------------------------------------------------------------- | | *HTTP Interface* | Local server on `127.0.0.1:9002`. Your application's single point of contact with the node. | | *Multiplexer* | Routes inbound TCP messages to the correct handler based on envelope fields. Unmatched messages go to a queue your app can read. | | *gVisor TCP* | Userspace TCP/IP stack. No TUN device, no root privileges. Listens on port 7000 for inbound peer connections. | | *Yggdrasil Core* | Manages the ed25519 keypair, derives a deterministic IPv6 address, and peers over TLS/TCP with other nodes. | The MCP Router and A2A Server are optional, separate Python processes for structured request/response protocols. See [Building Applications & Examples](/tech/agent-exchange-layer/examples-and-building) for details. #### Startup Flow Here is what happens when you run `./node -config node-config.json`: 1. CLI flags are parsed (`-config`, optional `-listen` override). 2. The config file is read for both Yggdrasil settings (`Peers`, `Listen`, `PrivateKeyPath`) and node settings (ports, limits, router URLs). 1. Yggdrasil core starts. It connects to configured peers, joins the spanning tree, logs your IPv6 address and public key. 2. gVisor TCP stack starts and listens on `tcp_port` (default 7000) for inbound connections from other nodes. 3. HTTP server starts then binds to `bridge_addr:api_port` (default `127.0.0.1:9002`). ### The Yggdrasil Network AXL uses [Yggdrasil](https://yggdrasil-network.github.io/), an encrypted IPv6 overlay network, for all peer-to-peer transport. * **Overlay network.** Yggdrasil runs on top of the regular internet (or LAN). It creates an encrypted mesh between all participating nodes. * **Spanning tree routing.** Peers form a spanning tree. Each node gets a deterministic IPv6 address derived from its public key. Routing happens along the tree without no centralized routing table. * **Identity = public key.** Nodes are identified by their 64-character hex-encoded `ed25519` public key, not by IP or hostname. * **Encrypted by default.** All connections use TLS, and Yggdrasil adds end-to-end encryption on top (see Encryption below). ### Peering To join the network, your node connects to at least one other node. This is configured in the `Peers` array in `node-config.json`. #### Bootstrap Peers Bootstrap peers are nodes that accept inbound connections and help route traffic. They're entry points into the mesh, not controllers and as such they cannot read your message content. They just relay encrypted bytes. ```json { "Peers": ["tls://1.2.3.4:9001"] } ``` Once connected to a bootstrap peer, your node can reach any other node in the mesh by their public key. The mesh handles all of the routing work so you don't need direct connectivity to every node. {% hint style="success" %} Bootstrap peers are a standard pattern used by Bitcoin, IPFS, Ethereum, and other P2P networks. Anyone can run one. {% endhint %} #### Running a Public Node (Being a Bootstrap Peer) If you want other nodes to connect to you, you can run a public node. Public nodes help route traffic through the mesh: they don't store other nodes' data or have access to their messages, they just forward encrypted bytes.&#x20; The more public nodes in the network, the more resilient it becomes.&#x20; To set one up: 1. Expose a TCP port to the network (LAN or internet). 2. Add a `Listen` address to your config: ```json { "PrivateKeyPath": "private.pem", "Listen": ["tls://0.0.0.0:9001"] } ``` 3. Share your IP and port with others. They add `tls://YOUR_IP:9001` to their `Peers`. Use a persistent identity (`PrivateKeyPath`) so your key doesn't change across restarts.&#x20; {% hint style="info" %} Keep the HTTP interface port (`9002`) locked to localhost. Only the peering port should be exposed. {% endhint %} #### LAN vs. Internet Peering * **On a LAN (same network):** Trivial. Both nodes just need each other's LAN IP and listen port. There is no port forwarding or firewall adjustments that need to be made with this route. * **Over the internet:** The listening node must expose its TCP port to the public internet (port forwarding, cloud VM with open port, etc.). Outbound-only nodes don't need to expose anything however: they can still communicate with each other because traffic is routed through public nodes in the mesh. #### Peer Discovery Running the following command returns: * `our_public_key`: your node's identity * `our_ipv6`: your node's Yggdrasil IPv6 address * `peers`: directly connected peers * `tree`: the spanning tree as your node sees it ```bash curl -s http://127.0.0.1:9002/topology | python3 -m json.tool ``` {% hint style="info" %} There is no built-in service registry.&#x20; You can see public keys in the tree, but you can't look up who owns them or what services they run. Keys and service names must be exchanged directly between people.&#x20; {% endhint %} ### Encryption: Two Layers There are two distinct layers of encryption. Understanding both matters. #### Layer 1: Peering Transport (TLS) The `tls://` URIs in your config establish encrypted links between directly connected peers. This is hop-by-hop meaning it secures t
프로젝트로 돌아가기