The Problem & Solution

Iceland's national career guidance platform used a static questionnaire. We replaced it with something smarter.

The Problem
  • Static questionnaire
  • One-size-fits-all approach
  • No personalization
  • No real conversation
The Solution
AI
Hæ! Hvað get ég aðstoðað þig með?
Ég er að klára grunnskóla
AI
Hvaða námsgreinar hefur þú mest áhuga á?
Stærðfræði Enska Tölvunarfræði
  • Dynamic AI conversation
  • Personalized to your situation
  • Interactive option selection
  • Bilingual (Icelandic & English)

Architecture Overview

A three-tier architecture connecting the Next.js frontend to AI agents running on AWS Bedrock AgentCore.

Frontend (SST)
Next.js 16
/api/chat
S3 + CloudFront
Backend (Terraform)
AgentCore Runtime
AgentCore Memory
Lambda / LiteLLM
External Services
Næsta Skref API
Claude AI (Bedrock)

The Agent System

Two specialized AI agents work together using Strands Swarm orchestration to guide users from intake to personalized recommendations.

LeadingQuestionsAgent

Intake & Data Collection

Detects the user's situation and asks structured questions one at a time before handing off.

Route 1
Primary → Secondary
5 questions
Route 2
Secondary → University
4-5 questions
Route 3
Career Change
5 questions
Bilingual display_options Confirms summary
Swarm Handoff
{ route, language, answers }

VegvisirAgent

Recommendations & Refinement

Uses 20+ API tools to search Iceland's education/career database and generate personalized recommendations.

Discovery
Recommendations
Refinement
20+ tools No grades policy Real links
User Message
Swarm Entry
Leading Questions
Handoff
Vegvisir
Response

User Flow

A complete walkthrough of the user's journey through the system.

1

User Opens Chat

The app loads with an initial greeting in Icelandic and three starter suggestion buttons.

"Hæ hæ! Hvað get ég aðstoðað þig með?"
2

Route Detection

The agent automatically detects which of three routes applies based on keywords in the user's first message.

Primary → Secondary Secondary → University Career Change
3

Guided Questions

The agent asks questions one at a time, using interactive option buttons for predefined choices.

Stærðfræði Enska Íslenska Tölvunarfræði
4

Summary & Confirm

After all questions are answered, the agent presents a summary and waits for the user's confirmation.

"Þú hefur áhuga á tölvunarfræði, dreymir um..."
5

Swarm Handoff

The LeadingQuestionsAgent hands off structured data to the VegvisirAgent via the Swarm protocol.

{ route: "route1", language: "is", answers: { ... } }
6

Personalized Recommendations

The VegvisirAgent searches the database with 20+ tools and presents tailored education/career recommendations with real links.

Tölvunarfræðibraut Menntaskólinn í Reykjavík
Verkfræðibraut Fjölbrautaskóli Suðurnesja

Technology Stack

Built with modern tools across the full stack — from AI frameworks to cloud infrastructure.

Python 3.13

Agent backend logic and API tools

Backend

Next.js 16

React 19 frontend with App Router

Frontend

Strands Agents

Python SDK for agent orchestration

AI

Bedrock AgentCore

AWS runtime for agent execution & memory

AI / Cloud

Terraform

Infrastructure as Code for backend

Infrastructure

SST v3

Serverless Stack for frontend deployment

Infrastructure

Vercel AI SDK

Streaming & chat hooks for React

Frontend

LiteLLM

OpenAI-compatible proxy for production

Backend

Docker

Multi-stage builds for agent container

DevOps

Claude Haiku 4.5

Anthropic LLM via AWS Bedrock

AI Model

TypeScript

Type-safe frontend development

Frontend

Tailwind CSS

Utility-first CSS framework

Frontend

Terraform Deep Dive

Three modular Terraform modules compose the entire backend infrastructure. Each is independently testable and reusable.

Module Dependency Graph
module.streaming
6 resources Lambda
Independent
module.memory
3 resources Bedrock Memory
aws_ssm_parameter
Data Source /vegvisir/api_key
memory_id
api_key
module.runtime
6 resources Bedrock AgentCore

Lambda Streaming Module

infra/modules/lambda/
Source Code
packages/backend/lambdas/streaming/
build.sh
uv pip install (ARM64)
ZIP Archive
.build/naestaskref-streaming.zip
Lambda Function
naestaskref-{stage}-streaming
Function URL
RESPONSE_STREAM
RuntimePython 3.13
ArchitectureARM64 (Graviton)
Memory256 MB
Timeout120 seconds
Handlerrun.sh (Web Adapter)
Invoke ModeRESPONSE_STREAM

Bedrock AgentCore Memory Module

infra/modules/memory/
Memory Store
7-day event expiry
Semantic Strategy
/vegvisir/semantic
Summarization Strategy
/vegvisir/summarization/{sessionId}

Semantic Memory

Stores conversation events and retrieves them based on semantic similarity. When the agent needs context, it searches for relevant past exchanges.

Summarization Memory

Creates rolling summaries of conversations per session. Provides the agent with condensed context without exceeding token limits.

Bedrock AgentCore Runtime Module

infra/modules/runtime/
Dockerfile
packages/backend/vegvisir/agent/
Docker Image
linux/arm64
ECR Push
naestaskref-{stage}-vegvisir
AgentCore Runtime
PUBLIC network
Endpoint
prod (version 1)

Injected Environment Variables

MEMORY_ID from module.memory
REGION eu-west-1
API_URL Azure API endpoint
API_KEY from SSM Parameter Store

IAM Role Permissions

ecr:GetAuthorizationToken ecr:BatchGetImage logs:CreateLogGroup logs:PutLogEvents bedrock:* bedrock-agentcore:* xray:PutTraceSegments
Terraform State & Variables

S3 Backend

ai-naestaskref-terraform-state
{stage}/terraform.tfstate

Versioning enabled for disaster recovery

Variables

stage 2-21 chars, lowercase, starts with letter
project default: "naestaskref"
role_arn optional external IAM role
Naming Pattern: naestaskref-{stage}-{component}

SST & Bridge Pattern

SST deploys the Next.js frontend and receives backend configuration from Terraform through a JSON output bridge.

Terraform → SST Output Bridge
terraform apply
outputs.tf
infra/outputs/{stage}.json
loadTerraformOutputs()
Next.js env vars
Environment Variable Mapping
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 Resources

sst.aws.Nextjs "NaestaSkref"
Creates:
Lambda Functions API Gateway S3 Bucket CloudFront CDN
prod: retain — prevents data loss on remove
non-prod: Full cleanup to reduce costs

Provider Selection

NEXT_PUBLIC_STAGE
=== "prod"
LiteLLM Provider
Lambda streaming URL
LiteLLM Proxy
!== "prod"
AgentCore Provider
AWS SDK direct
AgentCore Runtime

CI/CD & Deployment

Automated pipelines validate every PR and deploy to production on merge, with multi-stage isolation for development.

PR Validation
Push / PR
ESLint
Prettier
Vitest
terraform fmt
Pass / Fail
Production Deployment
Merge to main
QEMU ARM64 setup
Pre-build Lambda
terraform init
terraform apply -auto
SST deploy
Taskfile Orchestration
Bootstrap
task bootstrapCreate S3 state bucket + enable versioning
Infrastructure
task infra:init:{stage}Initialize Terraform backend
task infra:plan:{stage}Preview infrastructure changes
task infra:apply:{stage}Apply + export outputs JSON
task infra:destroy:{stage}Teardown all resources
Frontend
task frontend:env:{stage}Generate .env.local from outputs
task frontend:dev:{stage}SST dev mode + live reload
task frontend:deploy:{stage}Deploy frontend to AWS
task frontend:remove:{stage}Remove frontend resources
Full Stack
task deploy:{stage}infra:apply → frontend:deploy
task destroy:{stage}frontend:remove → infra:destroy
Multi-Stage Isolation

Each developer gets a fully isolated personal sandbox. All resources, state, and outputs are scoped by stage name.

tomas
naestaskref-tomas-streaming
naestaskref-tomas-vegvisir (runtime)
naestaskref-tomas-vegvisir-memory
tomas/terraform.tfstate
infra/outputs/tomas.json
hera
naestaskref-hera-streaming
naestaskref-hera-vegvisir (runtime)
naestaskref-hera-vegvisir-memory
hera/terraform.tfstate
infra/outputs/hera.json
prod
naestaskref-prod-streaming
naestaskref-prod-vegvisir (runtime)
naestaskref-prod-vegvisir-memory
prod/terraform.tfstate
infra/outputs/prod.json

Key Features & Innovations

What makes this project stand out from a typical chatbot.

Bilingual Support

Automatically detects whether the user speaks Icelandic or English from their first message, then conducts the entire conversation in that language.

Interactive Options

Single-select and multi-select UI components are streamed from the AI agent and rendered as interactive buttons in the chat.

Conversation Memory

AgentCore Memory with semantic and summarization strategies persists context across conversation turns.

Streaming Responses

Real-time token streaming via Lambda Response Streaming ensures the user sees responses as they're generated.

20+ API Tools

Jobs, education programs, schools, categories, glossary, and wisdom pills — all queryable by the agent in real time.

No-Grades Policy

The agent never asks about academic grades or GPA — ensuring inclusive, non-judgmental guidance for all users.

Hybrid IaC

Terraform manages backend resources while SST handles the frontend, connected via a JSON output bridge pattern.

Conditional Logic

Route-specific question flows with conditional follow-ups based on yes/no answers and user context.

UI Components

How the agent renders interactive UI in the chat — from layout to the component block protocol.

Split-View Layout

Chat Pane 35%
Viti Hæ hæ! Hvað get ég aðstoðað þig með?
Leita námsleið Háskólanám Vinnumarkaður
Ég Ég er að leita námsleið...
Viti Frábært! Hvað hefur þú áhuga á?
Tækni Heilbrigði Listir Viðskipti
Skrifaðu skilaboð...
Results Pane 65%
Hæ! Finndu þín næstu skref
Agent results appear here

Resizable panels via react-resizable-panels. The chat pane holds the conversation; the results pane displays agent outputs.

Component Block Protocol

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.

Agent calls tool
display_options(["A","B"])
Backend emits block
:::: OptionsList
{"options":["A","B"]}
::::
Registry lookup
componentRegistry["OptionsList"]
React renders
Interactive buttons in chat

MessageList

components/chat/MessageList.tsx

Core message renderer. Parses each message for :::: Component :::: blocks, renders text as Markdown and components separately below the bubble.

Markdown via ReactMarkdown Auto-scroll on new messages Thinking dots while streaming Initial greeting + suggestions

OptionsList

components/chat/OptionsList.tsx

Interactive option buttons rendered from agent tool calls. Supports single-select (instant submit) and multi-select (with confirm button).

Single & multi-select modes Auto-disables after selection Sends selection back as message

Component Registry

components/chat/component-registry.tsx

Maps component names from backend blocks to React render functions. Parses the :::: Name {json} :::: syntax via regex.

Name-based lookup JSON prop parsing Graceful fallback on bad JSON

SplitView

components/layout/SplitView.tsx

Resizable two-panel layout. Chat on the left (35%), results on the right (65%). User can drag the separator to resize.

Draggable divider Min 20% per panel react-resizable-panels

Adding a New Component

1
Backend tool

Create a function in ui_tools.py decorated with @ui_tool(component="YourComponent"). Parameters become props.

2
React component

Build the component in components/chat/. It receives data, onAction, and disabled props.

3
Register it

Add an entry in component-registry.tsx mapping the component name to the render function.

Quickstart

Get the project running on your machine in a few steps.

Prerequisites

AWS CLI Configured with credentials
Terraform v1.5+
Node.js v20+ & npm
Python 3.13 & uv
Task taskfile.dev runner
SST Ion CLI (npx sst)
1

Clone & install

git clone <repo-url> && cd code
npm install --prefix packages/frontend
2

Bootstrap (first time only)

task bootstrap

Creates the S3 bucket for Terraform state. Only needs to run once per AWS account.

3

Deploy infrastructure

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.

4

Start developing

task frontend:dev:yourstage

SST dev mode with hot-reload. Opens on localhost:3000.

Good to Know Commands

All commands use task (Taskfile). Run task help to see the full list.

Getting Started

$ task help Show all available commands
$ task bootstrap Create S3 state bucket (one-time)

Infrastructure (Terraform)

$ 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

Frontend (SST)

$ task frontend:dev:<stage> Start SST dev mode
$ task frontend:deploy:<stage> Deploy frontend
$ task frontend:remove:<stage> Remove frontend resources

Full Stack

$ task deploy:<stage> Deploy infra + frontend
$ task destroy:<stage> Remove frontend + destroy infra

Quick Examples

$ task deploy:tomas Deploy everything for stage tomas
$ task frontend:dev:hera Start dev mode for stage hera