intermesh 0.4.1__tar.gz → 0.4.2__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.
- {intermesh-0.4.1 → intermesh-0.4.2}/PKG-INFO +20 -13
- {intermesh-0.4.1 → intermesh-0.4.2}/README.md +19 -12
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/__init__.py +2 -2
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/agent.py +46 -8
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/cli.py +13 -7
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/crypto.py +25 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh.egg-info/PKG-INFO +20 -13
- {intermesh-0.4.1 → intermesh-0.4.2}/pyproject.toml +1 -1
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/adapters.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/admin.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/apikeys.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/approval.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/audit.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/bridge.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/config.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/egress.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/escrow.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/guardrails.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/hardware.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/health.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/hub.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/identity.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/logger.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/message.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/metrics.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/peering.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/pipeline.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/policy.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/ratelimit.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/schema.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/secret.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/signing.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/snapshot.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/store.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh/task.py +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh.egg-info/SOURCES.txt +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh.egg-info/dependency_links.txt +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh.egg-info/entry_points.txt +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh.egg-info/requires.txt +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/intermesh.egg-info/top_level.txt +0 -0
- {intermesh-0.4.1 → intermesh-0.4.2}/setup.cfg +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: intermesh
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Universal coordination protocol for AI agents — E2E encrypted, cross-language, federated
|
|
5
5
|
Author: InterMesh Protocol Authors
|
|
6
6
|
License: Apache-2.0
|
|
@@ -78,19 +78,26 @@ asyncio.run(main())
|
|
|
78
78
|
**Delegate work from an orchestrator:**
|
|
79
79
|
|
|
80
80
|
```python
|
|
81
|
+
import asyncio
|
|
81
82
|
from intermesh import InterMeshAgent
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
# `await` is not valid at module level in Python, so this lives in a
|
|
85
|
+
# coroutine. Copy-pasting it without the wrapper is a SyntaxError, not a
|
|
86
|
+
# runtime error — it fails before anything runs.
|
|
87
|
+
async def main():
|
|
88
|
+
orchestrator = InterMeshAgent(name="lead", roles=["admin"])
|
|
89
|
+
await orchestrator.connect()
|
|
90
|
+
|
|
91
|
+
# Find an agent by capability, then hand it a task
|
|
92
|
+
found = await orchestrator.discover(capabilities=["calculate"])
|
|
93
|
+
result = await orchestrator.submit_task(
|
|
94
|
+
title="Add two numbers",
|
|
95
|
+
assignee=found["agents"][0]["name"],
|
|
96
|
+
input_data={"a": 20, "b": 22},
|
|
97
|
+
)
|
|
98
|
+
print(result) # {"result": 42} — encrypted end-to-end in transit
|
|
85
99
|
|
|
86
|
-
|
|
87
|
-
found = await orchestrator.discover(capabilities=["calculate"])
|
|
88
|
-
result = await orchestrator.submit_task(
|
|
89
|
-
title="Add two numbers",
|
|
90
|
-
assignee=found["agents"][0]["name"],
|
|
91
|
-
input_data={"a": 20, "b": 22},
|
|
92
|
-
)
|
|
93
|
-
# {"result": 42} — encrypted end-to-end in transit
|
|
100
|
+
asyncio.run(main())
|
|
94
101
|
```
|
|
95
102
|
|
|
96
103
|
---
|
|
@@ -106,7 +113,7 @@ A LangChain runnable:
|
|
|
106
113
|
from intermesh import from_langchain
|
|
107
114
|
|
|
108
115
|
agent = from_langchain(my_chain, name="analyst", capabilities=["market_analysis"])
|
|
109
|
-
|
|
116
|
+
agent.run() # connects, then stays in service
|
|
110
117
|
```
|
|
111
118
|
|
|
112
119
|
Anything else — a CrewAI crew, an AutoGen agent, a LlamaIndex engine, or a
|
|
@@ -122,7 +129,7 @@ async def run(data):
|
|
|
122
129
|
return await asyncio.to_thread(lambda: my_crew.kickoff(inputs=data))
|
|
123
130
|
|
|
124
131
|
agent = from_callable(run, name="research", capabilities=["research"])
|
|
125
|
-
|
|
132
|
+
agent.run()
|
|
126
133
|
```
|
|
127
134
|
|
|
128
135
|
Or as a decorator:
|
|
@@ -47,19 +47,26 @@ asyncio.run(main())
|
|
|
47
47
|
**Delegate work from an orchestrator:**
|
|
48
48
|
|
|
49
49
|
```python
|
|
50
|
+
import asyncio
|
|
50
51
|
from intermesh import InterMeshAgent
|
|
51
52
|
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
# `await` is not valid at module level in Python, so this lives in a
|
|
54
|
+
# coroutine. Copy-pasting it without the wrapper is a SyntaxError, not a
|
|
55
|
+
# runtime error — it fails before anything runs.
|
|
56
|
+
async def main():
|
|
57
|
+
orchestrator = InterMeshAgent(name="lead", roles=["admin"])
|
|
58
|
+
await orchestrator.connect()
|
|
59
|
+
|
|
60
|
+
# Find an agent by capability, then hand it a task
|
|
61
|
+
found = await orchestrator.discover(capabilities=["calculate"])
|
|
62
|
+
result = await orchestrator.submit_task(
|
|
63
|
+
title="Add two numbers",
|
|
64
|
+
assignee=found["agents"][0]["name"],
|
|
65
|
+
input_data={"a": 20, "b": 22},
|
|
66
|
+
)
|
|
67
|
+
print(result) # {"result": 42} — encrypted end-to-end in transit
|
|
54
68
|
|
|
55
|
-
|
|
56
|
-
found = await orchestrator.discover(capabilities=["calculate"])
|
|
57
|
-
result = await orchestrator.submit_task(
|
|
58
|
-
title="Add two numbers",
|
|
59
|
-
assignee=found["agents"][0]["name"],
|
|
60
|
-
input_data={"a": 20, "b": 22},
|
|
61
|
-
)
|
|
62
|
-
# {"result": 42} — encrypted end-to-end in transit
|
|
69
|
+
asyncio.run(main())
|
|
63
70
|
```
|
|
64
71
|
|
|
65
72
|
---
|
|
@@ -75,7 +82,7 @@ A LangChain runnable:
|
|
|
75
82
|
from intermesh import from_langchain
|
|
76
83
|
|
|
77
84
|
agent = from_langchain(my_chain, name="analyst", capabilities=["market_analysis"])
|
|
78
|
-
|
|
85
|
+
agent.run() # connects, then stays in service
|
|
79
86
|
```
|
|
80
87
|
|
|
81
88
|
Anything else — a CrewAI crew, an AutoGen agent, a LlamaIndex engine, or a
|
|
@@ -91,7 +98,7 @@ async def run(data):
|
|
|
91
98
|
return await asyncio.to_thread(lambda: my_crew.kickoff(inputs=data))
|
|
92
99
|
|
|
93
100
|
agent = from_callable(run, name="research", capabilities=["research"])
|
|
94
|
-
|
|
101
|
+
agent.run()
|
|
95
102
|
```
|
|
96
103
|
|
|
97
104
|
Or as a decorator:
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
from intermesh.agent import InterMeshAgent
|
|
1
|
+
from intermesh.agent import InterMeshAgent, EncryptionMismatch
|
|
2
2
|
from intermesh.message import MessageType, InterMeshMessage
|
|
3
3
|
from intermesh.identity import AgentIdentity
|
|
4
4
|
from intermesh.task import InterMeshTask, TaskStatus
|
|
@@ -53,4 +53,4 @@ try:
|
|
|
53
53
|
except ImportError:
|
|
54
54
|
pass
|
|
55
55
|
|
|
56
|
-
__version__ = "0.4.
|
|
56
|
+
__version__ = "0.4.2"
|
|
@@ -10,8 +10,17 @@ import websockets
|
|
|
10
10
|
from intermesh.message import MessageType, InterMeshMessage
|
|
11
11
|
from intermesh.identity import AgentIdentity
|
|
12
12
|
from intermesh.task import InterMeshTask, TaskStatus
|
|
13
|
-
from intermesh.crypto import generate_keypair, get_public_key_pem, encrypt_for,
|
|
13
|
+
from intermesh.crypto import (generate_keypair, get_public_key_pem, encrypt_for,
|
|
14
|
+
decrypt_with, looks_encrypted)
|
|
14
15
|
from intermesh.egress import EgressPolicy, apply_egress
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class EncryptionMismatch(ValueError):
|
|
19
|
+
"""Les deux côtés ne s'accordent pas sur le chiffrement.
|
|
20
|
+
|
|
21
|
+
Levée plutôt que de rendre du texte chiffré à un gestionnaire, qui le
|
|
22
|
+
traiterait comme des données et rendrait un résultat faux.
|
|
23
|
+
"""
|
|
15
24
|
from intermesh.schema import SchemaRegistry, default_registry
|
|
16
25
|
|
|
17
26
|
|
|
@@ -329,10 +338,41 @@ class InterMeshAgent:
|
|
|
329
338
|
decrypted = decrypt_with(self._private_key, encrypted_content)
|
|
330
339
|
try: return json.loads(decrypted)
|
|
331
340
|
except json.JSONDecodeError: return decrypted
|
|
332
|
-
except Exception:
|
|
341
|
+
except Exception as exc:
|
|
342
|
+
# Une charge reconnaissable comme chiffrée mais qu'on n'arrive pas
|
|
343
|
+
# à ouvrir n'est pas une chaîne ordinaire : la rendre telle quelle
|
|
344
|
+
# la ferait traiter comme des données.
|
|
345
|
+
if looks_encrypted(encrypted_content):
|
|
346
|
+
raise EncryptionMismatch(
|
|
347
|
+
"Charge chiffrée illisible : elle a été chiffrée pour une autre "
|
|
348
|
+
"clé publique que celle de cet agent. Un agent qui se reconnecte "
|
|
349
|
+
"sous le même nom change de clé ; l'émetteur doit alors la "
|
|
350
|
+
"redemander (WHO_IS) avant de chiffrer."
|
|
351
|
+
) from exc
|
|
333
352
|
try: return json.loads(encrypted_content)
|
|
334
353
|
except json.JSONDecodeError: return encrypted_content
|
|
335
354
|
|
|
355
|
+
def _unwrap_incoming(self, value, what: str):
|
|
356
|
+
"""Rend la charge exploitable, ou refuse franchement.
|
|
357
|
+
|
|
358
|
+
Le piège que ceci ferme : avec `encrypt=False`, aucun déchiffrement
|
|
359
|
+
n'était tenté et le texte chiffré arrivait tel quel au gestionnaire,
|
|
360
|
+
qui le traitait comme des données. La tâche « réussissait » et
|
|
361
|
+
rendait un résultat faux — le pire mode de défaillance pour un
|
|
362
|
+
produit dont l'argument est le chiffrement de bout en bout.
|
|
363
|
+
"""
|
|
364
|
+
if self.encrypt:
|
|
365
|
+
return self._decrypt_content(value) if isinstance(value, str) else value
|
|
366
|
+
|
|
367
|
+
if looks_encrypted(value):
|
|
368
|
+
raise EncryptionMismatch(
|
|
369
|
+
f"{what} chiffré reçu alors que le chiffrement est désactivé sur cet "
|
|
370
|
+
f"agent (encrypt=False). Activez-le ici, ou désactivez-le chez "
|
|
371
|
+
f"l'émetteur : les deux côtés doivent s'accorder. Sans ce refus, le "
|
|
372
|
+
f"texte chiffré serait traité comme des données."
|
|
373
|
+
)
|
|
374
|
+
return value
|
|
375
|
+
|
|
336
376
|
async def _respond_to_request(self, msg) -> None:
|
|
337
377
|
"""Exécute le gestionnaire de requêtes et renvoie la réponse.
|
|
338
378
|
|
|
@@ -410,8 +450,7 @@ class InterMeshAgent:
|
|
|
410
450
|
asyncio.create_task(self._respond_to_request(msg))
|
|
411
451
|
|
|
412
452
|
elif msg.type == MessageType.MESSAGE:
|
|
413
|
-
content = msg.content
|
|
414
|
-
if self.encrypt and isinstance(content, str): content = self._decrypt_content(content)
|
|
453
|
+
content = self._unwrap_incoming(msg.content, "Message")
|
|
415
454
|
msg.content = content
|
|
416
455
|
if self._message_handler:
|
|
417
456
|
if inspect.iscoroutinefunction(self._message_handler): await self._message_handler(msg)
|
|
@@ -448,11 +487,10 @@ class InterMeshAgent:
|
|
|
448
487
|
task.update_status(TaskStatus.RUNNING)
|
|
449
488
|
await self.ws.send(InterMeshMessage(type=MessageType.TASK_UPDATE, sender=self.qualified_name, content=task.to_dict(), token=self.token).to_json())
|
|
450
489
|
|
|
451
|
-
decrypted_input = task.input_data
|
|
452
|
-
if self.encrypt and isinstance(decrypted_input, str):
|
|
453
|
-
decrypted_input = self._decrypt_content(decrypted_input)
|
|
454
|
-
|
|
455
490
|
try:
|
|
491
|
+
# Dans le bloc : un refus de déchiffrement doit rapporter la tâche
|
|
492
|
+
# en échec avec sa raison, pas remonter jusqu'à la boucle d'écoute.
|
|
493
|
+
decrypted_input = self._unwrap_incoming(task.input_data, "Contenu de tâche")
|
|
456
494
|
if self._task_handler:
|
|
457
495
|
output = await self._task_handler(decrypted_input, task) if inspect.iscoroutinefunction(self._task_handler) else self._task_handler(decrypted_input, task)
|
|
458
496
|
else:
|
|
@@ -13,17 +13,23 @@ from intermesh.crypto import generate_keypair, get_public_key_pem
|
|
|
13
13
|
from intermesh.identity import AgentIdentity
|
|
14
14
|
|
|
15
15
|
|
|
16
|
+
# Le dessin vit dans une chaîne brute : ses antislashs sont des traits,
|
|
17
|
+
# pas des séquences d'échappement. Les laisser dans une f-string produisait
|
|
18
|
+
# un SyntaxWarning à chaque appel de la CLI — un avertissement affiché à
|
|
19
|
+
# chaque commande coûte plus cher que le dessin ne rapporte.
|
|
20
|
+
_LOGO = r""" _____ __________________ __ ______________ __ __
|
|
21
|
+
/ _/ | / /_ __/ ____/ __ \/ |/ / ____/ ___// / / /
|
|
22
|
+
/ // |/ / / / / __/ / /_/ / /|_/ / __/ \__ \/ /_/ /
|
|
23
|
+
_/ // /| / / / / /___/ _, _/ / / / /___ ___/ / __ /
|
|
24
|
+
/___/_/ |_/ /_/ /_____/_/ |_/_/ /_/_____//____/_/ /_/"""
|
|
25
|
+
|
|
26
|
+
|
|
16
27
|
def print_banner():
|
|
17
28
|
# La version vient du paquet : une chaîne en dur ici finit toujours
|
|
18
29
|
# par mentir après quelques releases.
|
|
19
30
|
from intermesh import __version__
|
|
20
|
-
print(f""
|
|
21
|
-
|
|
22
|
-
/ | / / ____/ |/ _/ | INTERMESH PROTOCOL
|
|
23
|
-
/ |/ / __/ / /| |/ // /| | Universal Agent Infrastructure
|
|
24
|
-
/ /| / /___/ ___ / // ___ | CLI Developer Tool v{__version__}
|
|
25
|
-
/_/ |_/_____/_/ |_/___/_/ |_|\033[0m
|
|
26
|
-
""")
|
|
31
|
+
print(f"\n\033[36m{_LOGO}\033[0m\n")
|
|
32
|
+
print(f" Universal Agent Infrastructure · CLI v{__version__}\n")
|
|
27
33
|
|
|
28
34
|
|
|
29
35
|
def run_security_check(args):
|
|
@@ -60,6 +60,31 @@ def encrypt_for(recipient_public_key_pem: str, plaintext: str) -> str:
|
|
|
60
60
|
return base64.b64encode(json.dumps(envelope).encode()).decode()
|
|
61
61
|
|
|
62
62
|
|
|
63
|
+
ENVELOPE_KEYS = {"ek", "n", "ct"}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
def looks_encrypted(value) -> bool:
|
|
67
|
+
"""Reconnaît une charge produite par `encrypt_for`, sans la déchiffrer.
|
|
68
|
+
|
|
69
|
+
Sert à distinguer « cet agent reçoit du texte chiffré qu'il ne sait pas
|
|
70
|
+
ouvrir » de « cet agent reçoit une chaîne ordinaire ». Sans cette
|
|
71
|
+
distinction, un agent configuré sans chiffrement traitait le texte
|
|
72
|
+
chiffré comme des données : la tâche paraissait réussir et rendait un
|
|
73
|
+
résultat faux, ce qui est pire qu'un échec.
|
|
74
|
+
|
|
75
|
+
Le test est structurel — base64 d'un objet JSON portant exactement les
|
|
76
|
+
trois champs de l'enveloppe — et n'exige aucune clé. Une chaîne
|
|
77
|
+
ordinaire, même en base64, ne produit pas cette forme.
|
|
78
|
+
"""
|
|
79
|
+
if not isinstance(value, str) or len(value) < 32:
|
|
80
|
+
return False
|
|
81
|
+
try:
|
|
82
|
+
envelope = json.loads(base64.b64decode(value, validate=True))
|
|
83
|
+
except Exception:
|
|
84
|
+
return False
|
|
85
|
+
return isinstance(envelope, dict) and set(envelope) == ENVELOPE_KEYS
|
|
86
|
+
|
|
87
|
+
|
|
63
88
|
def decrypt_with(private_key, encrypted_b64: str) -> str:
|
|
64
89
|
"""Déchiffrement hybride RSA-OAEP + AES-256-GCM."""
|
|
65
90
|
envelope = json.loads(base64.b64decode(encrypted_b64))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: intermesh
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.2
|
|
4
4
|
Summary: Universal coordination protocol for AI agents — E2E encrypted, cross-language, federated
|
|
5
5
|
Author: InterMesh Protocol Authors
|
|
6
6
|
License: Apache-2.0
|
|
@@ -78,19 +78,26 @@ asyncio.run(main())
|
|
|
78
78
|
**Delegate work from an orchestrator:**
|
|
79
79
|
|
|
80
80
|
```python
|
|
81
|
+
import asyncio
|
|
81
82
|
from intermesh import InterMeshAgent
|
|
82
83
|
|
|
83
|
-
|
|
84
|
-
|
|
84
|
+
# `await` is not valid at module level in Python, so this lives in a
|
|
85
|
+
# coroutine. Copy-pasting it without the wrapper is a SyntaxError, not a
|
|
86
|
+
# runtime error — it fails before anything runs.
|
|
87
|
+
async def main():
|
|
88
|
+
orchestrator = InterMeshAgent(name="lead", roles=["admin"])
|
|
89
|
+
await orchestrator.connect()
|
|
90
|
+
|
|
91
|
+
# Find an agent by capability, then hand it a task
|
|
92
|
+
found = await orchestrator.discover(capabilities=["calculate"])
|
|
93
|
+
result = await orchestrator.submit_task(
|
|
94
|
+
title="Add two numbers",
|
|
95
|
+
assignee=found["agents"][0]["name"],
|
|
96
|
+
input_data={"a": 20, "b": 22},
|
|
97
|
+
)
|
|
98
|
+
print(result) # {"result": 42} — encrypted end-to-end in transit
|
|
85
99
|
|
|
86
|
-
|
|
87
|
-
found = await orchestrator.discover(capabilities=["calculate"])
|
|
88
|
-
result = await orchestrator.submit_task(
|
|
89
|
-
title="Add two numbers",
|
|
90
|
-
assignee=found["agents"][0]["name"],
|
|
91
|
-
input_data={"a": 20, "b": 22},
|
|
92
|
-
)
|
|
93
|
-
# {"result": 42} — encrypted end-to-end in transit
|
|
100
|
+
asyncio.run(main())
|
|
94
101
|
```
|
|
95
102
|
|
|
96
103
|
---
|
|
@@ -106,7 +113,7 @@ A LangChain runnable:
|
|
|
106
113
|
from intermesh import from_langchain
|
|
107
114
|
|
|
108
115
|
agent = from_langchain(my_chain, name="analyst", capabilities=["market_analysis"])
|
|
109
|
-
|
|
116
|
+
agent.run() # connects, then stays in service
|
|
110
117
|
```
|
|
111
118
|
|
|
112
119
|
Anything else — a CrewAI crew, an AutoGen agent, a LlamaIndex engine, or a
|
|
@@ -122,7 +129,7 @@ async def run(data):
|
|
|
122
129
|
return await asyncio.to_thread(lambda: my_crew.kickoff(inputs=data))
|
|
123
130
|
|
|
124
131
|
agent = from_callable(run, name="research", capabilities=["research"])
|
|
125
|
-
|
|
132
|
+
agent.run()
|
|
126
133
|
```
|
|
127
134
|
|
|
128
135
|
Or as a decorator:
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "intermesh"
|
|
7
|
-
version = "0.4.
|
|
7
|
+
version = "0.4.2"
|
|
8
8
|
description = "Universal coordination protocol for AI agents — E2E encrypted, cross-language, federated"
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
license = {text = "Apache-2.0"}
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|