Skip to content
Open
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
22 changes: 22 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,28 @@ LLM_MAX_TOKENS=4096
# MANAGER_LLM_MODEL=bedrock/us.anthropic.claude-opus-4-8
# Note: Opus 4.7+ Bedrock IDs are undated (us.anthropic.claude-opus-4-8); older models keep the dated -v1:0 profile form. See https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-anthropic-claude-opus-4-8.html

# Provider: AWS Bedrock via the Anthropic Messages endpoint (RECOMMENDED for
# Bedrock). Routes Claude through CrewAI's native Anthropic provider against
# Bedrock's /anthropic route, so the Converse toolUse/toolResult sanitizer
# (patches/crewai_bedrock_fix.py) is NOT needed. Only current-generation Claude
# tiers are served on the Messages API (Sonnet 5, Opus 4.7/4.8, Haiku 4.5);
# Sonnet 4.5 is Converse-only. Model ids are the Bedrock model / inference-
# profile ids (no provider prefix).
# ANTHROPIC_COMPATIBLE_LLM_API_BASE_URL=https://bedrock-runtime.us-west-2.amazonaws.com/anthropic
# ANTHROPIC_COMPATIBLE_LLM_API_KEY=your-bedrock-api-key
# DEFAULT_LLM_MODEL=us.anthropic.claude-sonnet-5
# MANAGER_LLM_MODEL=us.anthropic.claude-sonnet-5
# MEMORY_LLM_MODEL=us.anthropic.claude-haiku-4-5-20251001-v1:0
#
# AWS credentials for the deploy CLI (dev account). deploy.sh sources this .env,
# so either an AWS_PROFILE that is logged in, or explicit keys, work:
# AWS_PROFILE=genai
# -- or --
# AWS_ACCESS_KEY_ID=...
# AWS_SECRET_ACCESS_KEY=...
# AWS_SESSION_TOKEN=...
# AWS_REGION=us-west-2

# Provider: Custom OpenAI-compatible endpoint (alternative)
# For endpoints with no CrewAI native provider prefix of their own — NVIDIA
# NIM, Ollama, HuggingFace TGI, vLLM, LM Studio, etc. Set DEFAULT_LLM_MODEL to
Expand Down
7 changes: 7 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,10 @@ Dockerfile

# Internal issue tracker (not published)
.beads/

# Kiro specs/steering are managed OUTSIDE the repo (internal assets) — never commit.
.kiro/

# Generated CloudFormation package output (account-specific S3 URLs + hashes,
# regenerated every deploy) — never commit.
.packaged-agentcore.yaml
60 changes: 58 additions & 2 deletions docs/api/seller-discovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -349,9 +349,65 @@ All errors are logged at `WARNING` or `DEBUG` level. The client never raises `ht
!!! warning "Silent Failures"
Because the client returns empty results on failure, always check the length of discovery results. An empty list could mean "no sellers match" or "the registry is down." Monitor logs for `WARNING`-level messages to distinguish the two.

## Related
## Connecting to a Seller over OAuth (AgentCore)

When a discovered seller runs on Amazon Bedrock AgentCore behind a CUSTOM_JWT
authorizer, the registry record's `endpoint_url` is the runtime's OAuth HTTPS
invocations URL (or its ARN) and its `authentication` block advertises the
shared token endpoint + scope. The buyer authenticates with a short-lived
`client_credentials` bearer token — **not** SigV4/boto3, which OAuth runtimes
reject.

### Minting the token: `OAuthTokenProvider`

`ad_buyer.auth.oauth_token_provider.OAuthTokenProvider` reads the token endpoint
+ scope from the discovered record, holds the buyer's OWN `client_id`/`secret`
from settings (`BUYER_OAUTH_CLIENT_ID` / `BUYER_OAUTH_CLIENT_SECRET` — never
taken from the registry, never logged), and POSTs `client_credentials` over HTTP
Basic. Tokens are cached by `(token_endpoint, scope)` and refreshed proactively
before `expires_in` and reactively on a 401.

### Transport selection: `select_seller_client`

`ad_buyer.registry.transport_selector.select_seller_client(endpoint, authentication=..., region=..., token_provider=...)`
picks the right client from the discovered record:

| `endpoint_url` | Advertised OAuth? | Client |
|----------------|-------------------|--------|
| `arn:...` | No | `AgentCoreSellerProxy` (SigV4, same-account/dev) |
| `arn:...` | Yes | JWT client — derives the HTTPS invocations URL from the ARN, Bearer over HTTPS |
| `https://...` | Yes | JWT client on that URL |
| `https://...` | No | plain unauthenticated `IABMCPClient` |

The JWT path returns an `AgentCoreSellerProxy` in **JWT mode**: it preserves the
OpenDirect method surface (`list_products`/`search_products`/`get_product`/
`check_avails`) that `DealBookingFlow` expects, but frames MCP over a raw HTTPS
POST with a `Authorization: Bearer <jwt>` header (with 401 re-mint) instead of
SigV4.

**Fail-closed:** if the seller advertises OAuth but the buyer has no client
credentials configured, `select_seller_client` raises rather than silently
connecting unauthenticated.

```python
from ad_buyer.registry import RegistryClient
from ad_buyer.registry.transport_selector import select_seller_client

registry = RegistryClient(registry_url="https://registry.aamp.iab.com/agent-registry")
record = (await registry.discover_sellers(["ctv"]))[0]

- [Seller Agent Discovery](https://iabtechlab.github.io/seller-agent/api/agent-discovery/) — How seller agents register and expose their agent cards
# The discovered record carries endpoint_url + authentication (token endpoint + scope).
client = select_seller_client(
record.url,
authentication=getattr(record, "authentication", None),
)
# client now speaks the OpenDirect surface; the JWT is minted + refreshed under the hood.
```

`crew_tools.py` wires this automatically, routing on the discovered
`SELLER_TOKEN_ENDPOINT`.

## Related
- [Media Kit Discovery](media-kit.md) — Browse seller inventory after discovering sellers
- [Authentication](authentication.md) — API key setup for accessing seller endpoints
- [A2A Client](a2a-client.md) — Agent-to-agent communication protocol
Expand Down
43 changes: 43 additions & 0 deletions docs/architecture/storage-backends.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,49 @@ checks against the configured database.
file (e.g. `DesiredCount: 1` on ECS). Running multiple instances against the same
file — including a shared network file system — risks corruption.

## AgentCore deployment: SQLite in-memory by default, durable Postgres opt-in

When the buyer runs as an AgentCore runtime, `infra/aws/agentcore/deploy.sh`
defaults to `STORAGE_TYPE=sqlite` with an **in-memory** `DATABASE_URL`
(`sqlite:///:memory:`), in PUBLIC network mode. State written by the stores
above therefore does **not** survive a container recycle in that default
deployment. This is the right default for same-account/dev and for the agentic
planning/booking path, where the buyer's own records (deals/orders/negotiations)
are re-derivable from the seller of record and the campaign plan.

### Durable Postgres (opt-in): `--storage postgres`

For enterprise/durable deployments the buyer now has a real backend seam and a
`--storage postgres` path (mirrors the seller's group-6 work):

- **Aurora Serverless v2** (`aurora-postgresql` 16.9, `db.serverless`) deployed
beside Redis in `infra/aws/cloudformation/storage.yaml`, with
`ServerlessV2ScalingConfiguration.MinCapacity: 0` (**scale-to-zero**, bounded
max) so it costs nothing when idle.
- **RDS-managed credentials** — `ManageMasterUserPassword: true` puts the DB
password in an auto-rotating Secrets Manager secret. No plaintext password is
ever in CFN, env, or logs; the runtime reads the secret **by ARN** at startup
(`storage/db_secret.py`) and assembles the connection string in-process.
- **CUSTOMER_VPC mode** — `network-agentcore.yaml` + `main-agentcore.yaml`
provide the VPC interface endpoints (`bedrock-agentcore`/`bedrock-runtime`,
`sts`, `secretsmanager`, ECR, Logs) and the **443 self-ingress** on the
runtime SG required for a private-subnet runtime to reach Aurora and those
endpoints (same reachability lesson as the seller).
- **The backend seam** — `storage/connection_factory.py` selects on
`STORAGE_TYPE`; `storage/pg_connection.py` adapts the sqlite3 API to
psycopg, and `storage/schema_pg.py` derives Postgres DDL from the shared
schema. The 6 connection-owning stores route through the factory; the injected
sub-stores follow for free. `STORAGE_TYPE=hybrid` on the deployed runtime
puts KV state in Aurora with Redis alongside.

The entrypoint uses `setdefault` semantics so a deploy-supplied `hybrid`/Postgres
value wins while the dev default stays SQLite. As with the seller, product data
is independent of the KV backend.

If you need durable buyer state and do not want the Aurora path, you can still
run the buyer on ECS with a persistent `DATABASE_URL` (single writer — see the
warning above).

## Related

- [Deal Store](deal-store.md) — full schema and API reference for the primary store
Expand Down
198 changes: 198 additions & 0 deletions infra/aws/agentcore/auth-agentcore.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
AWSTemplateFormatVersion: "2010-09-09"
Description: >
Auth stack for the AAMP buyer AgentCore deployment. Houses the IAM
execution role the buyer runtime assumes, including a SCOPED grant to
invoke seller AgentCore runtimes for buyer->seller (agent-to-agent) calls.
This "auth" stack is the home for auth-related infra (IAM today; Cognito +
gateway authorizer for the cross-org path live alongside it — see NOTE).

# =============================================================================
# NOTE — SECURITY MODEL / WHEN TO USE THIS
# =============================================================================
# The SellerInvoke permission below lets the buyer runtime call the seller's
# AgentCore runtimes directly via bedrock-agentcore:InvokeAgentRuntime, gated
# by AWS IAM. That is appropriate ONLY for:
#
# * Same-account testing — buyer and seller deployed in one AWS account
# (the demo / integration-test setup), OR
# * Customers whose security standards explicitly ALLOW cross-account IAM
# access between the buyer's and seller's accounts (you would then extend
# the resource ARNs / add a cross-account role trust accordingly).
#
# It is NOT the recommended pattern for CROSS-ORGANIZATION buyer<->seller
# interactions (different companies, no shared IAM trust). For that, front the
# seller runtimes with an **AgentCore Gateway + CUSTOM_JWT authorizer backed by
# Cognito** (or the customer's OIDC IdP): the external buyer authenticates with
# a short-lived OAuth2 JWT bearer token at the gateway edge — no IAM in the
# seller's account required. See the `enterprise-auth-gateway` spec.
#
# In short: IAM (this stack) = same-account / trusted cross-account.
# Gateway + CUSTOM_JWT/Cognito = cross-org, zero shared IAM.
# =============================================================================

# =============================================================================
# Parameters
# =============================================================================
Parameters:
Environment:
Type: String
Default: staging
AllowedValues: [staging, production]
SellerRuntimeNamePrefix:
Type: String
Default: "a4a_aamp_seller_"
Description: >
Name prefix of the seller AgentCore runtimes this buyer may invoke. The
SellerInvoke policy wildcards on runtime/<prefix>* so the buyer can reach
the seller's http/mcp/a2a runtimes without hardcoding the CLI-assigned
ARN suffixes (which differ per deploy). Keep this as tight as your naming
allows — it is scoped to this account + region + the seller name prefix,
NOT a blanket bedrock-agentcore:* on all runtimes. Match your seller
deploy --name base (e.g. a4a_aamp_seller_ for a4a_aamp_seller_*_mcp).
SellerAccountId:
Type: String
Default: ""
Description: >
Optional. AWS account id that hosts the seller runtimes. Defaults to THIS
account (same-account model). Set this ONLY for a trusted cross-account
setup your security standards permit; the SellerInvoke resource ARNs then
point at that account. (Cross-ORG should use the gateway path instead.)

# =============================================================================
# Conditions
# =============================================================================
Conditions:
# Use this account for the seller ARNs unless a (trusted) cross-account id is given.
UseThisAccountForSeller: !Equals [!Ref SellerAccountId, ""]

# =============================================================================
# Resources
# =============================================================================
Resources:

# ---------------------------------------------------------------------------
# Buyer AgentCore runtime execution role
#
# Provisioned here (declarative, least-privilege) instead of letting the
# agentcore CLI auto-create a role, so the seller-invoke grant is owned by
# IaC. Pass this role to `agentcore configure --execution-role <arn>`.
# ---------------------------------------------------------------------------
BuyerRuntimeExecutionRole:
Type: AWS::IAM::Role
Properties:
RoleName: !Sub "aamp-buyer-${Environment}-runtime-role"
AssumeRolePolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Principal:
Service: bedrock-agentcore.amazonaws.com
Action: sts:AssumeRole
Policies:
# -- Standard AgentCore runtime execution permissions --
# Mirrors the policy the `agentcore` CLI auto-creates for a runtime
# (ECR image pull, CloudWatch Logs + log-delivery, X-Ray tracing,
# CloudWatch metrics, and AgentCore memory/workload access), so a
# CFN-owned role is a drop-in for the CLI-created one.
- PolicyName: AgentCoreRuntimeExecution
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: EcrImagePull
Effect: Allow
Action:
- ecr:BatchGetImage
- ecr:GetDownloadUrlForLayer
- ecr:BatchCheckLayerAvailability
Resource: !Sub "arn:aws:ecr:${AWS::Region}:${AWS::AccountId}:repository/bedrock-agentcore-*"
- Sid: EcrAuth
Effect: Allow
Action: ecr:GetAuthorizationToken
Resource: "*"
- Sid: Logs
Effect: Allow
Action:
- logs:CreateLogGroup
- logs:CreateLogStream
- logs:PutLogEvents
- logs:DescribeLogStreams
- logs:DescribeLogGroups
Resource: !Sub "arn:aws:logs:${AWS::Region}:${AWS::AccountId}:log-group:/aws/bedrock-agentcore/runtimes/*"
- Sid: XRayTracing
Effect: Allow
Action:
- xray:PutTraceSegments
- xray:PutTelemetryRecords
- xray:GetSamplingRules
- xray:GetSamplingTargets
Resource: "*"
- Sid: CloudWatchMetrics
Effect: Allow
Action: cloudwatch:PutMetricData
Resource: "*"
- Sid: AgentCoreMemory
Effect: Allow
Action:
- bedrock-agentcore:CreateEvent
- bedrock-agentcore:GetEvent
- bedrock-agentcore:GetMemory
- bedrock-agentcore:GetMemoryRecord
- bedrock-agentcore:ListActors
- bedrock-agentcore:ListEvents
- bedrock-agentcore:ListMemoryRecords
- bedrock-agentcore:ListSessions
Resource: "*"
# -- Bedrock model access (Claude via the Anthropic Messages endpoint) --
- PolicyName: BedrockInvoke
PolicyDocument:
Version: "2012-10-17"
Statement:
- Effect: Allow
Action:
- bedrock:InvokeModel
- bedrock:InvokeModelWithResponseStream
# Req 11: the runtime mints a short-lived Bedrock bearer token
# from THIS role at startup for the Anthropic Messages path
# (x-api-key), which is authenticated via CallWithBearerToken
# rather than SigV4 InvokeModel.
- bedrock:CallWithBearerToken
Resource: "*"
# -- SCOPED seller invoke (see NOTE at top of this file) --
- PolicyName: SellerInvoke
PolicyDocument:
Version: "2012-10-17"
Statement:
- Sid: InvokeSellerRuntimes
Effect: Allow
Action: bedrock-agentcore:InvokeAgentRuntime
# Wildcard on the seller name prefix + its endpoint children,
# scoped to a single account + region. Not bedrock-agentcore:*.
Resource:
- !If
- UseThisAccountForSeller
- !Sub "arn:aws:bedrock-agentcore:${AWS::Region}:${AWS::AccountId}:runtime/${SellerRuntimeNamePrefix}*"
- !Sub "arn:aws:bedrock-agentcore:${AWS::Region}:${SellerAccountId}:runtime/${SellerRuntimeNamePrefix}*"
- !If
- UseThisAccountForSeller
- !Sub "arn:aws:bedrock-agentcore:${AWS::Region}:${AWS::AccountId}:runtime/${SellerRuntimeNamePrefix}*/*"
- !Sub "arn:aws:bedrock-agentcore:${AWS::Region}:${SellerAccountId}:runtime/${SellerRuntimeNamePrefix}*/*"
Tags:
- Key: Name
Value: !Sub "aamp-buyer-${Environment}-runtime-role"
- Key: Project
Value: ad-buyer-system
- Key: Environment
Value: !Ref Environment

# =============================================================================
# Outputs
# =============================================================================
Outputs:
BuyerRuntimeExecutionRoleArn:
Description: >
Execution role ARN for the buyer AgentCore runtime. Pass to
`agentcore configure --execution-role <arn>` so the runtime can invoke
the seller runtimes (same-account / trusted cross-account only).
Value: !GetAtt BuyerRuntimeExecutionRole.Arn
Export:
Name: !Sub "${AWS::StackName}-BuyerRuntimeExecutionRoleArn"
Loading
Loading