macp-sdk-python 0.12.0__tar.gz → 0.13.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 (40) hide show
  1. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/PKG-INFO +1 -1
  2. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/pyproject.toml +1 -1
  3. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/handoff.py +10 -7
  4. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/projections.py +4 -1
  5. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/proposal.py +55 -5
  6. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/quorum.py +6 -2
  7. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk_python.egg-info/PKG-INFO +1 -1
  8. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/LICENSE +0 -0
  9. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/README.md +0 -0
  10. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/setup.cfg +0 -0
  11. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/__init__.py +0 -0
  12. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/_logging.py +0 -0
  13. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/agent/__init__.py +0 -0
  14. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/agent/cancel_callback.py +0 -0
  15. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/agent/dispatcher.py +0 -0
  16. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/agent/participant.py +0 -0
  17. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/agent/runner.py +0 -0
  18. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/agent/strategies.py +0 -0
  19. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/agent/transports.py +0 -0
  20. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/agent/types.py +0 -0
  21. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/auth.py +0 -0
  22. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/base_projection.py +0 -0
  23. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/base_session.py +0 -0
  24. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/client.py +0 -0
  25. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/commitment_hash.py +0 -0
  26. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/constants.py +0 -0
  27. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/decision.py +0 -0
  28. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/envelope.py +0 -0
  29. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/errors.py +0 -0
  30. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/policy.py +0 -0
  31. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/proto_registry.py +0 -0
  32. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/py.typed +0 -0
  33. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/retry.py +0 -0
  34. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/task.py +0 -0
  35. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/validation.py +0 -0
  36. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk/watchers.py +0 -0
  37. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk_python.egg-info/SOURCES.txt +0 -0
  38. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk_python.egg-info/dependency_links.txt +0 -0
  39. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk_python.egg-info/requires.txt +0 -0
  40. {macp_sdk_python-0.12.0 → macp_sdk_python-0.13.0}/src/macp_sdk_python.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: macp-sdk-python
3
- Version: 0.12.0
3
+ Version: 0.13.0
4
4
  Summary: Python SDK for the MACP Rust runtime
5
5
  Author-email: Multi-Agent Coordination Protocol <macp@multiagentcoordinationprotocol.org>
6
6
  License: Apache-2.0
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "macp-sdk-python"
7
- version = "0.12.0"
7
+ version = "0.13.0"
8
8
  description = "Python SDK for the MACP Rust runtime"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -30,9 +30,11 @@ class HandoffRecord:
30
30
  declined_by: str | None
31
31
  # True when the acceptance was an implicit accept synthesized by the
32
32
  # runtime (RFC-MACP-0010 §5.1) rather than an explicit client HandoffAccept.
33
- # Runtime >= 0.8.0 emits these automatically for every session (no
34
- # server-side opt-in exists) when an offer's implicit_accept_timeout_ms
35
- # elapses unactioned. Client-submitted accepts are always
33
+ # Runtime >= 0.8.0 emits these only for sessions whose bound governance
34
+ # policy declares a non-zero ``acceptance.implicit_accept_timeout_ms``
35
+ # (RFC-MACP-0010 §5.1, RFC-MACP-0012 §4.5), once that timeout elapses
36
+ # unactioned; with no policy bound the timeout resolves to 0 and no
37
+ # implicit accept ever fires. Client-submitted accepts are always
36
38
  # ``implicit=False`` (the runtime rejects a forged True).
37
39
  implicit: bool = False
38
40
 
@@ -177,9 +179,10 @@ class HandoffProjection(BaseProjection):
177
179
  """True if *handoff_id* was accepted by a runtime implicit accept.
178
180
 
179
181
  Distinguishes a timeout-driven implicit accept (RFC-MACP-0010 §5.1)
180
- from an explicit client ``HandoffAccept``. Runtime >= 0.8.0 emits
181
- these automatically for every session once an offer's
182
- ``implicit_accept_timeout_ms`` elapses unactioned.
182
+ from an explicit client ``HandoffAccept``. Runtime >= 0.8.0 emits one
183
+ only when the session's bound governance policy declares a non-zero
184
+ ``acceptance.implicit_accept_timeout_ms`` and that timeout elapses
185
+ unactioned; sessions with no such policy never see one.
183
186
  """
184
187
  handoff = self.handoffs.get(handoff_id)
185
188
  return handoff is not None and handoff.status == "accepted" and handoff.implicit
@@ -275,7 +278,7 @@ class HandoffSession(BaseSession):
275
278
  # ``implicit=true`` for runtime-synthesized accepts and the runtime
276
279
  # rejects a client-submitted True. Client accepts always leave the
277
280
  # field at its proto3 default (False). See the regression test in
278
- # tests/unit/test_handoff.py.
281
+ # tests/unit/test_absorb_runtime_v050.py.
279
282
  validate_required_field("handoff_id", handoff_id)
280
283
  payload = handoff_pb2.HandoffAcceptPayload(
281
284
  handoff_id=handoff_id,
@@ -132,7 +132,10 @@ class DecisionProjection(BaseProjection):
132
132
  """Count votes per proposal, keyed by proposal_id.
133
133
 
134
134
  ABSTAIN votes are tracked but excluded from the totals returned
135
- here (which counts only APPROVE votes).
135
+ here (which counts only APPROVE votes). A proposal_id that received
136
+ no votes at all is **absent** from the returned dict -- not present
137
+ with a 0 value. Use ``.get(proposal_id, 0)`` when checking an
138
+ arbitrary proposal_id.
136
139
  """
137
140
  totals: dict[str, int] = {}
138
141
  for proposal_id, sender_votes in self.votes.items():
@@ -25,11 +25,50 @@ class ProposalRecord:
25
25
  proposal_id: str
26
26
  title: str
27
27
  summary: str
28
- proposer: str
28
+ sender: str
29
29
  supersedes: str # "" if original
30
- status: str # "open" | "accepted" | "rejected" | "withdrawn"
30
+ # Reachable values only. An Accept is recorded on the projection's
31
+ # ``accepts`` list (and surfaced via ``accepted_proposal`` /
32
+ # ``is_accepted``), never on this field -- no code path assigns
33
+ # "accepted" here. A non-terminal Reject likewise leaves this "open";
34
+ # only ``terminal=True`` sets "rejected".
35
+ #
36
+ # That is by design, not an omission (issue #112). Acceptance is a
37
+ # per-sender, supersedable relation (RFC-MACP-0008 §5 rule 5), not a
38
+ # per-proposal fact, so a scalar field here cannot hold it: "alice
39
+ # accepts p2 while bob still accepts p1" is a legal state. This mirrors
40
+ # the runtime, whose ``ProposalDisposition`` is {Live, Withdrawn} with
41
+ # acceptance in a separate ``accepts`` map, and typescript-sdk's
42
+ # ``projections/proposal.ts``, which also never assigns "accepted".
43
+ # ``task.py``/``handoff.py`` do set "accepted" because their acceptance
44
+ # is one actor claiming one slot. See docs/modes/proposal.md.
45
+ status: str # "open" | "rejected" | "withdrawn"
31
46
  tags: list[str]
32
47
 
48
+ # ── Deprecated alias (issue #120 / multiagentcoordinationprotocol#177) ──
49
+ # ``proposer`` is kept as a read-only, INSTANCE-level property alias for
50
+ # one minor version, removed at this SDK's next major version. This is a
51
+ # different mechanism from the module-level ``__getattr__``/PEP 562
52
+ # aliases below (``RejectRecord``/``AcceptRecord``, issue #103): those
53
+ # intercept MODULE attribute access (``macp_sdk.proposal.RejectRecord``);
54
+ # PEP 562 has no bearing on INSTANCE attribute access
55
+ # (``some_record.proposer``), which is what this field needs. A
56
+ # ``@dataclass(slots=True)`` class derives ``__slots__`` strictly from
57
+ # its annotated fields, so a plain same-named class attribute alias is
58
+ # not an option here -- but a property is not a field and is untouched
59
+ # by the slots transformation, so it coexists cleanly: ``fields()``,
60
+ # ``__slots__``, ``repr()``, and ``dataclasses.replace()`` all still
61
+ # see/use only the real ``sender`` field. No setter is defined: nothing
62
+ # in this module ever assigns ``.proposer``.
63
+ @property
64
+ def proposer(self) -> str:
65
+ warnings.warn(
66
+ "ProposalRecord.proposer is deprecated; use ProposalRecord.sender instead.",
67
+ DeprecationWarning,
68
+ stacklevel=2,
69
+ )
70
+ return self.sender
71
+
33
72
 
34
73
  @dataclass(slots=True)
35
74
  class ProposalRejectRecord:
@@ -79,7 +118,7 @@ class ProposalProjection(BaseProjection):
79
118
  proposal_id=p.proposal_id,
80
119
  title=p.title,
81
120
  summary=p.summary,
82
- proposer=envelope.sender,
121
+ sender=envelope.sender,
83
122
  supersedes="",
84
123
  status="open",
85
124
  tags=list(p.tags),
@@ -93,7 +132,7 @@ class ProposalProjection(BaseProjection):
93
132
  proposal_id=p.proposal_id,
94
133
  title=p.title,
95
134
  summary=p.summary,
96
- proposer=envelope.sender,
135
+ sender=envelope.sender,
97
136
  supersedes=p.supersedes_proposal_id,
98
137
  status="open",
99
138
  tags=[],
@@ -126,9 +165,20 @@ class ProposalProjection(BaseProjection):
126
165
  )
127
166
  if p.terminal:
128
167
  rec = self.proposals.get(p.proposal_id)
168
+ # A terminal Reject must name a proposal this projection has
169
+ # actually seen (RFC-MACP-0008 §5 rule 3) before it ends the
170
+ # negotiation. "TerminalRejected" is in TERMINAL_PHASES
171
+ # (agent/participant.py:57-59), so moving phase here for an
172
+ # unknown proposal_id would fire a Participant's on_terminal
173
+ # and tear down its stream for a session that never actually
174
+ # terminated. Same shape as task.py's per-task gates
175
+ # (:172/:196/:212): gate the state transition, keep the
176
+ # audit append above unconditional. has_terminal_rejection()
177
+ # / is_terminally_rejected() deliberately still read
178
+ # self.rejections, not phase, so they stay True either way.
129
179
  if rec is not None:
130
180
  rec.status = "rejected"
131
- self._set_phase("TerminalRejected")
181
+ self._set_phase("TerminalRejected")
132
182
  return
133
183
 
134
184
  if mt == "Withdraw":
@@ -97,8 +97,12 @@ class QuorumProjection(BaseProjection):
97
97
  # enforces RFC-MACP-0011 §5 rule 3's *across-type* cardinality rule
98
98
  # ("at most one ballot across Approve, Reject, or Abstain"). First
99
99
  # ballot per sender stands; which-of-two-stands is inferred from
100
- # RFC-MACP-0007 §5.3 parity plus what the only runtime enforces
101
- # (quorum.rs:164/184/204) -- RFC-0011 itself is silent on that.
100
+ # RFC-MACP-0007 §5.3 parity plus what the only runtime enforces: the
101
+ # three same-sender ballot guards in macp-runtime's
102
+ # crates/macp-modes/src/mode/quorum.rs (QuorumMode's
103
+ # Approve/Reject/Abstain arms, each rejecting when
104
+ # state.ballots.contains_key(&env.sender), confirmed at runtime
105
+ # v0.8.6) -- RFC-0011 itself is silent on that.
102
106
  sender = envelope.sender
103
107
  sender_map = self.ballots.setdefault(request_id, {})
104
108
  existing = sender_map.get(sender)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: macp-sdk-python
3
- Version: 0.12.0
3
+ Version: 0.13.0
4
4
  Summary: Python SDK for the MACP Rust runtime
5
5
  Author-email: Multi-Agent Coordination Protocol <macp@multiagentcoordinationprotocol.org>
6
6
  License: Apache-2.0