Optava AI — Hackathon Submission Writeup


Inspiration

Every project team we have ever been part of suffers the same problem: knowledge evaporates. A decision gets made in a standup, the rationale lives in someone's head, and three weeks later nobody remembers why the architecture looks the way it does. New team members spend days asking questions that were already answered. Standups repeat the same status updates that were written in tickets nobody reads.

We were inspired by a simple but powerful idea — what if your project management tool actually remembered everything, understood it, and could answer questions about it in plain English? Not just storing data in rows and columns, but building a genuine semantic memory layer that an AI agent could reason over.

The CockroachDB × AWS Hackathon gave us the perfect forcing function. CockroachDB's native vector indexing meant we could store structured project data AND high-dimensional embeddings in a single distributed database. Amazon Bedrock gave us Claude and Titan Embed without any infrastructure to manage. The combination made a memory-first PM tool not just possible but surprisingly elegant to build.


What It Does

Optava is an AI project manager with persistent memory. Everything your team does — every decision logged, every task updated, every chat message sent — is stored as both structured data and a 1024-dimensional semantic vector in CockroachDB.

The core loop works like this:

When you log a decision, it is written to the decisions table and simultaneously embedded by Amazon Bedrock's Titan Embed v2 model. The resulting 1024-dimensional vector is stored in the memory_embeddings table with a CockroachDB native vector index. The same happens for tasks, notes, and every chat message.

When you ask the AI agent a question, two things happen in parallel. First, your question is embedded and matched against the entire memory store using CockroachDB's cosine similarity operator. Second, Claude itself uses MCP tool calling to decide which tables to query directly — it might call get_project_decisions, get_project_tasks, or run a custom SQL query — all against CockroachDB. Both result sets are merged into Claude's context and it generates a grounded response with full citation transparency.

The full feature set includes:

  • AI Memory Chat — Ask anything about your project history and get answers grounded in stored memory with similarity scores shown per citation
  • Decision Log — Every architectural and product decision stored with context, rationale, and alternatives, semantically searchable by meaning not keywords
  • Kanban Task Board — Full board with backlog, todo, in-progress, and done columns plus timeline and list views, filters, and a new task modal
  • AI Briefings — Claude synthesizes project status from tasks, decisions, and semantic memory searches into structured briefings with confidence scoring
  • Team Intelligence — Contribution leaderboard, expertise tracking, and activity breakdown derived from project memory
  • Analytics and Memory tabs — Live visibility into vector store health, embedding coverage, and task distribution

How We Built It

Frontend — Next.js 15 with the App Router, TypeScript, Tailwind CSS v4, and TanStack Query v5. The app is structured around a DB-first context pattern where the layout server component queries CockroachDB directly for the active organization and project on every request, passing it down via React context to all child components. No authentication is required for the hackathon build — the first org in the database is loaded automatically.

Backend — Express with TypeScript running on Node.js 20. Thirty-plus REST endpoints across ten route groups. Every response uses a consistent JSON envelope. The request logger middleware captures method, path, status, and duration for every request.

CockroachDB — The entire persistence layer. Sixteen tables covering users, organizations, projects, tasks, decisions, conversations, notes, briefings, team members, and the memory_embeddings vector store. The schema uses UUID primary keys everywhere for distributed write performance, JSONB columns for flexible metadata, and cascade deletes for clean workspace teardown. The vector index uses CockroachDB's native VECTOR(1024) type with CREATE VECTOR INDEX.

Amazon Bedrock — Two models. Titan Embed v2 produces 1024-dimensional embeddings for every memory write and every search query. Claude Haiku 4.5 handles both the RAG chat responses and the AI briefing generation. All inference is managed by Bedrock with zero infrastructure on our side.

MCP Tool Layer — We implemented a CockroachDB MCP tool calling pattern in mcp-client.ts. On every chat message, a planner call asks Claude which of five read-only tools to invoke — query_project_memory, get_project_decisions, get_project_tasks, get_memory_stats, and run_sql_query. The tools execute in parallel against CockroachDB and their results are merged with the vector search results into Claude's system prompt. The UI shows which tools were called per message as blue badges alongside the vector citation percentages.

Deployment — Backend on Render, frontend on Vercel, database on CockroachDB Cloud.


Challenges We Ran Into

The onboarding loop was the first major obstacle. We had built a NextAuth-based authentication flow where JWT tokens encoded the user's orgId and projectId. After every DB reset during development the cookie still held the old org data, causing a loop between the app layout and the onboarding page. The layout saw no org and redirected to onboarding. Onboarding checked the session, saw an org in the stale JWT, and redirected back. We resolved this by removing authentication entirely for the hackathon build and replacing the session-based context with a direct DB query in the layout server component.

CockroachDB's free tier RU limits caught us mid-development when we switched to a new account. The cluster hit its monthly Request Unit limit during active testing, which surfaced as connection errors that looked like code bugs. We learned to monitor RU consumption and created a new cluster to continue.

TypeScript strictness on Render — Our local development used tsx in watch mode which is tolerant of type errors. Render's build runs tsc directly and failed on 22 type errors that had never surfaced locally. The most common pattern was Express typing req.query values as string | string[] while our service functions expected string. We fixed each route with explicit type guards and casts and tightened the tsconfig.

The MCP integration required two Claude calls per chat message — one planner call to decide which tools to use and one generation call to produce the response. This added latency. We kept the planner call at temperature zero to make tool selection deterministic and fast, and we run the tool executions in parallel using Promise.all to minimize the overhead.

Vector index creation on CockroachDB required the min_partition_size parameter to be set explicitly. Without it the index creation failed silently in some configurations. We added it to the migration and added a verification step to confirm the index type shows as VECTOR in SHOW INDEXES.


Accomplishments That We're Proud Of

The dual-path retrieval architecture is the thing we are most proud of technically. Every chat message triggers both a cosine similarity search over 1024-dimensional vectors and a direct CockroachDB query via MCP tool calling. The results are merged and both are visible in the UI. This is not a typical RAG demo — it is a genuinely multi-modal memory retrieval system where the AI decides in real time which database queries to make.

Full ACID transactional embedding — every memory write commits the structured record and its vector simultaneously or not at all. There is no possibility of a decision existing in the decisions table without its embedding in memory_embeddings. This was an intentional design choice and CockroachDB's transaction support made it straightforward.

The UI transparency — judges and users can see exactly how every AI answer was generated. The Retrieved Memories panel shows which memories were retrieved and their similarity scores. The MCP tool badges show which database tools Claude called. The briefing source counts panel shows how many tasks, decisions, and notes were synthesized. Nothing is hidden.

Sixteen production-grade tables deployed to CockroachDB Cloud with proper indexes, foreign keys, cascade deletes, and JSONB columns — all created from a single migration file that runs in under ten seconds.

Shipping a complete product — not a demo or a prototype. Tasks, decisions, chat, briefings, team management, settings, and a full setup flow all work end to end. Every page has shimmer loading states, empty states with actionable CTAs, error handling, and retry logic.


What We Learned

CockroachDB's vector capabilities are production-ready. We expected to need a separate vector database like Pinecone or Weaviate alongside CockroachDB for relational data. Instead a single CockroachDB Cloud cluster handled all sixteen relational tables and the full vector store with native VECTOR(1024) columns and cosine similarity queries that return in milliseconds. Eliminating the separate vector store also eliminated an entire category of consistency problems.

MCP tool calling changes the agentic architecture fundamentally. Traditional RAG is retrieval then generation — you decide what to retrieve, then Claude generates from it. With MCP tool calling, Claude decides what to retrieve. The planner step means the AI is genuinely reasoning about what database queries will produce the best answer. This feels qualitatively different from RAG even though the underlying mechanisms are similar.

JWT token caching and server component caching interact in non-obvious ways. Next.js 15 server components cache aggressively. After a user completed the setup flow and their org was created in the database, the layout component cached the null result from before the org existed and kept showing the no-workspace screen. Adding export const dynamic = "force-dynamic" to the layout was the fix — a one-line change that took hours to diagnose.

TypeScript strictness is not optional in CI. Developing with tsx watch mode created a false sense of type safety. The production build exposed 22 errors that had existed for weeks. We now run tsc --noEmit as a pre-commit check.

Bedrock is remarkably easy to operate at hackathon scale. No model hosting, no GPU management, no rate limit negotiations. You put in your AWS credentials and call the API. The managed inference handled everything from embeddings to multi-turn chat to briefing generation without a single infrastructure decision beyond region selection.


What's Next for Optava AI

Google OAuth and multi-user workspaces — The auth layer is stubbed out with Auth.js v5 ready to be activated. The database schema already has users, sessions, accounts, and verification tokens tables. Reintroducing auth means swapping the DB-first layout query for a session-based context read.

Real-time memory updates via WebSocket — Currently the memory panel updates after each message completes. A WebSocket connection would allow streaming the retrieved memories and MCP tool results as they arrive, making the retrieval process visible in real time.

Agent task queue for background processing — The agent_tasks table exists in the schema but is not yet used. The plan is a background queue that runs nightly embedding health checks, identifies memory gaps, and proactively surfaces insights without user prompts.

Expanded MCP tool surface — The current five tools cover the most common query patterns. We want to add tools for cross-project memory search, team contribution queries, and timeline-based memory retrieval so Claude can answer questions like "what changed in the last two weeks" with a single structured query.

Email invitations and team management — The invitations system is fully built on the backend with Resend integration ready. The frontend invite modal and settings page are complete. Activating this requires only connecting the email service credentials.

Decision graph visualization — Decisions often reference each other. Logging "we chose CockroachDB" and "we eliminated a separate vector store because of CockroachDB" creates an implicit graph. We want to surface this as a visual decision tree showing how choices influenced later choices, powered by semantic similarity between decision rationales.

Share this project:

Updates

Submission history