macp-sdk-python 0.3.0__tar.gz → 0.4.1__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 (39) hide show
  1. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/PKG-INFO +11 -5
  2. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/README.md +2 -1
  3. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/pyproject.toml +19 -8
  4. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/__init__.py +2 -0
  5. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/base_session.py +17 -1
  6. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/client.py +73 -5
  7. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/envelope.py +23 -0
  8. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/policy.py +36 -3
  9. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/watchers.py +33 -10
  10. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk_python.egg-info/PKG-INFO +11 -5
  11. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk_python.egg-info/requires.txt +2 -2
  12. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/LICENSE +0 -0
  13. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/setup.cfg +0 -0
  14. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/_logging.py +0 -0
  15. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/agent/__init__.py +0 -0
  16. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/agent/cancel_callback.py +0 -0
  17. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/agent/dispatcher.py +0 -0
  18. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/agent/participant.py +0 -0
  19. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/agent/runner.py +0 -0
  20. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/agent/strategies.py +0 -0
  21. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/agent/transports.py +0 -0
  22. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/agent/types.py +0 -0
  23. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/auth.py +0 -0
  24. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/base_projection.py +0 -0
  25. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/constants.py +0 -0
  26. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/decision.py +0 -0
  27. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/errors.py +0 -0
  28. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/handoff.py +0 -0
  29. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/projections.py +0 -0
  30. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/proposal.py +0 -0
  31. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/proto_registry.py +0 -0
  32. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/py.typed +0 -0
  33. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/quorum.py +0 -0
  34. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/retry.py +0 -0
  35. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/task.py +0 -0
  36. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk/validation.py +0 -0
  37. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk_python.egg-info/SOURCES.txt +0 -0
  38. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk_python.egg-info/dependency_links.txt +0 -0
  39. {macp_sdk_python-0.3.0 → macp_sdk_python-0.4.1}/src/macp_sdk_python.egg-info/top_level.txt +0 -0
@@ -1,9 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: macp-sdk-python
3
- Version: 0.3.0
3
+ Version: 0.4.1
4
4
  Summary: Python SDK for the MACP Rust runtime
5
- Author-email: OpenAI <support@openai.com>
5
+ Author-email: Multi-Agent Coordination Protocol <macp@multiagentcoordinationprotocol.org>
6
6
  License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/multiagentcoordinationprotocol/macp-sdk-python
8
+ Project-URL: Repository, https://github.com/multiagentcoordinationprotocol/macp-sdk-python
9
+ Project-URL: Documentation, https://github.com/multiagentcoordinationprotocol/macp-sdk-python/tree/main/docs
10
+ Project-URL: Changelog, https://github.com/multiagentcoordinationprotocol/macp-sdk-python/blob/main/CHANGELOG.md
11
+ Project-URL: Bug Tracker, https://github.com/multiagentcoordinationprotocol/macp-sdk-python/issues
7
12
  Classifier: Development Status :: 3 - Alpha
8
13
  Classifier: Intended Audience :: Developers
9
14
  Classifier: License :: OSI Approved :: Apache Software License
@@ -15,9 +20,9 @@ Classifier: Topic :: Software Development :: Libraries
15
20
  Requires-Python: >=3.11
16
21
  Description-Content-Type: text/markdown
17
22
  License-File: LICENSE
18
- Requires-Dist: grpcio>=1.74.0
23
+ Requires-Dist: grpcio>=1.81.1
19
24
  Requires-Dist: protobuf>=5.27.0
20
- Requires-Dist: macp-proto<0.2.0,>=0.1.2
25
+ Requires-Dist: macp-proto<0.2.0,>=0.1.3
21
26
  Provides-Extra: dev
22
27
  Requires-Dist: grpcio-tools>=1.74.0; extra == "dev"
23
28
  Requires-Dist: pytest>=8.0; extra == "dev"
@@ -165,6 +170,7 @@ Business logic — voting rules, AI decision heuristics, policy enforcement —
165
170
 
166
171
  - `GetSession` returns metadata only (not mode state/transcript) — hence the local projection pattern; use `StreamSession` + `send_subscribe` for the full transcript
167
172
  - `StreamSession` is scoped to one session per stream; use `MacpStream.send_subscribe(session_id)` (RFC-MACP-0006-A1, since SDK 0.2.3 / `macp-proto 0.1.2`) to replay accepted history before live broadcast
168
- - For cross-session observability, use `MacpClient.list_sessions()` and `SessionLifecycleWatcher` (SDK 0.3.0) to enumerate active sessions and stream `CREATED` / `RESOLVED` / `EXPIRED` lifecycle events
173
+ - For cross-session observability, use `MacpClient.list_sessions()` and `SessionLifecycleWatcher` (SDK 0.3.0) to enumerate active sessions and stream `CREATED` / `RESOLVED` / `EXPIRED` / `CANCELLED` / `SUSPENDED` / `RESUMED` lifecycle events
174
+ - Pause and cancel sessions with `MacpClient.suspend_session()` / `resume_session()` / `cancel_session()` (SDK 0.4.0 / `macp-proto 0.1.3`). A suspended session is non-terminal and rejects messages until resumed; an accepted `cancel_session()` now terminates as `CANCELLED` (previously surfaced as `EXPIRED`)
169
175
  - `ListRoots` currently returns an empty list and `WatchRoots` idles — the runtime does not yet populate roots
170
176
  - Business policy (majority, quorum, veto) belongs in your orchestrator/policy layer
@@ -131,6 +131,7 @@ Business logic — voting rules, AI decision heuristics, policy enforcement —
131
131
 
132
132
  - `GetSession` returns metadata only (not mode state/transcript) — hence the local projection pattern; use `StreamSession` + `send_subscribe` for the full transcript
133
133
  - `StreamSession` is scoped to one session per stream; use `MacpStream.send_subscribe(session_id)` (RFC-MACP-0006-A1, since SDK 0.2.3 / `macp-proto 0.1.2`) to replay accepted history before live broadcast
134
- - For cross-session observability, use `MacpClient.list_sessions()` and `SessionLifecycleWatcher` (SDK 0.3.0) to enumerate active sessions and stream `CREATED` / `RESOLVED` / `EXPIRED` lifecycle events
134
+ - For cross-session observability, use `MacpClient.list_sessions()` and `SessionLifecycleWatcher` (SDK 0.3.0) to enumerate active sessions and stream `CREATED` / `RESOLVED` / `EXPIRED` / `CANCELLED` / `SUSPENDED` / `RESUMED` lifecycle events
135
+ - Pause and cancel sessions with `MacpClient.suspend_session()` / `resume_session()` / `cancel_session()` (SDK 0.4.0 / `macp-proto 0.1.3`). A suspended session is non-terminal and rejects messages until resumed; an accepted `cancel_session()` now terminates as `CANCELLED` (previously surfaced as `EXPIRED`)
135
136
  - `ListRoots` currently returns an empty list and `WatchRoots` idles — the runtime does not yet populate roots
136
137
  - Business policy (majority, quorum, veto) belongs in your orchestrator/policy layer
@@ -4,13 +4,13 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "macp-sdk-python"
7
- version = "0.3.0"
7
+ version = "0.4.1"
8
8
  description = "Python SDK for the MACP Rust runtime"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
11
11
  license = {text = "Apache-2.0"}
12
12
  authors = [
13
- {name = "OpenAI", email = "support@openai.com"}
13
+ {name = "Multi-Agent Coordination Protocol", email = "macp@multiagentcoordinationprotocol.org"}
14
14
  ]
15
15
  classifiers = [
16
16
  "Development Status :: 3 - Alpha",
@@ -23,16 +23,27 @@ classifiers = [
23
23
  "Topic :: Software Development :: Libraries",
24
24
  ]
25
25
  dependencies = [
26
- "grpcio>=1.74.0",
26
+ # macp-proto 0.1.3's generated gRPC stubs require grpcio>=1.81.1.
27
+ "grpcio>=1.81.1",
27
28
  "protobuf>=5.27.0",
28
- # Requires 0.1.2+ for the RFC-MACP-0006-A1 subscribe_session_id /
29
+ # Requires 0.1.3+ for the suspend/cancel/supersede surface: SuspendSession
30
+ # /ResumeSession RPCs, SESSION_STATE_SUSPENDED/CANCELLED, the SUSPENDED/
31
+ # RESUMED/CANCELLED lifecycle events, and CommitmentPayload.supersedes
32
+ # (CommitmentRef). 0.1.2 added the RFC-MACP-0006-A1 subscribe_session_id /
29
33
  # after_sequence fields on StreamSessionRequest (used by MacpStream
30
- # .send_subscribe and the gRPC agent transport adapter). 0.1.1 is
31
- # the earlier baseline for the context_id / extensions fields on
32
- # SessionStartPayload. See docs/contributing.md "Bumping macp-proto".
33
- "macp-proto>=0.1.2,<0.2.0",
34
+ # .send_subscribe and the gRPC agent transport adapter); 0.1.1 is the
35
+ # earlier baseline for context_id / extensions on SessionStartPayload.
36
+ # See docs/contributing.md "Bumping macp-proto".
37
+ "macp-proto>=0.1.3,<0.2.0",
34
38
  ]
35
39
 
40
+ [project.urls]
41
+ Homepage = "https://github.com/multiagentcoordinationprotocol/macp-sdk-python"
42
+ Repository = "https://github.com/multiagentcoordinationprotocol/macp-sdk-python"
43
+ Documentation = "https://github.com/multiagentcoordinationprotocol/macp-sdk-python/tree/main/docs"
44
+ Changelog = "https://github.com/multiagentcoordinationprotocol/macp-sdk-python/blob/main/CHANGELOG.md"
45
+ "Bug Tracker" = "https://github.com/multiagentcoordinationprotocol/macp-sdk-python/issues"
46
+
36
47
  [project.optional-dependencies]
37
48
  dev = [
38
49
  "grpcio-tools>=1.74.0", # only needed for proto-python package publishing
@@ -19,6 +19,7 @@ from .constants import (
19
19
  from .decision import DecisionSession
20
20
  from .envelope import (
21
21
  build_commitment_payload,
22
+ build_commitment_ref,
22
23
  build_envelope,
23
24
  build_progress_payload,
24
25
  build_root,
@@ -181,6 +182,7 @@ __all__ = [
181
182
  "TaskSession",
182
183
  "VotingRules",
183
184
  "build_commitment_payload",
185
+ "build_commitment_ref",
184
186
  "build_decision_policy",
185
187
  "build_envelope",
186
188
  "build_handoff_policy",
@@ -163,9 +163,25 @@ class BaseSession(ABC):
163
163
  return self.client.get_session(self.session_id, auth=auth or self.auth)
164
164
 
165
165
  def cancel(self, *, reason: str = "", auth: AuthConfig | None = None) -> envelope_pb2.Ack:
166
- """Cancel the session."""
166
+ """Cancel the session (terminates as ``CANCELLED`` since proto 0.1.3)."""
167
167
  return self.client.cancel_session(self.session_id, reason=reason, auth=auth or self.auth)
168
168
 
169
+ def suspend(self, *, reason: str = "", auth: AuthConfig | None = None) -> envelope_pb2.Ack:
170
+ """Suspend this session (proto 0.1.3+).
171
+
172
+ Moves it to the non-terminal ``SUSPENDED`` state; the runtime rejects
173
+ messages until :meth:`resume`. Delegates to
174
+ :meth:`MacpClient.suspend_session`.
175
+ """
176
+ return self.client.suspend_session(self.session_id, reason=reason, auth=auth or self.auth)
177
+
178
+ def resume(self, *, reason: str = "", auth: AuthConfig | None = None) -> envelope_pb2.Ack:
179
+ """Resume this suspended session (proto 0.1.3+), restoring ``OPEN``.
180
+
181
+ Delegates to :meth:`MacpClient.resume_session`.
182
+ """
183
+ return self.client.resume_session(self.session_id, reason=reason, auth=auth or self.auth)
184
+
169
185
  def open_stream(self, *, auth: AuthConfig | None = None) -> MacpStream:
170
186
  """Open a bidirectional stream for this session."""
171
187
  return self.client.open_stream(auth=auth or self.auth)
@@ -213,7 +213,7 @@ class MacpClient:
213
213
  root_certificates: bytes | None = None,
214
214
  default_timeout: float | None = None,
215
215
  client_name: str = "macp-sdk-python",
216
- client_version: str = "0.3.0",
216
+ client_version: str = "0.4.0",
217
217
  ) -> None:
218
218
  if secure is None:
219
219
  secure = not allow_insecure
@@ -370,6 +370,13 @@ class MacpClient:
370
370
  timeout: float | None = None,
371
371
  raise_on_nack: bool = True,
372
372
  ) -> envelope_pb2.Ack:
373
+ """Terminate a session.
374
+
375
+ Since runtime 0.4.0 / macp-proto 0.1.3 an accepted cancellation
376
+ moves the session to ``SESSION_STATE_CANCELLED`` (previously
377
+ ``EXPIRED``) and surfaces an ``EVENT_TYPE_CANCELLED`` lifecycle
378
+ event. The returned ``Ack.session_state`` reflects ``CANCELLED``.
379
+ """
373
380
  auth_cfg = self._require_auth(auth)
374
381
  request_kwargs: dict[str, object] = {
375
382
  "session_id": session_id,
@@ -395,6 +402,66 @@ class MacpClient:
395
402
  raise MacpAckError(self._failure_from_ack(ack))
396
403
  return ack
397
404
 
405
+ def suspend_session(
406
+ self,
407
+ session_id: str,
408
+ *,
409
+ reason: str = "",
410
+ auth: AuthConfig | None = None,
411
+ timeout: float | None = None,
412
+ raise_on_nack: bool = True,
413
+ ) -> envelope_pb2.Ack:
414
+ """Suspend an open session (macp-proto 0.1.3 / runtime 0.4.0).
415
+
416
+ Moves the session to the non-terminal ``SESSION_STATE_SUSPENDED``
417
+ state and emits an ``EVENT_TYPE_SUSPENDED`` lifecycle event. While
418
+ suspended the runtime rejects messages sent to the session with a
419
+ non-OPEN error; call :meth:`resume_session` to return it to OPEN.
420
+ The returned ``Ack.session_state`` reflects ``SUSPENDED``.
421
+ """
422
+ auth_cfg = self._require_auth(auth)
423
+ try:
424
+ response = self.stub.SuspendSession(
425
+ core_pb2.SuspendSessionRequest(session_id=session_id, reason=reason),
426
+ metadata=self._metadata(auth_cfg),
427
+ timeout=timeout or self.default_timeout,
428
+ )
429
+ except grpc.RpcError as rpc_err:
430
+ raise MacpTransportError(rpc_err.details() or str(rpc_err)) from rpc_err
431
+ ack = response.ack
432
+ if raise_on_nack and not ack.ok:
433
+ raise MacpAckError(self._failure_from_ack(ack))
434
+ return ack
435
+
436
+ def resume_session(
437
+ self,
438
+ session_id: str,
439
+ *,
440
+ reason: str = "",
441
+ auth: AuthConfig | None = None,
442
+ timeout: float | None = None,
443
+ raise_on_nack: bool = True,
444
+ ) -> envelope_pb2.Ack:
445
+ """Resume a suspended session (macp-proto 0.1.3 / runtime 0.4.0).
446
+
447
+ Returns a ``SESSION_STATE_SUSPENDED`` session to
448
+ ``SESSION_STATE_OPEN`` and emits an ``EVENT_TYPE_RESUMED`` lifecycle
449
+ event. The returned ``Ack.session_state`` reflects ``OPEN``.
450
+ """
451
+ auth_cfg = self._require_auth(auth)
452
+ try:
453
+ response = self.stub.ResumeSession(
454
+ core_pb2.ResumeSessionRequest(session_id=session_id, reason=reason),
455
+ metadata=self._metadata(auth_cfg),
456
+ timeout=timeout or self.default_timeout,
457
+ )
458
+ except grpc.RpcError as rpc_err:
459
+ raise MacpTransportError(rpc_err.details() or str(rpc_err)) from rpc_err
460
+ ack = response.ack
461
+ if raise_on_nack and not ack.ok:
462
+ raise MacpAckError(self._failure_from_ack(ack))
463
+ return ack
464
+
398
465
  def get_manifest(
399
466
  self, agent_id: str = "", *, timeout: float | None = None
400
467
  ) -> core_pb2.GetManifestResponse:
@@ -452,10 +519,11 @@ class MacpClient:
452
519
 
453
520
  The runtime emits an initial ``EVENT_TYPE_CREATED`` frame for every
454
521
  currently-open session, then live events as sessions transition
455
- through ``CREATED``, ``RESOLVED``, and ``EXPIRED``. Each event
456
- carries a full ``SessionMetadata`` (including ``context_id`` and
457
- ``extension_keys``), so callers can project run state without a
458
- follow-up ``get_session``.
522
+ through ``CREATED``, ``RESOLVED``, ``EXPIRED``, ``CANCELLED``, and
523
+ the non-terminal ``SUSPENDED`` / ``RESUMED`` pair (the latter three
524
+ since macp-proto 0.1.3). Each event carries a full
525
+ ``SessionMetadata`` (including ``context_id`` and ``extension_keys``),
526
+ so callers can project run state without a follow-up ``get_session``.
459
527
  """
460
528
  logger.debug("watch_sessions starting")
461
529
  auth_cfg = self._require_auth(auth)
@@ -80,6 +80,17 @@ def _has_outcome_positive_field() -> bool:
80
80
  return any(f.name == "outcome_positive" for f in core_pb2.CommitmentPayload.DESCRIPTOR.fields)
81
81
 
82
82
 
83
+ def _has_supersedes_field() -> bool:
84
+ """Check if the proto schema supports CommitmentPayload.supersedes (>=0.1.3)."""
85
+ return any(f.name == "supersedes" for f in core_pb2.CommitmentPayload.DESCRIPTOR.fields)
86
+
87
+
88
+ def build_commitment_ref(*, session_id: str, commitment_hash: str) -> core_pb2.CommitmentRef:
89
+ """Build a ``CommitmentRef`` (macp-proto 0.1.3) pointing at a prior
90
+ commitment, for use as ``build_commitment_payload(supersedes=...)``."""
91
+ return core_pb2.CommitmentRef(session_id=session_id, commitment_hash=commitment_hash)
92
+
93
+
83
94
  def build_commitment_payload(
84
95
  *,
85
96
  action: str,
@@ -90,7 +101,15 @@ def build_commitment_payload(
90
101
  configuration_version: str = DEFAULT_CONFIGURATION_VERSION,
91
102
  policy_version: str = DEFAULT_POLICY_VERSION,
92
103
  outcome_positive: bool | None = None,
104
+ supersedes: core_pb2.CommitmentRef | None = None,
93
105
  ) -> core_pb2.CommitmentPayload:
106
+ """Build a ``CommitmentPayload``.
107
+
108
+ ``supersedes`` (macp-proto 0.1.3) optionally references a prior
109
+ commitment this one revises, as a ``CommitmentRef`` of
110
+ ``(session_id, commitment_hash)``. It is absent by default and unrelated
111
+ to proposal-mode ``supersedes_proposal_id``.
112
+ """
94
113
  if outcome_positive is None:
95
114
  outcome_positive = infer_outcome_positive(action)
96
115
  kwargs: dict[str, object] = dict(
@@ -104,6 +123,10 @@ def build_commitment_payload(
104
123
  )
105
124
  if _has_outcome_positive_field():
106
125
  kwargs["outcome_positive"] = outcome_positive
126
+ if supersedes is not None:
127
+ if not _has_supersedes_field():
128
+ raise MacpSessionError("CommitmentPayload.supersedes requires macp-proto >= 0.1.3")
129
+ kwargs["supersedes"] = supersedes
107
130
  return core_pb2.CommitmentPayload(**kwargs)
108
131
 
109
132
 
@@ -22,9 +22,20 @@ class CommitmentRules:
22
22
  authority: str = "initiator_only"
23
23
  designated_roles: list[str] = field(default_factory=list)
24
24
  require_vote_quorum: bool = False
25
+ # RFC-MACP-0012 schema_version 2, Decision mode only: when True, a
26
+ # reject-majority resolves the session with a committed *negative* outcome
27
+ # (``outcome_positive = false``) instead of denying commitment. Emitted only
28
+ # by ``build_decision_policy`` (see ``_commitment_dict`` note) so version-1
29
+ # quorum/proposal/task/handoff schemas are unaffected. Default False keeps
30
+ # version-1 behaviour for existing callers.
31
+ allow_decline_over_approval: bool = False
25
32
 
26
33
 
27
34
  def _commitment_dict(c: CommitmentRules) -> dict[str, object]:
35
+ # Shared by all five mode builders — intentionally excludes the Decision-only
36
+ # ``allow_decline_over_approval`` field so it does not leak into the still
37
+ # version-1 quorum/proposal/task/handoff commitment schemas. Decision emits
38
+ # that field itself in ``build_decision_policy``.
28
39
  return {
29
40
  "authority": c.authority,
30
41
  "designated_roles": c.designated_roles,
@@ -52,6 +63,12 @@ class ObjectionHandlingRules:
52
63
 
53
64
  critical_severity_vetoes: bool = False
54
65
  veto_threshold: int = 1
66
+ # RFC-MACP-0012 schema_version 2: action taken when a critical objection
67
+ # would block commitment. One of ``"deny"`` (reject the commitment; legacy
68
+ # default), ``"finalize_decline"`` (resolve the session as a negative
69
+ # outcome), or ``"hold"`` (leave the session open). Default ``"deny"``
70
+ # preserves version-1 behaviour.
71
+ critical_objection_action: str = "deny"
55
72
 
56
73
 
57
74
  @dataclass(frozen=True, slots=True)
@@ -80,22 +97,38 @@ def build_decision_policy(
80
97
  voting_section: dict[str, object] = {
81
98
  "algorithm": v.algorithm,
82
99
  "threshold": v.threshold,
83
- "quorum": {"type": v.quorum_type or "count", "value": v.quorum_value or 0},
84
100
  }
101
+ # Emit ``quorum`` only when the caller sets it, mirroring the conditional
102
+ # ``weights`` emission below. This keeps the Decision ``rules`` JSON
103
+ # byte-identical to the typescript-sdk builder (which omits an unset quorum)
104
+ # and to the canonical example descriptors, which drop optional voting fields
105
+ # they do not set. ``quorum`` is optional with defaults ``type="count"``,
106
+ # ``value=0`` in decision-rules.schema.json, so omission is schema-equivalent.
107
+ if v.quorum_type is not None or v.quorum_value is not None:
108
+ voting_section["quorum"] = {
109
+ "type": v.quorum_type or "count",
110
+ "value": v.quorum_value or 0,
111
+ }
85
112
  if v.weights is not None:
86
113
  voting_section["weights"] = v.weights
87
114
 
115
+ # Decision-only: extend the shared commitment rules with the schema_version 2
116
+ # decline-over-approval switch without polluting the other four builders.
117
+ commitment_section = _commitment_dict(c)
118
+ commitment_section["allow_decline_over_approval"] = c.allow_decline_over_approval
119
+
88
120
  rules: dict[str, object] = {
89
121
  "voting": voting_section,
90
122
  "objection_handling": {
91
123
  "critical_severity_vetoes": o.critical_severity_vetoes,
92
124
  "veto_threshold": o.veto_threshold,
125
+ "critical_objection_action": o.critical_objection_action,
93
126
  },
94
127
  "evaluation": {
95
128
  "minimum_confidence": e.minimum_confidence,
96
129
  "required_before_voting": e.required_before_voting,
97
130
  },
98
- "commitment": _commitment_dict(c),
131
+ "commitment": commitment_section,
99
132
  }
100
133
 
101
134
  return policy_pb2.PolicyDescriptor(
@@ -103,7 +136,7 @@ def build_decision_policy(
103
136
  mode="macp.mode.decision.v1",
104
137
  description=description,
105
138
  rules=json.dumps(rules).encode(),
106
- schema_version=1,
139
+ schema_version=2,
107
140
  )
108
141
 
109
142
 
@@ -29,10 +29,13 @@ class PolicyChange:
29
29
  class SessionLifecycle:
30
30
  """A single session lifecycle event from ``WatchSessions``.
31
31
 
32
- Runtime event types (per ``SessionLifecycleEvent.EventType``): ``CREATED``
33
- on SessionStart acceptance (also emitted for pre-existing sessions at
34
- subscribe time), ``RESOLVED`` on mode-determined terminal outcome,
35
- ``EXPIRED`` on TTL expiry or explicit ``CancelSession``.
32
+ Runtime event types (per ``SessionLifecycleEvent.EventType``, since
33
+ macp-proto 0.1.3): ``CREATED`` on SessionStart acceptance (also emitted
34
+ for pre-existing sessions at subscribe time), ``RESOLVED`` on
35
+ mode-determined terminal outcome, ``EXPIRED`` on TTL/policy expiry,
36
+ ``CANCELLED`` on an accepted ``CancelSession`` (previously surfaced as
37
+ ``EXPIRED``), and the non-terminal pair ``SUSPENDED`` / ``RESUMED`` from
38
+ ``SuspendSession`` / ``ResumeSession``.
36
39
  """
37
40
 
38
41
  event_type: str = "UNSPECIFIED"
@@ -49,12 +52,31 @@ class SessionLifecycle:
49
52
 
50
53
  @property
51
54
  def is_expired(self) -> bool:
55
+ """``True`` only for TTL/policy expiry; explicit cancellation now
56
+ surfaces as ``CANCELLED`` — see ``is_cancelled``."""
52
57
  return self.event_type == "EXPIRED"
53
58
 
59
+ @property
60
+ def is_cancelled(self) -> bool:
61
+ """``True`` for an accepted ``CancelSession`` (terminal)."""
62
+ return self.event_type == "CANCELLED"
63
+
64
+ @property
65
+ def is_suspended(self) -> bool:
66
+ """``True`` after ``SuspendSession`` — non-terminal; the session can
67
+ still ``RESUMED``."""
68
+ return self.event_type == "SUSPENDED"
69
+
70
+ @property
71
+ def is_resumed(self) -> bool:
72
+ """``True`` after ``ResumeSession`` returns a suspended session to OPEN."""
73
+ return self.event_type == "RESUMED"
74
+
54
75
  @property
55
76
  def is_terminal(self) -> bool:
56
- """``True`` for RESOLVED or EXPIRED — the session won't emit more events."""
57
- return self.event_type in ("RESOLVED", "EXPIRED")
77
+ """``True`` for RESOLVED, EXPIRED, or CANCELLED — the session won't
78
+ emit more events. ``SUSPENDED`` / ``RESUMED`` are non-terminal."""
79
+ return self.event_type in ("RESOLVED", "EXPIRED", "CANCELLED")
58
80
 
59
81
 
60
82
  class ModeRegistryWatcher:
@@ -151,10 +173,11 @@ class SessionLifecycleWatcher:
151
173
 
152
174
  Wraps ``MacpClient.watch_sessions()`` and normalises each response into
153
175
  a ``SessionLifecycle`` record carrying the event type as a short
154
- string (``CREATED`` / ``RESOLVED`` / ``EXPIRED``) and the full
155
- ``SessionMetadata``. The runtime emits an initial CREATED event for
156
- every already-open session at subscribe time, then live events
157
- thereafter — see ``runtime/src/server.rs::watch_sessions``.
176
+ string (``CREATED`` / ``RESOLVED`` / ``EXPIRED`` / ``CANCELLED`` /
177
+ ``SUSPENDED`` / ``RESUMED``) and the full ``SessionMetadata``. The
178
+ runtime emits an initial CREATED event for every already-open session at
179
+ subscribe time, then live events thereafter — see
180
+ ``runtime/src/server.rs::watch_sessions``.
158
181
  """
159
182
 
160
183
  def __init__(self, client: MacpClient, *, auth: AuthConfig | None = None) -> None:
@@ -1,9 +1,14 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: macp-sdk-python
3
- Version: 0.3.0
3
+ Version: 0.4.1
4
4
  Summary: Python SDK for the MACP Rust runtime
5
- Author-email: OpenAI <support@openai.com>
5
+ Author-email: Multi-Agent Coordination Protocol <macp@multiagentcoordinationprotocol.org>
6
6
  License: Apache-2.0
7
+ Project-URL: Homepage, https://github.com/multiagentcoordinationprotocol/macp-sdk-python
8
+ Project-URL: Repository, https://github.com/multiagentcoordinationprotocol/macp-sdk-python
9
+ Project-URL: Documentation, https://github.com/multiagentcoordinationprotocol/macp-sdk-python/tree/main/docs
10
+ Project-URL: Changelog, https://github.com/multiagentcoordinationprotocol/macp-sdk-python/blob/main/CHANGELOG.md
11
+ Project-URL: Bug Tracker, https://github.com/multiagentcoordinationprotocol/macp-sdk-python/issues
7
12
  Classifier: Development Status :: 3 - Alpha
8
13
  Classifier: Intended Audience :: Developers
9
14
  Classifier: License :: OSI Approved :: Apache Software License
@@ -15,9 +20,9 @@ Classifier: Topic :: Software Development :: Libraries
15
20
  Requires-Python: >=3.11
16
21
  Description-Content-Type: text/markdown
17
22
  License-File: LICENSE
18
- Requires-Dist: grpcio>=1.74.0
23
+ Requires-Dist: grpcio>=1.81.1
19
24
  Requires-Dist: protobuf>=5.27.0
20
- Requires-Dist: macp-proto<0.2.0,>=0.1.2
25
+ Requires-Dist: macp-proto<0.2.0,>=0.1.3
21
26
  Provides-Extra: dev
22
27
  Requires-Dist: grpcio-tools>=1.74.0; extra == "dev"
23
28
  Requires-Dist: pytest>=8.0; extra == "dev"
@@ -165,6 +170,7 @@ Business logic — voting rules, AI decision heuristics, policy enforcement —
165
170
 
166
171
  - `GetSession` returns metadata only (not mode state/transcript) — hence the local projection pattern; use `StreamSession` + `send_subscribe` for the full transcript
167
172
  - `StreamSession` is scoped to one session per stream; use `MacpStream.send_subscribe(session_id)` (RFC-MACP-0006-A1, since SDK 0.2.3 / `macp-proto 0.1.2`) to replay accepted history before live broadcast
168
- - For cross-session observability, use `MacpClient.list_sessions()` and `SessionLifecycleWatcher` (SDK 0.3.0) to enumerate active sessions and stream `CREATED` / `RESOLVED` / `EXPIRED` lifecycle events
173
+ - For cross-session observability, use `MacpClient.list_sessions()` and `SessionLifecycleWatcher` (SDK 0.3.0) to enumerate active sessions and stream `CREATED` / `RESOLVED` / `EXPIRED` / `CANCELLED` / `SUSPENDED` / `RESUMED` lifecycle events
174
+ - Pause and cancel sessions with `MacpClient.suspend_session()` / `resume_session()` / `cancel_session()` (SDK 0.4.0 / `macp-proto 0.1.3`). A suspended session is non-terminal and rejects messages until resumed; an accepted `cancel_session()` now terminates as `CANCELLED` (previously surfaced as `EXPIRED`)
169
175
  - `ListRoots` currently returns an empty list and `WatchRoots` idles — the runtime does not yet populate roots
170
176
  - Business policy (majority, quorum, veto) belongs in your orchestrator/policy layer
@@ -1,6 +1,6 @@
1
- grpcio>=1.74.0
1
+ grpcio>=1.81.1
2
2
  protobuf>=5.27.0
3
- macp-proto<0.2.0,>=0.1.2
3
+ macp-proto<0.2.0,>=0.1.3
4
4
 
5
5
  [dev]
6
6
  grpcio-tools>=1.74.0
File without changes