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: 0 additions & 1 deletion .claude/CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ Two identity paths: `X-Wallet-Address` (wallet-based) and `X-Operator-Token` (cr

## Methods (sync + async)

- `get_reputation` / `aget_reputation` — cached reputation lookup (free)
- `assess` / `aassess` — identity gate with policy (paid). Accepts `operator_token` for non-wallet agents. Response includes `linked_wallets[]` and `resolved_operator`. Optional `signer: { address, network }` opts into server-side wallet-signer-match AND OFAC SDN wallet-address screening — the response then carries both a `signer_match` block (wallet-binding verdict: `pass` / `wallet_signer_mismatch` / `wallet_auth_requires_wallet_signing`) and a `signer_sanctions` block (discriminated union: `{status: "clear"}` | `{sanctioned: True, ofac_label, sdn_uid, listed_at}` | `{status: "unavailable"}`). Wallet-OFAC SDN enforcement on the `signer` block is unconditional whenever a signer is supplied — no `policy.require_sanctions_clear` opt-in required. A `sanctioned: True` OR `status: "unavailable"` verdict flips the response `decision` to `deny` with `decision_reasons` including `sanctions_flagged` or `sanctions_check_unavailable` respectively (fail-closed; OFAC strict-liability). `policy.require_sanctions_clear` is the separate NAME-based screen on the resolved operator's KYC identity.
- `create_session` / `acreate_session` — create verification session. Returns `agent_memory` + `next_steps`.
- `poll_session` / `apoll_session` — poll session status, returns credential when verified, plus `next_steps.action`.
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ jobs:
- uses: useblacksmith/checkout@v1
- uses: astral-sh/setup-uv@v10.2.0
with:
version: "0.12.20"
version: "0.12.22"
- run: uv python install 3.12
- uses: actions/cache@v6
with:
Expand All @@ -49,7 +49,7 @@ jobs:
- uses: useblacksmith/checkout@v1
- uses: astral-sh/setup-uv@v10.2.0
with:
version: "0.12.20"
version: "0.12.22"
- run: uv python install ${{ matrix.python }}
- run: uv sync --frozen --all-extras --all-groups --python ${{ matrix.python }}
- run: uv run --python ${{ matrix.python }} pytest tests/ -q
2 changes: 1 addition & 1 deletion .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ jobs:

- uses: astral-sh/setup-uv@v10.2.0
with:
version: "0.12.20"
version: "0.12.22"

- name: Set version from tag
run: sed -i "s/^version = .*/version = \"${GITHUB_REF_NAME#v}\"/" pyproject.toml
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/security.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ jobs:
- uses: useblacksmith/checkout@v1
- uses: astral-sh/setup-uv@v10.2.0
with:
version: "0.12.20"
version: "0.12.22"
- uses: actions/setup-python@v7
with:
python-version: "3.13"
Expand Down
19 changes: 5 additions & 14 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,6 @@ from agentscore import AgentScore

client = AgentScore(api_key="as_live_...")

# Look up cached reputation (free)
rep = client.get_reputation("0x1234...")
print(rep["score"]["value"], rep["score"]["grade"])

# Filter to a specific chain
base_rep = client.get_reputation("0x1234...", chain="base")

# Identity gate with policy (paid)
gated = client.assess(
"0x1234...",
Expand All @@ -39,9 +32,8 @@ 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"
# Verification level of the operator behind the wallet
print(gated["operator_verification"]["level"]) # "none" | "kyc_verified"
```

### Credential-Based Identity
Expand Down Expand Up @@ -75,7 +67,7 @@ client.create_session(kind="sign_in") # registration-only: account sign-in, no

### Wallet resolution

`assess()` responses include `resolved_operator` and `linked_wallets`, all same-operator sibling wallets (claimed via SIWE or captured via prior `associate_wallet`). The list may mix EVM addresses (`0x...` lowercased) and Solana addresses (base58, case-preserved) for cross-chain operators; merchants doing wallet-signer-match checks should accept a payment signed by any address in the list, regardless of chain. The `address` parameter on `assess()` and `get_reputation()` accepts either format; the network is auto-detected from the address shape.
`assess()` responses include `resolved_operator` and `linked_wallets`, all same-operator sibling wallets (claimed via SIWE or captured via prior `associate_wallet`). The list may mix EVM addresses (`0x...` lowercased) and Solana addresses (base58, case-preserved) for cross-chain operators; merchants doing wallet-signer-match checks should accept a payment signed by any address in the list, regardless of chain. The `address` parameter on `assess()` accepts either format; the network is auto-detected from the address shape.

### Server-side signer-match + sanctions screening

Expand Down Expand Up @@ -138,7 +130,6 @@ All methods have async variants prefixed with `a`:

```python
async with AgentScore(api_key="as_live_...") as client:
rep = await client.aget_reputation("0x1234...")
result = await client.aassess("0x1234...", policy={"require_kyc": True})

# Identity model methods
Expand All @@ -158,7 +149,7 @@ async with AgentScore(api_key="as_live_...") as client:

```python
with AgentScore(api_key="as_live_...") as client:
rep = client.get_reputation("0x1234...")
result = client.assess("0x1234...")
```

## Configuration
Expand All @@ -178,7 +169,7 @@ with AgentScore(api_key="as_live_...") as client:
from agentscore import AgentScore, AgentScoreError

try:
rep = client.get_reputation("0xinvalid")
client.assess("0xinvalid")
except AgentScoreError as e:
print(e.code, e.status_code, str(e))
```
Expand Down
32 changes: 0 additions & 32 deletions agentscore/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,12 @@
from agentscore.test_mode import AGENTSCORE_TEST_ADDRESSES, is_agentscore_test_address
from agentscore.types import (
AccountVerification,
Activity,
AgentMemoryHint,
AgentMemoryIdentityPaths,
AgentSummary,
AipProvenance,
AipSignatureMaterial,
AssessResponse,
AssociateWalletResponse,
ChainEntry,
ChainScore,
Classification,
CredentialCreateErrorNextSteps,
CredentialCreateErrorResponse,
CredentialCreateResponse,
Expand All @@ -32,23 +27,13 @@
CredentialRevokeResponse,
DecisionPolicy,
DenialCode,
EntityType,
EvidenceSummary,
Grade,
Identity,
Network,
NextStepsAction,
OperatorScore,
OperatorVerification,
PolicyCheck,
PolicyExplanation,
PolicyResult,
QuotaInfo,
RedactedClassification,
Reputation,
ReputationResponse,
ReputationStatus,
Score,
SessionCreateNextSteps,
SessionCreateRequest,
SessionCreateResponse,
Expand All @@ -60,7 +45,6 @@
SignerSanctionsClear,
SignerSanctionsHit,
SignerSanctionsUnavailable,
Subject,
VerificationLevel,
WalletAuthRequiresSigningBody,
WalletAuthRequiresSigningNextSteps,
Expand All @@ -73,19 +57,14 @@
__all__ = [
"AGENTSCORE_TEST_ADDRESSES",
"AccountVerification",
"Activity",
"AgentMemoryHint",
"AgentMemoryIdentityPaths",
"AgentScore",
"AgentScoreError",
"AgentSummary",
"AipProvenance",
"AipSignatureMaterial",
"AssessResponse",
"AssociateWalletResponse",
"ChainEntry",
"ChainScore",
"Classification",
"CredentialCreateErrorNextSteps",
"CredentialCreateErrorResponse",
"CredentialCreateResponse",
Expand All @@ -94,14 +73,9 @@
"CredentialRevokeResponse",
"DecisionPolicy",
"DenialCode",
"EntityType",
"EvidenceSummary",
"Grade",
"Identity",
"InvalidCredentialError",
"Network",
"NextStepsAction",
"OperatorScore",
"OperatorVerification",
"PaymentRequiredError",
"PolicyCheck",
Expand All @@ -110,11 +84,6 @@
"QuotaExceededError",
"QuotaInfo",
"RateLimitedError",
"RedactedClassification",
"Reputation",
"ReputationResponse",
"ReputationStatus",
"Score",
"SessionCreateNextSteps",
"SessionCreateRequest",
"SessionCreateResponse",
Expand All @@ -126,7 +95,6 @@
"SignerSanctionsClear",
"SignerSanctionsHit",
"SignerSanctionsUnavailable",
"Subject",
"TimeoutError",
"TokenExpiredError",
"VerificationLevel",
Expand Down
21 changes: 2 additions & 19 deletions agentscore/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,6 @@ def _build_error_from_response(response: httpx.Response) -> AgentScoreError:
CredentialRevokeResponse,
DecisionPolicy,
Network,
ReputationResponse,
SessionCreateResponse,
SessionPollResponse,
Signer,
Expand Down Expand Up @@ -247,14 +246,6 @@ def _handle_response(self, response: httpx.Response) -> Any:

# --- Sync methods ---

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:
params["chain"] = chain
client = self._get_sync_client()
return self._send_sync(lambda: client.get(f"/v1/reputation/{address}", params=params))

def assess(
self,
address: str | None = None,
Expand Down Expand Up @@ -310,7 +301,7 @@ def create_session(
operator_token: str | None = None,
kind: Literal["kyc", "sign_in"] | None = None,
) -> SessionCreateResponse:
"""Create an assessment session for deferred scoring.
"""Create a verification or sign-in session.

``address`` pre-associates the session with a known wallet (EVM ``0x...`` or
Solana base58). ``operator_token`` pre-associates with an existing ``opc_...`` —
Expand Down Expand Up @@ -404,14 +395,6 @@ def associate_wallet(

# --- Async methods ---

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:
params["chain"] = chain
client = self._get_async_client()
return await self._send_async(lambda: client.get(f"/v1/reputation/{address}", params=params))

async def aassess(
self,
address: str | None = None,
Expand Down Expand Up @@ -460,7 +443,7 @@ async def acreate_session(
operator_token: str | None = None,
kind: Literal["kyc", "sign_in"] | None = None,
) -> SessionCreateResponse:
"""Create an assessment session for deferred scoring.
"""Create a verification or sign-in session.

``address`` pre-associates the session with a known wallet (EVM ``0x...`` or
Solana base58). ``operator_token`` pre-associates with an existing ``opc_...`` —
Expand Down
Loading
Loading