AWS Lambda integration
No Lambda-specific adapter exists, and none is needed — guard_tool(), the same decorator the MCP integration uses, already works unmodified on a raw (event, context) handler.
Install
pip install agf-sdk
No extra needed — guard_tool ships in the core package. Requires agf-sdk ≥ 0.7.0 (see below).
Set your API key
export AGF_API_KEY=agfk_your_key_here
Generate a key from Settings → API Keys.
Guard your handler
Lambda calls a handler as a plain, synchronous, positional function — never async. guard_tool()is fully generic over its wrapped function's arguments, so it applies with no adaptation:
import os
from agf.client import AGFClient
from agf.keys import build_self_signed_chain
from agf.mcp import guard_tool
AGF_AGENT_ID = "did:agf:my-lambda-fn"
# Module-level — reused across warm-container invocations.
client = AGFClient(api_key=os.environ["AGF_API_KEY"])
def _chain_provider(event, context):
# Built fresh per call — a chain built once at cold-start expires in
# 5 minutes, and a warm container can sit idle past that.
return build_self_signed_chain(AGF_PRIVATE_KEY_PEM, AGF_AGENT_ID, "lambda:issue_refund", "agf")
@guard_tool(
client,
agent_id=AGF_AGENT_ID,
action_type="lambda:issue_refund",
chain_provider=_chain_provider,
validate_execution=True,
report_outcome=True,
)
def handler(event, context):
order_id = event["order_id"]
amount = event["amount"]
result = process_refund(order_id, amount)
return {"statusCode": 200, "body": result}process_refund stands in for your own application function. AGF_PRIVATE_KEY_PEM is the agent's private key: a PEM-encoded EC P-256 key created once with generate_keypair(), whose public half is registered for the agent. The example assumes the key is loaded into a variable of that name (for example from an environment variable). How you store and load it is part of your deployment. Create the keypair once and reuse it: a new keypair at every start signs chains with a key the runtime has not registered, which is answered with a DENY.
What the guard decides
In the example above the guard is created for lambda:issue_refund. The decision uses that action type, the resource (the function's name, handler here, unless you pass resource=), the delegation chain, and the agent id as audit context. By default it does not read event, so it does not by itself authorize the amount the handler reads from the event. See the next section to opt in.
If the decision is DENY or REVIEW_REQUIRED, AGFDeniedError or AGFReviewRequiredErroris raised out of the handler before your code runs. What Lambda does with an uncaught exception depends on your function's error handling.
Including call arguments in the decision (agf-sdk ≥ 0.11.0)
To make the decision depend on selected fields of the event, pass an opt-in context_provider and a required context_allowlist. The provider is called with the same (event, context) the handler receives and returns the fields to send as context.
@guard_tool(
client,
agent_id=AGF_AGENT_ID,
action_type="lambda:issue_refund",
chain_provider=_chain_provider,
# agf-sdk >= 0.11.0. The provider gets the handler's own arguments.
context_provider=lambda event, context: {
"amount": event["amount"], # send a decimal string such as "40.00"
"currency": event["currency"], # an ISO 4217 code
},
context_allowlist=["amount", "currency"],
)
def handler(event, context):
...- The amount is declared by your handler's input, not verified by the SDK or AXIS. The runtime accepts a non-negative integer or a decimal string with a currency code; a float, such as a JSON number like 12.5, is treated as malformed.
- The decision changes with a field only if your active policy reads it.
- The context is included in the signed decision artifact, and the runtime may add its own fields. Send only what the policy needs, never secrets or personal data.
- If the provider raises (for example a missing key) or returns a key outside the allowlist,
AGFContextErroris raised before any decision request and your handler does not run. validate_executiondoes not re-evaluate the event.
Warm containers need agf-sdk ≥ 0.7.0
A warm Lambda container reuses the same module-level client across many invocations. Before agf-sdk 0.7.0, a shared client could fail on its second synchronous guarded call (RuntimeError: Event loop is closed), which is what a warm container's second invocation does. 0.7.0 fixed the bridge guard_tool() uses. Pin agf-sdk>=0.7.0 in your Lambda deployment package. The full root-cause explanation is in the 0.7.0 release post.
If your handler already has other decorators
Common with observability toolkits like AWS Lambda Powertools. Apply guard_tool() innermost — closest to def handler:
# guard_tool composes with any other decorators your handler already
# has — place it innermost, right above def handler, so your existing
# observability decorators (logging, tracing, metrics) wrap AXIS's own
# call too:
@init_environment_variables(model=HandlerEnvVars)
@logger.inject_lambda_context(...)
@metrics.log_metrics
@tracer.capture_lambda_handler(capture_response=False)
@guard_tool(client, agent_id=AGF_AGENT_ID, action_type="lambda:issue_refund",
chain_provider=_chain_provider, validate_execution=True, report_outcome=True)
def handler(event, context):
...In our test with AWS Lambda Powertools 3.38.0 (the Logger decorator, and the Tracer decorator with a recording provider standing in for X-Ray), this puts the guard's policy call inside those decorators, so anything it logs carries the Lambda context fields, and a denial raised by the guard reaches the Tracer decorator, which records the error. With the guard outermost, neither is true. We did not test a live X-Ray daemon, or how the Metrics decorator handles a raised exception.
Related
MCP Gateway
The guard_tool() decorator this profile reuses unmodified
Quick start
End-to-end walkthrough from registration to first decision
API reference
Full REST API — /v1/decide, webhooks, JIT credentials
Blog: AWS Lambda authorization
What changes when an agent runs in Lambda: reused containers, short-lived delegation chains, and what a handler guard decides

