nest-plugins-reference 0.1.0__py3-none-any.whl

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.
Files changed (29) hide show
  1. nest_plugins_reference/__init__.py +4 -0
  2. nest_plugins_reference/auth/__init__.py +1 -0
  3. nest_plugins_reference/auth/jwt_auth.py +102 -0
  4. nest_plugins_reference/comms/__init__.py +1 -0
  5. nest_plugins_reference/comms/nest_native.py +114 -0
  6. nest_plugins_reference/coordination/__init__.py +1 -0
  7. nest_plugins_reference/coordination/contract_net.py +95 -0
  8. nest_plugins_reference/datafacts/__init__.py +1 -0
  9. nest_plugins_reference/datafacts/datafacts_v1.py +78 -0
  10. nest_plugins_reference/identity/__init__.py +1 -0
  11. nest_plugins_reference/identity/did_key.py +101 -0
  12. nest_plugins_reference/memory/__init__.py +1 -0
  13. nest_plugins_reference/memory/blackboard.py +80 -0
  14. nest_plugins_reference/negotiation/__init__.py +1 -0
  15. nest_plugins_reference/negotiation/alternating_offers.py +98 -0
  16. nest_plugins_reference/payments/__init__.py +1 -0
  17. nest_plugins_reference/payments/prepaid_credits.py +99 -0
  18. nest_plugins_reference/privacy/__init__.py +1 -0
  19. nest_plugins_reference/privacy/noop.py +59 -0
  20. nest_plugins_reference/py.typed +0 -0
  21. nest_plugins_reference/registry/__init__.py +1 -0
  22. nest_plugins_reference/registry/in_memory.py +87 -0
  23. nest_plugins_reference/transport/__init__.py +1 -0
  24. nest_plugins_reference/transport/in_memory.py +111 -0
  25. nest_plugins_reference/trust/__init__.py +1 -0
  26. nest_plugins_reference/trust/score_average.py +93 -0
  27. nest_plugins_reference-0.1.0.dist-info/METADATA +32 -0
  28. nest_plugins_reference-0.1.0.dist-info/RECORD +29 -0
  29. nest_plugins_reference-0.1.0.dist-info/WHEEL +4 -0
@@ -0,0 +1,4 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """NEST reference plugins: default implementations for all 12 layers."""
3
+
4
+ __version__ = "0.1.0"
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,102 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """JWT auth plugin — sign tokens with HMAC-SHA256 for simulation.
3
+
4
+ Example::
5
+
6
+ auth = JwtAuth(secret=b"my-secret")
7
+ token = await auth.issue(AgentId("a1"), ["read", "write"])
8
+ ctx = await auth.verify(token)
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import hashlib
14
+ import hmac
15
+ import json
16
+ import time
17
+
18
+ from nest_core.types import AgentId, AuthContext, Token
19
+
20
+
21
+ class JwtAuth:
22
+ """Simplified JWT-style auth using HMAC-SHA256.
23
+
24
+ Example::
25
+
26
+ auth = JwtAuth(secret=b"secret")
27
+ token = await auth.issue(AgentId("a1"), ["read"])
28
+ """
29
+
30
+ def __init__(self, secret: bytes = b"nest-default-secret", clock: float | None = None) -> None:
31
+ self._secret = secret
32
+ self._clock = clock
33
+ self._revoked: set[str] = set()
34
+
35
+ def _now(self) -> float:
36
+ if self._clock is not None:
37
+ return self._clock
38
+ return time.time()
39
+
40
+ def _sign(self, payload: str) -> str:
41
+ return hmac.new(self._secret, payload.encode(), hashlib.sha256).hexdigest()
42
+
43
+ async def issue(self, subject: AgentId, scopes: list[str]) -> Token:
44
+ """Issue a token for a subject with given scopes.
45
+
46
+ Example::
47
+
48
+ token = await auth.issue(AgentId("a1"), ["read", "write"])
49
+ """
50
+ now = self._now()
51
+ payload = json.dumps(
52
+ {
53
+ "sub": str(subject),
54
+ "scopes": scopes,
55
+ "iat": now,
56
+ "exp": now + 3600,
57
+ },
58
+ sort_keys=True,
59
+ )
60
+ sig = self._sign(payload)
61
+ return Token(f"{payload}|{sig}")
62
+
63
+ async def verify(self, token: Token) -> AuthContext:
64
+ """Verify a token and return its context.
65
+
66
+ Example::
67
+
68
+ ctx = await auth.verify(token)
69
+ assert ctx.subject == AgentId("a1")
70
+ """
71
+ raw = str(token)
72
+ if raw in self._revoked:
73
+ msg = "Token has been revoked"
74
+ raise ValueError(msg)
75
+
76
+ parts = raw.rsplit("|", 1)
77
+ if len(parts) != 2:
78
+ msg = "Invalid token format"
79
+ raise ValueError(msg)
80
+
81
+ payload_str, sig = parts
82
+ expected = self._sign(payload_str)
83
+ if not hmac.compare_digest(sig, expected):
84
+ msg = "Invalid token signature"
85
+ raise ValueError(msg)
86
+
87
+ data = json.loads(payload_str)
88
+ return AuthContext(
89
+ subject=AgentId(data["sub"]),
90
+ scopes=data["scopes"],
91
+ issued_at=data["iat"],
92
+ expires_at=data["exp"],
93
+ )
94
+
95
+ async def revoke(self, token: Token) -> None:
96
+ """Revoke a token.
97
+
98
+ Example::
99
+
100
+ await auth.revoke(token)
101
+ """
102
+ self._revoked.add(str(token))
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,114 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Nest-native communication plugin — minimal JSON envelope.
3
+
4
+ Example::
5
+
6
+ comms = NestNativeComms(AgentId("a1"), transport, registry)
7
+ raw = comms.serialize(msg)
8
+ msg2 = comms.deserialize(raw)
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import base64
14
+ import json
15
+ from typing import Any
16
+
17
+ from nest_core.types import (
18
+ AgentCard,
19
+ AgentId,
20
+ Message,
21
+ MessageId,
22
+ Query,
23
+ Response,
24
+ )
25
+
26
+
27
+ class NestNativeComms:
28
+ """Minimal JSON-based communication protocol.
29
+
30
+ Example::
31
+
32
+ comms = NestNativeComms(AgentId("a1"), transport=t, registry=r)
33
+ resp = await comms.send(AgentId("a2"), msg)
34
+ """
35
+
36
+ def __init__(
37
+ self,
38
+ agent_id: AgentId,
39
+ transport: Any = None,
40
+ registry: Any = None,
41
+ ) -> None:
42
+ self._agent_id = agent_id
43
+ self._transport = transport
44
+ self._registry = registry
45
+
46
+ def serialize(self, msg: Message) -> bytes:
47
+ """Serialize a Message to JSON bytes.
48
+
49
+ Example::
50
+
51
+ raw = comms.serialize(msg)
52
+ """
53
+ data = {
54
+ "id": str(msg.id),
55
+ "sender": str(msg.sender),
56
+ "receiver": str(msg.receiver),
57
+ "payload": base64.b64encode(msg.payload).decode("ascii"),
58
+ "correlation_id": str(msg.correlation_id) if msg.correlation_id else None,
59
+ "timestamp": msg.timestamp,
60
+ "metadata": msg.metadata,
61
+ }
62
+ return json.dumps(data, sort_keys=True).encode("utf-8")
63
+
64
+ def deserialize(self, raw: bytes) -> Message:
65
+ """Deserialize JSON bytes back to a Message.
66
+
67
+ Example::
68
+
69
+ msg = comms.deserialize(raw)
70
+ """
71
+ data = json.loads(raw)
72
+ return Message(
73
+ id=MessageId(data["id"]),
74
+ sender=AgentId(data["sender"]),
75
+ receiver=AgentId(data["receiver"]),
76
+ payload=base64.b64decode(data["payload"]),
77
+ correlation_id=data.get("correlation_id"),
78
+ timestamp=data.get("timestamp"),
79
+ metadata=data.get("metadata", {}),
80
+ )
81
+
82
+ async def send(self, to: AgentId, msg: Message) -> Response:
83
+ """Send a message via the transport layer.
84
+
85
+ Example::
86
+
87
+ resp = await comms.send(AgentId("a2"), msg)
88
+ """
89
+ raw = self.serialize(msg)
90
+ if self._transport is not None:
91
+ await self._transport.send(to, raw)
92
+ return Response(success=True)
93
+
94
+ async def advertise(self, card: AgentCard) -> None:
95
+ """Advertise an agent card to the registry.
96
+
97
+ Example::
98
+
99
+ await comms.advertise(my_card)
100
+ """
101
+ if self._registry is not None:
102
+ await self._registry.register(card)
103
+
104
+ async def discover(self, query: Query) -> list[AgentCard]:
105
+ """Discover agents via the registry.
106
+
107
+ Example::
108
+
109
+ cards = await comms.discover(Query(capabilities=["sell"]))
110
+ """
111
+ if self._registry is not None:
112
+ result: list[AgentCard] = await self._registry.lookup(query)
113
+ return result
114
+ return []
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,95 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Contract Net coordination plugin — classic FIPA Contract Net Protocol.
3
+
4
+ The Round object is shared between manager and workers. Bids are stored
5
+ in the round's metadata so any party can resolve them.
6
+
7
+ Example::
8
+
9
+ coord = ContractNet(AgentId("manager"))
10
+ rnd = await coord.propose(task)
11
+ bid = await coord.participate(rnd)
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import uuid
17
+
18
+ from nest_core.types import (
19
+ AgentId,
20
+ Bid,
21
+ Money,
22
+ Outcome,
23
+ Round,
24
+ Task,
25
+ Vote,
26
+ )
27
+
28
+
29
+ class ContractNet:
30
+ """FIPA Contract Net Protocol implementation.
31
+
32
+ Example::
33
+
34
+ coord = ContractNet(AgentId("a1"))
35
+ rnd = await coord.propose(Task(id="t1", description="work"))
36
+ """
37
+
38
+ def __init__(self, agent_id: AgentId) -> None:
39
+ self._agent_id = agent_id
40
+
41
+ async def propose(self, task: Task) -> Round:
42
+ """Propose a task for bidding.
43
+
44
+ Example::
45
+
46
+ rnd = await coord.propose(task)
47
+ """
48
+ round_id = str(uuid.uuid4())
49
+ rnd = Round(
50
+ id=round_id,
51
+ task=task,
52
+ participants=[],
53
+ metadata={"bids": []},
54
+ )
55
+ return rnd
56
+
57
+ async def participate(self, round: Round) -> Vote | Bid:
58
+ """Submit a bid for a round.
59
+
60
+ Example::
61
+
62
+ bid = await coord.participate(rnd)
63
+ """
64
+ bid = Bid(
65
+ bidder=self._agent_id,
66
+ round_id=round.id,
67
+ amount=Money(amount=1),
68
+ )
69
+ bids: list[dict[str, object]] = round.metadata.setdefault("bids", [])
70
+ bids.append({"bidder": str(bid.bidder), "amount": bid.amount.amount})
71
+ if self._agent_id not in round.participants:
72
+ round.participants.append(self._agent_id)
73
+ return bid
74
+
75
+ async def resolve(self, round: Round) -> Outcome:
76
+ """Resolve a round by selecting the lowest bidder.
77
+
78
+ Example::
79
+
80
+ outcome = await coord.resolve(rnd)
81
+ """
82
+ bids: list[dict[str, object]] = round.metadata.get("bids", [])
83
+ winner: AgentId | None = None
84
+ if bids:
85
+ best = min(bids, key=lambda b: int(str(b["amount"])))
86
+ winner = AgentId(str(best["bidder"]))
87
+ return Outcome(round_id=round.id, winner=winner, task=round.task)
88
+
89
+ async def commit(self, outcome: Outcome) -> None:
90
+ """Commit to an outcome.
91
+
92
+ Example::
93
+
94
+ await coord.commit(outcome)
95
+ """
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,78 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """DataFacts v1 plugin — dataset metadata registry.
3
+
4
+ Example::
5
+
6
+ df = DataFactsV1()
7
+ url = await df.publish(DatasetMetadata(name="weather", owner=AgentId("a1")))
8
+ meta = await df.fetch(url)
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import time
14
+
15
+ from nest_core.types import AccessGrant, AgentId, DataFactsUrl, DatasetMetadata
16
+
17
+
18
+ class DataFactsV1:
19
+ """In-memory DataFacts metadata registry.
20
+
21
+ Example::
22
+
23
+ df = DataFactsV1()
24
+ url = await df.publish(meta)
25
+ """
26
+
27
+ def __init__(self) -> None:
28
+ self._datasets: dict[DataFactsUrl, DatasetMetadata] = {}
29
+ self._grants: dict[DataFactsUrl, list[AccessGrant]] = {}
30
+ self._timestamps: dict[DataFactsUrl, float] = {}
31
+
32
+ async def publish(self, dataset: DatasetMetadata) -> DataFactsUrl:
33
+ """Publish dataset metadata and return its URL.
34
+
35
+ Example::
36
+
37
+ url = await df.publish(DatasetMetadata(name="weather", owner=AgentId("a1")))
38
+ """
39
+ url = DataFactsUrl(f"df://{dataset.name}")
40
+ self._datasets[url] = dataset
41
+ self._timestamps[url] = time.time()
42
+ return url
43
+
44
+ async def fetch(self, url: DataFactsUrl) -> DatasetMetadata:
45
+ """Fetch metadata for a dataset URL.
46
+
47
+ Example::
48
+
49
+ meta = await df.fetch(DataFactsUrl("df://weather"))
50
+ """
51
+ meta = self._datasets.get(url)
52
+ if meta is None:
53
+ msg = f"Dataset not found: {url}"
54
+ raise KeyError(msg)
55
+ return meta
56
+
57
+ async def request_access(self, url: DataFactsUrl, requester: AgentId) -> AccessGrant:
58
+ """Request access to a dataset (always grants in v1).
59
+
60
+ Example::
61
+
62
+ grant = await df.request_access(url, AgentId("a2"))
63
+ """
64
+ grant = AccessGrant(url=url, grantee=requester, tier="read")
65
+ self._grants.setdefault(url, []).append(grant)
66
+ return grant
67
+
68
+ async def verify_freshness(self, url: DataFactsUrl) -> bool:
69
+ """Check if a dataset was published within the last hour.
70
+
71
+ Example::
72
+
73
+ fresh = await df.verify_freshness(url)
74
+ """
75
+ ts = self._timestamps.get(url)
76
+ if ts is None:
77
+ return False
78
+ return (time.time() - ts) < 3600
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,101 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """DID:key identity plugin — Ed25519 key-based identity.
3
+
4
+ Uses hashlib for deterministic key derivation in simulation (no real crypto
5
+ needed for testing). For real deployments, swap to a proper Ed25519 library.
6
+
7
+ Example::
8
+
9
+ identity = DidKeyIdentity(AgentId("a1"), seed=b"secret")
10
+ sig = identity.sign(b"payload")
11
+ ok = identity.verify(b"payload", sig, AgentId("a1"))
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import hashlib
17
+ import hmac
18
+
19
+ from nest_core.types import AgentId, AgentIdentity, Signature
20
+
21
+
22
+ class DidKeyIdentity:
23
+ """Ed25519-style identity using HMAC-SHA256 for simulation.
24
+
25
+ Example::
26
+
27
+ ident = DidKeyIdentity(AgentId("a1"), seed=b"seed")
28
+ sig = ident.sign(b"hello")
29
+ """
30
+
31
+ def __init__(self, agent_id: AgentId, seed: bytes = b"") -> None:
32
+ self._agent_id = agent_id
33
+ self._seed = seed
34
+ key_material = hashlib.sha256(seed + agent_id.encode()).digest()
35
+ self._private_key = key_material
36
+ self._public_key = hashlib.sha256(key_material).digest()
37
+ self._known_keys: dict[AgentId, bytes] = {agent_id: self._public_key}
38
+ self._private_keys: dict[AgentId, bytes] = {agent_id: self._private_key}
39
+
40
+ def register_peer(
41
+ self,
42
+ agent_id: AgentId,
43
+ public_key: bytes,
44
+ private_key: bytes | None = None,
45
+ ) -> None:
46
+ """Register a peer's public key (and optionally private key) for verification.
47
+
48
+ Example::
49
+
50
+ ident.register_peer(AgentId("a2"), peer_pk)
51
+ """
52
+ self._known_keys[agent_id] = public_key
53
+ if private_key is not None:
54
+ self._private_keys[agent_id] = private_key
55
+
56
+ @property
57
+ def public_key(self) -> bytes:
58
+ """This agent's public key.
59
+
60
+ Example::
61
+
62
+ pk = ident.public_key
63
+ """
64
+ return self._public_key
65
+
66
+ def sign(self, payload: bytes) -> Signature:
67
+ """Sign a payload with this agent's private key.
68
+
69
+ Example::
70
+
71
+ sig = ident.sign(b"data")
72
+ """
73
+ sig_bytes = hmac.new(self._private_key, payload, hashlib.sha256).digest()
74
+ return Signature(signer=self._agent_id, value=sig_bytes, algorithm="hmac-sha256")
75
+
76
+ def verify(self, payload: bytes, sig: Signature, agent: AgentId) -> bool:
77
+ """Verify a signature from a given agent.
78
+
79
+ Example::
80
+
81
+ ok = ident.verify(b"data", sig, AgentId("a1"))
82
+ """
83
+ private_key = self._private_keys.get(agent)
84
+ if private_key is None:
85
+ return False
86
+ expected = hmac.new(private_key, payload, hashlib.sha256).digest()
87
+ return hmac.compare_digest(sig.value, expected)
88
+
89
+ async def resolve(self, agent: AgentId) -> AgentIdentity:
90
+ """Resolve an agent ID to its identity record.
91
+
92
+ Example::
93
+
94
+ info = await ident.resolve(AgentId("a1"))
95
+ """
96
+ pk = self._known_keys.get(agent, b"")
97
+ return AgentIdentity(
98
+ agent_id=agent,
99
+ public_key=pk,
100
+ method="did:key",
101
+ )
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,80 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Blackboard memory plugin — shared key-value store.
3
+
4
+ Example::
5
+
6
+ mem = Blackboard()
7
+ await mem.write("key", b"value")
8
+ val = await mem.read("key")
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+ from collections.abc import AsyncIterator
15
+
16
+
17
+ class Blackboard:
18
+ """Shared key-value blackboard for agent state.
19
+
20
+ Example::
21
+
22
+ bb = Blackboard()
23
+ await bb.write("counter", b"42")
24
+ val = await bb.read("counter")
25
+ """
26
+
27
+ def __init__(self) -> None:
28
+ self._store: dict[str, bytes] = {}
29
+ self._subscribers: dict[str, list[asyncio.Queue[bytes]]] = {}
30
+
31
+ async def read(self, key: str) -> bytes | None:
32
+ """Read a value by key.
33
+
34
+ Example::
35
+
36
+ val = await bb.read("counter")
37
+ """
38
+ return self._store.get(key)
39
+
40
+ async def write(self, key: str, value: bytes) -> None:
41
+ """Write a value for a key, notifying subscribers.
42
+
43
+ Example::
44
+
45
+ await bb.write("counter", b"42")
46
+ """
47
+ self._store[key] = value
48
+ for q in self._subscribers.get(key, []):
49
+ await q.put(value)
50
+
51
+ async def subscribe(self, key: str) -> AsyncIterator[bytes]:
52
+ """Subscribe to changes for a key.
53
+
54
+ Example::
55
+
56
+ async for val in bb.subscribe("counter"):
57
+ print(val)
58
+ """
59
+ q: asyncio.Queue[bytes] = asyncio.Queue()
60
+ self._subscribers.setdefault(key, []).append(q)
61
+ try:
62
+ while True:
63
+ yield await q.get()
64
+ finally:
65
+ self._subscribers[key].remove(q)
66
+
67
+ async def cas(self, key: str, expected: bytes, new: bytes) -> bool:
68
+ """Compare-and-swap: update only if current value matches expected.
69
+
70
+ Example::
71
+
72
+ ok = await bb.cas("counter", b"42", b"43")
73
+ """
74
+ current = self._store.get(key)
75
+ if current == expected:
76
+ self._store[key] = new
77
+ for q in self._subscribers.get(key, []):
78
+ await q.put(new)
79
+ return True
80
+ return False
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,98 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Alternating offers negotiation plugin — Rubinstein-style bargaining.
3
+
4
+ Example::
5
+
6
+ neg = AlternatingOffers(AgentId("a1"), patience=0.9)
7
+ session = await neg.open(AgentId("a2"), Terms(price=Money(amount=100)))
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import uuid
13
+
14
+ from nest_core.types import (
15
+ AgentId,
16
+ Agreement,
17
+ NegotiationResponse,
18
+ NegotiationSession,
19
+ NegotiationStatus,
20
+ Terms,
21
+ )
22
+
23
+
24
+ class AlternatingOffers:
25
+ """Rubinstein-style alternating-offers negotiation.
26
+
27
+ Example::
28
+
29
+ neg = AlternatingOffers(AgentId("a1"))
30
+ session = await neg.open(AgentId("a2"), terms)
31
+ """
32
+
33
+ def __init__(self, agent_id: AgentId, patience: float = 0.9) -> None:
34
+ self._agent_id = agent_id
35
+ self._patience = patience
36
+ self._sessions: dict[str, NegotiationSession] = {}
37
+
38
+ async def open(self, partner: AgentId, terms: Terms) -> NegotiationSession:
39
+ """Open a negotiation with initial terms.
40
+
41
+ Example::
42
+
43
+ session = await neg.open(AgentId("a2"), terms)
44
+ """
45
+ session = NegotiationSession(
46
+ id=str(uuid.uuid4()),
47
+ initiator=self._agent_id,
48
+ partner=partner,
49
+ status=NegotiationStatus.OPEN,
50
+ current_terms=terms,
51
+ history=[terms],
52
+ )
53
+ self._sessions[session.id] = session
54
+ return session
55
+
56
+ async def offer(self, session: NegotiationSession, terms: Terms) -> None:
57
+ """Make a counter-offer.
58
+
59
+ Example::
60
+
61
+ await neg.offer(session, Terms(price=Money(amount=80)))
62
+ """
63
+ session.current_terms = terms
64
+ session.history.append(terms)
65
+
66
+ async def respond(self, session: NegotiationSession) -> NegotiationResponse:
67
+ """Respond to the current offer using the patience discount.
68
+
69
+ Example::
70
+
71
+ resp = await neg.respond(session)
72
+ """
73
+ if session.current_terms is None or session.current_terms.price is None:
74
+ return NegotiationResponse(accepted=True)
75
+
76
+ rounds = len(session.history)
77
+ threshold = session.current_terms.price.amount * (self._patience**rounds)
78
+ if session.current_terms.price.amount <= threshold or rounds >= 10:
79
+ return NegotiationResponse(accepted=True)
80
+
81
+ return NegotiationResponse(accepted=False, counter_terms=session.current_terms)
82
+
83
+ async def close(self, session: NegotiationSession) -> Agreement | None:
84
+ """Close a session, returning an agreement if both parties accepted.
85
+
86
+ Example::
87
+
88
+ agreement = await neg.close(session)
89
+ """
90
+ if session.status == NegotiationStatus.AGREED or session.current_terms is not None:
91
+ session.status = NegotiationStatus.AGREED
92
+ return Agreement(
93
+ session_id=session.id,
94
+ terms=session.current_terms or Terms(),
95
+ parties=[session.initiator, session.partner],
96
+ )
97
+ session.status = NegotiationStatus.REJECTED
98
+ return None
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,99 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Prepaid credits payment plugin — simple debit/credit ledger.
3
+
4
+ Example::
5
+
6
+ payments = PrepaidCredits(AgentId("a1"), initial_balance=1000)
7
+ receipt = await payments.pay(AgentId("a2"), Money(amount=50), PaymentRef("p1"))
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ from nest_core.types import (
13
+ AgentId,
14
+ Money,
15
+ PaymentRef,
16
+ PaymentStatus,
17
+ Quote,
18
+ Receipt,
19
+ ServiceRef,
20
+ )
21
+
22
+
23
+ class PrepaidCredits:
24
+ """Simple debit/credit ledger for payments.
25
+
26
+ Example::
27
+
28
+ pay = PrepaidCredits(AgentId("a1"), initial_balance=1000)
29
+ receipt = await pay.pay(AgentId("a2"), Money(amount=50), PaymentRef("p1"))
30
+ """
31
+
32
+ def __init__(self, agent_id: AgentId, initial_balance: int = 1000) -> None:
33
+ self._agent_id = agent_id
34
+ self._balances: dict[AgentId, int] = {agent_id: initial_balance}
35
+ self._payments: dict[PaymentRef, Receipt] = {}
36
+
37
+ def balance(self, agent: AgentId) -> int:
38
+ """Check an agent's balance.
39
+
40
+ Example::
41
+
42
+ bal = pay.balance(AgentId("a1"))
43
+ """
44
+ return self._balances.get(agent, 0)
45
+
46
+ async def quote(self, service: ServiceRef) -> Quote:
47
+ """Return a fixed quote for any service.
48
+
49
+ Example::
50
+
51
+ q = await pay.quote(ServiceRef("svc"))
52
+ """
53
+ return Quote(service=service, price=Money(amount=10))
54
+
55
+ async def pay(self, to: AgentId, amount: Money, ref: PaymentRef) -> Receipt:
56
+ """Execute a payment from this agent to another.
57
+
58
+ Example::
59
+
60
+ receipt = await pay.pay(AgentId("a2"), Money(amount=50), PaymentRef("p1"))
61
+ """
62
+ payer_balance = self._balances.get(self._agent_id, 0)
63
+ if payer_balance < amount.amount:
64
+ msg = f"Insufficient balance: {payer_balance} < {amount.amount}"
65
+ raise ValueError(msg)
66
+
67
+ self._balances[self._agent_id] = payer_balance - amount.amount
68
+ self._balances[to] = self._balances.get(to, 0) + amount.amount
69
+
70
+ receipt = Receipt(ref=ref, payer=self._agent_id, payee=to, amount=amount)
71
+ self._payments[ref] = receipt
72
+ return receipt
73
+
74
+ async def verify_payment(self, ref: PaymentRef) -> PaymentStatus:
75
+ """Verify a payment status by reference.
76
+
77
+ Example::
78
+
79
+ status = await pay.verify_payment(PaymentRef("p1"))
80
+ """
81
+ if ref in self._payments:
82
+ return PaymentStatus.CONFIRMED
83
+ return PaymentStatus.FAILED
84
+
85
+ async def refund(self, ref: PaymentRef) -> None:
86
+ """Refund a payment.
87
+
88
+ Example::
89
+
90
+ await pay.refund(PaymentRef("p1"))
91
+ """
92
+ receipt = self._payments.get(ref)
93
+ if receipt is None:
94
+ msg = f"Payment not found: {ref}"
95
+ raise ValueError(msg)
96
+
97
+ self._balances[receipt.payee] = self._balances.get(receipt.payee, 0) - receipt.amount.amount
98
+ self._balances[receipt.payer] = self._balances.get(receipt.payer, 0) + receipt.amount.amount
99
+ del self._payments[ref]
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,59 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Noop privacy plugin — transparent passthrough, no actual encryption.
3
+
4
+ Example::
5
+
6
+ priv = NoopPrivacy()
7
+ ct = await priv.encrypt(b"data", [AgentId("a1")])
8
+ assert ct == b"data"
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from nest_core.types import AgentId, Proof, Statement, Witness
14
+
15
+
16
+ class NoopPrivacy:
17
+ """Transparent passthrough — no encryption, mock proofs always valid.
18
+
19
+ Example::
20
+
21
+ priv = NoopPrivacy()
22
+ ct = await priv.encrypt(b"data", [AgentId("a1")])
23
+ """
24
+
25
+ async def encrypt(self, data: bytes, audience: list[AgentId]) -> bytes:
26
+ """Return data unchanged (no encryption).
27
+
28
+ Example::
29
+
30
+ ct = await priv.encrypt(b"secret", [AgentId("a1")])
31
+ """
32
+ return data
33
+
34
+ async def decrypt(self, data: bytes) -> bytes:
35
+ """Return data unchanged (no decryption).
36
+
37
+ Example::
38
+
39
+ pt = await priv.decrypt(ct)
40
+ """
41
+ return data
42
+
43
+ async def prove(self, statement: Statement, witness: Witness) -> Proof:
44
+ """Generate a mock proof (always valid).
45
+
46
+ Example::
47
+
48
+ proof = await priv.prove(stmt, witness)
49
+ """
50
+ return Proof(statement=statement, data=b"mock-proof", scheme="noop")
51
+
52
+ async def verify_proof(self, statement: Statement, proof: Proof) -> bool:
53
+ """Verify a mock proof (always True).
54
+
55
+ Example::
56
+
57
+ ok = await priv.verify_proof(stmt, proof)
58
+ """
59
+ return True
File without changes
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,87 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """In-memory registry plugin — local dictionary-based agent discovery.
3
+
4
+ Example::
5
+
6
+ registry = InMemoryRegistry()
7
+ await registry.register(card)
8
+ results = await registry.lookup(Query(capabilities=["sell"]))
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import asyncio
14
+ from collections.abc import AsyncIterator
15
+
16
+ from nest_core.types import AgentCard, AgentId, Query
17
+
18
+
19
+ class InMemoryRegistry:
20
+ """Dictionary-backed agent registry.
21
+
22
+ Example::
23
+
24
+ reg = InMemoryRegistry()
25
+ await reg.register(AgentCard(agent_id=AgentId("a1"), name="Agent1"))
26
+ """
27
+
28
+ def __init__(self) -> None:
29
+ self._cards: dict[AgentId, AgentCard] = {}
30
+ self._subscribers: list[asyncio.Queue[AgentCard]] = []
31
+
32
+ async def register(self, card: AgentCard) -> None:
33
+ """Register an agent card.
34
+
35
+ Example::
36
+
37
+ await reg.register(card)
38
+ """
39
+ self._cards[card.agent_id] = card
40
+ for q in self._subscribers:
41
+ await q.put(card)
42
+
43
+ async def lookup(self, query: Query) -> list[AgentCard]:
44
+ """Look up agents matching a query.
45
+
46
+ Example::
47
+
48
+ results = await reg.lookup(Query(capabilities=["sell"]))
49
+ """
50
+ results: list[AgentCard] = []
51
+ for card in self._cards.values():
52
+ if self._matches(card, query):
53
+ results.append(card)
54
+ return results
55
+
56
+ async def subscribe(self, query: Query) -> AsyncIterator[AgentCard]:
57
+ """Subscribe to new agent registrations matching a query.
58
+
59
+ Example::
60
+
61
+ async for card in reg.subscribe(query):
62
+ print(card.name)
63
+ """
64
+ q: asyncio.Queue[AgentCard] = asyncio.Queue()
65
+ self._subscribers.append(q)
66
+ try:
67
+ while True:
68
+ card = await q.get()
69
+ if self._matches(card, query):
70
+ yield card
71
+ finally:
72
+ self._subscribers.remove(q)
73
+
74
+ async def deregister(self, agent: AgentId) -> None:
75
+ """Remove an agent from the registry.
76
+
77
+ Example::
78
+
79
+ await reg.deregister(AgentId("a1"))
80
+ """
81
+ self._cards.pop(agent, None)
82
+
83
+ @staticmethod
84
+ def _matches(card: AgentCard, query: Query) -> bool:
85
+ if query.capabilities and not all(cap in card.capabilities for cap in query.capabilities):
86
+ return False
87
+ return not (query.name_pattern and query.name_pattern not in card.name)
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,111 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """In-memory transport plugin — standalone version for non-simulator use.
3
+
4
+ Example::
5
+
6
+ transport = StandaloneInMemoryTransport(AgentId("a1"), network)
7
+ await transport.send(AgentId("a2"), b"hello")
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import asyncio
13
+
14
+ from nest_core.types import AgentId, TransportCapabilities
15
+
16
+
17
+ class InMemoryNetwork:
18
+ """Shared network that routes messages between in-memory transports.
19
+
20
+ Example::
21
+
22
+ network = InMemoryNetwork()
23
+ t1 = StandaloneInMemoryTransport(AgentId("a1"), network)
24
+ """
25
+
26
+ def __init__(self) -> None:
27
+ self._queues: dict[AgentId, asyncio.Queue[tuple[AgentId, bytes]]] = {}
28
+ self._agents: list[AgentId] = []
29
+
30
+ def register(self, agent_id: AgentId) -> asyncio.Queue[tuple[AgentId, bytes]]:
31
+ """Register an agent and return its message queue.
32
+
33
+ Example::
34
+
35
+ queue = network.register(AgentId("a1"))
36
+ """
37
+ q: asyncio.Queue[tuple[AgentId, bytes]] = asyncio.Queue()
38
+ self._queues[agent_id] = q
39
+ self._agents.append(agent_id)
40
+ return q
41
+
42
+ def get_agents(self) -> list[AgentId]:
43
+ """Return all registered agent IDs.
44
+
45
+ Example::
46
+
47
+ agents = network.get_agents()
48
+ """
49
+ return list(self._agents)
50
+
51
+ async def deliver(self, sender: AgentId, to: AgentId, payload: bytes) -> None:
52
+ """Deliver a message to the target agent's queue.
53
+
54
+ Example::
55
+
56
+ await network.deliver(AgentId("a1"), AgentId("a2"), b"hi")
57
+ """
58
+ q = self._queues.get(to)
59
+ if q is not None:
60
+ await q.put((sender, payload))
61
+
62
+
63
+ class StandaloneInMemoryTransport:
64
+ """In-memory transport for use outside the simulator (e.g., Tier 2).
65
+
66
+ Example::
67
+
68
+ network = InMemoryNetwork()
69
+ transport = StandaloneInMemoryTransport(AgentId("a1"), network)
70
+ await transport.send(AgentId("a2"), b"hello")
71
+ """
72
+
73
+ capabilities = TransportCapabilities(
74
+ supports_streaming=False,
75
+ ordered=True,
76
+ reliable=True,
77
+ )
78
+
79
+ def __init__(self, agent_id: AgentId, network: InMemoryNetwork) -> None:
80
+ self._agent_id = agent_id
81
+ self._network = network
82
+ self._queue = network.register(agent_id)
83
+
84
+ async def send(self, to: AgentId, payload: bytes) -> None:
85
+ """Send a payload to a specific agent.
86
+
87
+ Example::
88
+
89
+ await transport.send(AgentId("a2"), b"hello")
90
+ """
91
+ await self._network.deliver(self._agent_id, to, payload)
92
+
93
+ async def receive(self) -> tuple[AgentId, bytes]:
94
+ """Wait for the next message and return (sender, payload).
95
+
96
+ Example::
97
+
98
+ sender, data = await transport.receive()
99
+ """
100
+ return await self._queue.get()
101
+
102
+ async def broadcast(self, payload: bytes) -> None:
103
+ """Broadcast to all agents on the network.
104
+
105
+ Example::
106
+
107
+ await transport.broadcast(b"announcement")
108
+ """
109
+ for aid in self._network.get_agents():
110
+ if aid != self._agent_id:
111
+ await self.send(aid, payload)
@@ -0,0 +1 @@
1
+ # SPDX-License-Identifier: Apache-2.0
@@ -0,0 +1,93 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Score-average trust plugin — running mean of feedback scores.
3
+
4
+ Example::
5
+
6
+ trust = ScoreAverageTrust(identity)
7
+ await trust.report(AgentId("a1"), evidence)
8
+ score = await trust.score(AgentId("a1"))
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Any
14
+
15
+ from nest_core.types import (
16
+ AgentId,
17
+ Attestation,
18
+ Claim,
19
+ Evidence,
20
+ ReputationScore,
21
+ Signature,
22
+ )
23
+
24
+
25
+ class ScoreAverageTrust:
26
+ """Running-mean reputation based on reported feedback.
27
+
28
+ Example::
29
+
30
+ trust = ScoreAverageTrust()
31
+ score = await trust.score(AgentId("a1"))
32
+ """
33
+
34
+ def __init__(self, identity: Any = None) -> None:
35
+ self._identity = identity
36
+ self._scores: dict[AgentId, list[float]] = {}
37
+ self._stakes: dict[AgentId, int] = {}
38
+
39
+ async def score(self, agent: AgentId) -> ReputationScore:
40
+ """Get the running-mean reputation score for an agent.
41
+
42
+ Example::
43
+
44
+ rep = await trust.score(AgentId("a1"))
45
+ """
46
+ entries = self._scores.get(agent, [])
47
+ if not entries:
48
+ return ReputationScore(agent_id=agent, score=0.5, confidence=0.0, sample_count=0)
49
+ avg = sum(entries) / len(entries)
50
+ confidence = min(1.0, len(entries) / 100.0)
51
+ return ReputationScore(
52
+ agent_id=agent,
53
+ score=avg,
54
+ confidence=confidence,
55
+ sample_count=len(entries),
56
+ )
57
+
58
+ async def attest(self, agent: AgentId, claim: Claim) -> Attestation:
59
+ """Create an attestation about an agent.
60
+
61
+ Example::
62
+
63
+ att = await trust.attest(AgentId("a1"), claim)
64
+ """
65
+ sig = Signature(signer=AgentId("system"), value=b"attestation", algorithm="none")
66
+ if self._identity is not None:
67
+ sig = self._identity.sign(claim.model_dump_json().encode())
68
+ return Attestation(issuer=AgentId("system"), claim=claim, signature=sig)
69
+
70
+ async def report(self, agent: AgentId, evidence: Evidence) -> None:
71
+ """Report evidence, updating the agent's score.
72
+
73
+ Evidence kind 'positive' adds 1.0, 'negative' adds 0.0, 'byzantine' adds 0.0.
74
+
75
+ Example::
76
+
77
+ await trust.report(AgentId("a1"), Evidence(reporter=..., subject=..., kind="negative"))
78
+ """
79
+ score_val = 0.5
80
+ if evidence.kind == "positive":
81
+ score_val = 1.0
82
+ elif evidence.kind in ("negative", "byzantine"):
83
+ score_val = 0.0
84
+ self._scores.setdefault(agent, []).append(score_val)
85
+
86
+ async def stake(self, agent: AgentId, amount: int) -> None:
87
+ """Stake reputation on an agent.
88
+
89
+ Example::
90
+
91
+ await trust.stake(AgentId("a1"), 100)
92
+ """
93
+ self._stakes[agent] = self._stakes.get(agent, 0) + amount
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: nest-plugins-reference
3
+ Version: 0.1.0
4
+ Summary: NEST reference plugins: default implementations for all 12 layers
5
+ Project-URL: Homepage, https://github.com/mariagorskikh/nest
6
+ Project-URL: Repository, https://github.com/mariagorskikh/nest
7
+ License-Expression: Apache-2.0
8
+ Classifier: Development Status :: 3 - Alpha
9
+ Classifier: License :: OSI Approved :: Apache Software License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.12
12
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
13
+ Classifier: Topic :: Software Development :: Testing
14
+ Classifier: Typing :: Typed
15
+ Requires-Python: >=3.12
16
+ Requires-Dist: nest-core
17
+ Requires-Dist: nest-sdk
18
+ Description-Content-Type: text/markdown
19
+
20
+ # nest-plugins-reference
21
+
22
+ NEST reference plugins: default implementations for all 12 layers
23
+
24
+ Part of [NEST](https://github.com/mariagorskikh/nest) (Network Environment for Swarm Testing), built at MIT Media Lab.
25
+
26
+ ## Installation
27
+
28
+ ```bash
29
+ pip install nest-plugins-reference
30
+ ```
31
+
32
+ See the [main repository](https://github.com/mariagorskikh/nest) for full documentation.
@@ -0,0 +1,29 @@
1
+ nest_plugins_reference/__init__.py,sha256=JqKQjeEjHA1_vRzgRj0ZHR4QeW1U1tnrbljaAP22uMI,134
2
+ nest_plugins_reference/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
3
+ nest_plugins_reference/auth/__init__.py,sha256=bG7Ffen4LvQtgnYPFEpFccsWs81t4zqqeqn9ZeirH6E,38
4
+ nest_plugins_reference/auth/jwt_auth.py,sha256=Zcy_XCwxzaHfTDaOwcB-U1LsVcDDmjoRgW3ZOwKnypo,2736
5
+ nest_plugins_reference/comms/__init__.py,sha256=bG7Ffen4LvQtgnYPFEpFccsWs81t4zqqeqn9ZeirH6E,38
6
+ nest_plugins_reference/comms/nest_native.py,sha256=cQsavxpCus8tNu3VL2zWO-wSWVMUzzinIKr9LpJbXCU,3092
7
+ nest_plugins_reference/coordination/__init__.py,sha256=bG7Ffen4LvQtgnYPFEpFccsWs81t4zqqeqn9ZeirH6E,38
8
+ nest_plugins_reference/coordination/contract_net.py,sha256=gc48YV7jB4FECRikOppHGJ63La-tSq42_iUvqe53tIA,2454
9
+ nest_plugins_reference/datafacts/__init__.py,sha256=bG7Ffen4LvQtgnYPFEpFccsWs81t4zqqeqn9ZeirH6E,38
10
+ nest_plugins_reference/datafacts/datafacts_v1.py,sha256=igPD46EOJZFgHMS8cru38HpKDaNMzG0LFr2qpiEoIow,2255
11
+ nest_plugins_reference/identity/__init__.py,sha256=bG7Ffen4LvQtgnYPFEpFccsWs81t4zqqeqn9ZeirH6E,38
12
+ nest_plugins_reference/identity/did_key.py,sha256=6q2ypKi47iXJ5ydF9gtk7fOD0ihlxSsBh57oJpEuaXY,3055
13
+ nest_plugins_reference/memory/__init__.py,sha256=bG7Ffen4LvQtgnYPFEpFccsWs81t4zqqeqn9ZeirH6E,38
14
+ nest_plugins_reference/memory/blackboard.py,sha256=kbeYgoDelLfU5b_03PzAl7-jjDq5RkweoSFN2VGBVEQ,2085
15
+ nest_plugins_reference/negotiation/__init__.py,sha256=bG7Ffen4LvQtgnYPFEpFccsWs81t4zqqeqn9ZeirH6E,38
16
+ nest_plugins_reference/negotiation/alternating_offers.py,sha256=mbLbQhTyI7PZg2R_PL0Bmc8R2JcgTToOGfAlXaqKBoQ,3055
17
+ nest_plugins_reference/payments/__init__.py,sha256=bG7Ffen4LvQtgnYPFEpFccsWs81t4zqqeqn9ZeirH6E,38
18
+ nest_plugins_reference/payments/prepaid_credits.py,sha256=FmfY6T13U-7GoT4D_OEOOA7OGSl5dududnZHfvsH4xw,3020
19
+ nest_plugins_reference/privacy/__init__.py,sha256=bG7Ffen4LvQtgnYPFEpFccsWs81t4zqqeqn9ZeirH6E,38
20
+ nest_plugins_reference/privacy/noop.py,sha256=xBXRFkQ3ZLR9lAkPScxBO3hbZFFfGLqDojF7h7_qtLA,1495
21
+ nest_plugins_reference/registry/__init__.py,sha256=bG7Ffen4LvQtgnYPFEpFccsWs81t4zqqeqn9ZeirH6E,38
22
+ nest_plugins_reference/registry/in_memory.py,sha256=IRuHfgxS3TeiscI04nGWbWzrUqOYAEwVh-UfamF9ajs,2472
23
+ nest_plugins_reference/transport/__init__.py,sha256=bG7Ffen4LvQtgnYPFEpFccsWs81t4zqqeqn9ZeirH6E,38
24
+ nest_plugins_reference/transport/in_memory.py,sha256=seH6etRlnduW3YVwUTWMA7ZJxnXMTcUEbchMIYMrCBA,3116
25
+ nest_plugins_reference/trust/__init__.py,sha256=bG7Ffen4LvQtgnYPFEpFccsWs81t4zqqeqn9ZeirH6E,38
26
+ nest_plugins_reference/trust/score_average.py,sha256=spzqxBsCW8lWKF5wnxymTdjz2Xf5-f5sxDdTkjqz9Kk,2792
27
+ nest_plugins_reference-0.1.0.dist-info/METADATA,sha256=PPlhFGFSOxXs7WscPg1pZsSBiShFfchP8-u1APJ2hpc,1135
28
+ nest_plugins_reference-0.1.0.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
29
+ nest_plugins_reference-0.1.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.29.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any