crewai-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.
- crewai_verigent-0.1.0/PKG-INFO +111 -0
- crewai_verigent-0.1.0/README.md +91 -0
- crewai_verigent-0.1.0/pyproject.toml +32 -0
- crewai_verigent-0.1.0/src/crewai_verigent/__init__.py +17 -0
- crewai_verigent-0.1.0/src/crewai_verigent/middleware.py +166 -0
- crewai_verigent-0.1.0/src/crewai_verigent/parser.py +114 -0
- crewai_verigent-0.1.0/src/crewai_verigent/trust.py +152 -0
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: crewai-verigent
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Verigent trust verification for CrewAI agent delegation
|
|
5
|
+
Project-URL: Homepage, https://verigent.ai
|
|
6
|
+
Project-URL: Repository, https://github.com/verigentai/crewai-verigent
|
|
7
|
+
Author-email: verigentai <dev@verigent.ai>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: agents,crewai,delegation,trust,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.10
|
|
16
|
+
Requires-Dist: crewai>=0.30.0
|
|
17
|
+
Provides-Extra: verify
|
|
18
|
+
Requires-Dist: httpx>=0.25.0; extra == 'verify'
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
|
|
21
|
+
# crewai-verigent
|
|
22
|
+
|
|
23
|
+
Trust verification for CrewAI agent delegation.
|
|
24
|
+
|
|
25
|
+
## The problem
|
|
26
|
+
|
|
27
|
+
When one agent delegates a task to another, you're trusting that the receiving agent is competent for that specific job. A "research agent" delegating to an "analyst agent" has no way to verify that the analyst is actually good at analysis — it just has to hope.
|
|
28
|
+
|
|
29
|
+
Verigent solves this by giving agents verifiable capability profiles (VG keys) that declare what they're good at, scored across 12 classes. This plugin checks those profiles at delegation time and flags mismatches before they become failures.
|
|
30
|
+
|
|
31
|
+
## Install
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install crewai-verigent
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
For remote verification against verigent.ai:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
pip install crewai-verigent[verify]
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
## Usage
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from crewai import Agent, Crew, Task
|
|
47
|
+
from crewai_verigent import VerigentTrust
|
|
48
|
+
|
|
49
|
+
# Build your crew as normal
|
|
50
|
+
crew = Crew(agents=[researcher, analyst, writer], tasks=[...])
|
|
51
|
+
|
|
52
|
+
# Wrap it — trust checking is now active
|
|
53
|
+
trusted_crew = VerigentTrust(crew, min_score=50)
|
|
54
|
+
result = trusted_crew.kickoff()
|
|
55
|
+
|
|
56
|
+
# Check what happened
|
|
57
|
+
for d in trusted_crew.decisions:
|
|
58
|
+
print(f"{d['agent']}: score={d['score']} verified={d['verified']}")
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## What happens automatically
|
|
62
|
+
|
|
63
|
+
Once enabled, `VerigentTrust` does three things:
|
|
64
|
+
|
|
65
|
+
1. **Extracts VG keys** from each agent's backstory/system prompt, metadata, or headers
|
|
66
|
+
2. **Evaluates trust** when tasks are assigned or delegated — matching task keywords against the agent's 12 class scores
|
|
67
|
+
3. **Logs decisions** and optionally blocks delegation if the trust score is below your threshold
|
|
68
|
+
|
|
69
|
+
Agents without VG keys score 0 and are flagged as unverified. Set `block_unverified=True` to prevent delegation to unverified agents entirely.
|
|
70
|
+
|
|
71
|
+
## VG Keys
|
|
72
|
+
|
|
73
|
+
A VG key encodes an agent's verified capability profile:
|
|
74
|
+
|
|
75
|
+
```
|
|
76
|
+
VG:JARVIS-0A:V3-ARCH·Se4Op7An5Ar9Co2Ad6St8Sc3Sa5So1Br2Fo6
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
This tells you JARVIS-0A is tier V3, primarily an Architect, scoring 90% on Architecture and 80% on Stewardship but only 10% on Sovereignty.
|
|
80
|
+
|
|
81
|
+
Agents get VG keys by completing Verigent's evaluation process. The key can live in the agent's system prompt, an `X-Verigent` HTTP header, or a metadata field.
|
|
82
|
+
|
|
83
|
+
## Configuration
|
|
84
|
+
|
|
85
|
+
```python
|
|
86
|
+
VerigentTrust(
|
|
87
|
+
crew,
|
|
88
|
+
min_score=50, # Minimum trust score to allow delegation (0-100)
|
|
89
|
+
block_unverified=False, # Block delegation to agents without VG keys
|
|
90
|
+
on_decision=callback, # Called with (agent, task, TrustScore) on each check
|
|
91
|
+
)
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Decorator usage
|
|
95
|
+
|
|
96
|
+
```python
|
|
97
|
+
@VerigentTrust.wrap(min_score=60, block_unverified=True)
|
|
98
|
+
def build_crew():
|
|
99
|
+
return Crew(agents=[...], tasks=[...])
|
|
100
|
+
|
|
101
|
+
trusted_crew = build_crew()
|
|
102
|
+
result = trusted_crew.kickoff()
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
## Get your agents verified
|
|
106
|
+
|
|
107
|
+
Register your agents at [verigent.ai](https://verigent.ai) to get VG keys issued after evaluation.
|
|
108
|
+
|
|
109
|
+
## License
|
|
110
|
+
|
|
111
|
+
MIT
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# crewai-verigent
|
|
2
|
+
|
|
3
|
+
Trust verification for CrewAI agent delegation.
|
|
4
|
+
|
|
5
|
+
## The problem
|
|
6
|
+
|
|
7
|
+
When one agent delegates a task to another, you're trusting that the receiving agent is competent for that specific job. A "research agent" delegating to an "analyst agent" has no way to verify that the analyst is actually good at analysis — it just has to hope.
|
|
8
|
+
|
|
9
|
+
Verigent solves this by giving agents verifiable capability profiles (VG keys) that declare what they're good at, scored across 12 classes. This plugin checks those profiles at delegation time and flags mismatches before they become failures.
|
|
10
|
+
|
|
11
|
+
## Install
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
pip install crewai-verigent
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
For remote verification against verigent.ai:
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install crewai-verigent[verify]
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Usage
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from crewai import Agent, Crew, Task
|
|
27
|
+
from crewai_verigent import VerigentTrust
|
|
28
|
+
|
|
29
|
+
# Build your crew as normal
|
|
30
|
+
crew = Crew(agents=[researcher, analyst, writer], tasks=[...])
|
|
31
|
+
|
|
32
|
+
# Wrap it — trust checking is now active
|
|
33
|
+
trusted_crew = VerigentTrust(crew, min_score=50)
|
|
34
|
+
result = trusted_crew.kickoff()
|
|
35
|
+
|
|
36
|
+
# Check what happened
|
|
37
|
+
for d in trusted_crew.decisions:
|
|
38
|
+
print(f"{d['agent']}: score={d['score']} verified={d['verified']}")
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## What happens automatically
|
|
42
|
+
|
|
43
|
+
Once enabled, `VerigentTrust` does three things:
|
|
44
|
+
|
|
45
|
+
1. **Extracts VG keys** from each agent's backstory/system prompt, metadata, or headers
|
|
46
|
+
2. **Evaluates trust** when tasks are assigned or delegated — matching task keywords against the agent's 12 class scores
|
|
47
|
+
3. **Logs decisions** and optionally blocks delegation if the trust score is below your threshold
|
|
48
|
+
|
|
49
|
+
Agents without VG keys score 0 and are flagged as unverified. Set `block_unverified=True` to prevent delegation to unverified agents entirely.
|
|
50
|
+
|
|
51
|
+
## VG Keys
|
|
52
|
+
|
|
53
|
+
A VG key encodes an agent's verified capability profile:
|
|
54
|
+
|
|
55
|
+
```
|
|
56
|
+
VG:JARVIS-0A:V3-ARCH·Se4Op7An5Ar9Co2Ad6St8Sc3Sa5So1Br2Fo6
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
This tells you JARVIS-0A is tier V3, primarily an Architect, scoring 90% on Architecture and 80% on Stewardship but only 10% on Sovereignty.
|
|
60
|
+
|
|
61
|
+
Agents get VG keys by completing Verigent's evaluation process. The key can live in the agent's system prompt, an `X-Verigent` HTTP header, or a metadata field.
|
|
62
|
+
|
|
63
|
+
## Configuration
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
VerigentTrust(
|
|
67
|
+
crew,
|
|
68
|
+
min_score=50, # Minimum trust score to allow delegation (0-100)
|
|
69
|
+
block_unverified=False, # Block delegation to agents without VG keys
|
|
70
|
+
on_decision=callback, # Called with (agent, task, TrustScore) on each check
|
|
71
|
+
)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Decorator usage
|
|
75
|
+
|
|
76
|
+
```python
|
|
77
|
+
@VerigentTrust.wrap(min_score=60, block_unverified=True)
|
|
78
|
+
def build_crew():
|
|
79
|
+
return Crew(agents=[...], tasks=[...])
|
|
80
|
+
|
|
81
|
+
trusted_crew = build_crew()
|
|
82
|
+
result = trusted_crew.kickoff()
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
## Get your agents verified
|
|
86
|
+
|
|
87
|
+
Register your agents at [verigent.ai](https://verigent.ai) to get VG keys issued after evaluation.
|
|
88
|
+
|
|
89
|
+
## License
|
|
90
|
+
|
|
91
|
+
MIT
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "crewai-verigent"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Verigent trust verification for CrewAI agent delegation"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.10"
|
|
12
|
+
authors = [
|
|
13
|
+
{ name = "verigentai", email = "dev@verigent.ai" },
|
|
14
|
+
]
|
|
15
|
+
keywords = ["crewai", "verigent", "trust", "agents", "delegation"]
|
|
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
|
+
"crewai>=0.30.0",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
[project.optional-dependencies]
|
|
28
|
+
verify = ["httpx>=0.25.0"]
|
|
29
|
+
|
|
30
|
+
[project.urls]
|
|
31
|
+
Homepage = "https://verigent.ai"
|
|
32
|
+
Repository = "https://github.com/verigentai/crewai-verigent"
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"""Verigent trust verification for CrewAI agent delegation."""
|
|
2
|
+
|
|
3
|
+
from .parser import VGKey, parse_vg_key, extract_vg_key
|
|
4
|
+
from .trust import TrustScore, evaluate_trust, CLASS_NAMES
|
|
5
|
+
from .middleware import VerigentTrust
|
|
6
|
+
|
|
7
|
+
__all__ = [
|
|
8
|
+
"VGKey",
|
|
9
|
+
"parse_vg_key",
|
|
10
|
+
"extract_vg_key",
|
|
11
|
+
"TrustScore",
|
|
12
|
+
"evaluate_trust",
|
|
13
|
+
"CLASS_NAMES",
|
|
14
|
+
"VerigentTrust",
|
|
15
|
+
]
|
|
16
|
+
|
|
17
|
+
__version__ = "0.1.0"
|
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
"""CrewAI integration middleware — wraps Crew to add trust checking on delegation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import logging
|
|
6
|
+
from typing import Any, Callable, Optional
|
|
7
|
+
|
|
8
|
+
from crewai import Agent, Crew, Task
|
|
9
|
+
|
|
10
|
+
from .parser import VGKey, extract_vg_key
|
|
11
|
+
from .trust import TrustScore, evaluate_trust
|
|
12
|
+
|
|
13
|
+
logger = logging.getLogger("crewai_verigent")
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class VerigentTrust:
|
|
17
|
+
"""Wraps a CrewAI Crew to inject trust verification on agent delegation.
|
|
18
|
+
|
|
19
|
+
Usage:
|
|
20
|
+
crew = Crew(agents=[...], tasks=[...])
|
|
21
|
+
trusted_crew = VerigentTrust(crew)
|
|
22
|
+
result = trusted_crew.kickoff()
|
|
23
|
+
|
|
24
|
+
Or as a decorator:
|
|
25
|
+
@VerigentTrust.wrap
|
|
26
|
+
def build_crew():
|
|
27
|
+
return Crew(agents=[...], tasks=[...])
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self,
|
|
32
|
+
crew: Crew,
|
|
33
|
+
*,
|
|
34
|
+
min_score: int = 50,
|
|
35
|
+
block_unverified: bool = False,
|
|
36
|
+
on_decision: Optional[Callable[[Agent, Task, TrustScore], None]] = None,
|
|
37
|
+
):
|
|
38
|
+
self.crew = crew
|
|
39
|
+
self.min_score = min_score
|
|
40
|
+
self.block_unverified = block_unverified
|
|
41
|
+
self.on_decision = on_decision
|
|
42
|
+
self._decisions: list[dict[str, Any]] = []
|
|
43
|
+
|
|
44
|
+
self._patch_agents()
|
|
45
|
+
|
|
46
|
+
@classmethod
|
|
47
|
+
def wrap(
|
|
48
|
+
cls,
|
|
49
|
+
fn: Optional[Callable[..., Crew]] = None,
|
|
50
|
+
*,
|
|
51
|
+
min_score: int = 50,
|
|
52
|
+
block_unverified: bool = False,
|
|
53
|
+
):
|
|
54
|
+
"""Decorator to wrap a crew-building function with trust verification."""
|
|
55
|
+
def decorator(func: Callable[..., Crew]) -> Callable[..., VerigentTrust]:
|
|
56
|
+
def wrapper(*args, **kwargs) -> VerigentTrust:
|
|
57
|
+
crew = func(*args, **kwargs)
|
|
58
|
+
return cls(crew, min_score=min_score, block_unverified=block_unverified)
|
|
59
|
+
return wrapper
|
|
60
|
+
|
|
61
|
+
if fn is not None:
|
|
62
|
+
return decorator(fn)
|
|
63
|
+
return decorator
|
|
64
|
+
|
|
65
|
+
def _extract_key(self, agent: Agent) -> Optional[VGKey]:
|
|
66
|
+
"""Extract VG key from an agent's configuration."""
|
|
67
|
+
return extract_vg_key(
|
|
68
|
+
system_prompt=getattr(agent, "backstory", None) or getattr(agent, "system_prompt", None),
|
|
69
|
+
metadata=getattr(agent, "metadata", None),
|
|
70
|
+
)
|
|
71
|
+
|
|
72
|
+
def _patch_agents(self) -> None:
|
|
73
|
+
"""Patch agent delegation to include trust checks."""
|
|
74
|
+
for agent in self.crew.agents:
|
|
75
|
+
if hasattr(agent, "delegate_work"):
|
|
76
|
+
original_delegate = agent.delegate_work
|
|
77
|
+
agent.delegate_work = self._make_trust_wrapper(agent, original_delegate)
|
|
78
|
+
|
|
79
|
+
# Also patch execute_task for task assignment trust checks
|
|
80
|
+
original_execute = getattr(agent, "execute_task", None)
|
|
81
|
+
if original_execute:
|
|
82
|
+
agent.execute_task = self._make_task_wrapper(agent, original_execute)
|
|
83
|
+
|
|
84
|
+
def _make_trust_wrapper(self, delegator: Agent, original: Callable) -> Callable:
|
|
85
|
+
"""Create a delegation wrapper that checks trust before delegating."""
|
|
86
|
+
def wrapper(task: Any, context: Any = None, **kwargs) -> Any:
|
|
87
|
+
# Find the target agent from kwargs or context
|
|
88
|
+
target = kwargs.get("coworker") or kwargs.get("agent")
|
|
89
|
+
if target and isinstance(target, Agent):
|
|
90
|
+
score = self._check_trust(delegator, target, task)
|
|
91
|
+
if not score.passed and self.block_unverified:
|
|
92
|
+
logger.warning(
|
|
93
|
+
f"Blocked delegation from {delegator.role} to {target.role}: "
|
|
94
|
+
f"trust score {score.score} < {self.min_score}"
|
|
95
|
+
)
|
|
96
|
+
return f"Delegation blocked: {target.role} trust score too low ({score.score}/100)"
|
|
97
|
+
return original(task, context, **kwargs)
|
|
98
|
+
return wrapper
|
|
99
|
+
|
|
100
|
+
def _make_task_wrapper(self, agent: Agent, original: Callable) -> Callable:
|
|
101
|
+
"""Wrap task execution to log trust evaluation."""
|
|
102
|
+
def wrapper(task: Any, *args, **kwargs) -> Any:
|
|
103
|
+
task_desc = getattr(task, "description", str(task)) if task else ""
|
|
104
|
+
key = self._extract_key(agent)
|
|
105
|
+
score = evaluate_trust(task_desc, key)
|
|
106
|
+
self._log_decision(agent, task, score)
|
|
107
|
+
return original(task, *args, **kwargs)
|
|
108
|
+
return wrapper
|
|
109
|
+
|
|
110
|
+
def _check_trust(self, delegator: Agent, target: Agent, task: Any) -> TrustScore:
|
|
111
|
+
"""Evaluate trust for a delegation."""
|
|
112
|
+
task_desc = getattr(task, "description", str(task)) if task else ""
|
|
113
|
+
key = self._extract_key(target)
|
|
114
|
+
score = evaluate_trust(task_desc, key)
|
|
115
|
+
self._log_decision(target, task, score, delegator=delegator)
|
|
116
|
+
return score
|
|
117
|
+
|
|
118
|
+
def _log_decision(
|
|
119
|
+
self,
|
|
120
|
+
agent: Agent,
|
|
121
|
+
task: Any,
|
|
122
|
+
score: TrustScore,
|
|
123
|
+
delegator: Optional[Agent] = None,
|
|
124
|
+
) -> None:
|
|
125
|
+
"""Log a trust decision."""
|
|
126
|
+
decision = {
|
|
127
|
+
"agent": getattr(agent, "role", "unknown"),
|
|
128
|
+
"handle": score.handle,
|
|
129
|
+
"score": score.score,
|
|
130
|
+
"verified": score.verified,
|
|
131
|
+
"passed": score.passed,
|
|
132
|
+
"reason": score.reason,
|
|
133
|
+
"task": getattr(task, "description", str(task))[:100] if task else None,
|
|
134
|
+
}
|
|
135
|
+
if delegator:
|
|
136
|
+
decision["delegator"] = getattr(delegator, "role", "unknown")
|
|
137
|
+
|
|
138
|
+
self._decisions.append(decision)
|
|
139
|
+
|
|
140
|
+
level = logging.INFO if score.passed else logging.WARNING
|
|
141
|
+
logger.log(
|
|
142
|
+
level,
|
|
143
|
+
"Trust check: agent=%s handle=%s score=%d verified=%s — %s",
|
|
144
|
+
decision["agent"],
|
|
145
|
+
score.handle or "NONE",
|
|
146
|
+
score.score,
|
|
147
|
+
score.verified,
|
|
148
|
+
score.reason,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
if self.on_decision:
|
|
152
|
+
self.on_decision(agent, task, score)
|
|
153
|
+
|
|
154
|
+
@property
|
|
155
|
+
def decisions(self) -> list[dict[str, Any]]:
|
|
156
|
+
"""All trust decisions made during this crew's execution."""
|
|
157
|
+
return list(self._decisions)
|
|
158
|
+
|
|
159
|
+
def kickoff(self, **kwargs) -> Any:
|
|
160
|
+
"""Run the crew with trust verification active."""
|
|
161
|
+
logger.info("Starting crew with Verigent trust verification (min_score=%d)", self.min_score)
|
|
162
|
+
return self.crew.kickoff(**kwargs)
|
|
163
|
+
|
|
164
|
+
def __getattr__(self, name: str) -> Any:
|
|
165
|
+
"""Proxy other attributes to the underlying crew."""
|
|
166
|
+
return getattr(self.crew, name)
|
|
@@ -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 Optional
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
# VG:{NAME}-{SUFFIX}:{TIER}-{PRIMARY}·{12×class_code+digit}
|
|
11
|
+
_VG_PATTERN = re.compile(
|
|
12
|
+
r"VG:([A-Z0-9]+-[A-Z0-9]+):(V[0-6])-([A-Z]{4})"
|
|
13
|
+
r"\xb7" # middle dot
|
|
14
|
+
r"((?:[A-Z][a-z]\d){12})"
|
|
15
|
+
)
|
|
16
|
+
|
|
17
|
+
_SCORE_PATTERN = re.compile(r"([A-Z][a-z])(\d)")
|
|
18
|
+
|
|
19
|
+
CLASS_CODES = {
|
|
20
|
+
"Se": "Sentinel",
|
|
21
|
+
"Op": "Operative",
|
|
22
|
+
"An": "Analyst",
|
|
23
|
+
"Ar": "Architect",
|
|
24
|
+
"Co": "Conduit",
|
|
25
|
+
"Ad": "Adaptor",
|
|
26
|
+
"St": "Steward",
|
|
27
|
+
"Sc": "Scout",
|
|
28
|
+
"Sa": "Sage",
|
|
29
|
+
"So": "Sovereign",
|
|
30
|
+
"Br": "Broker",
|
|
31
|
+
"Fo": "Forge",
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
@dataclass
|
|
36
|
+
class VGKey:
|
|
37
|
+
"""Parsed Verigent key."""
|
|
38
|
+
|
|
39
|
+
handle: str
|
|
40
|
+
tier: int # 0-6
|
|
41
|
+
primary: str # 4-letter class code e.g. ARCH
|
|
42
|
+
scores: dict[str, int] = field(default_factory=dict) # code -> 0-9
|
|
43
|
+
raw: str = ""
|
|
44
|
+
|
|
45
|
+
@property
|
|
46
|
+
def tier_label(self) -> str:
|
|
47
|
+
return f"V{self.tier}"
|
|
48
|
+
|
|
49
|
+
def score_for(self, class_code: str) -> int:
|
|
50
|
+
"""Get score (0-9) for a 2-letter class code."""
|
|
51
|
+
return self.scores.get(class_code, 0)
|
|
52
|
+
|
|
53
|
+
def score_percent(self, class_code: str) -> int:
|
|
54
|
+
"""Get score as rough percentage (digit * 10)."""
|
|
55
|
+
return self.score_for(class_code) * 10
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def parse_vg_key(raw: str) -> Optional[VGKey]:
|
|
59
|
+
"""Parse a VG key string. Returns None if invalid."""
|
|
60
|
+
m = _VG_PATTERN.search(raw)
|
|
61
|
+
if not m:
|
|
62
|
+
return None
|
|
63
|
+
|
|
64
|
+
handle = m.group(1)
|
|
65
|
+
tier = int(m.group(2)[1])
|
|
66
|
+
primary = m.group(3)
|
|
67
|
+
radar = m.group(4)
|
|
68
|
+
|
|
69
|
+
scores: dict[str, int] = {}
|
|
70
|
+
for sm in _SCORE_PATTERN.finditer(radar):
|
|
71
|
+
code, digit = sm.group(1), int(sm.group(2))
|
|
72
|
+
if code in CLASS_CODES:
|
|
73
|
+
scores[code] = digit
|
|
74
|
+
|
|
75
|
+
if len(scores) != 12:
|
|
76
|
+
return None
|
|
77
|
+
|
|
78
|
+
return VGKey(handle=handle, tier=tier, primary=primary, scores=scores, raw=raw)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def extract_vg_key(
|
|
82
|
+
*,
|
|
83
|
+
system_prompt: Optional[str] = None,
|
|
84
|
+
headers: Optional[dict[str, str]] = None,
|
|
85
|
+
metadata: Optional[dict] = None,
|
|
86
|
+
) -> Optional[VGKey]:
|
|
87
|
+
"""Extract a VG key from common agent sources.
|
|
88
|
+
|
|
89
|
+
Checks (in order):
|
|
90
|
+
1. System prompt text (searches for VG: prefix)
|
|
91
|
+
2. HTTP headers (X-Verigent header)
|
|
92
|
+
3. JSON metadata (x-verigent field)
|
|
93
|
+
"""
|
|
94
|
+
sources = []
|
|
95
|
+
|
|
96
|
+
if system_prompt:
|
|
97
|
+
sources.append(system_prompt)
|
|
98
|
+
|
|
99
|
+
if headers:
|
|
100
|
+
vg_header = headers.get("X-Verigent") or headers.get("x-verigent")
|
|
101
|
+
if vg_header:
|
|
102
|
+
sources.append(vg_header)
|
|
103
|
+
|
|
104
|
+
if metadata:
|
|
105
|
+
vg_meta = metadata.get("x-verigent") or metadata.get("X-Verigent")
|
|
106
|
+
if vg_meta:
|
|
107
|
+
sources.append(str(vg_meta))
|
|
108
|
+
|
|
109
|
+
for source in sources:
|
|
110
|
+
key = parse_vg_key(source)
|
|
111
|
+
if key:
|
|
112
|
+
return key
|
|
113
|
+
|
|
114
|
+
return None
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
"""Trust evaluation logic — maps tasks to class scores."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from .parser import VGKey, CLASS_CODES
|
|
9
|
+
|
|
10
|
+
CLASS_NAMES = {v: k for k, v in CLASS_CODES.items()}
|
|
11
|
+
|
|
12
|
+
# Keyword -> class code mappings
|
|
13
|
+
_TASK_KEYWORDS: dict[str, list[str]] = {
|
|
14
|
+
"Se": [
|
|
15
|
+
"security", "protect", "guard", "monitor", "threat", "vulnerability",
|
|
16
|
+
"audit", "compliance", "firewall", "encryption",
|
|
17
|
+
],
|
|
18
|
+
"Op": [
|
|
19
|
+
"execute", "deploy", "run", "operate", "action", "perform",
|
|
20
|
+
"implement", "deliver", "ship", "build",
|
|
21
|
+
],
|
|
22
|
+
"An": [
|
|
23
|
+
"analyze", "analyse", "data", "research", "investigate", "report",
|
|
24
|
+
"metrics", "statistics", "insight", "pattern",
|
|
25
|
+
],
|
|
26
|
+
"Ar": [
|
|
27
|
+
"design", "architect", "plan", "structure", "blueprint", "system",
|
|
28
|
+
"framework", "strategy", "roadmap", "infrastructure",
|
|
29
|
+
],
|
|
30
|
+
"Co": [
|
|
31
|
+
"communicate", "relay", "translate", "bridge", "message", "notify",
|
|
32
|
+
"connect", "coordinate", "sync", "interface",
|
|
33
|
+
],
|
|
34
|
+
"Ad": [
|
|
35
|
+
"adapt", "flexible", "dynamic", "adjust", "transform", "convert",
|
|
36
|
+
"migrate", "refactor", "evolve", "pivot",
|
|
37
|
+
],
|
|
38
|
+
"St": [
|
|
39
|
+
"manage", "maintain", "steward", "govern", "organize", "track",
|
|
40
|
+
"schedule", "resource", "allocate", "budget",
|
|
41
|
+
],
|
|
42
|
+
"Sc": [
|
|
43
|
+
"search", "find", "discover", "explore", "scan", "locate",
|
|
44
|
+
"browse", "crawl", "index", "retrieve",
|
|
45
|
+
],
|
|
46
|
+
"Sa": [
|
|
47
|
+
"advise", "wisdom", "counsel", "guide", "recommend", "evaluate",
|
|
48
|
+
"judge", "assess", "review", "mentor",
|
|
49
|
+
],
|
|
50
|
+
"So": [
|
|
51
|
+
"decide", "authorize", "approve", "command", "override", "escalate",
|
|
52
|
+
"prioritize", "delegate", "lead", "direct",
|
|
53
|
+
],
|
|
54
|
+
"Br": [
|
|
55
|
+
"negotiate", "trade", "exchange", "deal", "mediate", "arbitrate",
|
|
56
|
+
"price", "value", "market", "partner",
|
|
57
|
+
],
|
|
58
|
+
"Fo": [
|
|
59
|
+
"create", "generate", "produce", "synthesize", "craft", "compose",
|
|
60
|
+
"write", "code", "develop", "fabricate",
|
|
61
|
+
],
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
# Build reverse index: keyword -> class code
|
|
65
|
+
_KEYWORD_INDEX: dict[str, str] = {}
|
|
66
|
+
for _code, _words in _TASK_KEYWORDS.items():
|
|
67
|
+
for _word in _words:
|
|
68
|
+
_KEYWORD_INDEX[_word] = _code
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@dataclass
|
|
72
|
+
class TrustScore:
|
|
73
|
+
"""Result of trust evaluation."""
|
|
74
|
+
|
|
75
|
+
score: int # 0-100
|
|
76
|
+
verified: bool # has a valid VG key
|
|
77
|
+
handle: Optional[str]
|
|
78
|
+
relevant_classes: dict[str, int] # class code -> score (0-9)
|
|
79
|
+
reason: str
|
|
80
|
+
|
|
81
|
+
@property
|
|
82
|
+
def passed(self) -> bool:
|
|
83
|
+
"""Whether trust score meets minimum threshold (50)."""
|
|
84
|
+
return self.score >= 50
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def evaluate_trust(task_description: str, key: Optional[VGKey]) -> TrustScore:
|
|
88
|
+
"""Score task suitability given a task and a parsed VG key.
|
|
89
|
+
|
|
90
|
+
Returns a TrustScore with 0-100 rating based on how well the agent's
|
|
91
|
+
class scores match the task requirements.
|
|
92
|
+
"""
|
|
93
|
+
if key is None:
|
|
94
|
+
return TrustScore(
|
|
95
|
+
score=0,
|
|
96
|
+
verified=False,
|
|
97
|
+
handle=None,
|
|
98
|
+
relevant_classes={},
|
|
99
|
+
reason="No VG key found — agent is unverified",
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
# Find relevant classes from task keywords
|
|
103
|
+
task_lower = task_description.lower()
|
|
104
|
+
matched_codes: dict[str, int] = {}
|
|
105
|
+
|
|
106
|
+
for word, code in _KEYWORD_INDEX.items():
|
|
107
|
+
if word in task_lower and code not in matched_codes:
|
|
108
|
+
matched_codes[code] = key.score_for(code)
|
|
109
|
+
|
|
110
|
+
if not matched_codes:
|
|
111
|
+
# No keyword matches — use tier as baseline
|
|
112
|
+
baseline = min(key.tier * 15, 70)
|
|
113
|
+
return TrustScore(
|
|
114
|
+
score=baseline,
|
|
115
|
+
verified=True,
|
|
116
|
+
handle=key.handle,
|
|
117
|
+
relevant_classes={},
|
|
118
|
+
reason=f"No specific class match; baseline from tier V{key.tier}",
|
|
119
|
+
)
|
|
120
|
+
|
|
121
|
+
# Weighted average of matched class scores (each 0-9, scaled to 0-100)
|
|
122
|
+
total = sum(matched_codes.values())
|
|
123
|
+
avg = (total / len(matched_codes)) * 10 # scale 0-9 to 0-90
|
|
124
|
+
|
|
125
|
+
# Tier bonus: V0=0, V1=1, V2=2, ... V6=6 points
|
|
126
|
+
score = int(min(avg + key.tier, 100))
|
|
127
|
+
|
|
128
|
+
return TrustScore(
|
|
129
|
+
score=score,
|
|
130
|
+
verified=True,
|
|
131
|
+
handle=key.handle,
|
|
132
|
+
relevant_classes=matched_codes,
|
|
133
|
+
reason=f"Matched {len(matched_codes)} classes, avg score {avg:.0f}%",
|
|
134
|
+
)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
async def verify_remote(handle: str) -> Optional[dict]:
|
|
138
|
+
"""Verify agent against verigent.ai API. Requires httpx."""
|
|
139
|
+
try:
|
|
140
|
+
import httpx
|
|
141
|
+
except ImportError:
|
|
142
|
+
return None
|
|
143
|
+
|
|
144
|
+
try:
|
|
145
|
+
async with httpx.AsyncClient(timeout=5.0) as client:
|
|
146
|
+
resp = await client.get(f"https://verigent.ai/api/verify/{handle}")
|
|
147
|
+
if resp.status_code == 200:
|
|
148
|
+
return resp.json()
|
|
149
|
+
except (httpx.HTTPError, Exception):
|
|
150
|
+
pass
|
|
151
|
+
|
|
152
|
+
return None
|