We wanted an AI agent that composes email, schedules meetings, and drafts Jira tickets using the same code paths as the web UI — same Zod validation, same name resolution, same provider defaults. Bedrock Agents run in AWS. Our business logic runs in Next.js. Something has to bridge them.
Action group Lambdas are thin HTTP clients. They POST to internal routes on our deployed app with a shared secret header and the user's email from Bedrock session attributes. No duplicate compose logic in Lambda. This post is that architecture.
Why the agent is not in Next.js
Bedrock Agents provide session memory, tool orchestration, and trace output natively. Running the full agent loop inside a serverless Next.js route would mean reimplementing orchestration, managing long-running streams, and coupling deploy cycles to model changes.
Lambdas handle action group invocation — Bedrock calls Lambda with OpenAPI-shaped events; Lambda calls us back. The Next.js app remains the source of truth for permissions, Prisma, and OAuth tokens.
Lambda as HTTP bridge
// action-utils.mjs
export async function callInternalApi(path, body) {
const response = await fetch(`${CHRONOFLOW_INTERNAL_BASE_URL}${path}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'x-chronoflow-agent-secret': INTERNAL_AGENT_SECRET,
},
body: JSON.stringify(body),
})
return response.json()
}Gmail actions call /api/internal/ai/compose-email or reply routes. Meeting actions call meeting prep. Jira actions call ticket draft. Each internal route verifies the secret via verifyInternalAgentRequest before running the same handlers the UI would trigger.
extractInput normalises Bedrock event shape variance — sometimes JSON body, sometimes OpenAPI property arrays. Zod schemas run after normalisation.
Shared secret and user identity
INTERNAL_AGENT_SECRET must match exactly between Lambda env and the web app. Mismatch → 401 on every tool → agent apologises generically while logs show auth failure.
userEmail comes from sessionAttributes or promptSessionAttributes set when the web app invokes the agent. Compose and meeting prep use it for OAuth token lookup and attendee resolution. Missing email → tools fail closed.
CHRONOFLOW_INTERNAL_BASE_URL must be publicly reachable from Lambda — localhost does not work in production action groups.Agent version pinning
Bedrock aliases pin to the first published agent version unless you force republish. We hash instructions, OpenAPI schemas, and model ID into the alias description so CDK deploys bump the version when prompts change. Without this, prompt fixes never reach prod.
Adding a new tool
The checklist is deliberate: extend OpenAPI in CDK, implement Lambda handler calling internal API, add Zod schema on the route, register mapper in agent-tool-mappers.ts, add UI card in the chat drawer. Skipping the mapper means trace extraction succeeds but nothing renders.
Next: trace deduplication · multi-tool requests. Join the Kvika beta.