nmp-protocol 1.0.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.
- nmp_protocol-1.0.0/PKG-INFO +94 -0
- nmp_protocol-1.0.0/README.md +67 -0
- nmp_protocol-1.0.0/nmp/__init__.py +7 -0
- nmp_protocol-1.0.0/nmp/message.py +277 -0
- nmp_protocol-1.0.0/pyproject.toml +49 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: nmp-protocol
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: NMP (NeuroMessage Protocol) — Open communication protocol for multi-agent systems with behavioral modulation signals.
|
|
5
|
+
Project-URL: Homepage, https://nmp-protocol.org
|
|
6
|
+
Project-URL: Documentation, https://nmp-protocol.org
|
|
7
|
+
Project-URL: Repository, https://github.com/AIP-Labs/nmp-protocol
|
|
8
|
+
Project-URL: Specification, https://nmp-protocol.org
|
|
9
|
+
Project-URL: Bug Tracker, https://github.com/AIP-Labs/nmp-protocol/issues
|
|
10
|
+
Author-email: Renato Aparecido Gomes <renato@koloni.dev>
|
|
11
|
+
License-Expression: Apache-2.0
|
|
12
|
+
Keywords: a2a,active-inference,agent-communication,behavioral-signals,mcp,multi-agent,neuro-message-protocol,nmp,protocol,somatic-markers
|
|
13
|
+
Classifier: Development Status :: 4 - Beta
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: License :: OSI Approved :: Apache Software License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
22
|
+
Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
24
|
+
Classifier: Topic :: System :: Networking
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
|
|
28
|
+
# NMP — NeuroMessage Protocol
|
|
29
|
+
|
|
30
|
+
**Open communication protocol for multi-agent AI systems with behavioral modulation.**
|
|
31
|
+
|
|
32
|
+
NMP is to agent communication what HTTP is to web communication — but with built-in behavioral signals. While MCP handles tool access and A2A handles agent coordination, NMP carries **intention**, **modulation**, and **accountability** alongside data.
|
|
33
|
+
|
|
34
|
+
## Quick Start
|
|
35
|
+
|
|
36
|
+
```bash
|
|
37
|
+
pip install nmp-protocol
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
```python
|
|
41
|
+
from nmp import create_message, TrustLevel, validate_message
|
|
42
|
+
|
|
43
|
+
# Create an NMP message with behavioral signals
|
|
44
|
+
msg = create_message(
|
|
45
|
+
from_agent="router",
|
|
46
|
+
to_agent="legal-analyzer",
|
|
47
|
+
content="Analyze this contract for risk clauses",
|
|
48
|
+
trust=TrustLevel.VERIFIED,
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
# Modulate behavioral signals
|
|
52
|
+
msg.signals.urgency = 0.7 # Time-critical
|
|
53
|
+
msg.signals.quality = 0.9 # High analysis depth needed
|
|
54
|
+
msg.signals.inhibition = 0.0 # No blocking needed
|
|
55
|
+
|
|
56
|
+
# Validate
|
|
57
|
+
errors = validate_message(msg)
|
|
58
|
+
assert not errors
|
|
59
|
+
|
|
60
|
+
# Serialize
|
|
61
|
+
json_str = msg.to_json()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## 6 Behavioral Signals
|
|
65
|
+
|
|
66
|
+
| Signal | Range | Biological Analog | Purpose |
|
|
67
|
+
|--------|-------|-------------------|---------|
|
|
68
|
+
| `urgency` | [0, 1] | Noradrenaline | Processing speed (Yerkes-Dodson curve) |
|
|
69
|
+
| `quality` | [0, 1] | Serotonin | Analysis depth |
|
|
70
|
+
| `inhibition` | [0, 1] | GABA | Blocking / pausing |
|
|
71
|
+
| `activation` | [0, 1] | Glutamate | Processing intensity |
|
|
72
|
+
| `focus` | [0, 1] | Acetylcholine | Context narrowing |
|
|
73
|
+
| `reward` | [-0.5, 1] | Dopamine | Feedback signal |
|
|
74
|
+
|
|
75
|
+
## Trust Levels
|
|
76
|
+
|
|
77
|
+
Trust can only decrease through a pipeline: `min(current_trust, source_trust)`
|
|
78
|
+
|
|
79
|
+
| Level | Value | Meaning |
|
|
80
|
+
|-------|-------|---------|
|
|
81
|
+
| `HOSTILE` | 0 | Known malicious source |
|
|
82
|
+
| `UNTRUSTED` | 1 | Unknown / unverified |
|
|
83
|
+
| `VERIFIED` | 2 | Authenticated but not fully trusted |
|
|
84
|
+
| `TRUSTED` | 3 | Fully trusted internal agent |
|
|
85
|
+
|
|
86
|
+
## Specification
|
|
87
|
+
|
|
88
|
+
Full spec: [nmp-protocol.org](https://nmp-protocol.org)
|
|
89
|
+
|
|
90
|
+
Reference implementations: Python (this package), [TypeScript](https://www.npmjs.com/package/nmp-protocol), [Go](https://pkg.go.dev/github.com/AIP-Labs/nmp-protocol)
|
|
91
|
+
|
|
92
|
+
## License
|
|
93
|
+
|
|
94
|
+
Apache 2.0. Spec licensed under CC BY-SA 4.0.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# NMP — NeuroMessage Protocol
|
|
2
|
+
|
|
3
|
+
**Open communication protocol for multi-agent AI systems with behavioral modulation.**
|
|
4
|
+
|
|
5
|
+
NMP is to agent communication what HTTP is to web communication — but with built-in behavioral signals. While MCP handles tool access and A2A handles agent coordination, NMP carries **intention**, **modulation**, and **accountability** alongside data.
|
|
6
|
+
|
|
7
|
+
## Quick Start
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pip install nmp-protocol
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
from nmp import create_message, TrustLevel, validate_message
|
|
15
|
+
|
|
16
|
+
# Create an NMP message with behavioral signals
|
|
17
|
+
msg = create_message(
|
|
18
|
+
from_agent="router",
|
|
19
|
+
to_agent="legal-analyzer",
|
|
20
|
+
content="Analyze this contract for risk clauses",
|
|
21
|
+
trust=TrustLevel.VERIFIED,
|
|
22
|
+
)
|
|
23
|
+
|
|
24
|
+
# Modulate behavioral signals
|
|
25
|
+
msg.signals.urgency = 0.7 # Time-critical
|
|
26
|
+
msg.signals.quality = 0.9 # High analysis depth needed
|
|
27
|
+
msg.signals.inhibition = 0.0 # No blocking needed
|
|
28
|
+
|
|
29
|
+
# Validate
|
|
30
|
+
errors = validate_message(msg)
|
|
31
|
+
assert not errors
|
|
32
|
+
|
|
33
|
+
# Serialize
|
|
34
|
+
json_str = msg.to_json()
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
## 6 Behavioral Signals
|
|
38
|
+
|
|
39
|
+
| Signal | Range | Biological Analog | Purpose |
|
|
40
|
+
|--------|-------|-------------------|---------|
|
|
41
|
+
| `urgency` | [0, 1] | Noradrenaline | Processing speed (Yerkes-Dodson curve) |
|
|
42
|
+
| `quality` | [0, 1] | Serotonin | Analysis depth |
|
|
43
|
+
| `inhibition` | [0, 1] | GABA | Blocking / pausing |
|
|
44
|
+
| `activation` | [0, 1] | Glutamate | Processing intensity |
|
|
45
|
+
| `focus` | [0, 1] | Acetylcholine | Context narrowing |
|
|
46
|
+
| `reward` | [-0.5, 1] | Dopamine | Feedback signal |
|
|
47
|
+
|
|
48
|
+
## Trust Levels
|
|
49
|
+
|
|
50
|
+
Trust can only decrease through a pipeline: `min(current_trust, source_trust)`
|
|
51
|
+
|
|
52
|
+
| Level | Value | Meaning |
|
|
53
|
+
|-------|-------|---------|
|
|
54
|
+
| `HOSTILE` | 0 | Known malicious source |
|
|
55
|
+
| `UNTRUSTED` | 1 | Unknown / unverified |
|
|
56
|
+
| `VERIFIED` | 2 | Authenticated but not fully trusted |
|
|
57
|
+
| `TRUSTED` | 3 | Fully trusted internal agent |
|
|
58
|
+
|
|
59
|
+
## Specification
|
|
60
|
+
|
|
61
|
+
Full spec: [nmp-protocol.org](https://nmp-protocol.org)
|
|
62
|
+
|
|
63
|
+
Reference implementations: Python (this package), [TypeScript](https://www.npmjs.com/package/nmp-protocol), [Go](https://pkg.go.dev/github.com/AIP-Labs/nmp-protocol)
|
|
64
|
+
|
|
65
|
+
## License
|
|
66
|
+
|
|
67
|
+
Apache 2.0. Spec licensed under CC BY-SA 4.0.
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"""NMP v1.0 — Standalone Python Reference Implementation."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
import json, math, uuid, time
|
|
4
|
+
from dataclasses import dataclass, field, asdict
|
|
5
|
+
from enum import IntEnum
|
|
6
|
+
from typing import Any
|
|
7
|
+
|
|
8
|
+
class TrustLevel(IntEnum):
|
|
9
|
+
HOSTILE = 0
|
|
10
|
+
UNTRUSTED = 1
|
|
11
|
+
VERIFIED = 2
|
|
12
|
+
TRUSTED = 3
|
|
13
|
+
|
|
14
|
+
class SystemMode(IntEnum):
|
|
15
|
+
REFLEX = 0
|
|
16
|
+
FAST = 1
|
|
17
|
+
STANDARD = 2
|
|
18
|
+
DEEP = 3
|
|
19
|
+
|
|
20
|
+
@dataclass
|
|
21
|
+
class NMPSignals:
|
|
22
|
+
urgency: float = 0.5
|
|
23
|
+
quality: float = 0.5
|
|
24
|
+
inhibition: float = 0.0
|
|
25
|
+
activation: float = 0.5
|
|
26
|
+
focus: float = 0.5
|
|
27
|
+
reward: float = 0.0
|
|
28
|
+
|
|
29
|
+
def __post_init__(self):
|
|
30
|
+
self.urgency = max(0.0, min(1.0, self.urgency))
|
|
31
|
+
self.quality = max(0.0, min(1.0, self.quality))
|
|
32
|
+
self.inhibition = max(0.0, min(1.0, self.inhibition))
|
|
33
|
+
self.activation = max(0.0, min(1.0, self.activation))
|
|
34
|
+
self.focus = max(0.0, min(1.0, self.focus))
|
|
35
|
+
self.reward = max(-0.5, min(1.0, self.reward))
|
|
36
|
+
|
|
37
|
+
def validate(self) -> list[str]:
|
|
38
|
+
errors = []
|
|
39
|
+
for field_name in ("urgency", "quality", "inhibition", "activation", "focus"):
|
|
40
|
+
val = getattr(self, field_name)
|
|
41
|
+
if not 0.0 <= val <= 1.0:
|
|
42
|
+
errors.append(f"{field_name} must be 0.0-1.0, got {val}")
|
|
43
|
+
if not -0.5 <= self.reward <= 1.0:
|
|
44
|
+
errors.append(f"reward must be -0.5-1.0, got {self.reward}")
|
|
45
|
+
return errors
|
|
46
|
+
|
|
47
|
+
def urgency_performance(self) -> float:
|
|
48
|
+
"""Yerkes-Dodson inverted-U curve: peak at urgency=0.5."""
|
|
49
|
+
return math.exp(-((self.urgency - 0.5) ** 2) / (2 * 0.15 ** 2))
|
|
50
|
+
|
|
51
|
+
def detect_conflicts(self) -> list[str]:
|
|
52
|
+
"""Detect signal conflicts per NMP spec."""
|
|
53
|
+
conflicts = []
|
|
54
|
+
if self.urgency > 0.7 and self.quality > 0.7:
|
|
55
|
+
conflicts.append("urgency_vs_quality")
|
|
56
|
+
if self.inhibition > 0.5 and self.activation > 0.5:
|
|
57
|
+
conflicts.append("inhibition_vs_activation")
|
|
58
|
+
if self.urgency > 0.7 and self.reward > 0.7:
|
|
59
|
+
conflicts.append("impulsive_decision")
|
|
60
|
+
return conflicts
|
|
61
|
+
|
|
62
|
+
def to_dict(self) -> dict[str, float]:
|
|
63
|
+
return asdict(self)
|
|
64
|
+
|
|
65
|
+
@classmethod
|
|
66
|
+
def from_dict(cls, d: dict[str, float]) -> NMPSignals:
|
|
67
|
+
return cls(**{k: v for k, v in d.items() if k in cls.__dataclass_fields__})
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
@dataclass
|
|
71
|
+
class Budget:
|
|
72
|
+
max_tokens: int = 5000
|
|
73
|
+
tokens_used: int = 0
|
|
74
|
+
system_mode: int = 1 # SystemMode value
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def usage_ratio(self) -> float:
|
|
78
|
+
return self.tokens_used / max(1, self.max_tokens)
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def exhausted(self) -> bool:
|
|
82
|
+
return self.tokens_used >= self.max_tokens * 0.95
|
|
83
|
+
|
|
84
|
+
def consume(self, tokens: int) -> None:
|
|
85
|
+
self.tokens_used += tokens
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
@dataclass
|
|
89
|
+
class TraceEntry:
|
|
90
|
+
agent: str = ""
|
|
91
|
+
action: str = ""
|
|
92
|
+
tokens: int = 0
|
|
93
|
+
timestamp: str = field(default_factory=lambda: time.strftime("%Y-%m-%dT%H:%M:%SZ"))
|
|
94
|
+
signals_snapshot: dict[str, float] = field(default_factory=dict)
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class NMPMessage:
|
|
99
|
+
version: str = "1.0"
|
|
100
|
+
id: str = field(default_factory=lambda: uuid.uuid4().hex[:12])
|
|
101
|
+
trace_id: str = field(default_factory=lambda: uuid.uuid4().hex[:16])
|
|
102
|
+
timestamp: str = field(default_factory=lambda: time.strftime("%Y-%m-%dT%H:%M:%SZ"))
|
|
103
|
+
from_agent: str = ""
|
|
104
|
+
to_agent: str = ""
|
|
105
|
+
tenant_id: str = "default"
|
|
106
|
+
trust_level: int = 3
|
|
107
|
+
trust_source: str = "user_input"
|
|
108
|
+
payload_type: str = "task"
|
|
109
|
+
content: str = ""
|
|
110
|
+
context: dict[str, Any] = field(default_factory=dict)
|
|
111
|
+
signals: NMPSignals = field(default_factory=NMPSignals)
|
|
112
|
+
budget: Budget = field(default_factory=Budget)
|
|
113
|
+
trace: list[TraceEntry] = field(default_factory=list)
|
|
114
|
+
results: dict[str, Any] = field(default_factory=dict)
|
|
115
|
+
|
|
116
|
+
def add_trace(self, agent: str, action: str, tokens: int = 0):
|
|
117
|
+
entry = TraceEntry(
|
|
118
|
+
agent=agent, action=action, tokens=tokens,
|
|
119
|
+
signals_snapshot=self.signals.to_dict(),
|
|
120
|
+
)
|
|
121
|
+
self.trace.append(entry)
|
|
122
|
+
self.budget.consume(tokens)
|
|
123
|
+
|
|
124
|
+
def lower_trust(self, new_level: int | TrustLevel) -> None:
|
|
125
|
+
new_val = int(new_level)
|
|
126
|
+
if new_val > self.trust_level:
|
|
127
|
+
raise ValueError(f"Cannot raise trust from {self.trust_level} to {new_val}")
|
|
128
|
+
self.trust_level = new_val
|
|
129
|
+
|
|
130
|
+
def to_json(self) -> str:
|
|
131
|
+
return json.dumps(self.to_dict(), indent=2, ensure_ascii=False, default=str)
|
|
132
|
+
|
|
133
|
+
def to_dict(self) -> dict[str, Any]:
|
|
134
|
+
return {
|
|
135
|
+
"version": self.version,
|
|
136
|
+
"id": self.id,
|
|
137
|
+
"trace_id": self.trace_id,
|
|
138
|
+
"timestamp": self.timestamp,
|
|
139
|
+
"from": self.from_agent,
|
|
140
|
+
"to": self.to_agent,
|
|
141
|
+
"tenant_id": self.tenant_id,
|
|
142
|
+
"trust": {
|
|
143
|
+
"level": self.trust_level,
|
|
144
|
+
"source": self.trust_source,
|
|
145
|
+
},
|
|
146
|
+
"payload": {
|
|
147
|
+
"type": self.payload_type,
|
|
148
|
+
"content": self.content,
|
|
149
|
+
"context": self.context,
|
|
150
|
+
},
|
|
151
|
+
"signals": self.signals.to_dict(),
|
|
152
|
+
"budget": {
|
|
153
|
+
"max_tokens": self.budget.max_tokens,
|
|
154
|
+
"tokens_used": self.budget.tokens_used,
|
|
155
|
+
"system_mode": self.budget.system_mode,
|
|
156
|
+
},
|
|
157
|
+
"trace": [
|
|
158
|
+
{"agent": t.agent, "action": t.action, "tokens": t.tokens,
|
|
159
|
+
"timestamp": t.timestamp, "signals_snapshot": t.signals_snapshot}
|
|
160
|
+
for t in self.trace
|
|
161
|
+
],
|
|
162
|
+
"results": self.results,
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
@classmethod
|
|
166
|
+
def from_json(cls, data: str) -> NMPMessage:
|
|
167
|
+
d = json.loads(data)
|
|
168
|
+
return cls.from_dict(d)
|
|
169
|
+
|
|
170
|
+
@classmethod
|
|
171
|
+
def from_dict(cls, d: dict[str, Any]) -> NMPMessage:
|
|
172
|
+
trust_data = d.get("trust", {})
|
|
173
|
+
payload_data = d.get("payload", {})
|
|
174
|
+
signals_data = d.get("signals", {})
|
|
175
|
+
budget_data = d.get("budget", {})
|
|
176
|
+
trace_data = d.get("trace", [])
|
|
177
|
+
|
|
178
|
+
msg = cls(
|
|
179
|
+
version=d.get("version", "1.0"),
|
|
180
|
+
id=d.get("id", uuid.uuid4().hex[:12]),
|
|
181
|
+
trace_id=d.get("trace_id", uuid.uuid4().hex[:16]),
|
|
182
|
+
timestamp=d.get("timestamp", time.strftime("%Y-%m-%dT%H:%M:%SZ")),
|
|
183
|
+
from_agent=d.get("from", ""),
|
|
184
|
+
to_agent=d.get("to", ""),
|
|
185
|
+
tenant_id=d.get("tenant_id", "default"),
|
|
186
|
+
trust_level=trust_data.get("level", 3) if isinstance(trust_data, dict) else 3,
|
|
187
|
+
trust_source=trust_data.get("source", "user_input") if isinstance(trust_data, dict) else "user_input",
|
|
188
|
+
payload_type=payload_data.get("type", "task") if isinstance(payload_data, dict) else "task",
|
|
189
|
+
content=payload_data.get("content", "") if isinstance(payload_data, dict) else "",
|
|
190
|
+
context=payload_data.get("context", {}) if isinstance(payload_data, dict) else {},
|
|
191
|
+
signals=NMPSignals(**{k: v for k, v in signals_data.items() if k in NMPSignals.__dataclass_fields__}),
|
|
192
|
+
budget=Budget(
|
|
193
|
+
max_tokens=budget_data.get("max_tokens", 5000) if isinstance(budget_data, dict) else 5000,
|
|
194
|
+
tokens_used=budget_data.get("tokens_used", 0) if isinstance(budget_data, dict) else 0,
|
|
195
|
+
system_mode=budget_data.get("system_mode", 1) if isinstance(budget_data, dict) else 1,
|
|
196
|
+
),
|
|
197
|
+
results=d.get("results", {}),
|
|
198
|
+
)
|
|
199
|
+
for t in trace_data:
|
|
200
|
+
if isinstance(t, dict):
|
|
201
|
+
msg.trace.append(TraceEntry(
|
|
202
|
+
agent=t.get("agent", ""),
|
|
203
|
+
action=t.get("action", ""),
|
|
204
|
+
tokens=t.get("tokens", 0),
|
|
205
|
+
timestamp=t.get("timestamp", ""),
|
|
206
|
+
signals_snapshot=t.get("signals_snapshot", {}),
|
|
207
|
+
))
|
|
208
|
+
return msg
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
def create_message(content: str = "", from_agent: str = "", trust: int | None = None,
|
|
212
|
+
system_mode: int = 1, max_tokens: int = 5000,
|
|
213
|
+
trust_level: int | None = None, trust_source: str = "user_input",
|
|
214
|
+
**kwargs) -> NMPMessage:
|
|
215
|
+
effective_trust = trust_level if trust_level is not None else (trust if trust is not None else 3)
|
|
216
|
+
return NMPMessage(
|
|
217
|
+
content=content, from_agent=from_agent,
|
|
218
|
+
trust_level=effective_trust, trust_source=trust_source,
|
|
219
|
+
budget=Budget(max_tokens=max_tokens, system_mode=system_mode),
|
|
220
|
+
**kwargs,
|
|
221
|
+
)
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
def validate_message(msg_or_dict) -> list[str]:
|
|
225
|
+
"""Validate an NMPMessage or a dict representation."""
|
|
226
|
+
errors = []
|
|
227
|
+
if isinstance(msg_or_dict, NMPMessage):
|
|
228
|
+
errors.extend(msg_or_dict.signals.validate())
|
|
229
|
+
if msg_or_dict.version != "1.0":
|
|
230
|
+
errors.append(f"Unsupported version: {msg_or_dict.version}")
|
|
231
|
+
if not 0 <= msg_or_dict.trust_level <= 3:
|
|
232
|
+
errors.append(f"Invalid trust_level: {msg_or_dict.trust_level}")
|
|
233
|
+
if msg_or_dict.payload_type not in ("task", "response", "feedback", "alert", "pain", "reflex"):
|
|
234
|
+
errors.append(f"Invalid payload_type: {msg_or_dict.payload_type}")
|
|
235
|
+
if msg_or_dict.budget.tokens_used > msg_or_dict.budget.max_tokens:
|
|
236
|
+
errors.append("Budget exceeded")
|
|
237
|
+
return errors
|
|
238
|
+
|
|
239
|
+
# Dict validation (schema compliance)
|
|
240
|
+
d = msg_or_dict
|
|
241
|
+
if not isinstance(d, dict):
|
|
242
|
+
return ["Input must be NMPMessage or dict"]
|
|
243
|
+
|
|
244
|
+
if "version" not in d:
|
|
245
|
+
errors.append("Missing required field: version")
|
|
246
|
+
elif d["version"] != "1.0":
|
|
247
|
+
errors.append(f"Unsupported version: {d['version']}")
|
|
248
|
+
|
|
249
|
+
trust = d.get("trust", {})
|
|
250
|
+
if isinstance(trust, dict):
|
|
251
|
+
level = trust.get("level", 3)
|
|
252
|
+
if not 0 <= level <= 3:
|
|
253
|
+
errors.append(f"Invalid trust.level: {level}")
|
|
254
|
+
|
|
255
|
+
payload = d.get("payload", {})
|
|
256
|
+
if isinstance(payload, dict):
|
|
257
|
+
ptype = payload.get("type", "task")
|
|
258
|
+
if ptype not in ("task", "response", "feedback", "alert", "pain", "reflex"):
|
|
259
|
+
errors.append(f"Invalid payload.type: {ptype}")
|
|
260
|
+
|
|
261
|
+
signals = d.get("signals", {})
|
|
262
|
+
if isinstance(signals, dict):
|
|
263
|
+
for fname in ("urgency", "quality", "inhibition", "activation", "focus"):
|
|
264
|
+
val = signals.get(fname, 0.5)
|
|
265
|
+
if not 0.0 <= val <= 1.0:
|
|
266
|
+
errors.append(f"{fname} must be 0.0-1.0, got {val}")
|
|
267
|
+
reward = signals.get("reward", 0.0)
|
|
268
|
+
if not -0.5 <= reward <= 1.0:
|
|
269
|
+
errors.append(f"reward must be -0.5-1.0, got {reward}")
|
|
270
|
+
|
|
271
|
+
budget = d.get("budget", {})
|
|
272
|
+
if isinstance(budget, dict):
|
|
273
|
+
sm = budget.get("system_mode", 1)
|
|
274
|
+
if not isinstance(sm, int) or sm not in (0, 1, 2, 3):
|
|
275
|
+
errors.append(f"Invalid budget.system_mode: {sm}")
|
|
276
|
+
|
|
277
|
+
return errors
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "nmp-protocol"
|
|
7
|
+
version = "1.0.0"
|
|
8
|
+
description = "NMP (NeuroMessage Protocol) — Open communication protocol for multi-agent systems with behavioral modulation signals."
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "Apache-2.0"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "Renato Aparecido Gomes", email = "renato@koloni.dev" },
|
|
14
|
+
]
|
|
15
|
+
keywords = [
|
|
16
|
+
"nmp", "neuro-message-protocol", "multi-agent", "protocol",
|
|
17
|
+
"behavioral-signals", "agent-communication", "mcp", "a2a",
|
|
18
|
+
"somatic-markers", "active-inference",
|
|
19
|
+
]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 4 - Beta",
|
|
22
|
+
"Intended Audience :: Developers",
|
|
23
|
+
"License :: OSI Approved :: Apache Software License",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3.10",
|
|
26
|
+
"Programming Language :: Python :: 3.11",
|
|
27
|
+
"Programming Language :: Python :: 3.12",
|
|
28
|
+
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Programming Language :: Python :: 3.14",
|
|
30
|
+
"Topic :: Scientific/Engineering :: Artificial Intelligence",
|
|
31
|
+
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
32
|
+
"Topic :: System :: Networking",
|
|
33
|
+
]
|
|
34
|
+
|
|
35
|
+
[project.urls]
|
|
36
|
+
Homepage = "https://nmp-protocol.org"
|
|
37
|
+
Documentation = "https://nmp-protocol.org"
|
|
38
|
+
Repository = "https://github.com/AIP-Labs/nmp-protocol"
|
|
39
|
+
Specification = "https://nmp-protocol.org"
|
|
40
|
+
"Bug Tracker" = "https://github.com/AIP-Labs/nmp-protocol/issues"
|
|
41
|
+
|
|
42
|
+
[tool.hatch.build.targets.sdist]
|
|
43
|
+
include = ["nmp/"]
|
|
44
|
+
|
|
45
|
+
[tool.hatch.build.targets.wheel]
|
|
46
|
+
packages = ["nmp"]
|
|
47
|
+
|
|
48
|
+
[tool.pytest.ini_options]
|
|
49
|
+
testpaths = ["tests"]
|