polytrax 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 (55) hide show
  1. polytrax-0.1.0/LICENSE +21 -0
  2. polytrax-0.1.0/MANIFEST.in +20 -0
  3. polytrax-0.1.0/PKG-INFO +108 -0
  4. polytrax-0.1.0/README.md +68 -0
  5. polytrax-0.1.0/polytrax/src/polytrax/__init__.py +7 -0
  6. polytrax-0.1.0/polytrax/src/polytrax/adapters/__init__.py +27 -0
  7. polytrax-0.1.0/polytrax/src/polytrax/adapters/base.py +54 -0
  8. polytrax-0.1.0/polytrax/src/polytrax/adapters/exceptions.py +19 -0
  9. polytrax-0.1.0/polytrax/src/polytrax/adapters/generic.py +228 -0
  10. polytrax-0.1.0/polytrax/src/polytrax/adapters/langgraph.py +412 -0
  11. polytrax-0.1.0/polytrax/src/polytrax/analysis/__init__.py +32 -0
  12. polytrax-0.1.0/polytrax/src/polytrax/analysis/containment.py +661 -0
  13. polytrax-0.1.0/polytrax/src/polytrax/analysis/graph.py +148 -0
  14. polytrax-0.1.0/polytrax/src/polytrax/analysis/localization.py +1282 -0
  15. polytrax-0.1.0/polytrax/src/polytrax/analysis/propagation.py +220 -0
  16. polytrax-0.1.0/polytrax/src/polytrax/analysis/token_estimation.py +207 -0
  17. polytrax-0.1.0/polytrax/src/polytrax/analysis/tps_replay.py +336 -0
  18. polytrax-0.1.0/polytrax/src/polytrax/analysis/traversal.py +124 -0
  19. polytrax-0.1.0/polytrax/src/polytrax/benchmark/__init__.py +46 -0
  20. polytrax-0.1.0/polytrax/src/polytrax/benchmark/runner.py +757 -0
  21. polytrax-0.1.0/polytrax/src/polytrax/benchmark/schema.py +153 -0
  22. polytrax-0.1.0/polytrax/src/polytrax/evaluation/__init__.py +51 -0
  23. polytrax-0.1.0/polytrax/src/polytrax/evaluation/aggregate.py +182 -0
  24. polytrax-0.1.0/polytrax/src/polytrax/evaluation/dimension_pipelines.py +132 -0
  25. polytrax-0.1.0/polytrax/src/polytrax/evaluation/judge_base.py +56 -0
  26. polytrax-0.1.0/polytrax/src/polytrax/evaluation/llm_judge.py +391 -0
  27. polytrax-0.1.0/polytrax/src/polytrax/evaluation/rubric.py +138 -0
  28. polytrax-0.1.0/polytrax/src/polytrax/evaluation/runner.py +352 -0
  29. polytrax-0.1.0/polytrax/src/polytrax/evaluation/scoring.py +169 -0
  30. polytrax-0.1.0/polytrax/src/polytrax/evaluation/stub_judge.py +189 -0
  31. polytrax-0.1.0/polytrax/src/polytrax/instrumentation/__init__.py +19 -0
  32. polytrax-0.1.0/polytrax/src/polytrax/instrumentation/builder.py +132 -0
  33. polytrax-0.1.0/polytrax/src/polytrax/instrumentation/instrumentor.py +104 -0
  34. polytrax-0.1.0/polytrax/src/polytrax/reporting/__init__.py +15 -0
  35. polytrax-0.1.0/polytrax/src/polytrax/reporting/report.py +248 -0
  36. polytrax-0.1.0/polytrax/src/polytrax/runtime/__init__.py +28 -0
  37. polytrax-0.1.0/polytrax/src/polytrax/runtime/audit.py +36 -0
  38. polytrax-0.1.0/polytrax/src/polytrax/runtime/controller.py +66 -0
  39. polytrax-0.1.0/polytrax/src/polytrax/runtime/policies.py +66 -0
  40. polytrax-0.1.0/polytrax/src/polytrax/runtime/tps.py +254 -0
  41. polytrax-0.1.0/polytrax/src/polytrax/runtime/types.py +47 -0
  42. polytrax-0.1.0/polytrax/src/polytrax/sinks/__init__.py +21 -0
  43. polytrax-0.1.0/polytrax/src/polytrax/sinks/base.py +63 -0
  44. polytrax-0.1.0/polytrax/src/polytrax/sinks/jsonl.py +89 -0
  45. polytrax-0.1.0/polytrax/src/polytrax/sinks/memory.py +60 -0
  46. polytrax-0.1.0/polytrax/src/polytrax/trust/__init__.py +21 -0
  47. polytrax-0.1.0/polytrax/src/polytrax/trust/evaluator.py +246 -0
  48. polytrax-0.1.0/polytrax/src/polytrax/trust/policies.py +262 -0
  49. polytrax-0.1.0/polytrax/src/polytrax/usl/__init__.py +38 -0
  50. polytrax-0.1.0/polytrax/src/polytrax/usl/io.py +75 -0
  51. polytrax-0.1.0/polytrax/src/polytrax/usl/schema.py +138 -0
  52. polytrax-0.1.0/polytrax/src/polytrax/usl/validate.py +126 -0
  53. polytrax-0.1.0/polytrax/src/polytrax.egg-info/SOURCES.txt +52 -0
  54. polytrax-0.1.0/pyproject.toml +65 -0
  55. polytrax-0.1.0/setup.cfg +4 -0
polytrax-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 PolyTrax Contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,20 @@
1
+ include LICENSE
2
+ include README.md
3
+ include pyproject.toml
4
+
5
+ graft polytrax/src/polytrax
6
+ exclude polytrax/src/polytrax/.DS_Store
7
+
8
+ prune apps
9
+ prune build
10
+ prune demo
11
+ prune dist
12
+ prune evaluation
13
+ prune polytrax/tests
14
+ prune polytrax/src/polytrax.egg-info
15
+ prune polytrax_lecacy_poc
16
+ prune tests
17
+
18
+ global-exclude .DS_Store
19
+ global-exclude __pycache__
20
+ global-exclude *.py[cod]
@@ -0,0 +1,108 @@
1
+ Metadata-Version: 2.4
2
+ Name: polytrax
3
+ Version: 0.1.0
4
+ Summary: Communication-level observability framework for trust integrity in multi-agent systems
5
+ Author: Mindula Jayasinghe
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/Mindulaaa/polytrax
8
+ Project-URL: Documentation, https://github.com/Mindulaaa/polytrax/tree/main/docs
9
+ Project-URL: Repository, https://github.com/Mindulaaa/polytrax
10
+ Project-URL: Issues, https://github.com/Mindulaaa/polytrax/issues
11
+ Keywords: multi-agent,ai,observability,trust,evaluation,langgraph
12
+ Classifier: Development Status :: 3 - Alpha
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Operating System :: OS Independent
15
+ Classifier: Programming Language :: Python :: 3
16
+ Classifier: Programming Language :: Python :: 3 :: Only
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Software Development :: Libraries
22
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Provides-Extra: openai
28
+ Requires-Dist: openai>=1.0.0; extra == "openai"
29
+ Provides-Extra: langgraph
30
+ Requires-Dist: langgraph>=0.2.0; extra == "langgraph"
31
+ Provides-Extra: semantic
32
+ Requires-Dist: numpy>=1.24; extra == "semantic"
33
+ Requires-Dist: sentence-transformers>=2.2.2; extra == "semantic"
34
+ Provides-Extra: full
35
+ Requires-Dist: langgraph>=0.2.0; extra == "full"
36
+ Requires-Dist: numpy>=1.24; extra == "full"
37
+ Requires-Dist: openai>=1.0.0; extra == "full"
38
+ Requires-Dist: sentence-transformers>=2.2.2; extra == "full"
39
+ Dynamic: license-file
40
+
41
+ # PolyTrax
42
+
43
+ PolyTrax is a Python library for communication-level observability, runtime trust control, and post-run analysis in multi-agent systems.
44
+
45
+ It provides:
46
+
47
+ - Universal Structured Log (USL) schema and validation
48
+ - Event instrumentation utilities and sinks
49
+ - Runtime Trust Perturbation Score (R-TPS) monitoring and intervention policies
50
+ - Offline propagation and containment analysis
51
+ - Evaluation tooling (deterministic and LLM-based judges)
52
+
53
+ ## Install
54
+
55
+ Core library:
56
+
57
+ ```bash
58
+ pip install polytrax
59
+ ```
60
+
61
+ With optional integrations:
62
+
63
+ ```bash
64
+ pip install "polytrax[openai]" # LLM judge via OpenAI-compatible APIs
65
+ pip install "polytrax[langgraph]" # LangGraph adapter
66
+ pip install "polytrax[semantic]" # Semantic drift/localization extras
67
+ pip install "polytrax[full]" # All optional dependencies
68
+ ```
69
+
70
+ ## Quick Example
71
+
72
+ ```python
73
+ from polytrax.instrumentation import Instrumentor, EventBuilder
74
+ from polytrax.sinks import MemorySink
75
+
76
+ sink = MemorySink()
77
+ instr = Instrumentor(run_id="demo_run", sinks=[sink])
78
+ builder = EventBuilder(run_id="demo_run")
79
+
80
+ event = (
81
+ builder
82
+ .with_sender("collector_1")
83
+ .with_receiver("analyst_1")
84
+ .with_payload({"message": "hello"})
85
+ .build()
86
+ )
87
+
88
+ instr.emit(event)
89
+ print(len(sink.events))
90
+ ```
91
+
92
+ ## CLI
93
+
94
+ Evaluation runner:
95
+
96
+ ```bash
97
+ polytrax-eval --runs-dir ./runs --output-filename eval.json
98
+ ```
99
+
100
+ ## Project Layout
101
+
102
+ - Publishable library package: `polytrax/src/polytrax`
103
+ - Demo frontend/backend app (not packaged): `apps/polytrax_demo`
104
+ - Evaluation protocols and scripts: `evaluation`
105
+
106
+ ## License
107
+
108
+ MIT
@@ -0,0 +1,68 @@
1
+ # PolyTrax
2
+
3
+ PolyTrax is a Python library for communication-level observability, runtime trust control, and post-run analysis in multi-agent systems.
4
+
5
+ It provides:
6
+
7
+ - Universal Structured Log (USL) schema and validation
8
+ - Event instrumentation utilities and sinks
9
+ - Runtime Trust Perturbation Score (R-TPS) monitoring and intervention policies
10
+ - Offline propagation and containment analysis
11
+ - Evaluation tooling (deterministic and LLM-based judges)
12
+
13
+ ## Install
14
+
15
+ Core library:
16
+
17
+ ```bash
18
+ pip install polytrax
19
+ ```
20
+
21
+ With optional integrations:
22
+
23
+ ```bash
24
+ pip install "polytrax[openai]" # LLM judge via OpenAI-compatible APIs
25
+ pip install "polytrax[langgraph]" # LangGraph adapter
26
+ pip install "polytrax[semantic]" # Semantic drift/localization extras
27
+ pip install "polytrax[full]" # All optional dependencies
28
+ ```
29
+
30
+ ## Quick Example
31
+
32
+ ```python
33
+ from polytrax.instrumentation import Instrumentor, EventBuilder
34
+ from polytrax.sinks import MemorySink
35
+
36
+ sink = MemorySink()
37
+ instr = Instrumentor(run_id="demo_run", sinks=[sink])
38
+ builder = EventBuilder(run_id="demo_run")
39
+
40
+ event = (
41
+ builder
42
+ .with_sender("collector_1")
43
+ .with_receiver("analyst_1")
44
+ .with_payload({"message": "hello"})
45
+ .build()
46
+ )
47
+
48
+ instr.emit(event)
49
+ print(len(sink.events))
50
+ ```
51
+
52
+ ## CLI
53
+
54
+ Evaluation runner:
55
+
56
+ ```bash
57
+ polytrax-eval --runs-dir ./runs --output-filename eval.json
58
+ ```
59
+
60
+ ## Project Layout
61
+
62
+ - Publishable library package: `polytrax/src/polytrax`
63
+ - Demo frontend/backend app (not packaged): `apps/polytrax_demo`
64
+ - Evaluation protocols and scripts: `evaluation`
65
+
66
+ ## License
67
+
68
+ MIT
@@ -0,0 +1,7 @@
1
+ """PolyTrax — multi-agent communication framework."""
2
+
3
+ from .usl import USL_VERSION
4
+
5
+ __version__ = "0.1.0"
6
+
7
+ __all__ = ["USL_VERSION", "__version__"]
@@ -0,0 +1,27 @@
1
+ # polytrax/src/polytrax/adapters/__init__.py
2
+ """
3
+ polytrax.adapters — runtime adapter contracts and integrations.
4
+
5
+ - RuntimeAdapter: abstract contract for runtime integrations.
6
+ - GenericAdapter: in-process adapter for tests/demos (no runtime dependencies).
7
+ - LangGraphAdapter: optional adapter for LangGraph workflows (requires langgraph).
8
+ """
9
+
10
+ from .base import RuntimeAdapter
11
+ from .generic import GenericAdapter
12
+ from .exceptions import AdapterError, AdapterNotAttachedError, AdapterControlError
13
+
14
+ # LangGraphAdapter is optional; langgraph may not be installed.
15
+ try:
16
+ from .langgraph import LangGraphAdapter
17
+ except Exception: # pragma: no cover
18
+ LangGraphAdapter = None # type: ignore[assignment,misc]
19
+
20
+ __all__ = [
21
+ "RuntimeAdapter",
22
+ "GenericAdapter",
23
+ "LangGraphAdapter",
24
+ "AdapterError",
25
+ "AdapterNotAttachedError",
26
+ "AdapterControlError",
27
+ ]
@@ -0,0 +1,54 @@
1
+ # polytrax/src/polytrax/adapters/base.py
2
+ """
3
+ RuntimeAdapter contract.
4
+
5
+ Adapters translate runtime-specific signals (messages, tool calls, state changes)
6
+ into USL events emitted via polytrax.instrumentation.Instrumentor.
7
+
8
+ This module intentionally contains no runtime-specific imports.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ import abc
14
+ from typing import Any
15
+
16
+ from polytrax.instrumentation import EventBuilder, Instrumentor
17
+
18
+
19
+ class RuntimeAdapter(abc.ABC):
20
+ """
21
+ Abstract base class for runtime adapters.
22
+
23
+ Concrete adapters should:
24
+ - attach/detach to a runtime lifecycle
25
+ - optionally support controls (halt run, gate a message edge)
26
+ - emit USL events via the provided Instrumentor
27
+ """
28
+
29
+ def __init__(self, instrumentor: Instrumentor) -> None:
30
+ self._instrumentor = instrumentor
31
+
32
+ @abc.abstractmethod
33
+ def attach(self) -> None:
34
+ """Attach adapter to runtime lifecycle (or mark as attached)."""
35
+
36
+ @abc.abstractmethod
37
+ def detach(self) -> None:
38
+ """Detach adapter from runtime lifecycle (or mark as detached)."""
39
+
40
+ @abc.abstractmethod
41
+ def halt_run(self, reason: str) -> None:
42
+ """Request that the runtime halts the current run for the given reason."""
43
+
44
+ @abc.abstractmethod
45
+ def gate_message(self, sender: str, receiver: str, *, reason: str) -> None:
46
+ """Request that messages from sender->receiver are gated/blocked with the given reason."""
47
+
48
+ def emit_event(self, event: dict) -> None:
49
+ """Emit an already-built USL event dict."""
50
+ self._instrumentor.emit(event)
51
+
52
+ def emit_built(self, builder: EventBuilder, **kwargs: Any) -> dict:
53
+ """Build via EventBuilder, emit, and return the built event."""
54
+ return self._instrumentor.emit_built(builder, **kwargs)
@@ -0,0 +1,19 @@
1
+ # polytrax/src/polytrax/adapters/exceptions.py
2
+ """
3
+ Adapter exceptions.
4
+
5
+ Adapters are runtime integration layers. These exceptions provide a small,
6
+ stable surface for control/attachment errors without depending on any runtime.
7
+ """
8
+
9
+
10
+ class AdapterError(Exception):
11
+ """Base class for all adapter-related errors."""
12
+
13
+
14
+ class AdapterNotAttachedError(AdapterError):
15
+ """Raised when an adapter operation requires attach() but the adapter is not attached."""
16
+
17
+
18
+ class AdapterControlError(AdapterError):
19
+ """Raised when an adapter cannot complete a requested control action (halt/gate/etc.)."""
@@ -0,0 +1,228 @@
1
+ # polytrax/src/polytrax/adapters/generic.py
2
+ """
3
+ GenericAdapter: lightweight in-process adapter for demos/tests.
4
+
5
+ This simulates a multi-agent "run" deterministically without any external runtime.
6
+ Useful for validating end-to-end plumbing:
7
+ USL builder/validator -> Instrumentor -> sinks.
8
+
9
+ Supported architectures:
10
+ - pipeline: A->B->C->... (wrap around)
11
+ - peer: round-robin edges among agents (every ordered pair excluding self)
12
+ - supervisor: Supervisor<->AgentX alternating
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ from typing import Callable, Dict, List, Optional, Set, Tuple
18
+
19
+ from polytrax.instrumentation import EventBuilder, Instrumentor
20
+
21
+ from .base import RuntimeAdapter
22
+ from .exceptions import AdapterNotAttachedError, AdapterControlError
23
+
24
+
25
+ class GenericAdapter(RuntimeAdapter):
26
+ """
27
+ In-process adapter that emits USL events for a simulated multi-agent run.
28
+
29
+ Notes:
30
+ - Deterministic: same (agents, architecture, steps) -> same event sequence.
31
+ - Trust is optional and never required; you may include it via payload_factory if desired.
32
+ """
33
+
34
+ def __init__(
35
+ self,
36
+ instrumentor: Instrumentor,
37
+ *,
38
+ run_id: str,
39
+ agents: Optional[List[str]] = None,
40
+ architecture: str = "pipeline", # "pipeline", "peer", "supervisor"
41
+ ) -> None:
42
+ super().__init__(instrumentor)
43
+ self._run_id = run_id
44
+ self._architecture = architecture
45
+
46
+ # Default agent set if none provided.
47
+ self._agents: List[str] = list(agents) if agents is not None else ["A", "B", "C"]
48
+
49
+ if not self._agents:
50
+ raise ValueError("agents must contain at least one agent name")
51
+
52
+ if self._architecture not in {"pipeline", "peer", "supervisor"}:
53
+ raise ValueError('architecture must be one of: "pipeline", "peer", "supervisor"')
54
+
55
+ # Ensure Supervisor exists for supervisor architecture.
56
+ if self._architecture == "supervisor" and "Supervisor" not in self._agents:
57
+ self._agents = ["Supervisor"] + self._agents
58
+
59
+ # Provide defaults so non-message lifecycle events can emit without sender/receiver.
60
+ self._builder = EventBuilder(
61
+ run_id=self._run_id,
62
+ default_sender="polytrax",
63
+ default_receiver="polytrax",
64
+ )
65
+
66
+ self._attached: bool = False
67
+ self._halted: bool = False
68
+ self._gated_edges: Set[Tuple[str, str]] = set()
69
+
70
+ def attach(self) -> None:
71
+ self._attached = True
72
+ self.emit_built(
73
+ self._builder,
74
+ event_type="adapter_attached",
75
+ payload={
76
+ "adapter": "generic",
77
+ "architecture": self._architecture,
78
+ "agents": list(self._agents),
79
+ },
80
+ )
81
+
82
+ def detach(self) -> None:
83
+ # Detach is allowed even if already detached; keep deterministic and simple.
84
+ self._attached = False
85
+ self.emit_built(
86
+ self._builder,
87
+ event_type="adapter_detached",
88
+ payload={"adapter": "generic"},
89
+ )
90
+
91
+ def gate_message(self, sender: str, receiver: str, *, reason: str) -> None:
92
+ if not self._attached:
93
+ raise AdapterNotAttachedError("gate_message requires adapter to be attached")
94
+ self._gated_edges.add((sender, receiver))
95
+ self.emit_built(
96
+ self._builder,
97
+ sender=sender,
98
+ receiver=receiver,
99
+ event_type="gate_applied",
100
+ payload={"sender": sender, "receiver": receiver, "reason": reason},
101
+ )
102
+
103
+ def halt_run(self, reason: str) -> None:
104
+ if not self._attached:
105
+ raise AdapterNotAttachedError("halt_run requires adapter to be attached")
106
+ self._halted = True
107
+ self.emit_built(
108
+ self._builder,
109
+ event_type="run_halted",
110
+ payload={"reason": reason},
111
+ )
112
+
113
+ def run(
114
+ self,
115
+ *,
116
+ steps: int = 5,
117
+ payload_factory: Optional[Callable[[int, str, str], Dict]] = None,
118
+ ) -> None:
119
+ """
120
+ Simulate a run and emit message events.
121
+
122
+ For each step i:
123
+ - choose (sender, receiver) based on architecture
124
+ - if the edge is gated: emit message_blocked (and do not emit message_sent)
125
+ - else emit message_sent
126
+
127
+ payload_factory (optional):
128
+ Callable(step_i, sender, receiver) -> dict payload
129
+ Trust is not required; if you want, you can return {"trust": {...}, ...}
130
+ and it will be included in payload only. USL trust field is not touched by default.
131
+ """
132
+ if not self._attached:
133
+ raise AdapterNotAttachedError("run requires adapter to be attached")
134
+ if self._halted:
135
+ raise AdapterControlError("run cannot start: adapter is halted")
136
+ if steps < 0:
137
+ raise ValueError("steps must be >= 0")
138
+
139
+ # Precompute deterministic edge sequences for each architecture.
140
+ if self._architecture == "pipeline":
141
+ edges = self._pipeline_edges()
142
+ elif self._architecture == "peer":
143
+ edges = self._peer_edges()
144
+ else: # supervisor
145
+ edges = self._supervisor_edges()
146
+
147
+ if not edges:
148
+ # Should not happen, but keep behavior explicit.
149
+ raise AdapterControlError("no valid edges available to simulate run")
150
+
151
+ for i in range(steps):
152
+ if self._halted:
153
+ break
154
+
155
+ sender, receiver = edges[i % len(edges)]
156
+
157
+ # Default payload (deterministic)
158
+ if payload_factory is None:
159
+ payload: Dict = {
160
+ "step": i,
161
+ "content": f"msg-{i} {sender}->{receiver}",
162
+ "meta": {
163
+ "architecture": self._architecture,
164
+ },
165
+ }
166
+ else:
167
+ payload = payload_factory(i, sender, receiver)
168
+ if not isinstance(payload, dict):
169
+ raise TypeError("payload_factory must return a dict")
170
+
171
+ if (sender, receiver) in self._gated_edges:
172
+ self.emit_built(
173
+ self._builder,
174
+ sender=sender,
175
+ receiver=receiver,
176
+ event_type="message_blocked",
177
+ payload={
178
+ "step": i,
179
+ "sender": sender,
180
+ "receiver": receiver,
181
+ "reason": "gated_edge",
182
+ "intended": payload,
183
+ },
184
+ )
185
+ continue
186
+
187
+ self.emit_built(
188
+ self._builder,
189
+ sender=sender,
190
+ receiver=receiver,
191
+ event_type="message_sent",
192
+ payload=payload,
193
+ )
194
+
195
+ def _pipeline_edges(self) -> List[Tuple[str, str]]:
196
+ # A->B->C->... wrap
197
+ agents = list(self._agents)
198
+ if len(agents) == 1:
199
+ # Self-loop only.
200
+ return [(agents[0], agents[0])]
201
+ edges: List[Tuple[str, str]] = []
202
+ for idx, a in enumerate(agents):
203
+ b = agents[(idx + 1) % len(agents)]
204
+ edges.append((a, b))
205
+ return edges
206
+
207
+ def _peer_edges(self) -> List[Tuple[str, str]]:
208
+ # Deterministic ordered pairs, excluding self.
209
+ agents = list(self._agents)
210
+ edges: List[Tuple[str, str]] = []
211
+ for s in agents:
212
+ for r in agents:
213
+ if s != r:
214
+ edges.append((s, r))
215
+ return edges
216
+
217
+ def _supervisor_edges(self) -> List[Tuple[str, str]]:
218
+ # Supervisor<->AgentX alternating: Sup->A0, A0->Sup, Sup->A1, A1->Sup, ...
219
+ agents = [a for a in self._agents if a != "Supervisor"]
220
+ if not agents:
221
+ # Only Supervisor exists -> self-loop.
222
+ return [("Supervisor", "Supervisor")]
223
+
224
+ edges: List[Tuple[str, str]] = []
225
+ for a in agents:
226
+ edges.append(("Supervisor", a))
227
+ edges.append((a, "Supervisor"))
228
+ return edges