Agentic Web Protocol (AWP)

ADK-Rust bietet Agentic Web Protocol (AWP)-Typen und Axum-Integration, um Websites und Dienste für AI-Agenten zugänglich zu machen. Die Implementierung erstreckt sich über zwei Crates: awp-types (reine Protokolltypen) und adk-awp (Routen, Middleware und Service-Schnittstellen). Anwendungen stellen Agenten-Dispatch, Authentifizierung, Autorisierung und dauerhafte Webhook-Zustellung bereit.

Übersicht

AWP ermöglicht es jeder Website, ihre Fähigkeiten, Richtlinien und ihren Geschäftskontext in einem maschinenlesbaren Format zu deklarieren. AI-Agenten können diese Fähigkeiten entdecken, Protokollversionen aushandeln, Ereignisse abonnieren und über typisierte A2A-Nachrichten interagieren. adk-awp erzwingt an seiner HTTP-Grenze Body- und Ratenlimits; Anwendungs-Handler erzwingen Identitäts- und Fähigkeitsautorisierung.

Verwenden Sie AWP, wenn:

  • Sie möchten, dass AI-Agenten Ihren Dienst programmatisch entdecken und mit ihm interagieren
  • Sie Metadaten zum Vertrauensniveau und einen Hook für anwendungsseitig durchgesetzte Zugriffskontrolle benötigen
  • Sie sowohl menschliche Besucher als auch AI-Agenten über dieselben Endpunkte bedienen möchten
  • Sie Ereignisabonnements und HMAC-SHA256-Signaturprimitive benötigen
  • Sie einen Zustandsautomaten für die Dienstüberwachung möchten

Architektur

AWP Request-Flow

Rendering architecture…

Anwendungsaufbau

┌─────────────────────────────────────────────────┐
│                  Your Application                │
│                                                  │
│  ┌──────────────┐  ┌──────────────────────────┐ │
│  │  LLM Agent   │  │  awp_routes(state)       │ │
│  │  (adk-agent) │  │  ├ /.well-known/awp.json │ │
│  │              │  │  ├ /awp/manifest          │ │
│  │  Instructions│  │  ├ /awp/health            │ │
│  │  derived from│  │  └ /awp/a2a              │ │
│  │  business.   │  │  auth + management routes│ │
│  │  toml        │  │                           │ │
│  └──────────────┘  └──────────────────────────┘ │
│         ▲                      ▲                 │
│         │                      │                 │
│    ┌────┴──────────────────────┴────┐            │
│    │     BusinessContextLoader      │            │
│    │     (business.toml + ArcSwap)  │            │
│    └────────────────────────────────┘            │
└─────────────────────────────────────────────────┘

Pakete

CrateZweckAbhängigkeiten
awp-typesProtokolltypen (Enums, Structs, Fehler)Keine adk-*-Abhängigkeiten — nur serde, uuid, chrono, thiserror
adk-awpRouten, Middleware, Service-Interfaces und In-Memory-Implementierungenawp-types, adk-core, axum 0.8, tokio, dashmap

Die Aufspaltung bedeutet, dass jedes Rust-Projekt von awp-types abhängen kann, ohne den ADK-Baum einzubeziehen.

Schnellstart

1. Erstelle ein business.toml

site_name = "My Shop"
site_description = "An online store powered by AWP"
domain = "myshop.example.com"
contact = "hello@myshop.example.com"

[business]
country = "US"
currency = "USD"
languages = ["en"]

[brand_voice]
tone = "friendly and helpful"
greeting = "Welcome! How can I help?"

[[capabilities]]
name = "browse_products"
description = "Browse the product catalog"
endpoint = "/api/products"
method = "GET"
access_level = "anonymous"

[[capabilities]]
name = "place_order"
description = "Place an order"
endpoint = "/api/orders"
method = "POST"
access_level = "known"

[[products]]
sku = "WIDGET-001"
name = "Standard Widget"
price = 1999
inventory = 500
tags = ["widget"]

[[policies]]
name = "privacy"
description = "Minimal data collection, no tracking."
policy_type = "privacy"

[payments]
providers = ["stripe"]
auto_approve_threshold = 5000

[support]
escalation_contacts = ["support@myshop.example.com"]
hours = "Mon-Fri 9-5 EST"

2. Lade und serviere AWP-Routen

use std::sync::Arc;

use adk_awp::{AwpA2aHandler, AwpState, BusinessContextLoader, awp_routes};
use async_trait::async_trait;
use awp_types::AwpError;
use axum::http::{HeaderMap, header};
use serde_json::{Value, json};

struct ApplicationA2a {
    bearer_token: Arc<str>,
}

#[async_trait]
impl AwpA2aHandler for ApplicationA2a {
    async fn handle(&self, headers: HeaderMap, message: Value) -> Result<Value, AwpError> {
        let expected = format!("Bearer {}", self.bearer_token);
        let authorized = headers
            .get(header::AUTHORIZATION)
            .and_then(|value| value.to_str().ok())
            .is_some_and(|value| value == expected);
        if !authorized {
            return Err(AwpError::Unauthorized("invalid A2A credential".to_string()));
        }
        // Authorize the requested capability and dispatch to the application agent.
        Ok(json!({ "status": "processed", "messageId": message["id"] }))
    }
}

let loader = BusinessContextLoader::from_file("business.toml".as_ref())?;
let a2a_token: Arc<str> = std::env::var("AWP_A2A_TOKEN")?.into();
let state = AwpState::builder(loader.context_ref())
    .a2a_handler(Arc::new(ApplicationA2a { bearer_token: a2a_token }))
    .build();

let app = axum::Router::new()
    .merge(awp_routes(state))
    .merge(your_custom_routes);

let listener = tokio::net::TcpListener::bind("127.0.0.1:3456").await?;
axum::serve(
    listener,
    app.into_make_service_with_connect_info::<std::net::SocketAddr>(),
)
.await?;

Dies registriert die vier öffentlichen AWP-Endpunkte mit Versionsaushandlung, Ratenbegrenzung und einem Limit von 64 KiB A2A. Ohne einen AwpA2aHandler gibt POST /awp/a2a 503 zurück und bestätigt niemals Arbeit, die nicht weitergeleitet wurde. ConnectInfo stellt die Peer-Adresse bereit, die verwendet wird, um anonyme Ratenbegrenzungs-Buckets zu isolieren; ohne sie teilen unbekannte Aufrufer absichtlich einen Bucket.

Öffentliche AWP-Endpunkte

MethodePfadBeschreibung
GET/.well-known/awp.jsonDiscovery-Dokument — Einstiegspunkt für Agenten
GET/awp/manifestJSON-LD Fähigkeitsmanifest
GET/awp/healthGesundheitsstatus (Healthy/Degrading/Degraded)
POST/awp/a2aVon der Anwendung bereitgestellter A2A-Dispatch

Authentifizierte Verwaltungsendpunkte

awp_management_routes() gibt die Abonnementverwaltung separat und ohne Authentifizierungsschicht zurück. Wenden Sie die Auth-Middleware der Anwendung an, bevor Sie sie zusammenführen:

MethodePfadBeschreibung
POST/awp/events/subscribeWebhook-Abonnement erstellen
GET/awp/events/subscriptionsAlle Abonnements auflisten
DELETE/awp/events/subscriptions/{id}Ein Abonnement löschen

Erkennungsdokument

Das Erkennungsdokument unter /.well-known/awp.json wird automatisch aus deinem business.toml generiert:

{
  "version": { "major": 1, "minor": 0 },
  "siteName": "My Shop",
  "siteDescription": "An online store powered by AWP",
  "capabilityManifestUrl": "https://myshop.example.com/awp/manifest",
  "a2aEndpointUrl": "https://myshop.example.com/awp/a2a",
  "eventsEndpointUrl": "https://myshop.example.com/awp/events/subscribe",
  "healthEndpointUrl": "https://myshop.example.com/awp/health",
  "supportedTrustLevels": ["anonymous"]
}

Fähigkeitsmanifest

Das Manifest unter /awp/manifest verwendet das JSON-LD-Format:

{
  "@context": "https://schema.org",
  "@type": "WebAPI",
  "name": "My Shop",
  "description": "An online store powered by AWP",
  "capabilities": [
    {
      "name": "browse_products",
      "description": "Browse the product catalog",
      "endpoint": "/api/products",
      "method": "GET"
    }
  ]
}

Vertrauensstufen

AWP verwendet vier Vertrauensstufen mit zunehmendem Zugriff:

EbeneDiskriminanteWie zugewiesen
Anonymous0Keine Anmeldedaten
Known1Gültiger API-Schlüssel oder JWT
Partner2JWT mit partner-Bereich
Internal3JWT mit internal-Bereich

Vertrauensstufen sind geordnet: Anonymous < Known < Partner < Internal. Jede Fähigkeit in business.toml deklariert ihre minimale access_level.

DefaultTrustAssigner klassifiziert jede Anfrage als Anonymous. Ein Bearer- oder API-Key-Header wird erst dann als vertrauenswürdig angesehen, wenn ein Anwendungs-Verifizierer ihn validiert. Höhere Vertrauensstufen erfordern daher einen benutzerdefinierten Zuweiser.

Konfigurieren Sie .supported_trust_levels(...) zusammen mit diesem Zuweiser, damit die Erkennung nur Stufen bekannt gibt, die die Bereitstellung verifizieren kann.

Benutzerdefinierte Vertrauenszuweisung

Implementieren Sie das TrustLevelAssigner-Trait für benutzerdefinierte Logik:

use std::sync::Arc;

use adk_awp::TrustLevelAssigner;
use async_trait::async_trait;
use awp_types::TrustLevel;
use axum::http::{HeaderMap, header};

struct MyTrustAssigner {
    bearer_token: Arc<str>,
}

#[async_trait]
impl TrustLevelAssigner for MyTrustAssigner {
    async fn assign(&self, headers: &HeaderMap) -> TrustLevel {
        let expected = format!("Bearer {}", self.bearer_token);
        if headers
            .get(header::AUTHORIZATION)
            .and_then(|value| value.to_str().ok())
            .is_some_and(|value| value == expected)
        {
            TrustLevel::Known
        } else {
            TrustLevel::Anonymous
        }
    }
}

Verwenden Sie dieselbe verifizierte Identitäts- und Scope-Quelle wie der Rest der Anwendung, wenn Sie Partner oder Internal zuweisen.

Ratenbegrenzung

Die integrierte InMemoryRateLimiter verwendet einen Sliding-Window-Algorithmus mit Limits pro Vertrauensstufe:

VertrauensstufeStandardlimit
Anonym30 Anfragen/Minute
Bekannt120 Anfragen/Minute
Partner600 Anfragen/Minute
InternUnbegrenzt

Abgelehnte Anfragen erhalten HTTP 429 mit einem Retry-After-Header.

Benutzerdefinierte Limits

use std::collections::HashMap;
use awp_types::TrustLevel;
use adk_awp::{InMemoryRateLimiter, RateLimitConfig};

let mut limits = HashMap::new();
limits.insert(TrustLevel::Anonymous, RateLimitConfig {
    max_requests: 10,
    window_secs: 60,
});
limits.insert(TrustLevel::Known, RateLimitConfig {
    max_requests: 100,
    window_secs: 60,
});

let limiter = InMemoryRateLimiter::with_config(limits);

Versionsverhandlung

Alle AWP-Routen enthalten Middleware für die Versionsverhandlung:

  • Clients senden den AWP-Version: 1.1-Header (optional — standardmäßig die aktuelle Version)
  • Der Server prüft die Kompatibilität der Hauptversion
  • Kompatible Anfragen werden weitergeleitet; inkompatible Anfragen erhalten HTTP 406
  • Ungültige Versionswerte erhalten HTTP 400
  • Die Antwort enthält den AWP-Version: 1.0-Header

Ereignis-Abonnements

Die Verwaltung von Abonnements ist ein privilegierter Bereich. Mount awp_management_routes() hinter der Authentifizierung, bevor diese Anfragen akzeptiert werden. Die Callback-URLs muss absolut HTTPS URLs sein, und Signierungsgeheimnisse müssen mindestens 32 Bytes enthalten:

# Subscribe
curl -X POST http://localhost:3456/awp/events/subscribe \
  -H "Authorization: Bearer $AWP_ADMIN_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{
    "subscriber": "my-agent",
    "callbackUrl": "https://my-agent.example/webhook",
    "eventTypes": ["health.changed"],
    "secret": "replace-with-at-least-32-random-bytes"
  }'

# List subscriptions
curl -H "Authorization: Bearer $AWP_ADMIN_TOKEN" \
  http://localhost:3456/awp/events/subscriptions

InMemoryEventSubscriptionService signiert und protokolliert passende Zustellungen, führt aber keine Netzwerk-E/A aus. Produktionsanwendungen implementieren EventSubscriptionService mit Zielvalidierung, einer dauerhaften Warteschlange, begrenzten Wiederholungen und ihrem HTTP-Client.

Eine HTTP-Zustellungsimplementierung kann einen X-AWP-Signature-Header mit der HMAC-SHA256-Signatur tragen:

X-AWP-Signature: sha256=<hex_digest>

Überprüfen Sie Signaturen mit adk_awp::verify_signature(payload, secret, signature).

Zustandsmaschine für den Gesundheitsstatus

Der Gesundheitsendpunkt verfolgt den Dienststatus mit streng validierten Übergängen:

Healthy → Degrading → Degraded
    ↑         │           │
    └─────────┘           │
    └─────────────────────┘

Statusänderungen lösen health.changed-Ereignisse an alle passenden Abonnenten aus.

use adk_awp::HealthStateMachine;

// Transition to degrading
health.report_degrading("database latency high").await?;

// Transition to degraded
health.report_degraded("database unreachable").await?;

// Recover
health.report_healthy().await?;

Ungültige Übergänge (z. B. Healthy → Degraded) geben einen Fehler zurück.

AWP enthält eine Schnittstelle für die Einwilligungsspeicherung. Die Einhaltung gesetzlicher Vorgaben erfordert außerdem anwendungsspezifische Hinweise, rechtliche Grundlage, Aufbewahrung, Zugriffskontrollen und Löschrichtlinien; die Auswahl einer Speicherimplementierung begründet noch keine Compliance:

use adk_awp::InMemoryConsentService;

let consent = InMemoryConsentService::new();

// Capture consent
consent.capture_consent("visitor-123", "analytics").await?;

// Check consent
let has_consent = consent.check_consent("visitor-123", "analytics").await?;

// Revoke consent
consent.revoke_consent("visitor-123", "analytics").await?;

Erkennung des Anfragetypers

AWP erkennt, ob eine Anfrage von einem Menschen oder einem KI-Agenten stammt:

  1. X-AWP-Channel: agent-Header → Agent (explizite Überschreibung)
  2. Accept: application/json + Agent-User-Agent-Muster → Agent
  3. Andernfalls → Mensch

Agent-User-Agent-Muster: bot, crawler, spider, agent, gpt, claude, gemini, perplexity, anthropic, openai.

use adk_awp::detect_requester_type;
use axum::http::HeaderMap;

let mut headers = HeaderMap::new();
headers.insert("X-AWP-Channel", "agent".parse().unwrap());
let requester = detect_requester_type(&headers);
// RequesterType::Agent

AWP-Nachrichtentypen

Über generische A2A-Nachrichten hinaus definiert AWP kategorisierte Nachrichtentypen für das Agenten-Routing:

TypBeschreibung
VisitorIntentSignalKauf- oder Dienstleistungsabsicht
ContentGapSignalFehlender oder veralteter Inhalt erkannt
PaymentIntentNachricht zum Zahlungslebenszyklus
SupportEscalationWeiterleitung an den menschlichen Support
ReviewSignalBewertung oder Feedback von einer Plattform
OperationsProposalBestand, Terminplanungsvorschlag
InvokeCapabilityEine deklarierte Fähigkeit aufrufen
RenderUiUI-Rendering anfordern
OutboundTriggerProaktive ausgehende Nachricht
use awp_types::{AwpMessageType, AwpTypedMessage};

let msg = AwpTypedMessage {
    id: uuid::Uuid::now_v7(),
    sender: "visitor-agent".to_string(),
    recipient: "payment-agent".to_string(),
    awp_type: AwpMessageType::PaymentIntent,
    timestamp: chrono::Utc::now(),
    payload: serde_json::json!({"sku": "WIDGET-001", "amount": 2500}),
};

Zahlungsabsichten

AWP definiert einen vereinfachten Zahlungslebenszyklus für eigentümerrichtliniengesteuerte Zahlungen:

Draft → PendingApproval → Approved → Executing → Settled
                                                → Rejected
                                                → Cancelled

Das PaymentPolicy bewertet, ob automatisch genehmigt oder eine Genehmigung des Eigentümers erforderlich ist:

use awp_types::{PaymentPolicy, TrustLevel};

let policy = PaymentPolicy::default(); // $50 auto-approve, $500 require approval

let decision = policy.evaluate(2500, TrustLevel::Known);
// PaymentPolicyDecision::AutoApprove (amount $25 <= $50 threshold)

let decision = policy.evaluate(60_000, TrustLevel::Partner);
// PaymentPolicyDecision::RequireApproval (amount $600 > $500 threshold)

Schema von business.toml

Das vollständige Schema unterstützt eine umfangreiche Geschäftskonfiguration:

AbschnittFelderErforderlich
(Wurzel)site_name, site_description, domain, contactJa (außer Kontakt)
[business]name, country, languages, currency, timezoneNein
[brand_voice]tone, greeting, escalation_messageNein
[[products]]sku, name, price, inventory, tags, descriptionNein
[[capabilities]]name, description, endpoint, method, access_levelJa
[[policies]]name, description, policy_typeJa
[channels]whatsapp, email, website, smsNein
[payments]providers, auto_approve_threshold, require_approval_thresholdNein
[support]escalation_contacts, hours, slaNein
[content]topics, auto_draft, publish_delayNein
[reviews]platforms, auto_respond_thresholdNein
[outreach]follow_up_delay, require_consentNein

Alle erweiterten Abschnitte sind optional — vorhandene minimale business.toml-Dateien funktionieren weiterhin.

Heißes Neuladen

Das BusinessContextLoader unterstützt Hot-Reload über ArcSwap:

let loader = BusinessContextLoader::from_file("business.toml".as_ref())?;
loader.watch("business.toml".into()).await?;
// Changes to business.toml are picked up automatically every 5 seconds

Beispiel ausführen

Ein vollständiges AWP-Agentenbeispiel ist enthalten:

cd examples/awp_agent
cp .env.example .env   # add your GOOGLE_API_KEY
cargo run

Das Beispiel:

  1. Lädt business.toml mit Produkten, Richtlinien und Markenstimme
  2. Erstellt einen LLM-Agenten mit Anweisungen, die aus dem Geschäftskontext abgeleitet sind
  3. Installiert authentifizierte A2A-Weiterleitung zu diesem Agenten
  4. Bindet Verwaltungsrouten hinter einem separaten Demo-Anmeldedatensatz ein
  5. Führt jeden Endpunkt aus und gibt die Protokollverifizierung aus

Bewährte Vorgehensweisen

  1. Mit einem minimalen business.toml beginnen — nur site_name, site_description, domain, Fähigkeiten und Richtlinien sind erforderlich
  2. Fähigkeitsautorisierung erzwingenaccess_level sind Manifest-Metadaten; der Anwendungshandler muss sie durchsetzen
  3. Hot-Reload in der Produktion aktivieren — rufe loader.watch() für Konfigurationsaktualisierungen ohne Ausfallzeit auf
  4. Benutzerdefiniertes TrustLevelAssigner implementieren — die Standardeinstellung weist absichtlich nur Anonymous zu
  5. Verwaltungsrouten authentifizieren — Subscription-CRUD niemals von einem ungeschützten Router aus bereitstellen
  6. Echtes A2A-Dispatch installieren — die fail-closed-Standardeinstellung gibt 503 zurück
  7. Zuverlässige Ereigniszustellung verwenden — Zielrichtlinie, Warteschlangierung und begrenzte Wiederholungen implementieren
  8. Webhook-Signaturen überprüfenX-AWP-Signature auf eingehenden Webhooks validieren

Vorherig: ← A2A Protocol | Weiter: Bewertung →