Iceland's national career guidance platform used a static questionnaire. We replaced it with something smarter.
A three-tier architecture connecting the Next.js frontend to AI agents running on AWS Bedrock AgentCore.
Two specialized AI agents work together using Strands Swarm orchestration to guide users from intake to personalized recommendations.
Detects the user's situation and asks structured questions one at a time before handing off.
{ route, language, answers }
Uses 20+ API tools to search Iceland's education/career database and generate personalized recommendations.
A complete walkthrough of the user's journey through the system.
The app loads with an initial greeting in Icelandic and three starter suggestion buttons.
The agent automatically detects which of three routes applies based on keywords in the user's first message.
The agent asks questions one at a time, using interactive option buttons for predefined choices.
After all questions are answered, the agent presents a summary and waits for the user's confirmation.
The LeadingQuestionsAgent hands off structured data to the VegvisirAgent via the Swarm protocol.
{ route: "route1", language: "is", answers: { ... } }
The VegvisirAgent searches the database with 20+ tools and presents tailored education/career recommendations with real links.
Built with modern tools across the full stack — from AI frameworks to cloud infrastructure.
Agent backend logic and API tools
BackendReact 19 frontend with App Router
FrontendPython SDK for agent orchestration
AIAWS runtime for agent execution & memory
AI / CloudInfrastructure as Code for backend
InfrastructureServerless Stack for frontend deployment
InfrastructureStreaming & chat hooks for React
FrontendOpenAI-compatible proxy for production
BackendMulti-stage builds for agent container
DevOpsAnthropic LLM via AWS Bedrock
AI ModelType-safe frontend development
FrontendUtility-first CSS framework
FrontendThree modular Terraform modules compose the entire backend infrastructure. Each is independently testable and reusable.
Stores conversation events and retrieves them based on semantic similarity. When the agent needs context, it searches for relevant past exchanges.
Creates rolling summaries of conversations per session. Provides the agent with condensed context without exceeding token limits.
MEMORY_ID from module.memoryREGION eu-west-1API_URL Azure API endpointAPI_KEY from SSM Parameter Storeai-naestaskref-terraform-state
{stage}/terraform.tfstate
Versioning enabled for disaster recovery
stage
2-21 chars, lowercase, starts with letter
project
default: "naestaskref"
role_arn
optional external IAM role
naestaskref-{stage}-{component}
SST deploys the Next.js frontend and receives backend configuration from Terraform through a JSON output bridge.
| Terraform Output | Next.js Environment Variable | Source | |
|---|---|---|---|
streaming_function_arn |
STREAMING_FUNCTION_ARN |
Terraform | |
streaming_function_url |
STREAMING_FUNCTION_URL |
Terraform | |
memory_id |
MEMORY_ID |
Terraform | |
agent_runtime_arn |
AGENT_RUNTIME_ARN |
Terraform | |
agent_runtime_endpoint |
AGENT_RUNTIME_ENDPOINT |
Terraform | |
| n/a | NEXT_PUBLIC_STAGE |
SST | |
| n/a | LITELLM_URL |
SST |
sst.aws.Nextjs
"NaestaSkref"
retain — prevents data loss on remove
NEXT_PUBLIC_STAGE
Automated pipelines validate every PR and deploy to production on merge, with multi-stage isolation for development.
task bootstrapCreate S3 state bucket + enable versioningtask infra:init:{stage}Initialize Terraform backendtask infra:plan:{stage}Preview infrastructure changestask infra:apply:{stage}Apply + export outputs JSONtask infra:destroy:{stage}Teardown all resourcestask frontend:env:{stage}Generate .env.local from outputstask frontend:dev:{stage}SST dev mode + live reloadtask frontend:deploy:{stage}Deploy frontend to AWStask frontend:remove:{stage}Remove frontend resourcestask deploy:{stage}infra:apply → frontend:deploytask destroy:{stage}frontend:remove → infra:destroyEach developer gets a fully isolated personal sandbox. All resources, state, and outputs are scoped by stage name.
What makes this project stand out from a typical chatbot.
Automatically detects whether the user speaks Icelandic or English from their first message, then conducts the entire conversation in that language.
Single-select and multi-select UI components are streamed from the AI agent and rendered as interactive buttons in the chat.
AgentCore Memory with semantic and summarization strategies persists context across conversation turns.
Real-time token streaming via Lambda Response Streaming ensures the user sees responses as they're generated.
Jobs, education programs, schools, categories, glossary, and wisdom pills — all queryable by the agent in real time.
The agent never asks about academic grades or GPA — ensuring inclusive, non-judgmental guidance for all users.
Terraform manages backend resources while SST handles the frontend, connected via a JSON output bridge pattern.
Route-specific question flows with conditional follow-ups based on yes/no answers and user context.
How the agent renders interactive UI in the chat — from layout to the component block protocol.
Resizable panels via react-resizable-panels. The chat pane holds the conversation; the results pane displays agent outputs.
The agent renders interactive UI by emitting component blocks in the text stream. The frontend parses these out and renders them as React components below the chat bubble.
display_options(["A","B"])
:::: OptionsList
{"options":["A","B"]}
::::
componentRegistry["OptionsList"]
Core message renderer. Parses each message for :::: Component :::: blocks, renders text as Markdown and components separately below the bubble.
Interactive option buttons rendered from agent tool calls. Supports single-select (instant submit) and multi-select (with confirm button).
Maps component names from backend blocks to React render functions. Parses the :::: Name {json} :::: syntax via regex.
Resizable two-panel layout. Chat on the left (35%), results on the right (65%). User can drag the separator to resize.
Create a function in ui_tools.py decorated with @ui_tool(component="YourComponent"). Parameters become props.
Build the component in components/chat/. It receives data, onAction, and disabled props.
Add an entry in component-registry.tsx mapping the component name to the render function.
Get the project running on your machine in a few steps.
git clone <repo-url> && cd code
npm install --prefix packages/frontend
task bootstrap
Creates the S3 bucket for Terraform state. Only needs to run once per AWS account.
task infra:init:yourstage
task infra:apply:yourstage
Replace yourstage with your name (e.g. tomas, hera). This provisions Lambda, AgentCore, and all backend resources.
task frontend:dev:yourstage
SST dev mode with hot-reload. Opens on localhost:3000.
All commands use task (Taskfile). Run task help to see the full list.
$ task help
Show all available commands
$ task bootstrap
Create S3 state bucket (one-time)
$ task infra:init:<stage>
Initialize Terraform backend
$ task infra:plan:<stage>
Preview infrastructure changes
$ task infra:apply:<stage>
Apply changes & export outputs
$ task infra:destroy:<stage>
Destroy infrastructure
$ task frontend:dev:<stage>
Start SST dev mode
$ task frontend:deploy:<stage>
Deploy frontend
$ task frontend:remove:<stage>
Remove frontend resources
$ task deploy:<stage>
Deploy infra + frontend
$ task destroy:<stage>
Remove frontend + destroy infra
$ task deploy:tomas
Deploy everything for stage tomas
$ task frontend:dev:hera
Start dev mode for stage hera