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.
- autogen_verigent-0.1.0/PKG-INFO +131 -0
- autogen_verigent-0.1.0/README.md +113 -0
- autogen_verigent-0.1.0/pyproject.toml +32 -0
- autogen_verigent-0.1.0/src/autogen_verigent/__init__.py +25 -0
- autogen_verigent-0.1.0/src/autogen_verigent/middleware.py +175 -0
- autogen_verigent-0.1.0/src/autogen_verigent/parser.py +114 -0
- autogen_verigent-0.1.0/src/autogen_verigent/trust.py +97 -0
|
@@ -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
|