Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,4 @@ dist/
.ruff_cache/
.env
coverage/
.coverage
21 changes: 14 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,20 @@ base_rep = client.get_reputation("0x1234...", chain="base")
result = client.assess("0x1234...", policy={"min_grade": "B", "min_score": 35})
print(result["decision"], result["decision_reasons"])

# Browse agents
agents = client.get_agents(chain="base", limit=10)
print(len(agents["items"]), agents["count"])

# Ecosystem stats
stats = client.get_stats()
print(stats["erc8004"]["known_agents"])
# Compliance assessment with verification policy
gated = client.assess("0x1234...", policy={
"require_kyc": True,
"require_sanctions_clear": True,
"min_age": 21,
})

if gated["decision"] == "deny":
print(gated["decision_reasons"]) # ["kyc_required"]
print(gated.get("verify_url")) # URL for operator verification

# Check verification level
rep = client.get_reputation("0x1234...")
print(rep.get("verification_level")) # "none" | "wallet_claimed" | "kyc_verified"
```

### Async
Expand Down
18 changes: 4 additions & 14 deletions agentscore/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,40 +3,30 @@
from agentscore.client import AgentScore
from agentscore.errors import AgentScoreError
from agentscore.types import (
AgentRecord,
AgentsListResponse,
AssessResponse,
DecisionPolicy,
EntityType,
Grade,
OperatorVerification,
Reputation,
ReputationResponse,
ReputationResponseFull,
ReputationStatus,
StatsERC8004,
StatsPayments,
StatsReputation,
StatsResponse,
VerificationLevel,
)

__version__ = _pkg_version("agentscore-py")

__all__ = [
"AgentRecord",
"AgentScore",
"AgentScoreError",
"AgentsListResponse",
"AssessResponse",
"DecisionPolicy",
"EntityType",
"Grade",
"OperatorVerification",
"Reputation",
"ReputationResponse",
"ReputationResponseFull",
"ReputationStatus",
"StatsERC8004",
"StatsPayments",
"StatsReputation",
"StatsResponse",
"VerificationLevel",
"__version__",
]
36 changes: 4 additions & 32 deletions agentscore/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,9 @@

if TYPE_CHECKING:
from agentscore.types import (
AgentsListResponse,
AssessResponse,
DecisionPolicy,
ReputationResponseFull,
StatsResponse,
ReputationResponse,
)


Expand All @@ -37,7 +35,7 @@ def __init__(
def _headers(self) -> dict:
return {
"Accept": "application/json",
"Authorization": f"Bearer {self.api_key}",
"X-API-Key": self.api_key,
"User-Agent": f"agentscore-py/{_pkg_version('agentscore-py')}",
}

Expand Down Expand Up @@ -86,7 +84,7 @@ def _handle_response(self, response: httpx.Response) -> dict:

# --- Sync methods ---

def get_reputation(self, address: str, chain: str | None = None) -> ReputationResponseFull:
def get_reputation(self, address: str, chain: str | None = None) -> ReputationResponse:
"""Get cached reputation for an address (free, read-only). Optionally filter by chain."""
params: dict[str, str] = {}
if chain:
Expand Down Expand Up @@ -114,22 +112,9 @@ def assess(
response = client.post("/v1/assess", json=body)
return self._handle_response(response)

def get_agents(self, **filters: Any) -> AgentsListResponse:
"""Browse agents (free)."""
params = {k: str(v).lower() if isinstance(v, bool) else str(v) for k, v in filters.items() if v is not None}
client = self._get_sync_client()
response = client.get("/v1/agents", params=params)
return self._handle_response(response)

def get_stats(self) -> StatsResponse:
"""Get ecosystem stats (free)."""
client = self._get_sync_client()
response = client.get("/v1/stats")
return self._handle_response(response)

# --- Async methods ---

async def aget_reputation(self, address: str, chain: str | None = None) -> ReputationResponseFull:
async def aget_reputation(self, address: str, chain: str | None = None) -> ReputationResponse:
"""Get cached reputation for an address (free, read-only). Optionally filter by chain."""
params: dict[str, str] = {}
if chain:
Expand Down Expand Up @@ -157,19 +142,6 @@ async def aassess(
response = await client.post("/v1/assess", json=body)
return self._handle_response(response)

async def aget_agents(self, **filters: Any) -> AgentsListResponse:
"""Browse agents (free)."""
params = {k: str(v).lower() if isinstance(v, bool) else str(v) for k, v in filters.items() if v is not None}
client = self._get_async_client()
response = await client.get("/v1/agents", params=params)
return self._handle_response(response)

async def aget_stats(self) -> StatsResponse:
"""Get ecosystem stats (free)."""
client = self._get_async_client()
response = await client.get("/v1/stats")
return self._handle_response(response)

def close(self):
if self._sync_client:
self._sync_client.close()
Expand Down
152 changes: 57 additions & 95 deletions agentscore/types.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@

Grade = Literal["A", "B", "C", "D", "F"]
EntityType = Literal["agent", "service", "hybrid", "wallet", "bot", "unknown"]
ReputationStatus = Literal["unknown", "known_unscored", "scored", "stale", "indexing"]
ReputationStatus = Literal["scored", "stale", "known_unscored"]
VerificationLevel = Literal["none", "wallet_claimed", "kyc_verified"]


class Subject(TypedDict):
Expand All @@ -23,23 +24,26 @@ class Classification(TypedDict):


class Score(TypedDict):
value: int | None
value: float | None
grade: Grade | None
scored_at: str | None
status: ReputationStatus
version: str


class ChainScore(TypedDict):
value: int | None
class _ChainScoreRequired(TypedDict):
value: float | None
grade: Grade | None
confidence: float | None
dimensions: dict[str, float] | None
scored_at: str | None
status: ReputationStatus
version: str


class ChainScore(_ChainScoreRequired, total=False):
confidence: float | None
dimensions: dict[str, float] | None


class Identity(TypedDict):
ens_name: str | None
website_url: str | None
Expand Down Expand Up @@ -71,36 +75,27 @@ class Reputation(TypedDict):
last_feedback_at: str | None


class EvidenceSummary(TypedDict, total=False):
candidate_tx_count: int
confirmed_or_likely_tx: int
endpoint_count: int
github_stars: int
github_url: str | None
has_a2a_agent_card: bool
has_ens: bool
has_github: bool
has_website: bool
healthy_endpoints: int
is_erc8004: bool
class EvidenceSummary(TypedDict):
metadata_kind: str | None
metadata_quality: str | None
multi_chain_count: int
reputation_feedback_count: int
reputation_trust_avg: float | None
reputation_uptime_avg: float | None
reputation_activity_avg: float | None
reputation_client_count: int
verified_tx_count: int
has_a2a_agent_card: bool
website_url: str | None
website_reachable: bool
website_mentions_mcp: bool
website_mentions_x402: bool
website_reachable: bool
website_url: str | None
github_url: str | None
github_stars: int | None


class OperatorScore(TypedDict):
score: int
class RedactedClassification(TypedDict):
entity_type: EntityType


class _OperatorScoreRequired(TypedDict):
score: float
grade: Grade


class OperatorScore(_OperatorScoreRequired, total=False):
agent_count: int
chains_active: list[str]

Expand All @@ -109,20 +104,23 @@ class AgentSummary(TypedDict):
token_id: int
chain: str
name: str | None
score: int
score: float
grade: Grade


class ChainEntry(TypedDict):
class _ChainEntryRequired(TypedDict):
chain: str
score: ChainScore
classification: Classification
classification: Classification | RedactedClassification


class ChainEntry(_ChainEntryRequired, total=False):
identity: Identity
activity: Activity
evidence_summary: EvidenceSummary


class ReputationResponse(TypedDict):
class _ReputationResponseRequired(TypedDict):
subject: Subject
score: Score
chains: list[ChainEntry]
Expand All @@ -131,19 +129,35 @@ class ReputationResponse(TypedDict):
updated_at: str | None


class ReputationResponseFull(ReputationResponse, total=False):
class ReputationResponse(_ReputationResponseRequired, total=False):
reputation: Reputation
operator_score: OperatorScore
agents: list[AgentSummary]
reputation: Reputation
verification_level: VerificationLevel


class _OperatorVerificationRequired(TypedDict):
level: VerificationLevel


class OperatorVerification(_OperatorVerificationRequired, total=False):
operator_type: str | None
claimed_at: str | None
verified_at: str | None


class DecisionPolicy(TypedDict, total=False):
min_grade: Grade
min_score: int
require_verified_payment_activity: bool
require_kyc: bool
require_sanctions_clear: bool
min_age: int
blocked_jurisdictions: list[str]
require_entity_type: str


class AssessResponse(TypedDict):
class _AssessResponseRequired(TypedDict):
subject: Subject
score: Score
chains: list[ChainEntry]
Expand All @@ -153,64 +167,12 @@ class AssessResponse(TypedDict):
data_semantics: str
caveats: list[str]
updated_at: str | None


class AssessResponse(_AssessResponseRequired, total=False):
operator_score: OperatorScore | None
reputation: Reputation | None
agents: list[AgentSummary]


class AgentRecord(TypedDict):
chain: str
token_id: int
owner_address: str
agent_wallet: str | None
name: str | None
description: str | None
metadata_quality: str
score: int | None
grade: Grade | None
entity_type: EntityType | None
endpoint_count: int
website_url: str | None
github_url: str | None
has_candidate_payment_activity: bool
has_verified_payment_activity: bool
agents_sharing_owner: int | None
updated_at: str


class AgentsListResponse(TypedDict):
items: list[AgentRecord]
next_cursor: str | None
count: int
version: str


class StatsPayments(TypedDict):
addresses_with_candidate_payment_activity: int
addresses_with_verified_payment_activity: int
total_candidate_transactions: int
total_verified_transactions: int
verification_status_summary: dict[str, int]


class StatsERC8004(TypedDict):
known_agents: int
by_chain: dict[str, int]
metadata_quality_distribution: dict[str, int]


class StatsReputation(TypedDict):
total_addresses: int
scored_addresses: int
entity_distribution: dict[str, int]
score_distribution: dict[str, int]


class StatsResponse(TypedDict, total=False):
version: str
as_of_time: str
data_semantics: str
erc8004: StatsERC8004
reputation: StatsReputation
payments: StatsPayments
caveats: list[str]
operator_verification: OperatorVerification
resolved_operator: str
verify_url: str
Loading
Loading