autogen-verigent 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.
@@ -0,0 +1,131 @@
1
+ Metadata-Version: 2.4
2
+ Name: autogen-verigent
3
+ Version: 0.1.0
4
+ Summary: Verigent trust verification for Microsoft AutoGen multi-agent systems
5
+ Project-URL: Homepage, https://verigent.ai
6
+ Project-URL: Repository, https://github.com/verigentai/autogen-verigent
7
+ Author: verigentai
8
+ License-Expression: MIT
9
+ Keywords: autogen,multi-agent,trust,verification,verigent
10
+ Classifier: Development Status :: 3 - Alpha
11
+ Classifier: Intended Audience :: Developers
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: Software Development :: Libraries
15
+ Requires-Python: >=3.9
16
+ Requires-Dist: pyautogen>=0.2.0
17
+ Description-Content-Type: text/markdown
18
+
19
+ # autogen-verigent
20
+
21
+ Trust verification for Microsoft AutoGen multi-agent systems.
22
+
23
+ ## The Problem
24
+
25
+ When multiple AI agents collaborate in an AutoGen GroupChat, you have no way to verify their capabilities or prioritise the right agent for a given task. Agents self-declare what they can do — there's no independent trust signal.
26
+
27
+ **autogen-verigent** integrates [Verigent](https://verigent.ai) trust keys into AutoGen's speaker selection, so agents with verified capabilities get priority for relevant tasks.
28
+
29
+ ## Install
30
+
31
+ ```bash
32
+ pip install autogen-verigent
33
+ ```
34
+
35
+ ## Quick Start
36
+
37
+ ```python
38
+ from autogen import AssistantAgent, UserProxyAgent
39
+ from autogen_verigent import VerigentAgent, VerigentGroupChat
40
+
41
+ # Create your agents as normal
42
+ coder = AssistantAgent(name="coder", system_message="You write code.")
43
+ reviewer = AssistantAgent(name="reviewer", system_message="You review code.")
44
+ user = UserProxyAgent(name="user")
45
+
46
+ # Wrap with Verigent identity
47
+ vg_coder = VerigentAgent(
48
+ coder,
49
+ "VG:CODER-01:V4-ARCH·Se4Op7An5Ar9Co2Ad6St8Sc3Sa5So1Br2Fo6"
50
+ )
51
+ vg_reviewer = VerigentAgent(
52
+ reviewer,
53
+ "VG:REVIEWER-02:V3-ANAL·Se6Op3An8Ar4Co5Ad3St7Sc6Sa7So2Br1Fo2"
54
+ )
55
+
56
+ # Create trust-aware GroupChat
57
+ group = VerigentGroupChat(
58
+ agents=[vg_coder, vg_reviewer, user],
59
+ task_type="code", # prioritises Architect, Forge, Analyst scores
60
+ max_round=12,
61
+ )
62
+ ```
63
+
64
+ ## Trust-Weighted Speaker Selection
65
+
66
+ When `VerigentGroupChat` selects the next speaker, it:
67
+
68
+ 1. Parses each agent's VG key (tier + 12 class scores)
69
+ 2. Computes a relevance score based on the `task_type`
70
+ 3. Combines tier (40%) and relevance (60%) into a composite weight
71
+ 4. Selects the highest-scoring eligible agent
72
+
73
+ ### Task Types
74
+
75
+ | Task Type | Prioritised Classes |
76
+ |-----------|-------------------|
77
+ | `code` | Architect, Forge, Analyst |
78
+ | `analysis` | Analyst, Sage, Scout |
79
+ | `communication` | Conduit, Broker, Adaptor |
80
+ | `security` | Sentinel, Steward, Scout |
81
+ | `research` | Scout, Sage, Analyst |
82
+ | `coordination` | Conduit, Steward, Sovereign |
83
+ | `creative` | Forge, Adaptor, Sage |
84
+ | `operations` | Operative, Steward, Sentinel |
85
+ | `strategy` | Sovereign, Architect, Sage |
86
+ | `execution` | Operative, Forge, Sentinel |
87
+
88
+ ## VG Key Format
89
+
90
+ ```
91
+ VG:{NAME}-{SUFFIX}:{TIER}-{PRIMARY}·{12×class_code+digit}
92
+ ```
93
+
94
+ Example: `VG:JARVIS-0A:V3-ARCH·Se4Op7An5Ar9Co2Ad6St8Sc3Sa5So1Br2Fo6`
95
+
96
+ - **Tier**: V0 (unverified) to V6 (maximum trust)
97
+ - **Primary**: Dominant capability class
98
+ - **Scores**: 12 class scores (0-9), each representing verified capability
99
+
100
+ ## Transparency Log
101
+
102
+ Access the trust decision log for auditability:
103
+
104
+ ```python
105
+ for entry in group.trust_log:
106
+ print(entry["selected"], entry["candidates"])
107
+ ```
108
+
109
+ ## API Reference
110
+
111
+ ### `VerigentAgent(agent, vg_key)`
112
+ Wraps a ConversableAgent with Verigent identity. Injects the key into the system message and parses keys from incoming messages.
113
+
114
+ ### `VerigentGroupChat(agents, task_type, trust_bias, ...)`
115
+ Extends GroupChat with trust-weighted speaker selection. Set `trust_bias=0` to disable trust influence, `trust_bias=1` for pure trust-based selection.
116
+
117
+ ### `parse_key(text) -> VGKey | None`
118
+ Parse a VG key from any text string.
119
+
120
+ ### `evaluate_trust(key, task_type) -> TrustScore`
121
+ Compute trust score for a key given a task context.
122
+
123
+ ## Links
124
+
125
+ - [Verigent](https://verigent.ai) — Agent trust verification
126
+ - [AutoGen](https://github.com/microsoft/autogen) — Multi-agent framework
127
+ - [PyPI](https://pypi.org/project/autogen-verigent/)
128
+
129
+ ## License
130
+
131
+ MIT
@@ -0,0 +1,113 @@
1
+ # autogen-verigent
2
+
3
+ Trust verification for Microsoft AutoGen multi-agent systems.
4
+
5
+ ## The Problem
6
+
7
+ When multiple AI agents collaborate in an AutoGen GroupChat, you have no way to verify their capabilities or prioritise the right agent for a given task. Agents self-declare what they can do — there's no independent trust signal.
8
+
9
+ **autogen-verigent** integrates [Verigent](https://verigent.ai) trust keys into AutoGen's speaker selection, so agents with verified capabilities get priority for relevant tasks.
10
+
11
+ ## Install
12
+
13
+ ```bash
14
+ pip install autogen-verigent
15
+ ```
16
+
17
+ ## Quick Start
18
+
19
+ ```python
20
+ from autogen import AssistantAgent, UserProxyAgent
21
+ from autogen_verigent import VerigentAgent, VerigentGroupChat
22
+
23
+ # Create your agents as normal
24
+ coder = AssistantAgent(name="coder", system_message="You write code.")
25
+ reviewer = AssistantAgent(name="reviewer", system_message="You review code.")
26
+ user = UserProxyAgent(name="user")
27
+
28
+ # Wrap with Verigent identity
29
+ vg_coder = VerigentAgent(
30
+ coder,
31
+ "VG:CODER-01:V4-ARCH·Se4Op7An5Ar9Co2Ad6St8Sc3Sa5So1Br2Fo6"
32
+ )
33
+ vg_reviewer = VerigentAgent(
34
+ reviewer,
35
+ "VG:REVIEWER-02:V3-ANAL·Se6Op3An8Ar4Co5Ad3St7Sc6Sa7So2Br1Fo2"
36
+ )
37
+
38
+ # Create trust-aware GroupChat
39
+ group = VerigentGroupChat(
40
+ agents=[vg_coder, vg_reviewer, user],
41
+ task_type="code", # prioritises Architect, Forge, Analyst scores
42
+ max_round=12,
43
+ )
44
+ ```
45
+
46
+ ## Trust-Weighted Speaker Selection
47
+
48
+ When `VerigentGroupChat` selects the next speaker, it:
49
+
50
+ 1. Parses each agent's VG key (tier + 12 class scores)
51
+ 2. Computes a relevance score based on the `task_type`
52
+ 3. Combines tier (40%) and relevance (60%) into a composite weight
53
+ 4. Selects the highest-scoring eligible agent
54
+
55
+ ### Task Types
56
+
57
+ | Task Type | Prioritised Classes |
58
+ |-----------|-------------------|
59
+ | `code` | Architect, Forge, Analyst |
60
+ | `analysis` | Analyst, Sage, Scout |
61
+ | `communication` | Conduit, Broker, Adaptor |
62
+ | `security` | Sentinel, Steward, Scout |
63
+ | `research` | Scout, Sage, Analyst |
64
+ | `coordination` | Conduit, Steward, Sovereign |
65
+ | `creative` | Forge, Adaptor, Sage |
66
+ | `operations` | Operative, Steward, Sentinel |
67
+ | `strategy` | Sovereign, Architect, Sage |
68
+ | `execution` | Operative, Forge, Sentinel |
69
+
70
+ ## VG Key Format
71
+
72
+ ```
73
+ VG:{NAME}-{SUFFIX}:{TIER}-{PRIMARY}·{12×class_code+digit}
74
+ ```
75
+
76
+ Example: `VG:JARVIS-0A:V3-ARCH·Se4Op7An5Ar9Co2Ad6St8Sc3Sa5So1Br2Fo6`
77
+
78
+ - **Tier**: V0 (unverified) to V6 (maximum trust)
79
+ - **Primary**: Dominant capability class
80
+ - **Scores**: 12 class scores (0-9), each representing verified capability
81
+
82
+ ## Transparency Log
83
+
84
+ Access the trust decision log for auditability:
85
+
86
+ ```python
87
+ for entry in group.trust_log:
88
+ print(entry["selected"], entry["candidates"])
89
+ ```
90
+
91
+ ## API Reference
92
+
93
+ ### `VerigentAgent(agent, vg_key)`
94
+ Wraps a ConversableAgent with Verigent identity. Injects the key into the system message and parses keys from incoming messages.
95
+
96
+ ### `VerigentGroupChat(agents, task_type, trust_bias, ...)`
97
+ Extends GroupChat with trust-weighted speaker selection. Set `trust_bias=0` to disable trust influence, `trust_bias=1` for pure trust-based selection.
98
+
99
+ ### `parse_key(text) -> VGKey | None`
100
+ Parse a VG key from any text string.
101
+
102
+ ### `evaluate_trust(key, task_type) -> TrustScore`
103
+ Compute trust score for a key given a task context.
104
+
105
+ ## Links
106
+
107
+ - [Verigent](https://verigent.ai) — Agent trust verification
108
+ - [AutoGen](https://github.com/microsoft/autogen) — Multi-agent framework
109
+ - [PyPI](https://pypi.org/project/autogen-verigent/)
110
+
111
+ ## License
112
+
113
+ MIT
@@ -0,0 +1,32 @@
1
+ [build-system]
2
+ requires = ["hatchling"]
3
+ build-backend = "hatchling.build"
4
+
5
+ [project]
6
+ name = "autogen-verigent"
7
+ version = "0.1.0"
8
+ description = "Verigent trust verification for Microsoft AutoGen multi-agent systems"
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ requires-python = ">=3.9"
12
+ authors = [
13
+ { name = "verigentai" },
14
+ ]
15
+ keywords = ["autogen", "verigent", "trust", "multi-agent", "verification"]
16
+ classifiers = [
17
+ "Development Status :: 3 - Alpha",
18
+ "Intended Audience :: Developers",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Programming Language :: Python :: 3",
21
+ "Topic :: Software Development :: Libraries",
22
+ ]
23
+ dependencies = [
24
+ "pyautogen>=0.2.0",
25
+ ]
26
+
27
+ [project.urls]
28
+ Homepage = "https://verigent.ai"
29
+ Repository = "https://github.com/verigentai/autogen-verigent"
30
+
31
+ [tool.hatch.build.targets.wheel]
32
+ packages = ["src/autogen_verigent"]
@@ -0,0 +1,25 @@
1
+ """autogen-verigent — Trust verification for AutoGen multi-agent systems."""
2
+
3
+ from .parser import VGKey, parse_key, find_keys, extract_from_headers, extract_from_metadata, CLASS_CODES
4
+ from .trust import TrustScore, evaluate_trust, rank_agents, TASK_CLASS_MAP
5
+ from .middleware import VerigentAgent, VerigentGroupChat
6
+
7
+ __version__ = "0.1.0"
8
+
9
+ __all__ = [
10
+ # Parser
11
+ "VGKey",
12
+ "parse_key",
13
+ "find_keys",
14
+ "extract_from_headers",
15
+ "extract_from_metadata",
16
+ "CLASS_CODES",
17
+ # Trust
18
+ "TrustScore",
19
+ "evaluate_trust",
20
+ "rank_agents",
21
+ "TASK_CLASS_MAP",
22
+ # Middleware
23
+ "VerigentAgent",
24
+ "VerigentGroupChat",
25
+ ]
@@ -0,0 +1,175 @@
1
+ """AutoGen integration — trust-aware GroupChat and agent wrapper."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ from typing import Dict, List, Optional, Union
7
+
8
+ from autogen import Agent, ConversableAgent, GroupChat, GroupChatManager
9
+
10
+ from .parser import VGKey, find_keys, parse_key
11
+ from .trust import TrustScore, evaluate_trust, rank_agents
12
+
13
+ logger = logging.getLogger(__name__)
14
+
15
+
16
+ class VerigentAgent:
17
+ """
18
+ Wrapper that adds Verigent trust identity to an AutoGen agent.
19
+
20
+ Injects the VG key into the agent's system message and provides
21
+ methods to parse keys from incoming messages.
22
+ """
23
+
24
+ def __init__(self, agent: ConversableAgent, vg_key: str):
25
+ """
26
+ Args:
27
+ agent: An AutoGen ConversableAgent (or subclass)
28
+ vg_key: Raw VG key string for this agent
29
+ """
30
+ self.agent = agent
31
+ self.raw_key = vg_key
32
+ self.key = parse_key(vg_key)
33
+ if not self.key:
34
+ raise ValueError(f"Invalid VG key: {vg_key}")
35
+
36
+ # Inject key into system message
37
+ current_msg = agent.system_message or ""
38
+ agent.update_system_message(
39
+ f"{current_msg}\n\n[Verigent Identity: {vg_key}]"
40
+ )
41
+
42
+ # Track keys observed from other agents
43
+ self.observed_keys: Dict[str, VGKey] = {}
44
+
45
+ # Register reply hook to parse incoming keys
46
+ agent.register_hook(
47
+ hookable_method="process_last_received_message",
48
+ hook=self._parse_incoming_key,
49
+ )
50
+
51
+ def _parse_incoming_key(self, message: Optional[str]) -> Optional[str]:
52
+ """Hook: parse VG keys from incoming messages."""
53
+ if message:
54
+ keys = find_keys(message)
55
+ for k in keys:
56
+ self.observed_keys[k.full_handle] = k
57
+ logger.debug(
58
+ f"[Verigent] {self.agent.name} observed key: "
59
+ f"{k.full_handle} (V{k.tier})"
60
+ )
61
+ return message # pass through unmodified
62
+
63
+ @property
64
+ def name(self) -> str:
65
+ return self.agent.name
66
+
67
+ @property
68
+ def trust_tier(self) -> int:
69
+ return self.key.tier if self.key else 0
70
+
71
+
72
+ class VerigentGroupChat(GroupChat):
73
+ """
74
+ GroupChat with trust-weighted speaker selection.
75
+
76
+ Agents with higher relevant Verigent scores are prioritised
77
+ when selecting the next speaker for a given task context.
78
+ """
79
+
80
+ def __init__(
81
+ self,
82
+ agents: List[Union[ConversableAgent, VerigentAgent]],
83
+ messages: List[dict] = None,
84
+ max_round: int = 10,
85
+ task_type: Optional[str] = None,
86
+ trust_bias: float = 0.7,
87
+ **kwargs,
88
+ ):
89
+ """
90
+ Args:
91
+ agents: List of agents (VerigentAgent or plain ConversableAgent)
92
+ messages: Initial messages
93
+ max_round: Maximum conversation rounds
94
+ task_type: Task type for trust scoring (see trust.TASK_CLASS_MAP)
95
+ trust_bias: How much trust influences selection (0=ignore, 1=only trust)
96
+ **kwargs: Passed to GroupChat
97
+ """
98
+ self.task_type = task_type
99
+ self.trust_bias = trust_bias
100
+ self._verigent_agents: Dict[str, VerigentAgent] = {}
101
+ self._trust_log: List[dict] = []
102
+
103
+ # Unwrap VerigentAgents for base GroupChat
104
+ raw_agents = []
105
+ for a in agents:
106
+ if isinstance(a, VerigentAgent):
107
+ self._verigent_agents[a.agent.name] = a
108
+ raw_agents.append(a.agent)
109
+ else:
110
+ raw_agents.append(a)
111
+
112
+ super().__init__(
113
+ agents=raw_agents,
114
+ messages=messages or [],
115
+ max_round=max_round,
116
+ **kwargs,
117
+ )
118
+
119
+ def select_speaker(
120
+ self, last_speaker: Agent, selector: ConversableAgent
121
+ ) -> Agent:
122
+ """
123
+ Override speaker selection with trust-weighted logic.
124
+
125
+ Falls back to default selection when trust data is insufficient.
126
+ """
127
+ # Get eligible agents (exclude last speaker)
128
+ eligible = [a for a in self.agents if a != last_speaker]
129
+ if not eligible:
130
+ return super().select_speaker(last_speaker, selector)
131
+
132
+ # Collect VG keys for eligible agents
133
+ keyed_agents: Dict[str, VGKey] = {}
134
+ for agent in eligible:
135
+ va = self._verigent_agents.get(agent.name)
136
+ if va and va.key:
137
+ keyed_agents[agent.name] = va.key
138
+
139
+ # If fewer than 2 agents have keys, fall back to default
140
+ if len(keyed_agents) < 2:
141
+ return super().select_speaker(last_speaker, selector)
142
+
143
+ # Rank by trust
144
+ scores = rank_agents(keyed_agents, self.task_type)
145
+
146
+ # Log selection reasoning
147
+ self._trust_log.append({
148
+ "task_type": self.task_type,
149
+ "candidates": [
150
+ {"agent": s.agent_handle, "score": round(s.composite, 3)}
151
+ for s in scores
152
+ ],
153
+ "selected": scores[0].agent_handle if scores else None,
154
+ })
155
+
156
+ # Select highest-scoring agent
157
+ if scores and self.trust_bias > 0:
158
+ top_handle = scores[0].agent_handle
159
+ for agent in eligible:
160
+ va = self._verigent_agents.get(agent.name)
161
+ if va and va.key and va.key.full_handle == top_handle:
162
+ logger.info(
163
+ f"[Verigent] Trust-selected: {agent.name} "
164
+ f"(score={scores[0].composite:.3f}, "
165
+ f"task={self.task_type})"
166
+ )
167
+ return agent
168
+
169
+ # Fallback
170
+ return super().select_speaker(last_speaker, selector)
171
+
172
+ @property
173
+ def trust_log(self) -> List[dict]:
174
+ """Access the trust decision log for transparency."""
175
+ return self._trust_log
@@ -0,0 +1,114 @@
1
+ """VG key parser — pure regex, no external dependencies."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import re
6
+ from dataclasses import dataclass, field
7
+ from typing import Dict, List, Optional
8
+
9
+ # Class code mapping
10
+ CLASS_CODES: Dict[str, str] = {
11
+ "Se": "Sentinel",
12
+ "Op": "Operative",
13
+ "An": "Analyst",
14
+ "Ar": "Architect",
15
+ "Co": "Conduit",
16
+ "Ad": "Adaptor",
17
+ "St": "Steward",
18
+ "Sc": "Scout",
19
+ "Sa": "Sage",
20
+ "So": "Sovereign",
21
+ "Br": "Broker",
22
+ "Fo": "Forge",
23
+ }
24
+
25
+ # VG key pattern
26
+ # VG:{NAME}-{SUFFIX}:{TIER}-{PRIMARY}·{12×class_code+digit}
27
+ _KEY_PATTERN = re.compile(
28
+ r"VG:([A-Za-z0-9_-]+)-([A-Za-z0-9]+)" # handle: NAME-SUFFIX
29
+ r":V([0-6])" # tier: V0-V6
30
+ r"-([A-Z]{4})" # primary class (4-letter)
31
+ r"·((?:[A-Z][a-z]\d){12})" # 12 class scores
32
+ )
33
+
34
+ _SCORE_PATTERN = re.compile(r"([A-Z][a-z])(\d)")
35
+
36
+
37
+ @dataclass
38
+ class VGKey:
39
+ """Parsed Verigent key."""
40
+
41
+ raw: str
42
+ handle: str
43
+ suffix: str
44
+ tier: int
45
+ primary: str
46
+ scores: Dict[str, int] = field(default_factory=dict)
47
+
48
+ @property
49
+ def full_handle(self) -> str:
50
+ return f"{self.handle}-{self.suffix}"
51
+
52
+ @property
53
+ def primary_class(self) -> str:
54
+ return CLASS_CODES.get(self.primary[:2], self.primary)
55
+
56
+ def score(self, class_code: str) -> int:
57
+ """Get score for a class code (0-9). Returns 0 if not found."""
58
+ return self.scores.get(class_code, 0)
59
+
60
+ def score_normalised(self, class_code: str) -> float:
61
+ """Get score as 0.0-1.0 float."""
62
+ return self.score(class_code) / 9.0 if self.score(class_code) else 0.0
63
+
64
+
65
+ def parse_key(text: str) -> Optional[VGKey]:
66
+ """Parse a single VG key from text. Returns None if no valid key found."""
67
+ m = _KEY_PATTERN.search(text)
68
+ if not m:
69
+ return None
70
+
71
+ name, suffix, tier_str, primary, scores_raw = m.groups()
72
+ scores: Dict[str, int] = {}
73
+ for sm in _SCORE_PATTERN.finditer(scores_raw):
74
+ code, digit = sm.groups()
75
+ if code in CLASS_CODES:
76
+ scores[code] = int(digit)
77
+
78
+ if len(scores) != 12:
79
+ return None
80
+
81
+ return VGKey(
82
+ raw=m.group(0),
83
+ handle=name,
84
+ suffix=suffix,
85
+ tier=int(tier_str),
86
+ primary=primary[:2],
87
+ scores=scores,
88
+ )
89
+
90
+
91
+ def find_keys(text: str) -> List[VGKey]:
92
+ """Find all VG keys in a block of text."""
93
+ results = []
94
+ for m in _KEY_PATTERN.finditer(text):
95
+ key = parse_key(m.group(0))
96
+ if key:
97
+ results.append(key)
98
+ return results
99
+
100
+
101
+ def extract_from_headers(headers: dict) -> Optional[VGKey]:
102
+ """Extract VG key from HTTP headers (X-Verigent)."""
103
+ value = headers.get("X-Verigent") or headers.get("x-verigent")
104
+ if value:
105
+ return parse_key(value)
106
+ return None
107
+
108
+
109
+ def extract_from_metadata(metadata: dict) -> Optional[VGKey]:
110
+ """Extract VG key from JSON metadata (x-verigent field)."""
111
+ value = metadata.get("x-verigent") or metadata.get("X-Verigent")
112
+ if isinstance(value, str):
113
+ return parse_key(value)
114
+ return None
@@ -0,0 +1,97 @@
1
+ """Trust evaluation logic for Verigent keys."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from dataclasses import dataclass, field
6
+ from typing import Dict, List, Optional
7
+
8
+ from .parser import VGKey, CLASS_CODES
9
+
10
+ # Task-type to relevant class codes mapping
11
+ TASK_CLASS_MAP: Dict[str, List[str]] = {
12
+ "code": ["Ar", "Fo", "An"],
13
+ "analysis": ["An", "Sa", "Sc"],
14
+ "communication": ["Co", "Br", "Ad"],
15
+ "security": ["Se", "St", "Sc"],
16
+ "research": ["Sc", "Sa", "An"],
17
+ "coordination": ["Co", "St", "So"],
18
+ "creative": ["Fo", "Ad", "Sa"],
19
+ "operations": ["Op", "St", "Se"],
20
+ "strategy": ["So", "Ar", "Sa"],
21
+ "execution": ["Op", "Fo", "Se"],
22
+ }
23
+
24
+
25
+ @dataclass
26
+ class TrustScore:
27
+ """Computed trust score for an agent given a task context."""
28
+
29
+ agent_handle: str
30
+ tier_score: float # 0.0-1.0 based on tier
31
+ relevance_score: float # 0.0-1.0 based on class scores for task
32
+ composite: float # weighted combination
33
+
34
+ @property
35
+ def weight(self) -> float:
36
+ """Speaker selection weight (higher = more likely to be chosen)."""
37
+ return self.composite
38
+
39
+
40
+ def evaluate_trust(
41
+ key: VGKey,
42
+ task_type: Optional[str] = None,
43
+ tier_weight: float = 0.4,
44
+ relevance_weight: float = 0.6,
45
+ ) -> TrustScore:
46
+ """
47
+ Evaluate trust for an agent given their VG key and optional task context.
48
+
49
+ Args:
50
+ key: Parsed VG key
51
+ task_type: Optional task type from TASK_CLASS_MAP
52
+ tier_weight: Weight for tier in composite score (default 0.4)
53
+ relevance_weight: Weight for relevance in composite score (default 0.6)
54
+
55
+ Returns:
56
+ TrustScore with computed weights
57
+ """
58
+ # Tier score: V0=0, V1=0.17, V2=0.33, V3=0.5, V4=0.67, V5=0.83, V6=1.0
59
+ tier_score = key.tier / 6.0
60
+
61
+ # Relevance score: average of relevant class scores
62
+ if task_type and task_type in TASK_CLASS_MAP:
63
+ relevant_codes = TASK_CLASS_MAP[task_type]
64
+ relevant_scores = [key.score(c) for c in relevant_codes]
65
+ relevance_score = sum(relevant_scores) / (len(relevant_scores) * 9.0)
66
+ else:
67
+ # No task context — use average of all scores
68
+ all_scores = list(key.scores.values())
69
+ relevance_score = sum(all_scores) / (len(all_scores) * 9.0) if all_scores else 0.0
70
+
71
+ composite = (tier_weight * tier_score) + (relevance_weight * relevance_score)
72
+
73
+ return TrustScore(
74
+ agent_handle=key.full_handle,
75
+ tier_score=tier_score,
76
+ relevance_score=relevance_score,
77
+ composite=composite,
78
+ )
79
+
80
+
81
+ def rank_agents(
82
+ keys: Dict[str, VGKey],
83
+ task_type: Optional[str] = None,
84
+ ) -> List[TrustScore]:
85
+ """
86
+ Rank multiple agents by trust score for a given task.
87
+
88
+ Args:
89
+ keys: Dict mapping agent name to their VG key
90
+ task_type: Optional task type for relevance scoring
91
+
92
+ Returns:
93
+ List of TrustScores sorted by composite (descending)
94
+ """
95
+ scores = [evaluate_trust(key, task_type) for key in keys.values()]
96
+ scores.sort(key=lambda s: s.composite, reverse=True)
97
+ return scores