Inspiration
Every day I ask AI assistants valuable questions and get great answers — then lose them forever. Chat history is linear, unsearchable, and ephemeral. I kept re-Googling the same questions, knowing I had already found the answer somewhere but unable to find it again.
The insight was simple: AI answers are valuable knowledge. They deserve to be saved, organized, and searchable — not lost in an endless scroll of chat history. That's why I built ChatScroll.
What it does
ChatScroll transforms AI conversations into a personal knowledge library:
- Chat with Gemini 2.5 Flash AI on any topic
- Save the best answers as "Scrolls" with one click — AI automatically suggests the right folder
- Search semantically — find "blood thinner medication" and discover your warfarin scroll even if those exact words aren't in it, powered by Amazon Aurora pgvector
- Study Mode — review your knowledge library as flashcards with keyboard navigation
- Write your own Scrolls from books, articles, or your own expertise — not just AI answers
- Share any Scroll via public link — others can import it directly to their own library
- Export scrolls as PDF (browser-native print) or Markdown for portability
- Related Scrolls — open any scroll and see semantically similar ones from the same category, powered by pgvector cosine similarity scoped to the same folder subtree
How I built it
Frontend: Next.js 14 App Router with TypeScript and Tailwind CSS, deployed on Vercel. AWS Amplify handles Cognito authentication with JWT tokens. Anonymous sessions use a browser-generated UUID that automatically migrates to a real account on sign-up — no data is ever lost.
Backend: ASP.NET Core 9 API running on AWS ECS Fargate (containerized, no EC2 management). Docker images are built via GitHub Actions, pushed to Amazon ECR, and deployed with an automatic ECS task definition registration on every push to master.
Amazon Aurora PostgreSQL — the knowledge layer: Aurora Serverless v2 with three PostgreSQL extensions working together:
| Extension | What I use it for |
|---|---|
| pgvector | 3072-dim embeddings per Scroll via gemini-embedding-001; cosine similarity search with threshold; Related Scrolls via nearest-neighbour scoped to folder subtree |
| ltree | Folder paths as dot-separated label trees (programming.containers); subtree queries without recursive CTEs |
| tsvector | Full-text search index; ranked with ts_rank; hybrid search combining semantic + keyword results |
Aurora also stores conversation metadata, user records keyed by Cognito sub, and the full folder hierarchy.
Amazon DynamoDB — the chat layer: DynamoDB stores the high-volume chat message stream:
- PK:
conversationId— all messages in one partition - SK:
{timestamp}#{messageId}— chronological reads and time-range queries - TTL:
expiresAt(90 days) — messages auto-expire with zero infrastructure, no cron jobs needed - PAY_PER_REQUEST — scales to millions of messages
This dual-database pattern keeps Aurora lean (structure + search) while DynamoDB absorbs the write volume of real-time chat.
Challenges I ran into
1. AWS Bedrock/Titan access restrictions
My original plan used Amazon Titan (via AWS Bedrock) for
both chat and embeddings — BedrockAiService.cs targeting
amazon.titan-text-premier-v1:0 and
amazon.titan-embed-text-v2:0. Every call returned
ValidationException: Operation not allowed despite
correct IAM roles and task policies. After exhausting
every configuration option, I discovered this was an
account/region-level restriction requiring an AWS Support
case to resolve — not fixable via console. I pivoted to
Google Gemini 2.5 Flash for chat and gemini-embedding-001
for embeddings, which proved more capable for this use case.
2. ECS deployment pipeline — stale task definitions
For a while, --force-new-deployment was restarting ECS
using the old task definition instead of picking up newly
pushed Docker images. I discovered that force-new-deployment
alone only restarts the service with the existing task
definition — it does NOT update which image is running.
I rewrote the GitHub Actions workflow to: fetch the current
task definition, update the container image to the new
SHA-tagged ECR image, register a new task definition
revision, then explicitly pass the new ARN to
update-service. Every deploy now correctly runs the
new image.
3. Scoping semantic search to a category
Early versions of Related Scrolls returned completely
unrelated results — a scroll about warfarin would surface
Docker and OOP notes. I fixed this by resolving the root
parent folder of the current scroll, collecting all
descendant folder IDs, and filtering with
AND folder_id = ANY($allowedIds::uuid[]). Searching
"blood thinner medication" now correctly returns only
medicine scrolls, ranked by cosine similarity.
4. pgvector at 3072 dimensions exceeds Aurora's index
limits
After migrating to gemini-embedding-001 (3072 dimensions),
I discovered Aurora's pgvector version caps both ivfflat
AND hnsw indexes at 2000 dimensions. I dropped the existing
ivfflat index and use exact search instead — which works
perfectly at personal knowledge base scale and is simpler
to maintain.
5. Anonymous-to-authenticated data migration
When a user signs up after an anonymous session, all their
Aurora data (scrolls, folders, conversations) needs to
re-parent to their Cognito sub. I built a
POST /api/users/me/migrate-anonymous endpoint that runs
four UPDATE statements atomically in a transaction, with
safety guards to prevent misuse. The frontend calls it
once on first login and removes the anonymous UUID from
localStorage — users never lose their data when they
create an account.
Accomplishments that I'm proud of
- Three PostgreSQL extensions working together — pgvector, ltree, and tsvector in a single Aurora schema, each solving a distinct problem the others can't
- Semantic search that actually works — searching "containerization technology" correctly surfaces the Docker scroll with zero medicine results contaminating it
- Deliberate dual-database architecture — Aurora for structured relational data needing complex queries and vector search; DynamoDB for high-volume time-series chat data needing infinite scale
- Full CI/CD pipeline — every git push automatically builds, pushes to ECR, registers a new ECS task definition revision, and deploys with zero manual steps
- Anonymous-first UX — users can chat and save Scrolls before ever creating an account; their full history follows them when they sign up
- Branded Cognito email — verification emails use ChatScroll branding with the real logo and amber design
What I learned
- Choose databases for their strengths. Aurora's pgvector + ltree + tsvector combination is uniquely powerful for knowledge management. DynamoDB's composite keys and TTL are perfect for chat history. The right tool for the right job makes everything simpler.
- ECS task definitions are immutable.
force-new-deploymentalone won't pick up new images — you must register a new task definition revision and explicitly pass its ARN toupdate-service. - Always log AI service failures explicitly. Silent error swallowing in embedding generation meant all embeddings were NULL for a long time with no visible error — smart search appeared to work but was actually falling back to keyword search silently.
- pgvector at high dimensions has index limits on Aurora — both ivfflat and hnsw cap at 2000 dims. At personal knowledge base scale, exact search is the right choice.
What's next for ChatScroll
- Spaced repetition — surface scrolls you haven't reviewed in a while, turning the library into an active learning tool
- Team libraries — shared scroll collections for organizations and study groups
- Browser extension — save any webpage or article as a Scroll directly from the browser
- Multi-model support — let users choose their AI backend per conversation
- Community scrolls — discover and import top-rated scrolls from the community on any topic
- Real Cognito multi-tenancy — enterprise SSO and team-level permissions
Built With
- amazon-aurora-postgresql
- amazon-dynamodb
- amazon-ecr
- amazon-ecs
- asp.net-core
- aws-cloudwatch
- aws-cognito
- c#
- docker
- github-actions
- google-gemini-api
- next.js
- pgvector
- tailwind-css
- typescript
- vercel
Log in or sign up for Devpost to join the conversation.