Action groups with hand-written OpenAPI that drifts from Lambda handlers create phantom tools — agents “succeed” at calling operations that 500 in production. The unfair advantage is schema-from-code (or code-from-schema) plus a deploy gate that fails when handler signatures and OpenAPI disagree.
⚡ TL;DR: Generate OpenAPI from Zod/TypeBox handler contracts (or vice versa); CI diffs schema vs last release; Bedrock action group publishes only on match. Reject manually edited YAML that isn’t in the codegen path. Pair with Bedrock Agents idempotent tools and Spec-First AI Development.
One contract, two projections
// tools/createBooking.ts
import { z } from "zod";
export const CreateBookingInput = z.object({
idempotencyKey: z.string().uuid(),
listingId: z.string().min(1),
nights: z.number().int().min(1).max(30),
});
export type CreateBookingInput = z.infer<typeof CreateBookingInput>;
export async function handler(event: unknown) {
const input = CreateBookingInput.parse(event); // ✅ runtime == schema
return createBooking(input);
}
# ✅ Generate OpenAPI fragment for the action group
pnpm tsx scripts/zod-to-openapi.ts tools/*.ts > action-groups/bookings.openapi.json
❌ Hand-maintaining bookings.openapi.json in the console while handlers evolve in git.
Deploy gate: schema ↔ handler
// ci/assert-action-group-sync.ts
import { CreateBookingInput } from "../tools/createBooking";
import openapi from "../action-groups/bookings.openapi.json";
function schemasEqual(a: unknown, b: unknown) {
return JSON.stringify(a) === JSON.stringify(b);
}
const fromZod = zodToJsonSchema(CreateBookingInput);
const fromOas = openapi.paths["/createBooking"].post.requestBody.content["application/json"].schema;
if (!schemasEqual(fromZod, fromOas)) {
throw new Error("action_group_schema_drift"); // ✅ fail deploy
}
Wire Bedrock without phantom ops
{
"actionGroupName": "bookings",
"apiSchema": { "payload": "<openapi>" },
"actionGroupExecutor": { "lambda": "arn:aws:lambda:...:function:createBooking" },
"functionSchema": null
}
| Check | Fail means |
|---|---|
| Zod parse in Lambda | Bad agent args |
| CI schema sync | Drift before agents see it |
| Idempotency key required | Duplicate bookings |
| Auth context forwarded | Confused deputy |
Reuse DynamoDB conditional writes from Bedrock Agents idempotent tool calls. For GraphQL edges, same idea as AppSync With Bedrock.
Closing checklist
✅ Dos
– ✅ Single source of truth (Zod/TypeBox or OpenAPI)
– ✅ Codegen the other artifact
– ✅ Fail deploy on drift
– ✅ Require idempotency on writes
– ✅ Version action groups with agent aliases
❌ Don’ts
– ❌ Don’t edit OpenAPI only in the Bedrock console
– ❌ Don’t expose debug/admin ops to agents
– ❌ Don’t skip runtime parse in the Lambda
– ❌ Don’t share one fat Lambda for unrelated tools without contracts
– ❌ Don’t omit auth context propagation
Related reading
- Bedrock Agents: Idempotent Tool Calls Against DynamoDB Writes
- Spec-First AI Development: OpenAPI Remains the Only Source of Truth
- AppSync With Bedrock: GraphQL Resolvers That Call Tools Safely
- Bedrock Converse API: Tool Choice Modes for Production Coding Agents
Last updated on September 11, 2026
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.
