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
2 changes: 1 addition & 1 deletion docs/INTERFACE.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Agent Guild — machine interface (GENERATED)

*Generated from `live/guild/contract/contract.json` v2 (service 2.5.19). Do not edit by hand — run `make contract`.*
*Generated from `live/guild/contract/contract.json` v2 (service 2.5.20). Do not edit by hand — run `make contract`.*

- Host: https://agent-guild-5d5r.onrender.com
- MCP (streamable HTTP): https://agent-guild-5d5r.onrender.com/mcp/
Expand Down
8 changes: 6 additions & 2 deletions live/guild/app/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,12 @@
# Single source of truth for the service version. Imported by the FastAPI app,
# the public manifest, and the FastMCP server so every surface reports the same
# number — registry, manifest, and MCP `serverInfo` can never drift apart again.
__version__ = "2.5.19" # PATCH (machine-visible semver, 2026-08-14):
# lets registry validators discover the paid search
__version__ = "2.5.20" # PATCH (machine-visible semver, 2026-08-14):
# makes the x402 Bazaar output contract for /search
# match its paid ranked SearchResponse, so validating
# autonomous buyers can accept the result after
# settlement. History of 2.5.19: lets registry
# validators discover the paid search
# operation with a non-executable bare probe while
# malformed paid or authenticated calls still fail
# before settlement. History of 2.5.18: completes the
Expand Down
67 changes: 65 additions & 2 deletions live/guild/app/x402.py
Original file line number Diff line number Diff line change
Expand Up @@ -670,14 +670,77 @@ def resource_info(preq: "PaidRequest") -> ResourceInfo:
},
}

# Some HTTP resources deliberately share a billing operation while returning
# different wire contracts. In particular, GET /check and GET /search both
# settle as ``best_agent`` but /search returns a ranked SearchResponse rather
# than the check response. A paying machine must be able to validate the
# response against the exact schema advertised before payment, so route-level
# contracts take precedence over the operation-level discovery fallback.
_BAZAAR_ROUTE_OUTPUTS = {
("best_agent", "/search"): {
"capability": "fact-check",
"count": 1,
"results": [{
"id": "agent_example",
"did": "did:key:z6MkExampleAgent",
"name": "Example Agent",
"capabilities": ["fact-check"],
"metadata": {},
"trust": 0.93,
"rank": 1,
"confidence": 0.82,
"attestations_received": 4,
}],
},
}

_BAZAAR_ROUTE_OUTPUT_SCHEMAS = {
("best_agent", "/search"): {
"type": "object",
"properties": {
"capability": {"type": "string"},
"count": {"type": "integer", "minimum": 0},
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "string"},
"did": {"type": "string", "pattern": "^did:"},
"name": {"type": "string"},
"capabilities": {
"type": "array",
"items": {"type": "string"},
},
"metadata": {"type": "object"},
"trust": {"type": "number"},
"rank": {"type": "integer"},
"confidence": {"type": "number"},
"attestations_received": {"type": "integer"},
},
"required": [
"id", "did", "name", "capabilities", "metadata",
"trust", "rank", "confidence",
"attestations_received",
],
},
},
},
"required": ["capability", "count", "results"],
},
}


def bazaar_extension(preq: "PaidRequest") -> dict[str, Any]:
query = dict(preq.query)
route_key = (preq.operation, preq.path)
output: dict[str, Any] = {
"type": "json",
"example": _BAZAAR_OUTPUT.get(preq.operation, {}),
"example": _BAZAAR_ROUTE_OUTPUTS.get(
route_key, _BAZAAR_OUTPUT.get(preq.operation, {})),
}
output_schema = _BAZAAR_OUTPUT_SCHEMAS.get(preq.operation)
output_schema = _BAZAAR_ROUTE_OUTPUT_SCHEMAS.get(
route_key, _BAZAAR_OUTPUT_SCHEMAS.get(preq.operation))
if output_schema:
output["schema"] = output_schema
info: dict[str, Any] = {
Expand Down
2 changes: 1 addition & 1 deletion live/guild/contract/contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -1050,6 +1050,6 @@
"mcp_url": "https://agent-guild-5d5r.onrender.com/mcp/",
"name": "Agent Guild",
"repository": "https://github.com/AgentTanuki/agent-guild",
"version": "2.5.19"
"version": "2.5.20"
}
}
19 changes: 18 additions & 1 deletion live/guild/tests/test_bazaar_service_metadata.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from app.payments import (
check_request, deep_preflight_request, evidence_bundle_request,
machine_envelope_request, payment_decision_request,
protected_payment_decision_request,
protected_payment_decision_request, search_request,
)
from x402.extensions.bazaar import (
extract_discovery_info_from_extension,
Expand Down Expand Up @@ -69,9 +69,26 @@ def test_other_paid_products_receive_operation_specific_tags_without_schema_drif
assert "decision" not in str(output)


def test_search_declares_its_ranked_result_contract_without_changing_check():
search_output = x402.bazaar_extension(
search_request("fact-check"))["info"]["output"]
check_output = x402.bazaar_extension(
check_request("fact-check"))["info"]["output"]

assert set(search_output["schema"]["required"]) == {
"capability", "count", "results"}
assert set(search_output["schema"]["properties"]["results"][
"items"]["required"]) >= {
"id", "did", "name", "capabilities", "trust", "rank",
"confidence", "attestations_received"}
assert set(check_output["schema"]["required"]) == {
"schema_version", "capability", "status"}


def test_flagship_bazaar_examples_match_declared_schemas_and_official_sdk():
requests = [
check_request("fact-check"),
search_request("fact-check"),
check_request("fact-check", signed=True, ttl_seconds=3600),
machine_envelope_request("a" * 64),
payment_decision_request("b" * 64),
Expand Down
6 changes: 6 additions & 0 deletions live/guild/tests/test_discovery_probe_paid_routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,12 @@ def test_search_bare_registry_probe_is_non_executable(client, settle_spy):
assert detail["discovery_only"] is True
assert detail["executable"] is False
assert response.headers.get("PAYMENT-REQUIRED")
challenge = json.loads(base64.b64decode(
response.headers["PAYMENT-REQUIRED"]))
output_schema = challenge["extensions"]["bazaar"]["info"][
"output"]["schema"]
assert set(output_schema["required"]) == {
"capability", "count", "results"}
assert response.headers.get("WWW-Authenticate", "").startswith("Payment ")
assert settle_spy == []

Expand Down
8 changes: 8 additions & 0 deletions live/guild/tests/test_x402_v2.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@

import pytest
from fastapi.testclient import TestClient
from jsonschema import Draft202012Validator

from x402.schemas import PaymentPayload, ResourceInfo

Expand Down Expand Up @@ -202,10 +203,17 @@ def test_paid_request_end_to_end_serves_result_and_settlement(monkeypatch):
monkeypatch.setattr(x402, "_facilitator", lambda: fac)
from app.main import app
with TestClient(app) as client:
quote = client.get("/search?capability=anything")
assert quote.status_code == 402
quoted = json.loads(base64.b64decode(
quote.headers["PAYMENT-REQUIRED"]))
output_schema = quoted["extensions"]["bazaar"]["info"][
"output"]["schema"]
p = make_payload(SEARCH)
r = client.get("/search?capability=anything",
headers={"PAYMENT-SIGNATURE": sig_header(p)})
assert r.status_code == 200
Draft202012Validator(output_schema).validate(r.json())
resp_hdr = r.headers.get("PAYMENT-RESPONSE")
assert resp_hdr, "success must carry the PAYMENT-RESPONSE header"
settled = json.loads(base64.b64decode(resp_hdr))
Expand Down
2 changes: 1 addition & 1 deletion server.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"$schema": "https://static.modelcontextprotocol.io/schemas/2025-12-11/server.schema.json",
"name": "io.github.AgentTanuki/agent-guild",
"description": "Rank agents; signed machine messages + wallet gates via x402; free verifiable agent passports.",
"version": "2.5.19",
"version": "2.5.20",
"repository": {
"url": "https://github.com/AgentTanuki/agent-guild",
"source": "github"
Expand Down
Loading