nest-core 0.1.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.
Files changed (52) hide show
  1. nest_core-0.1.0/.gitignore +10 -0
  2. nest_core-0.1.0/PKG-INFO +32 -0
  3. nest_core-0.1.0/README.md +13 -0
  4. nest_core-0.1.0/nest_core/__init__.py +4 -0
  5. nest_core-0.1.0/nest_core/inspect.py +136 -0
  6. nest_core-0.1.0/nest_core/layers/__init__.py +35 -0
  7. nest_core-0.1.0/nest_core/layers/auth.py +53 -0
  8. nest_core-0.1.0/nest_core/layers/comms.py +71 -0
  9. nest_core-0.1.0/nest_core/layers/coordination.py +62 -0
  10. nest_core-0.1.0/nest_core/layers/datafacts.py +64 -0
  11. nest_core-0.1.0/nest_core/layers/identity.py +53 -0
  12. nest_core-0.1.0/nest_core/layers/memory.py +62 -0
  13. nest_core-0.1.0/nest_core/layers/negotiation.py +68 -0
  14. nest_core-0.1.0/nest_core/layers/payments.py +63 -0
  15. nest_core-0.1.0/nest_core/layers/privacy.py +62 -0
  16. nest_core-0.1.0/nest_core/layers/registry.py +64 -0
  17. nest_core-0.1.0/nest_core/layers/transport.py +55 -0
  18. nest_core-0.1.0/nest_core/layers/trust.py +62 -0
  19. nest_core-0.1.0/nest_core/metrics.py +408 -0
  20. nest_core-0.1.0/nest_core/plugins.py +113 -0
  21. nest_core-0.1.0/nest_core/py.typed +0 -0
  22. nest_core-0.1.0/nest_core/runner.py +188 -0
  23. nest_core-0.1.0/nest_core/scenario.py +159 -0
  24. nest_core-0.1.0/nest_core/scenarios.py +73 -0
  25. nest_core-0.1.0/nest_core/scenarios_builtin/__init__.py +2 -0
  26. nest_core-0.1.0/nest_core/scenarios_builtin/auction.py +146 -0
  27. nest_core-0.1.0/nest_core/scenarios_builtin/consensus.py +130 -0
  28. nest_core-0.1.0/nest_core/scenarios_builtin/marketplace.py +425 -0
  29. nest_core-0.1.0/nest_core/scenarios_builtin/reputation.py +183 -0
  30. nest_core-0.1.0/nest_core/scenarios_builtin/supply_chain.py +135 -0
  31. nest_core-0.1.0/nest_core/scenarios_builtin/voting.py +154 -0
  32. nest_core-0.1.0/nest_core/sim/__init__.py +27 -0
  33. nest_core-0.1.0/nest_core/sim/agent.py +144 -0
  34. nest_core-0.1.0/nest_core/sim/clock.py +49 -0
  35. nest_core-0.1.0/nest_core/sim/events.py +92 -0
  36. nest_core-0.1.0/nest_core/sim/simulator.py +381 -0
  37. nest_core-0.1.0/nest_core/sim/trace.py +71 -0
  38. nest_core-0.1.0/nest_core/sim/transport.py +95 -0
  39. nest_core-0.1.0/nest_core/types.py +545 -0
  40. nest_core-0.1.0/nest_core/validators.py +882 -0
  41. nest_core-0.1.0/pyproject.toml +32 -0
  42. nest_core-0.1.0/tests/test_failures.py +187 -0
  43. nest_core-0.1.0/tests/test_imports.py +30 -0
  44. nest_core-0.1.0/tests/test_inspect.py +107 -0
  45. nest_core-0.1.0/tests/test_layers.py +57 -0
  46. nest_core-0.1.0/tests/test_metrics.py +320 -0
  47. nest_core-0.1.0/tests/test_properties.py +442 -0
  48. nest_core-0.1.0/tests/test_scenario.py +177 -0
  49. nest_core-0.1.0/tests/test_scenarios.py +334 -0
  50. nest_core-0.1.0/tests/test_sim.py +236 -0
  51. nest_core-0.1.0/tests/test_types.py +73 -0
  52. nest_core-0.1.0/tests/test_validators.py +589 -0
@@ -0,0 +1,10 @@
1
+ __pycache__/
2
+ *.pyc
3
+ *.pyo
4
+ .venv/
5
+ .ruff_cache/
6
+ .pytest_cache/
7
+ *.egg-info/
8
+ dist/
9
+ build/
10
+ uv.lock
@@ -0,0 +1,32 @@
1
+ Metadata-Version: 2.4
2
+ Name: nest-core
3
+ Version: 0.1.0
4
+ Summary: NEST core engine: layer interfaces, runtime, event loop, and simulator
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: pydantic>=2.0
17
+ Requires-Dist: pyyaml>=6.0
18
+ Description-Content-Type: text/markdown
19
+
20
+ # nest-core
21
+
22
+ NEST core engine: layer interfaces, runtime, event loop, and simulator
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-core
30
+ ```
31
+
32
+ See the [main repository](https://github.com/mariagorskikh/nest) for full documentation.
@@ -0,0 +1,13 @@
1
+ # nest-core
2
+
3
+ NEST core engine: layer interfaces, runtime, event loop, and simulator
4
+
5
+ Part of [NEST](https://github.com/mariagorskikh/nest) (Network Environment for Swarm Testing), built at MIT Media Lab.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ pip install nest-core
11
+ ```
12
+
13
+ See the [main repository](https://github.com/mariagorskikh/nest) for full documentation.
@@ -0,0 +1,4 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """NEST core engine: layer interfaces, runtime, event loop, and simulator."""
3
+
4
+ __version__ = "0.1.0"
@@ -0,0 +1,136 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Trace file inspection and analysis.
3
+
4
+ Example::
5
+
6
+ summary = analyze_trace("traces/marketplace.jsonl")
7
+ print(summary.total_events)
8
+ """
9
+
10
+ from __future__ import annotations
11
+
12
+ import json
13
+ from collections import Counter
14
+ from dataclasses import dataclass, field
15
+ from pathlib import Path
16
+
17
+
18
+ @dataclass
19
+ class AgentStats:
20
+ """Per-agent statistics extracted from a trace."""
21
+
22
+ sends: int = 0
23
+ receives: int = 0
24
+ started: bool = False
25
+ stopped: bool = False
26
+
27
+
28
+ @dataclass
29
+ class TraceSummary:
30
+ """Summary statistics for a JSONL trace file."""
31
+
32
+ total_events: int = 0
33
+ duration: float = 0.0
34
+ event_kinds: dict[str, int] = field(default_factory=lambda: dict[str, int]())
35
+ agent_count: int = 0
36
+ message_count: int = 0
37
+ unique_correlations: int = 0
38
+ agents: dict[str, AgentStats] = field(default_factory=lambda: dict[str, AgentStats]())
39
+
40
+
41
+ def analyze_trace(path: str | Path) -> TraceSummary:
42
+ """Analyze a JSONL trace file and return summary statistics.
43
+
44
+ Example::
45
+
46
+ summary = analyze_trace("trace.jsonl")
47
+ """
48
+ path = Path(path)
49
+ summary = TraceSummary()
50
+ kind_counter: Counter[str] = Counter()
51
+ agents_seen: set[str] = set()
52
+ corr_ids: set[str] = set()
53
+ min_ts = float("inf")
54
+ max_ts = float("-inf")
55
+
56
+ with path.open() as f:
57
+ for line in f:
58
+ line = line.strip()
59
+ if not line:
60
+ continue
61
+
62
+ event = json.loads(line)
63
+ summary.total_events += 1
64
+
65
+ ts = event.get("ts", 0.0)
66
+ if ts < min_ts:
67
+ min_ts = ts
68
+ if ts > max_ts:
69
+ max_ts = ts
70
+
71
+ kind = event.get("kind", "unknown")
72
+ kind_counter[kind] += 1
73
+
74
+ agent = event.get("agent", "")
75
+ if agent:
76
+ agents_seen.add(agent)
77
+ if agent not in summary.agents:
78
+ summary.agents[agent] = AgentStats()
79
+
80
+ stats = summary.agents[agent]
81
+ if kind == "send":
82
+ stats.sends += 1
83
+ elif kind == "receive":
84
+ stats.receives += 1
85
+ elif kind == "start":
86
+ stats.started = True
87
+ elif kind == "stop":
88
+ stats.stopped = True
89
+
90
+ corr = event.get("corr")
91
+ if corr is not None:
92
+ corr_ids.add(str(corr))
93
+
94
+ if kind in ("send", "receive"):
95
+ summary.message_count += 1
96
+
97
+ summary.event_kinds = dict(kind_counter)
98
+ summary.agent_count = len(agents_seen)
99
+ summary.unique_correlations = len(corr_ids)
100
+ if max_ts > min_ts:
101
+ summary.duration = max_ts - min_ts
102
+
103
+ return summary
104
+
105
+
106
+ def format_summary(summary: TraceSummary) -> str:
107
+ """Format a TraceSummary as a human-readable report.
108
+
109
+ Example::
110
+
111
+ print(format_summary(summary))
112
+ """
113
+ lines: list[str] = []
114
+ lines.append("NEST Trace Summary")
115
+ lines.append("=" * 40)
116
+ lines.append(f" Total events: {summary.total_events}")
117
+ lines.append(f" Agents: {summary.agent_count}")
118
+ lines.append(f" Messages: {summary.message_count}")
119
+ lines.append(f" Correlation IDs: {summary.unique_correlations}")
120
+ lines.append(f" Duration: {summary.duration:.1f} ticks")
121
+ lines.append("")
122
+ lines.append("Event breakdown:")
123
+ for kind, count in sorted(summary.event_kinds.items()):
124
+ lines.append(f" {kind:20s} {count:>6d}")
125
+
126
+ lines.append("")
127
+ lines.append("Top agents by sends:")
128
+ top_senders = sorted(
129
+ summary.agents.items(),
130
+ key=lambda kv: kv[1].sends,
131
+ reverse=True,
132
+ )[:10]
133
+ for agent_name, stats in top_senders:
134
+ lines.append(f" {agent_name:20s} sent={stats.sends:>4d} recv={stats.receives:>4d}")
135
+
136
+ return "\n".join(lines)
@@ -0,0 +1,35 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Layer interface definitions for all 12 pluggable layers.
3
+
4
+ Example::
5
+
6
+ from nest_core.layers import Transport, Payments, Registry
7
+ """
8
+
9
+ from nest_core.layers.auth import Auth
10
+ from nest_core.layers.comms import CommsProtocol
11
+ from nest_core.layers.coordination import Coordination
12
+ from nest_core.layers.datafacts import DataFacts
13
+ from nest_core.layers.identity import Identity
14
+ from nest_core.layers.memory import Memory
15
+ from nest_core.layers.negotiation import Negotiation
16
+ from nest_core.layers.payments import Payments
17
+ from nest_core.layers.privacy import Privacy
18
+ from nest_core.layers.registry import Registry
19
+ from nest_core.layers.transport import Transport
20
+ from nest_core.layers.trust import Trust
21
+
22
+ __all__ = [
23
+ "Auth",
24
+ "CommsProtocol",
25
+ "Coordination",
26
+ "DataFacts",
27
+ "Identity",
28
+ "Memory",
29
+ "Negotiation",
30
+ "Payments",
31
+ "Privacy",
32
+ "Registry",
33
+ "Transport",
34
+ "Trust",
35
+ ]
@@ -0,0 +1,53 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Auth layer interface: authentication and authorization.
3
+
4
+ Example::
5
+
6
+ class MyAuth(Auth):
7
+ async def issue(self, subject, scopes):
8
+ return Token(jwt.encode({"sub": subject, "scopes": scopes}, key))
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Protocol, runtime_checkable
14
+
15
+ from nest_core.types import AgentId, AuthContext, Token
16
+
17
+
18
+ @runtime_checkable
19
+ class Auth(Protocol):
20
+ """Authentication and authorization for agents.
21
+
22
+ Example::
23
+
24
+ auth: Auth = JwtAuth(identity)
25
+ token = await auth.issue(AgentId("a1"), ["read", "write"])
26
+ """
27
+
28
+ async def issue(self, subject: AgentId, scopes: list[str]) -> Token:
29
+ """Issue an auth token for a subject with given scopes.
30
+
31
+ Example::
32
+
33
+ token = await auth.issue(AgentId("a1"), ["read"])
34
+ """
35
+ ...
36
+
37
+ async def verify(self, token: Token) -> AuthContext:
38
+ """Verify a token and return its auth context.
39
+
40
+ Example::
41
+
42
+ ctx = await auth.verify(token)
43
+ """
44
+ ...
45
+
46
+ async def revoke(self, token: Token) -> None:
47
+ """Revoke a previously issued token.
48
+
49
+ Example::
50
+
51
+ await auth.revoke(token)
52
+ """
53
+ ...
@@ -0,0 +1,71 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Communication layer interface: message format, request/response, discovery.
3
+
4
+ Example::
5
+
6
+ class MyComms(CommsProtocol):
7
+ def serialize(self, msg):
8
+ return json.dumps(msg.model_dump()).encode()
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Protocol, runtime_checkable
14
+
15
+ from nest_core.types import AgentCard, AgentId, Message, Query, Response
16
+
17
+
18
+ @runtime_checkable
19
+ class CommsProtocol(Protocol):
20
+ """Wire protocol for agent communication.
21
+
22
+ Example::
23
+
24
+ comms: CommsProtocol = NestNativeComms()
25
+ raw = comms.serialize(msg)
26
+ """
27
+
28
+ def serialize(self, msg: Message) -> bytes:
29
+ """Serialize a Message into bytes for transport.
30
+
31
+ Example::
32
+
33
+ raw = comms.serialize(msg)
34
+ """
35
+ ...
36
+
37
+ def deserialize(self, raw: bytes) -> Message:
38
+ """Deserialize bytes back into a Message.
39
+
40
+ Example::
41
+
42
+ msg = comms.deserialize(raw)
43
+ """
44
+ ...
45
+
46
+ async def send(self, to: AgentId, msg: Message) -> Response:
47
+ """Send a message and wait for a response.
48
+
49
+ Example::
50
+
51
+ resp = await comms.send(AgentId("a2"), msg)
52
+ """
53
+ ...
54
+
55
+ async def advertise(self, card: AgentCard) -> None:
56
+ """Advertise this agent's capabilities.
57
+
58
+ Example::
59
+
60
+ await comms.advertise(my_card)
61
+ """
62
+ ...
63
+
64
+ async def discover(self, query: Query) -> list[AgentCard]:
65
+ """Discover agents matching a query.
66
+
67
+ Example::
68
+
69
+ cards = await comms.discover(Query(capabilities=["sell_data"]))
70
+ """
71
+ ...
@@ -0,0 +1,62 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Coordination layer interface: how groups decide.
3
+
4
+ Example::
5
+
6
+ class MyCoordination(Coordination):
7
+ async def propose(self, task):
8
+ return Round(id="r1", task=task)
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Protocol, runtime_checkable
14
+
15
+ from nest_core.types import Bid, Outcome, Round, Task, Vote
16
+
17
+
18
+ @runtime_checkable
19
+ class Coordination(Protocol):
20
+ """Group decision-making protocol.
21
+
22
+ Example::
23
+
24
+ coord: Coordination = ContractNet()
25
+ rnd = await coord.propose(task)
26
+ """
27
+
28
+ async def propose(self, task: Task) -> Round:
29
+ """Propose a task for group coordination.
30
+
31
+ Example::
32
+
33
+ rnd = await coord.propose(Task(id="t1", description="process data"))
34
+ """
35
+ ...
36
+
37
+ async def participate(self, round: Round) -> Vote | Bid:
38
+ """Participate in a coordination round with a vote or bid.
39
+
40
+ Example::
41
+
42
+ bid = await coord.participate(rnd)
43
+ """
44
+ ...
45
+
46
+ async def resolve(self, round: Round) -> Outcome:
47
+ """Resolve a coordination round to an outcome.
48
+
49
+ Example::
50
+
51
+ outcome = await coord.resolve(rnd)
52
+ """
53
+ ...
54
+
55
+ async def commit(self, outcome: Outcome) -> None:
56
+ """Commit to a resolved outcome.
57
+
58
+ Example::
59
+
60
+ await coord.commit(outcome)
61
+ """
62
+ ...
@@ -0,0 +1,64 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """DataFacts layer interface: dataset metadata, freshness, integrity, access.
3
+
4
+ Example::
5
+
6
+ class MyDataFacts(DataFacts):
7
+ async def publish(self, dataset):
8
+ url = DataFactsUrl(f"df://{dataset.name}")
9
+ self._store[url] = dataset
10
+ return url
11
+ """
12
+
13
+ from __future__ import annotations
14
+
15
+ from typing import Protocol, runtime_checkable
16
+
17
+ from nest_core.types import AccessGrant, AgentId, DataFactsUrl, DatasetMetadata
18
+
19
+
20
+ @runtime_checkable
21
+ class DataFacts(Protocol):
22
+ """Dataset metadata exchange protocol.
23
+
24
+ Example::
25
+
26
+ df: DataFacts = DataFactsV1()
27
+ url = await df.publish(meta)
28
+ """
29
+
30
+ async def publish(self, dataset: DatasetMetadata) -> DataFactsUrl:
31
+ """Publish dataset metadata and return its URL.
32
+
33
+ Example::
34
+
35
+ url = await df.publish(DatasetMetadata(name="weather", owner=AgentId("a1")))
36
+ """
37
+ ...
38
+
39
+ async def fetch(self, url: DataFactsUrl) -> DatasetMetadata:
40
+ """Fetch metadata for a published dataset.
41
+
42
+ Example::
43
+
44
+ meta = await df.fetch(DataFactsUrl("df://weather"))
45
+ """
46
+ ...
47
+
48
+ async def request_access(self, url: DataFactsUrl, requester: AgentId) -> AccessGrant:
49
+ """Request access to a dataset.
50
+
51
+ Example::
52
+
53
+ grant = await df.request_access(url, AgentId("a2"))
54
+ """
55
+ ...
56
+
57
+ async def verify_freshness(self, url: DataFactsUrl) -> bool:
58
+ """Check if a dataset's metadata is still fresh.
59
+
60
+ Example::
61
+
62
+ fresh = await df.verify_freshness(url)
63
+ """
64
+ ...
@@ -0,0 +1,53 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Identity layer interface: who is this agent, and how do I verify them?
3
+
4
+ Example::
5
+
6
+ class MyIdentity(Identity):
7
+ def sign(self, payload):
8
+ return Signature(signer=self.agent_id, value=do_sign(payload))
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Protocol, runtime_checkable
14
+
15
+ from nest_core.types import AgentId, AgentIdentity, Signature
16
+
17
+
18
+ @runtime_checkable
19
+ class Identity(Protocol):
20
+ """Agent identity, signing, and verification.
21
+
22
+ Example::
23
+
24
+ identity: Identity = DidKeyIdentity(agent_id)
25
+ sig = identity.sign(b"data")
26
+ """
27
+
28
+ def sign(self, payload: bytes) -> Signature:
29
+ """Sign a payload with this agent's private key.
30
+
31
+ Example::
32
+
33
+ sig = identity.sign(b"important data")
34
+ """
35
+ ...
36
+
37
+ def verify(self, payload: bytes, sig: Signature, agent: AgentId) -> bool:
38
+ """Verify a signature from a given agent.
39
+
40
+ Example::
41
+
42
+ ok = identity.verify(b"data", sig, AgentId("a2"))
43
+ """
44
+ ...
45
+
46
+ async def resolve(self, agent: AgentId) -> AgentIdentity:
47
+ """Resolve an agent ID to its full identity record.
48
+
49
+ Example::
50
+
51
+ info = await identity.resolve(AgentId("a2"))
52
+ """
53
+ ...
@@ -0,0 +1,62 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Memory layer interface: shared state between agents.
3
+
4
+ Example::
5
+
6
+ class MyMemory(Memory):
7
+ async def read(self, key):
8
+ return self._store.get(key)
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from collections.abc import AsyncIterator
14
+ from typing import Protocol, runtime_checkable
15
+
16
+
17
+ @runtime_checkable
18
+ class Memory(Protocol):
19
+ """Shared key-value state accessible to agents.
20
+
21
+ Example::
22
+
23
+ mem: Memory = Blackboard()
24
+ await mem.write("counter", b"42")
25
+ """
26
+
27
+ async def read(self, key: str) -> bytes | None:
28
+ """Read a value by key, returning None if absent.
29
+
30
+ Example::
31
+
32
+ val = await mem.read("counter")
33
+ """
34
+ ...
35
+
36
+ async def write(self, key: str, value: bytes) -> None:
37
+ """Write a value for a key.
38
+
39
+ Example::
40
+
41
+ await mem.write("counter", b"42")
42
+ """
43
+ ...
44
+
45
+ async def subscribe(self, key: str) -> AsyncIterator[bytes]:
46
+ """Subscribe to changes for a key.
47
+
48
+ Example::
49
+
50
+ async for val in mem.subscribe("counter"):
51
+ print(val)
52
+ """
53
+ ...
54
+
55
+ async def cas(self, key: str, expected: bytes, new: bytes) -> bool:
56
+ """Compare-and-swap: update only if the current value matches expected.
57
+
58
+ Example::
59
+
60
+ ok = await mem.cas("counter", b"42", b"43")
61
+ """
62
+ ...
@@ -0,0 +1,68 @@
1
+ # SPDX-License-Identifier: Apache-2.0
2
+ """Negotiation layer interface: bargaining between agents over terms.
3
+
4
+ Example::
5
+
6
+ class MyNegotiation(Negotiation):
7
+ async def open(self, partner, terms):
8
+ return NegotiationSession(id="n1", initiator=self._me, partner=partner)
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from typing import Protocol, runtime_checkable
14
+
15
+ from nest_core.types import (
16
+ AgentId,
17
+ Agreement,
18
+ NegotiationResponse,
19
+ NegotiationSession,
20
+ Terms,
21
+ )
22
+
23
+
24
+ @runtime_checkable
25
+ class Negotiation(Protocol):
26
+ """Bargaining protocol between two or more agents.
27
+
28
+ Example::
29
+
30
+ neg: Negotiation = AlternatingOffers(agent_id)
31
+ session = await neg.open(AgentId("a2"), Terms(price=Money(amount=100)))
32
+ """
33
+
34
+ async def open(self, partner: AgentId, terms: Terms) -> NegotiationSession:
35
+ """Open a negotiation session with initial terms.
36
+
37
+ Example::
38
+
39
+ session = await neg.open(AgentId("a2"), terms)
40
+ """
41
+ ...
42
+
43
+ async def offer(self, session: NegotiationSession, terms: Terms) -> None:
44
+ """Make an offer in an ongoing negotiation.
45
+
46
+ Example::
47
+
48
+ await neg.offer(session, Terms(price=Money(amount=80)))
49
+ """
50
+ ...
51
+
52
+ async def respond(self, session: NegotiationSession) -> NegotiationResponse:
53
+ """Respond to the latest offer in a negotiation.
54
+
55
+ Example::
56
+
57
+ resp = await neg.respond(session)
58
+ """
59
+ ...
60
+
61
+ async def close(self, session: NegotiationSession) -> Agreement | None:
62
+ """Close a negotiation session, returning an agreement if reached.
63
+
64
+ Example::
65
+
66
+ agreement = await neg.close(session)
67
+ """
68
+ ...