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.
- polytrax-0.1.0/LICENSE +21 -0
- polytrax-0.1.0/MANIFEST.in +20 -0
- polytrax-0.1.0/PKG-INFO +108 -0
- polytrax-0.1.0/README.md +68 -0
- polytrax-0.1.0/polytrax/src/polytrax/__init__.py +7 -0
- polytrax-0.1.0/polytrax/src/polytrax/adapters/__init__.py +27 -0
- polytrax-0.1.0/polytrax/src/polytrax/adapters/base.py +54 -0
- polytrax-0.1.0/polytrax/src/polytrax/adapters/exceptions.py +19 -0
- polytrax-0.1.0/polytrax/src/polytrax/adapters/generic.py +228 -0
- polytrax-0.1.0/polytrax/src/polytrax/adapters/langgraph.py +412 -0
- polytrax-0.1.0/polytrax/src/polytrax/analysis/__init__.py +32 -0
- polytrax-0.1.0/polytrax/src/polytrax/analysis/containment.py +661 -0
- polytrax-0.1.0/polytrax/src/polytrax/analysis/graph.py +148 -0
- polytrax-0.1.0/polytrax/src/polytrax/analysis/localization.py +1282 -0
- polytrax-0.1.0/polytrax/src/polytrax/analysis/propagation.py +220 -0
- polytrax-0.1.0/polytrax/src/polytrax/analysis/token_estimation.py +207 -0
- polytrax-0.1.0/polytrax/src/polytrax/analysis/tps_replay.py +336 -0
- polytrax-0.1.0/polytrax/src/polytrax/analysis/traversal.py +124 -0
- polytrax-0.1.0/polytrax/src/polytrax/benchmark/__init__.py +46 -0
- polytrax-0.1.0/polytrax/src/polytrax/benchmark/runner.py +757 -0
- polytrax-0.1.0/polytrax/src/polytrax/benchmark/schema.py +153 -0
- polytrax-0.1.0/polytrax/src/polytrax/evaluation/__init__.py +51 -0
- polytrax-0.1.0/polytrax/src/polytrax/evaluation/aggregate.py +182 -0
- polytrax-0.1.0/polytrax/src/polytrax/evaluation/dimension_pipelines.py +132 -0
- polytrax-0.1.0/polytrax/src/polytrax/evaluation/judge_base.py +56 -0
- polytrax-0.1.0/polytrax/src/polytrax/evaluation/llm_judge.py +391 -0
- polytrax-0.1.0/polytrax/src/polytrax/evaluation/rubric.py +138 -0
- polytrax-0.1.0/polytrax/src/polytrax/evaluation/runner.py +352 -0
- polytrax-0.1.0/polytrax/src/polytrax/evaluation/scoring.py +169 -0
- polytrax-0.1.0/polytrax/src/polytrax/evaluation/stub_judge.py +189 -0
- polytrax-0.1.0/polytrax/src/polytrax/instrumentation/__init__.py +19 -0
- polytrax-0.1.0/polytrax/src/polytrax/instrumentation/builder.py +132 -0
- polytrax-0.1.0/polytrax/src/polytrax/instrumentation/instrumentor.py +104 -0
- polytrax-0.1.0/polytrax/src/polytrax/reporting/__init__.py +15 -0
- polytrax-0.1.0/polytrax/src/polytrax/reporting/report.py +248 -0
- polytrax-0.1.0/polytrax/src/polytrax/runtime/__init__.py +28 -0
- polytrax-0.1.0/polytrax/src/polytrax/runtime/audit.py +36 -0
- polytrax-0.1.0/polytrax/src/polytrax/runtime/controller.py +66 -0
- polytrax-0.1.0/polytrax/src/polytrax/runtime/policies.py +66 -0
- polytrax-0.1.0/polytrax/src/polytrax/runtime/tps.py +254 -0
- polytrax-0.1.0/polytrax/src/polytrax/runtime/types.py +47 -0
- polytrax-0.1.0/polytrax/src/polytrax/sinks/__init__.py +21 -0
- polytrax-0.1.0/polytrax/src/polytrax/sinks/base.py +63 -0
- polytrax-0.1.0/polytrax/src/polytrax/sinks/jsonl.py +89 -0
- polytrax-0.1.0/polytrax/src/polytrax/sinks/memory.py +60 -0
- polytrax-0.1.0/polytrax/src/polytrax/trust/__init__.py +21 -0
- polytrax-0.1.0/polytrax/src/polytrax/trust/evaluator.py +246 -0
- polytrax-0.1.0/polytrax/src/polytrax/trust/policies.py +262 -0
- polytrax-0.1.0/polytrax/src/polytrax/usl/__init__.py +38 -0
- polytrax-0.1.0/polytrax/src/polytrax/usl/io.py +75 -0
- polytrax-0.1.0/polytrax/src/polytrax/usl/schema.py +138 -0
- polytrax-0.1.0/polytrax/src/polytrax/usl/validate.py +126 -0
- polytrax-0.1.0/polytrax/src/polytrax.egg-info/SOURCES.txt +52 -0
- polytrax-0.1.0/pyproject.toml +65 -0
- 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]
|
polytrax-0.1.0/PKG-INFO
ADDED
|
@@ -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
|
polytrax-0.1.0/README.md
ADDED
|
@@ -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,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
|