elo-node 0.4.0__tar.gz
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.
- elo_node-0.4.0/PKG-INFO +107 -0
- elo_node-0.4.0/README.md +85 -0
- elo_node-0.4.0/elo/__init__.py +29 -0
- elo_node-0.4.0/elo/__main__.py +153 -0
- elo_node-0.4.0/elo/node.py +498 -0
- elo_node-0.4.0/elo/security.py +201 -0
- elo_node-0.4.0/elo/transport/__init__.py +34 -0
- elo_node-0.4.0/elo/transport/protocol.py +166 -0
- elo_node-0.4.0/elo/transport/routing.py +135 -0
- elo_node-0.4.0/elo/transport/tcp.py +327 -0
- elo_node-0.4.0/elo/transport/tracker.py +97 -0
- elo_node-0.4.0/elo/types.py +126 -0
- elo_node-0.4.0/elo_node.egg-info/PKG-INFO +107 -0
- elo_node-0.4.0/elo_node.egg-info/SOURCES.txt +20 -0
- elo_node-0.4.0/elo_node.egg-info/dependency_links.txt +1 -0
- elo_node-0.4.0/elo_node.egg-info/entry_points.txt +2 -0
- elo_node-0.4.0/elo_node.egg-info/requires.txt +5 -0
- elo_node-0.4.0/elo_node.egg-info/top_level.txt +1 -0
- elo_node-0.4.0/pyproject.toml +40 -0
- elo_node-0.4.0/setup.cfg +4 -0
- elo_node-0.4.0/tests/test_security.py +125 -0
- elo_node-0.4.0/tests/test_unit.py +234 -0
elo_node-0.4.0/PKG-INFO
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: elo-node
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Elo — malha P2P de mensagens para agentes de IA. Zero infraestrutura.
|
|
5
|
+
Author: Elo Contributors
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/xalq/elo
|
|
8
|
+
Project-URL: Repository, https://github.com/xalq/elo
|
|
9
|
+
Keywords: p2p,agents,ai,distributed,messaging,mesh
|
|
10
|
+
Classifier: Development Status :: 2 - Pre-Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Requires-Python: >=3.11
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
Requires-Dist: cryptography>=42.0
|
|
19
|
+
Provides-Extra: dev
|
|
20
|
+
Requires-Dist: pytest>=8.0; extra == "dev"
|
|
21
|
+
Requires-Dist: pytest-asyncio>=0.25; extra == "dev"
|
|
22
|
+
|
|
23
|
+
# Elo Node — Malha P2P para Agentes de IA
|
|
24
|
+
|
|
25
|
+
**Zero infraestrutura. Um processo. Uma porta TCP. Uma chave ed25519.**
|
|
26
|
+
|
|
27
|
+
Elo é uma malha de mensagens P2P descentralizada para comunicação entre agentes de IA. Sem servidor central, sem Kafka, sem Redis, sem NATS. Apenas TCP direto entre nós.
|
|
28
|
+
|
|
29
|
+
```bash
|
|
30
|
+
pip install elo-node
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```python
|
|
34
|
+
import asyncio
|
|
35
|
+
from elo import Node
|
|
36
|
+
|
|
37
|
+
async def main():
|
|
38
|
+
node = Node("meu-agente", port=7878)
|
|
39
|
+
await node.connect()
|
|
40
|
+
await node.register(agents=["analyst"], tools=["web-search"])
|
|
41
|
+
|
|
42
|
+
@node.on_task
|
|
43
|
+
async def handle(task):
|
|
44
|
+
return {"result": f"processed by {node.node_id}"}
|
|
45
|
+
|
|
46
|
+
await node.run()
|
|
47
|
+
|
|
48
|
+
asyncio.run(main())
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
## Recursos
|
|
52
|
+
|
|
53
|
+
- **P2P descentralizado** — descoberta via tracker público ou DHT Kademlia
|
|
54
|
+
- **Assinatura ed25519** — identidade criptográfica, mensagens autenticadas
|
|
55
|
+
- **Capabilities** — publish/subscribe de capacidades entre nós
|
|
56
|
+
- **Zero infra** — sem Kafka, Redis, NATS, ou servidor central
|
|
57
|
+
- **CLI nativo** — `python -m elo serve`, `status`, `init`, `id`
|
|
58
|
+
|
|
59
|
+
## CLI
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python -m elo status # Node ID, hash, chaves
|
|
63
|
+
python -m elo id # Apenas o node_id
|
|
64
|
+
python -m elo pubkey # Chave pública (hex + b64)
|
|
65
|
+
python -m elo init # Gerar identidade persistente
|
|
66
|
+
python -m elo serve # Iniciar nó interativo
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
## Arquitetura
|
|
70
|
+
|
|
71
|
+
```
|
|
72
|
+
┌──────────────────┐ TCP/JSON ┌──────────────────┐
|
|
73
|
+
│ Node A │◄──────────────►│ Node B │
|
|
74
|
+
│ ed25519 key │ │ ed25519 key │
|
|
75
|
+
│ Capabilities │ │ Capabilities │
|
|
76
|
+
│ Interests │ │ Interests │
|
|
77
|
+
└──────────────────┘ └──────────────────┘
|
|
78
|
+
│ │
|
|
79
|
+
│ Tracker (opcional) │
|
|
80
|
+
└───────────── DHT ────────────────┘
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
Cada nó:
|
|
84
|
+
1. Gera identidade ed25519 na primeira execução
|
|
85
|
+
2. Escuta em uma porta TCP
|
|
86
|
+
3. Anuncia capacidades (ex: "analyst", "web-search")
|
|
87
|
+
4. Descobre outros nós via tracker compartilhado ou peers manuais
|
|
88
|
+
5. Troca mensagens assinadas (tasks, results, events)
|
|
89
|
+
|
|
90
|
+
## Compatibilidade
|
|
91
|
+
|
|
92
|
+
- Python 3.11+
|
|
93
|
+
- Linux, macOS, Windows
|
|
94
|
+
|
|
95
|
+
## Desenvolvimento
|
|
96
|
+
|
|
97
|
+
```bash
|
|
98
|
+
git clone https://github.com/xalq/elo
|
|
99
|
+
cd elo/py
|
|
100
|
+
pip install -e ".[dev]"
|
|
101
|
+
pytest
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
## Projetos Relacionados
|
|
105
|
+
|
|
106
|
+
- [Hermes Agent](https://hermes-agent.nousresearch.com) — runtime de agentes autônomos
|
|
107
|
+
- [Honcho](https://github.com/argmax-inc/honcho) — memória persistente para agentes
|
elo_node-0.4.0/README.md
ADDED
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Elo Node — Malha P2P para Agentes de IA
|
|
2
|
+
|
|
3
|
+
**Zero infraestrutura. Um processo. Uma porta TCP. Uma chave ed25519.**
|
|
4
|
+
|
|
5
|
+
Elo é uma malha de mensagens P2P descentralizada para comunicação entre agentes de IA. Sem servidor central, sem Kafka, sem Redis, sem NATS. Apenas TCP direto entre nós.
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install elo-node
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
```python
|
|
12
|
+
import asyncio
|
|
13
|
+
from elo import Node
|
|
14
|
+
|
|
15
|
+
async def main():
|
|
16
|
+
node = Node("meu-agente", port=7878)
|
|
17
|
+
await node.connect()
|
|
18
|
+
await node.register(agents=["analyst"], tools=["web-search"])
|
|
19
|
+
|
|
20
|
+
@node.on_task
|
|
21
|
+
async def handle(task):
|
|
22
|
+
return {"result": f"processed by {node.node_id}"}
|
|
23
|
+
|
|
24
|
+
await node.run()
|
|
25
|
+
|
|
26
|
+
asyncio.run(main())
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Recursos
|
|
30
|
+
|
|
31
|
+
- **P2P descentralizado** — descoberta via tracker público ou DHT Kademlia
|
|
32
|
+
- **Assinatura ed25519** — identidade criptográfica, mensagens autenticadas
|
|
33
|
+
- **Capabilities** — publish/subscribe de capacidades entre nós
|
|
34
|
+
- **Zero infra** — sem Kafka, Redis, NATS, ou servidor central
|
|
35
|
+
- **CLI nativo** — `python -m elo serve`, `status`, `init`, `id`
|
|
36
|
+
|
|
37
|
+
## CLI
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
python -m elo status # Node ID, hash, chaves
|
|
41
|
+
python -m elo id # Apenas o node_id
|
|
42
|
+
python -m elo pubkey # Chave pública (hex + b64)
|
|
43
|
+
python -m elo init # Gerar identidade persistente
|
|
44
|
+
python -m elo serve # Iniciar nó interativo
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
## Arquitetura
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
┌──────────────────┐ TCP/JSON ┌──────────────────┐
|
|
51
|
+
│ Node A │◄──────────────►│ Node B │
|
|
52
|
+
│ ed25519 key │ │ ed25519 key │
|
|
53
|
+
│ Capabilities │ │ Capabilities │
|
|
54
|
+
│ Interests │ │ Interests │
|
|
55
|
+
└──────────────────┘ └──────────────────┘
|
|
56
|
+
│ │
|
|
57
|
+
│ Tracker (opcional) │
|
|
58
|
+
└───────────── DHT ────────────────┘
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Cada nó:
|
|
62
|
+
1. Gera identidade ed25519 na primeira execução
|
|
63
|
+
2. Escuta em uma porta TCP
|
|
64
|
+
3. Anuncia capacidades (ex: "analyst", "web-search")
|
|
65
|
+
4. Descobre outros nós via tracker compartilhado ou peers manuais
|
|
66
|
+
5. Troca mensagens assinadas (tasks, results, events)
|
|
67
|
+
|
|
68
|
+
## Compatibilidade
|
|
69
|
+
|
|
70
|
+
- Python 3.11+
|
|
71
|
+
- Linux, macOS, Windows
|
|
72
|
+
|
|
73
|
+
## Desenvolvimento
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
git clone https://github.com/xalq/elo
|
|
77
|
+
cd elo/py
|
|
78
|
+
pip install -e ".[dev]"
|
|
79
|
+
pytest
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Projetos Relacionados
|
|
83
|
+
|
|
84
|
+
- [Hermes Agent](https://hermes-agent.nousresearch.com) — runtime de agentes autônomos
|
|
85
|
+
- [Honcho](https://github.com/argmax-inc/honcho) — memória persistente para agentes
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Elo — malha P2P de mensagens para agentes de IA.
|
|
3
|
+
|
|
4
|
+
Um processo. Uma porta TCP. Uma chave ed25519.
|
|
5
|
+
Zero infraestrutura externa.
|
|
6
|
+
|
|
7
|
+
Uso:
|
|
8
|
+
from elo import Node
|
|
9
|
+
|
|
10
|
+
node = Node("meu-agente", port=7878)
|
|
11
|
+
await node.connect()
|
|
12
|
+
await node.register(agents=["analyst"], tools=["web-search"])
|
|
13
|
+
|
|
14
|
+
@node.on_task
|
|
15
|
+
async def handle(task):
|
|
16
|
+
return {"result": "ok"}
|
|
17
|
+
|
|
18
|
+
await node.run()
|
|
19
|
+
"""
|
|
20
|
+
|
|
21
|
+
from elo.node import Node
|
|
22
|
+
from elo.security import EphemeralIdentity, generate_and_save_identity, load_identity
|
|
23
|
+
from elo.types import Task, Result, Event, NodeInfo, Capabilities
|
|
24
|
+
|
|
25
|
+
__all__ = [
|
|
26
|
+
"Node", "Task", "Result", "Event", "NodeInfo", "Capabilities",
|
|
27
|
+
"EphemeralIdentity", "generate_and_save_identity", "load_identity",
|
|
28
|
+
]
|
|
29
|
+
__version__ = "0.4.0"
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Elo CLI — gerenciamento de identidade e status do nó.
|
|
2
|
+
|
|
3
|
+
Uso:
|
|
4
|
+
python -m elo status # Status completo (id, chaves)
|
|
5
|
+
python -m elo id # Apenas o node_id
|
|
6
|
+
python -m elo pubkey # Chave pública completa
|
|
7
|
+
python -m elo init # Gera e salva identidade persistente
|
|
8
|
+
python -m elo serve # Inicia um nó interativo
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import asyncio
|
|
15
|
+
import hashlib
|
|
16
|
+
import json
|
|
17
|
+
import sys
|
|
18
|
+
from pathlib import Path
|
|
19
|
+
|
|
20
|
+
from elo.security import (
|
|
21
|
+
EphemeralIdentity,
|
|
22
|
+
generate_and_save_identity,
|
|
23
|
+
load_identity,
|
|
24
|
+
pubkey_to_id,
|
|
25
|
+
DEFAULT_KEY_DIR,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _short_id(node_id: str, n: int = 12) -> str:
|
|
30
|
+
h = hashlib.sha256(node_id.encode()).hexdigest()[:16]
|
|
31
|
+
return f"{node_id[:n]}... (sha256:{h})"
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def cmd_status() -> None:
|
|
35
|
+
"""Exibe o status completo do nó (identidade, chaves)."""
|
|
36
|
+
identity = EphemeralIdentity()
|
|
37
|
+
node_id = identity.node_id
|
|
38
|
+
pubkey_bytes = identity.public_key.public_bytes_raw()
|
|
39
|
+
|
|
40
|
+
print("=" * 52)
|
|
41
|
+
print(" ELO NODE STATUS")
|
|
42
|
+
print("=" * 52)
|
|
43
|
+
print(f" Node ID: {node_id}")
|
|
44
|
+
print(f" Hash: {_short_id(node_id)}")
|
|
45
|
+
print("-" * 52)
|
|
46
|
+
print(f" Algorithm: ed25519")
|
|
47
|
+
print("-" * 52)
|
|
48
|
+
print(f" Public key (hex):")
|
|
49
|
+
print(f" {pubkey_bytes.hex()}")
|
|
50
|
+
print(f" Public key (b64):")
|
|
51
|
+
print(f" {node_id}")
|
|
52
|
+
print("-" * 52)
|
|
53
|
+
|
|
54
|
+
seed_path = DEFAULT_KEY_DIR / "identity.seed"
|
|
55
|
+
if seed_path.exists():
|
|
56
|
+
try:
|
|
57
|
+
priv, x25519 = load_identity()
|
|
58
|
+
pub = priv.public_key()
|
|
59
|
+
saved_id = pubkey_to_id(pub)
|
|
60
|
+
print(f" Persisted: {DEFAULT_KEY_DIR}")
|
|
61
|
+
print(f" Saved ID: {saved_id}")
|
|
62
|
+
except Exception:
|
|
63
|
+
print(f" Persisted: ERROR loading from {DEFAULT_KEY_DIR}")
|
|
64
|
+
else:
|
|
65
|
+
print(f" Persisted: no (ephemeral)")
|
|
66
|
+
print(f" Use 'python -m elo init' to save")
|
|
67
|
+
print(f" Default dir: {DEFAULT_KEY_DIR}")
|
|
68
|
+
|
|
69
|
+
print("=" * 52)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def cmd_id() -> None:
|
|
73
|
+
identity = EphemeralIdentity()
|
|
74
|
+
print(identity.node_id)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def cmd_pubkey() -> None:
|
|
78
|
+
identity = EphemeralIdentity()
|
|
79
|
+
node_id = identity.node_id
|
|
80
|
+
pubkey = identity.public_key.public_bytes_raw()
|
|
81
|
+
print(f"node_id (b64): {node_id}")
|
|
82
|
+
print(f"public (hex): {pubkey.hex()}")
|
|
83
|
+
print(f"algorithm: ed25519")
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def cmd_init() -> None:
|
|
87
|
+
key_dir = DEFAULT_KEY_DIR
|
|
88
|
+
pub, priv = generate_and_save_identity(key_dir)
|
|
89
|
+
node_id = pubkey_to_id(pub)
|
|
90
|
+
print(f"Identity generated and saved to: {key_dir}")
|
|
91
|
+
print(f" identity.seed -- ed25519 private key")
|
|
92
|
+
print()
|
|
93
|
+
print(f"Node ID: {node_id}")
|
|
94
|
+
print(f"Hash: {_short_id(node_id)}")
|
|
95
|
+
print()
|
|
96
|
+
print("!! Keep identity.seed safe -- it is your node's identity.")
|
|
97
|
+
print(" Without it, peers will not recognize this node after restart.")
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def cmd_serve() -> None:
|
|
101
|
+
from elo import Node
|
|
102
|
+
|
|
103
|
+
async def _serve():
|
|
104
|
+
node = Node("elo-cli", port=7878)
|
|
105
|
+
await node.connect()
|
|
106
|
+
await node.register(agents=["echo-cli"])
|
|
107
|
+
|
|
108
|
+
@node.on_task
|
|
109
|
+
async def handle(task):
|
|
110
|
+
print(f"[task] {task.capability}: {task.payload}")
|
|
111
|
+
return {"echo": task.payload, "from": node.node_id}
|
|
112
|
+
|
|
113
|
+
print(f"[elo] serving on port {node.port} | id={_short_id(node.node_id)}")
|
|
114
|
+
print(f"[elo] Press Ctrl+C to stop")
|
|
115
|
+
await node.run()
|
|
116
|
+
|
|
117
|
+
try:
|
|
118
|
+
asyncio.run(_serve())
|
|
119
|
+
except KeyboardInterrupt:
|
|
120
|
+
print("\n[elo] stopped")
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def main() -> None:
|
|
124
|
+
parser = argparse.ArgumentParser(
|
|
125
|
+
prog="elo",
|
|
126
|
+
description="Elo CLI — malha P2P para agentes de IA",
|
|
127
|
+
)
|
|
128
|
+
sub = parser.add_subparsers(dest="command", help="Comandos")
|
|
129
|
+
|
|
130
|
+
sub.add_parser("status", help="Status completo do nó (identidade + chaves)")
|
|
131
|
+
sub.add_parser("id", help="Apenas o node_id")
|
|
132
|
+
sub.add_parser("pubkey", help="Chave pública em formatos úteis")
|
|
133
|
+
sub.add_parser("init", help="Gerar e salvar identidade persistente")
|
|
134
|
+
sub.add_parser("serve", help="Iniciar um nó interativo (porta 7878)")
|
|
135
|
+
|
|
136
|
+
args = parser.parse_args()
|
|
137
|
+
|
|
138
|
+
commands = {
|
|
139
|
+
"status": cmd_status,
|
|
140
|
+
"id": cmd_id,
|
|
141
|
+
"pubkey": cmd_pubkey,
|
|
142
|
+
"init": cmd_init,
|
|
143
|
+
"serve": cmd_serve,
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if args.command in commands:
|
|
147
|
+
commands[args.command]()
|
|
148
|
+
else:
|
|
149
|
+
parser.print_help()
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
if __name__ == "__main__":
|
|
153
|
+
main()
|