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.
@@ -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