macp-sdk-python 0.2.1__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.
macp_sdk/__init__.py ADDED
@@ -0,0 +1,207 @@
1
+ from ._logging import configure_logging
2
+ from .auth import AuthConfig
3
+ from .base_projection import BaseProjection
4
+ from .base_session import BaseSession
5
+ from .client import MacpClient, MacpStream
6
+ from .constants import (
7
+ DEFAULT_CONFIGURATION_VERSION,
8
+ DEFAULT_MODE_VERSION,
9
+ DEFAULT_POLICY_VERSION,
10
+ MACP_VERSION,
11
+ MODE_DECISION,
12
+ MODE_HANDOFF,
13
+ MODE_MULTI_ROUND,
14
+ MODE_PROPOSAL,
15
+ MODE_QUORUM,
16
+ MODE_TASK,
17
+ STANDARD_MODES,
18
+ )
19
+ from .decision import DecisionSession
20
+ from .envelope import (
21
+ build_commitment_payload,
22
+ build_envelope,
23
+ build_progress_payload,
24
+ build_root,
25
+ build_session_start_payload,
26
+ build_signal_payload,
27
+ infer_outcome_positive,
28
+ new_commitment_id,
29
+ new_message_id,
30
+ new_session_id,
31
+ serialize_message,
32
+ )
33
+ from .errors import (
34
+ DUPLICATE_MESSAGE,
35
+ FORBIDDEN,
36
+ INTERNAL_ERROR,
37
+ INVALID_ENVELOPE,
38
+ INVALID_POLICY_DEFINITION,
39
+ INVALID_SESSION_ID,
40
+ MODE_NOT_SUPPORTED,
41
+ PAYLOAD_TOO_LARGE,
42
+ POLICY_DENIED,
43
+ RATE_LIMITED,
44
+ SESSION_ALREADY_EXISTS,
45
+ SESSION_NOT_FOUND,
46
+ SESSION_NOT_OPEN,
47
+ UNAUTHENTICATED,
48
+ UNKNOWN_POLICY_VERSION,
49
+ UNSUPPORTED_PROTOCOL_VERSION,
50
+ AckFailure,
51
+ MacpAckError,
52
+ MacpIdentityMismatchError,
53
+ MacpRetryError,
54
+ MacpSdkError,
55
+ MacpSessionError,
56
+ MacpTimeoutError,
57
+ MacpTransportError,
58
+ )
59
+ from .handoff import HandoffProjection, HandoffRecord, HandoffSession
60
+ from .policy import (
61
+ AbstentionRules,
62
+ CommitmentRules,
63
+ CounterProposalRules,
64
+ EvaluationRules,
65
+ HandoffAcceptanceRules,
66
+ ObjectionHandlingRules,
67
+ ProposalAcceptanceRules,
68
+ QuorumThreshold,
69
+ RejectionRules,
70
+ TaskAssignmentRules,
71
+ TaskCompletionRules,
72
+ VotingRules,
73
+ build_decision_policy,
74
+ build_handoff_policy,
75
+ build_proposal_policy,
76
+ build_quorum_policy,
77
+ build_task_policy,
78
+ )
79
+ from .projections import DecisionProjection
80
+ from .proposal import ProposalProjection, ProposalSession, RejectRecord
81
+ from .proto_registry import ProtoRegistry
82
+ from .quorum import QuorumProjection, QuorumSession
83
+ from .retry import RetryPolicy, retry_send
84
+ from .task import TaskProjection, TaskSession
85
+ from .validation import (
86
+ validate_confidence,
87
+ validate_participant_count,
88
+ validate_participants,
89
+ validate_recommendation,
90
+ validate_required_field,
91
+ validate_session_id,
92
+ validate_session_start,
93
+ validate_severity,
94
+ validate_signal_type,
95
+ validate_ttl_ms,
96
+ validate_vote,
97
+ )
98
+ from .watchers import (
99
+ ModeRegistryWatcher,
100
+ PolicyChange,
101
+ PolicyWatcher,
102
+ RootsWatcher,
103
+ SignalWatcher,
104
+ )
105
+
106
+ __all__ = [
107
+ "DEFAULT_CONFIGURATION_VERSION",
108
+ "DEFAULT_MODE_VERSION",
109
+ "DEFAULT_POLICY_VERSION",
110
+ "DUPLICATE_MESSAGE",
111
+ "FORBIDDEN",
112
+ "INTERNAL_ERROR",
113
+ "INVALID_ENVELOPE",
114
+ "INVALID_POLICY_DEFINITION",
115
+ "INVALID_SESSION_ID",
116
+ "MACP_VERSION",
117
+ "MODE_DECISION",
118
+ "MODE_HANDOFF",
119
+ "MODE_MULTI_ROUND",
120
+ "MODE_NOT_SUPPORTED",
121
+ "MODE_PROPOSAL",
122
+ "MODE_QUORUM",
123
+ "MODE_TASK",
124
+ "PAYLOAD_TOO_LARGE",
125
+ "POLICY_DENIED",
126
+ "RATE_LIMITED",
127
+ "SESSION_ALREADY_EXISTS",
128
+ "SESSION_NOT_FOUND",
129
+ "SESSION_NOT_OPEN",
130
+ "STANDARD_MODES",
131
+ "UNAUTHENTICATED",
132
+ "UNKNOWN_POLICY_VERSION",
133
+ "UNSUPPORTED_PROTOCOL_VERSION",
134
+ "AbstentionRules",
135
+ "AckFailure",
136
+ "AuthConfig",
137
+ "BaseProjection",
138
+ "BaseSession",
139
+ "CommitmentRules",
140
+ "CounterProposalRules",
141
+ "DecisionProjection",
142
+ "DecisionSession",
143
+ "EvaluationRules",
144
+ "HandoffAcceptanceRules",
145
+ "HandoffProjection",
146
+ "HandoffRecord",
147
+ "HandoffSession",
148
+ "MacpAckError",
149
+ "MacpClient",
150
+ "MacpIdentityMismatchError",
151
+ "MacpRetryError",
152
+ "MacpSdkError",
153
+ "MacpSessionError",
154
+ "MacpStream",
155
+ "MacpTimeoutError",
156
+ "MacpTransportError",
157
+ "ModeRegistryWatcher",
158
+ "ObjectionHandlingRules",
159
+ "PolicyChange",
160
+ "PolicyWatcher",
161
+ "ProposalAcceptanceRules",
162
+ "ProposalProjection",
163
+ "ProposalSession",
164
+ "ProtoRegistry",
165
+ "QuorumProjection",
166
+ "QuorumSession",
167
+ "QuorumThreshold",
168
+ "RejectRecord",
169
+ "RejectionRules",
170
+ "RetryPolicy",
171
+ "RootsWatcher",
172
+ "SignalWatcher",
173
+ "TaskAssignmentRules",
174
+ "TaskCompletionRules",
175
+ "TaskProjection",
176
+ "TaskSession",
177
+ "VotingRules",
178
+ "build_commitment_payload",
179
+ "build_decision_policy",
180
+ "build_envelope",
181
+ "build_handoff_policy",
182
+ "build_progress_payload",
183
+ "build_proposal_policy",
184
+ "build_quorum_policy",
185
+ "build_root",
186
+ "build_session_start_payload",
187
+ "build_signal_payload",
188
+ "build_task_policy",
189
+ "configure_logging",
190
+ "infer_outcome_positive",
191
+ "new_commitment_id",
192
+ "new_message_id",
193
+ "new_session_id",
194
+ "retry_send",
195
+ "serialize_message",
196
+ "validate_confidence",
197
+ "validate_participant_count",
198
+ "validate_participants",
199
+ "validate_recommendation",
200
+ "validate_required_field",
201
+ "validate_session_id",
202
+ "validate_session_start",
203
+ "validate_severity",
204
+ "validate_signal_type",
205
+ "validate_ttl_ms",
206
+ "validate_vote",
207
+ ]
macp_sdk/_logging.py ADDED
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ import logging
4
+
5
+ logger = logging.getLogger("macp_sdk")
6
+
7
+
8
+ def configure_logging(
9
+ level: int = logging.INFO,
10
+ fmt: str = "%(asctime)s %(name)s %(levelname)s %(message)s",
11
+ ) -> None:
12
+ """Configure the macp_sdk logger with a stream handler."""
13
+ handler = logging.StreamHandler()
14
+ handler.setFormatter(logging.Formatter(fmt))
15
+ logger.addHandler(handler)
16
+ logger.setLevel(level)
@@ -0,0 +1,64 @@
1
+ from .dispatcher import Dispatcher
2
+ from .participant import Participant, ParticipantActions
3
+ from .runner import from_bootstrap
4
+ from .strategies import (
5
+ CommitmentDecision,
6
+ CommitmentStrategy,
7
+ EvaluationResult,
8
+ EvaluationStrategy,
9
+ VoteDecision,
10
+ VotingStrategy,
11
+ commitment_handler,
12
+ evaluation_handler,
13
+ function_committer,
14
+ function_evaluator,
15
+ function_voter,
16
+ majority_committer,
17
+ majority_voter,
18
+ voting_handler,
19
+ )
20
+ from .transports import (
21
+ GrpcTransportAdapter,
22
+ HttpTransportAdapter,
23
+ TransportAdapter,
24
+ )
25
+ from .types import (
26
+ HandlerContext,
27
+ IncomingMessage,
28
+ MessageHandler,
29
+ PhaseChangeHandler,
30
+ SessionInfo,
31
+ TerminalHandler,
32
+ TerminalResult,
33
+ )
34
+
35
+ __all__ = [
36
+ "CommitmentDecision",
37
+ "CommitmentStrategy",
38
+ "Dispatcher",
39
+ "EvaluationResult",
40
+ "EvaluationStrategy",
41
+ "GrpcTransportAdapter",
42
+ "HandlerContext",
43
+ "HttpTransportAdapter",
44
+ "IncomingMessage",
45
+ "MessageHandler",
46
+ "Participant",
47
+ "ParticipantActions",
48
+ "PhaseChangeHandler",
49
+ "SessionInfo",
50
+ "TerminalHandler",
51
+ "TerminalResult",
52
+ "TransportAdapter",
53
+ "VoteDecision",
54
+ "VotingStrategy",
55
+ "commitment_handler",
56
+ "evaluation_handler",
57
+ "from_bootstrap",
58
+ "function_committer",
59
+ "function_evaluator",
60
+ "function_voter",
61
+ "majority_committer",
62
+ "majority_voter",
63
+ "voting_handler",
64
+ ]
@@ -0,0 +1,105 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import defaultdict
4
+
5
+ from .._logging import logger
6
+ from .types import (
7
+ HandlerContext,
8
+ IncomingMessage,
9
+ MessageHandler,
10
+ PhaseChangeHandler,
11
+ TerminalHandler,
12
+ TerminalResult,
13
+ )
14
+
15
+
16
+ class Dispatcher:
17
+ """Routes incoming MACP messages to registered handlers.
18
+
19
+ Supports per-message-type handlers, per-phase-change handlers,
20
+ wildcard ``'*'`` handlers (invoked for every event), and a single
21
+ terminal handler.
22
+ """
23
+
24
+ def __init__(self) -> None:
25
+ self._handlers: dict[str, list[MessageHandler]] = defaultdict(list)
26
+ self._wildcard_handlers: list[MessageHandler] = []
27
+ self._phase_handlers: dict[str, list[PhaseChangeHandler]] = defaultdict(list)
28
+ self._wildcard_phase_handlers: list[PhaseChangeHandler] = []
29
+ self._terminal_handler: TerminalHandler | None = None
30
+
31
+ def on(self, message_type: str, handler: MessageHandler) -> None:
32
+ """Register a handler for a specific message type.
33
+
34
+ Use ``'*'`` as the message_type to register a wildcard handler that
35
+ is invoked for every message after type-specific handlers.
36
+ """
37
+ if message_type == "*":
38
+ self._wildcard_handlers.append(handler)
39
+ else:
40
+ self._handlers[message_type].append(handler)
41
+
42
+ def on_phase_change(self, phase: str, handler: PhaseChangeHandler) -> None:
43
+ """Register a handler for a specific phase transition.
44
+
45
+ Use ``'*'`` as the phase to register a wildcard handler that is
46
+ invoked for every phase change after phase-specific handlers.
47
+ """
48
+ if phase == "*":
49
+ self._wildcard_phase_handlers.append(handler)
50
+ else:
51
+ self._phase_handlers[phase].append(handler)
52
+
53
+ def on_terminal(self, handler: TerminalHandler) -> None:
54
+ """Register the terminal state handler (only one is allowed)."""
55
+ self._terminal_handler = handler
56
+
57
+ def dispatch(self, message: IncomingMessage, ctx: HandlerContext) -> None:
58
+ """Dispatch a message to all matching handlers.
59
+
60
+ Type-specific handlers are invoked first (in registration order),
61
+ followed by wildcard ``'*'`` handlers. If no handler matches at
62
+ all, a debug-level log is emitted.
63
+ """
64
+ handlers = self._handlers.get(message.message_type)
65
+ if not handlers and not self._wildcard_handlers:
66
+ logger.debug("no handler for message_type=%s", message.message_type)
67
+ return
68
+ for handler in handlers or []:
69
+ handler(message, ctx)
70
+ for handler in self._wildcard_handlers:
71
+ handler(message, ctx)
72
+
73
+ def dispatch_phase_change(self, phase: str, ctx: HandlerContext) -> None:
74
+ """Dispatch a phase change event to matching handlers.
75
+
76
+ Phase-specific handlers are invoked first, followed by wildcard
77
+ ``'*'`` phase handlers.
78
+ """
79
+ handlers = self._phase_handlers.get(phase)
80
+ for handler in handlers or []:
81
+ handler(phase, ctx)
82
+ for handler in self._wildcard_phase_handlers:
83
+ handler(phase, ctx)
84
+
85
+ def dispatch_terminal(self, result: TerminalResult) -> None:
86
+ """Invoke the terminal handler if registered."""
87
+ if self._terminal_handler is not None:
88
+ self._terminal_handler(result)
89
+ else:
90
+ logger.debug("terminal result with no handler: state=%s", result.state)
91
+
92
+ @property
93
+ def registered_message_types(self) -> list[str]:
94
+ """Return all message types that have at least one handler."""
95
+ return list(self._handlers.keys())
96
+
97
+ @property
98
+ def registered_phases(self) -> list[str]:
99
+ """Return all phases that have at least one handler."""
100
+ return list(self._phase_handlers.keys())
101
+
102
+ @property
103
+ def has_terminal_handler(self) -> bool:
104
+ """Return whether a terminal handler is registered."""
105
+ return self._terminal_handler is not None