Protocolo Web Agentic (AWP)
ADK-Rust fornece tipos do Agentic Web Protocol (AWP) e integração com Axum para tornar sites e serviços acessíveis a agentes de IA. A implementação abrange dois crates: awp-types (tipos de protocolo puros) e adk-awp (rotas, middleware e interfaces de serviço). As aplicações fornecem despacho de agentes, autenticação, autorização e entrega durável de webhooks.
Visão geral
AWP permite que qualquer site declare suas capacidades, políticas e contexto de negócios em um formato legível por máquina. Agentes de IA podem descobrir essas capacidades, negociar versões do protocolo, assinar eventos e interagir por meio de mensagens tipadas de A2A. adk-awp impõe limites de corpo e de taxa em sua fronteira de HTTP; os manipuladores da aplicação impõem autorização de identidade e de capacidade.
Use AWP quando:
- Você quiser que agentes de IA descubram e interajam com seu serviço programaticamente
- Você precisar de metadados de nível de confiança e de um ponto de integração para controle de acesso imposto pela aplicação
- Você quiser atender visitantes humanos e agentes de IA pelos mesmos endpoints
- Você precisar de assinaturas de eventos e primitivas de assinatura HMAC-SHA256
- Você quiser uma máquina de estados de saúde para monitoramento de serviço
Arquitetura
Fluxo de requisição de AWP
Layout da aplicação
┌─────────────────────────────────────────────────┐
│ 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) │ │
│ └────────────────────────────────┘ │
└─────────────────────────────────────────────────┘
Crates
| Crate | Finalidade | Dependências |
|---|---|---|
awp-types | Tipos de protocolo (enums, structs, erros) | Zero dependências de adk-* — apenas serde, uuid, chrono, thiserror |
adk-awp | Rotas, middleware, interfaces de serviço e implementações em memória | awp-types, adk-core, axum 0.8, tokio, dashmap |
A divisão significa que qualquer projeto Rust pode depender de awp-types sem puxar a árvore ADK.
Início rápido
1. Crie um 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. Carregue e sirva as rotas AWP
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?;
Isso registra os quatro endpoints públicos AWP com negociação de versão, limitação
de taxa e um limite de corpo de 64 KiB A2A. Sem um AwpA2aHandler,
POST /awp/a2a retorna 503 e nunca reconhece trabalho que não foi
disparado. ConnectInfo fornece o endereço do par usado para isolar buckets
anônimos de limitação de taxa; sem ele, chamadores desconhecidos compartilham
intencionalmente um único bucket.
Endpoints públicos AWP
| Método | Caminho | Descrição |
|---|---|---|
| GET | /.well-known/awp.json | Documento de descoberta — ponto de entrada para agentes |
| GET | /awp/manifest | manifesto de capacidade JSON-LD |
| GET | /awp/health | Estado de saúde (Saudável/Em degradação/Degradado) |
| POST | /awp/a2a | Dispatch fornecido pela aplicação de A2A |
Endpoints de gerenciamento autenticados
awp_management_routes() retorna o gerenciamento de assinatura separadamente e
sem uma camada de autenticação. Aplique o middleware de autenticação do aplicativo
antes de mesclá-lo:
| Método | Caminho | Descrição |
|---|---|---|
| POST | /awp/events/subscribe | Criar uma assinatura de webhook |
| GET | /awp/events/subscriptions | Listar todas as assinaturas |
| DELETE | /awp/events/subscriptions/{id} | Excluir uma assinatura |
Documento de descoberta
O documento de descoberta em /.well-known/awp.json é gerado automaticamente a partir do seu business.toml:
{
"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"]
}
Manifesto de capacidades
O manifesto em /awp/manifest usa o formato JSON-LD:
{
"@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"
}
]
}
Níveis de confiança
AWP usa quatro níveis de confiança com acesso crescente:
| Nível | Discriminante | Como é atribuído |
|---|---|---|
Anonymous | 0 | Sem credenciais |
Known | 1 | Chave API válida ou JWT |
Partner | 2 | JWT com escopo partner |
Internal | 3 | JWT com escopo internal |
Os níveis de confiança são ordenados: Anonymous < Known < Partner < Internal. Cada capability em business.toml declara seu access_level mínimo.
DefaultTrustAssigner classifica cada request como Anonymous. Um cabeçalho de portador ou API
key não é confiável até que um verificador da aplicação o valide. Níveis de
confiança mais altos, portanto, exigem um atribuidor personalizado.
Configure .supported_trust_levels(...) junto com esse atribuidor para que a descoberta
anuncie apenas os níveis que a implantação pode verificar.
Atribuição Personalizada de Confiança
Implemente a trait TrustLevelAssigner para lógica personalizada:
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
}
}
}
Use a mesma identidade verificada e a mesma fonte de escopo que o restante da aplicação
ao atribuir Partner ou Internal.
Limitação de Taxa
O InMemoryRateLimiter integrado usa um algoritmo de janela deslizante com limites por nível de confiança:
| Nível de Confiança | Limite Padrão |
|---|---|
| Anônimo | 30 requisições/minuto |
| Conhecido | 120 requisições/minuto |
| Parceiro | 600 solicitações/minuto |
| Interno | Ilimitado |
Requests rejeitadas recebem HTTP 429 com um cabeçalho Retry-After.
Limites Personalizados
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);
Negociação de Versão
Todos os caminhos AWP incluem middleware de negociação de versão:
- Os clientes enviam o cabeçalho
AWP-Version: 1.1(opcional — por padrão, usa a versão atual) - O servidor verifica a compatibilidade da versão principal
- Requests compatíveis prosseguem; requests incompatíveis recebem HTTP 406
- Valores de versão malformados recebem HTTP 400
- A resposta inclui o cabeçalho
AWP-Version: 1.0
Assinaturas de Eventos
O gerenciamento de assinaturas é uma superfície privilegiada. Monte
awp_management_routes() atrás de autenticação antes de aceitar esses
requests. O callback URLs deve ser um HTTPS URLs absoluto e os segredos de assinatura devem
conter pelo menos 32 bytes:
# 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 assina e registra as entregas correspondentes, mas
não realiza I/O de rede. Aplicações em produção implementam
EventSubscriptionService com validação de destino, uma fila durável,
tentativas limitadas e seu cliente HTTP.
Uma implementação de entrega HTTP pode carregar um cabeçalho X-AWP-Signature com a
assinatura HMAC-SHA256:
X-AWP-Signature: sha256=<hex_digest>
Verifique as assinaturas com adk_awp::verify_signature(payload, secret, signature).
Máquina de Estados de Saúde
O endpoint de saúde acompanha o estado do serviço com transições estritamente validadas:
Healthy → Degrading → Degraded
↑ │ │
└─────────┘ │
└─────────────────────┘
As mudanças de estado emitem eventos health.changed para todos os assinantes correspondentes.
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?;
Transições inválidas (por exemplo, Healthy → Degraded) retornam um erro.
Armazenamento de Consentimento
AWP inclui uma interface de armazenamento de consentimento. A conformidade regulatória também exige aviso específico da aplicação, base legal, retenção, controles de acesso e política de exclusão; selecionar uma implementação de armazenamento não estabelece conformidade:
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?;
Detecção do Tipo de Solicitante
AWP detecta se uma request vem de um humano ou de um agente de IA:
- Cabeçalho
X-AWP-Channel: agent→ Agent (substituição explícita) Accept: application/json+ padrão de User-Agent de agent → Agent- Caso contrário → Human
Padrões de User-Agent de Agent: 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
Tipos de Mensagem AWP
Além das mensagens genéricas A2A, AWP define categorias de mensagens tipadas para roteamento de agent:
| Tipo | Descrição |
|---|---|
VisitorIntentSignal | Intenção de compra ou serviço |
ContentGapSignal | Conteúdo ausente ou desatualizado detectado |
PaymentIntent | Mensagem do ciclo de pagamento |
SupportEscalation | Escalonamento para suporte humano |
ReviewSignal | Avaliação ou feedback de uma plataforma |
OperationsProposal | Inventário, proposta de agendamento |
InvokeCapability | Invocar uma capacidade declarada |
RenderUi | Solicitar renderização de UI |
OutboundTrigger | Mensagem de saída proativa |
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}),
};
Intenções de Pagamento
AWP define um ciclo de vida simplificado de pagamento para pagamentos orientados por política do proprietário:
Draft → PendingApproval → Approved → Executing → Settled
→ Rejected
→ Cancelled
A PaymentPolicy avalia se deve aprovar automaticamente ou exigir aprovação do proprietário:
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)
Esquema de business.toml
O esquema completo oferece suporte a uma rica configuração de negócios:
| Seção | Campos | Obrigatório |
|---|---|---|
| (raiz) | site_name, site_description, domain, contact | Sim (exceto contato) |
[business] | name, country, languages, currency, timezone | Não |
[brand_voice] | tone, greeting, escalation_message | Não |
[[products]] | sku, name, price, inventory, tags, description | Não |
[[capabilities]] | name, description, endpoint, method, access_level | Sim |
[[policies]] | name, description, policy_type | Sim |
[channels] | whatsapp, email, website, sms | Não |
[payments] | providers, auto_approve_threshold, require_approval_threshold | Não |
[support] | escalation_contacts, hours, sla | Não |
[content] | topics, auto_draft, publish_delay | Não |
[reviews] | platforms, auto_respond_threshold | Não |
[outreach] | follow_up_delay, require_consent | Não |
Todas as seções estendidas são opcionais — arquivos business.toml mínimos existentes continuam funcionando.
Recarregamento a quente
O BusinessContextLoader suporta recarregamento a quente via 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
Executando o Exemplo
Um exemplo completo de agente AWP está incluído:
cd examples/awp_agent
cp .env.example .env # add your GOOGLE_API_KEY
cargo run
O exemplo:
- Carrega
business.tomlcom produtos, políticas e voz da marca - Cria um agente LLM com instruções derivadas do contexto de negócios
- Instala despacho autenticado A2A para esse agente
- Monta rotas de gerenciamento atrás de uma credencial de demonstração separada
- Exercita cada endpoint e imprime a verificação do protocolo
Boas Práticas
- Comece com um
business.tomlmínimo — apenassite_name,site_description,domain, capacidades e políticas são necessários - Imponha autorização de capacidade —
access_levelé metadado do manifesto; o manipulador da aplicação deve impô-la - Habilite o recarregamento a quente em produção — chame
loader.watch()para atualizações de configuração sem tempo de inatividade - Implemente
TrustLevelAssignerpersonalizado — o padrão, intencionalmente, atribui apenasAnonymous - Autentique rotas de gerenciamento — nunca exponha assinaturas CRUD a partir de um router desprotegido
- Instale despacho A2A real — o padrão fail-closed retorna
503 - Use entrega durável de eventos — implemente política de destino, enfileiramento e tentativas limitadas
- Verifique assinaturas de webhook — valide
X-AWP-Signatureem webhooks de entrada
Relacionado
- Protocolo A2A — Comunicação entre agentes (complementar ao AWP)
- Implantação do Servidor — Executando agentes como servidores HTTP
- Controle de Acesso — Permissões baseadas em função
Anterior: ← Protocolo A2A | Próximo: Avaliação →