Inspiration
Most resume tools treat every application as a blank page. You paste in a job description, get a generic rewrite, and start over next time with no memory of what came before. We wanted an agent that actually remembers a person's career the way a good mentor would: their projects, their skills, the roles they're aiming for, and how all of that connects across different versions of who they're presenting themselves as. The CockroachDB and AWS hackathon's framing of "agentic memory that never goes down" matched exactly the problem we'd been circling: an agent is only as useful as what it remembers between conversations, and most demos fake that memory instead of actually building it.
What it does
CareerCopilot is an AI career agent with persistent, structured memory. A user talks to it the way they'd talk to a career coach: mentioning a project they shipped, a certification they finished, a role they're targeting. The agent quietly extracts and files each of those facts into the right place, structured data for things like projects and skills, embeddings for anything that needs to be found by meaning rather than exact match.
When asked to build a resume for a specific role, the agent doesn't just fetch a stored file. It pulls everything relevant from memory, including projects that were never explicitly tagged for that role but are conceptually close to it, and synthesizes a tailored resume on the spot, rendering it as a real downloadable PDF. Upload an existing resume and the same memory gets enriched from it. Ask what's already saved and the agent queries its own database live to answer honestly instead of guessing.
How we built it
The core loop is a Python backend running a hand built agent loop against Gemini, deliberately not relying on a prebuilt SDK wrapper so we could route tool calls ourselves between two very different destinations: CockroachDB's Cloud Managed MCP Server for live reads, and local Python tools for things like PDF generation.
CockroachDB is the persistent memory layer end to end. Structured tables hold resumes, projects, skills, certifications, applications, career goals, and bio data, each versioned where it matters, resumes especially, so a new version is created rather than overwritten. A separate memory_embeddings table holds vectors in a native VECTOR column with a VECTOR INDEX partitioned by user, so semantic search stays fast and correctly scoped as more users are added, and never accidentally searches across users.
Two CockroachDB tools do real work in the running app. The Managed MCP Server is wired directly into the agent's tool calling loop, so when a conversation genuinely needs a live answer about what's stored, the agent writes and runs the SQL itself through that connection. Distributed Vector Indexing powers every "find something relevant" moment, most visibly when generating a resume for a role that has no exact match in memory yet.
On the AWS side, an SQS queue decouples the live chat response from background fact extraction, and a Lambda function does the actual extraction: classifying a conversation turn into structured fact types, writing each one into the right table, and embedding it for future semantic recall. S3 holds two distinct things, generated PDF resumes and uploaded resume files a user submits directly.
A Redis layer handles session state and a short lived cache for generated PDF links, so an LLM never has to reproduce a long signed URL verbatim, which turned out to matter more than expected.
Challenges we ran into
Nearly every hard bug in this project traced back to the same root cause: something silently returning the wrong identity, the wrong type, or nothing at all, with no loud error to point at it. A missing self on a helper method meant every vector query failed with a confusing type error. A silent fallback in user resolution meant a database connection hiccup could quietly swap a real UUID for a raw string, and every downstream memory lookup would come back empty with no exception anywhere in the chain.
CockroachDB specific quirks took real debugging time too: getting pgvector style casting to actually work against CockroachDB's vector type, discovering that ALTER COLUMN TYPE is blocked on any column participating in an index and needing to drop and rebuild it, and learning through trial and error which system schemas the Managed MCP Server blocks for security and which column selections blow past its response size limit.
The subtlest class of bug was letting an LLM write its own SQL and its own explanations. Left unguided, it would guess plausible sounding column names that did not exist, invent a confident sounding technical explanation for a tool failure instead of relaying the real error, and once, quietly rewrite a signed download URL into something invalid because it looked like noise. Each of those needed an explicit, specific instruction, not a vague "be careful" one, before it stopped happening.
Accomplishments that we're proud of
Getting to a resume generation flow that is genuinely dynamic rather than a lookup dressed up to look dynamic. The agent is synthesizing new content from whatever memory actually exists for a person, project by project, and that content becomes new memory for next time.
Making the memory model actually hold together across a moving target: structured tables for anything that needs precise reads and writes, a single pointer based embeddings table for anything that needs to be found by meaning, and a consistent extraction pipeline that keeps both in sync as new fact types were added along the way.
Getting two genuinely different CockroachDB capabilities to do load bearing work in the same running application at once, live SQL through the Managed MCP Server and semantic recall through Distributed Vector Indexing, rather than picking one and initializing the other for the sake of a checklist.
What we learned
The honest lesson of this build is that an agent's memory architecture is mostly about discipline in the unglamorous middle layer: making sure the thing that writes data and the thing that reads it agree on what a user's identity actually is, making sure a partially failed background job does not silently corrupt what looks like a successful one, and making sure a model that is allowed to write its own queries is told, explicitly and specifically, exactly what it is not allowed to guess.
We also learned that treating an LLM as a component that can fail in its own particular ways, confidently fabricating an explanation rather than admitting it does not know one, mangling a string it was never meant to reproduce verbatim, matters just as much as handling a database exception. Those failure modes need the same explicit guarding a database connection would get.
What's next for Career Copilot
Cover letters and Interview preparation grounded in the same memory, pulling a user's actual projects and phrasing likely questions around them instead of generic prompts. Deeper application tracking that closes the loop from applied to interview to offer using the same structured tables that already exist. Tightening the extraction prompt further so employers and dates are captured with enough structure to stop merging separate roles into one vague block on a generated resume. And moving the Redis backed session and artifact layer onto a managed service so the whole system survives past a single hackathon demo box.
Built With
- amazon-web-services
- cockroachdb
- fastapi
- javascript
- python
- react
Log in or sign up for Devpost to join the conversation.