maria jastrzębskaAI research & engineeringGet in touch ↗
← Back to Projects

CareerApp · Agentic career support

Advocate

I am building Advocate, a career-support application available through CareerApp. It combines a conversational agent with persistent career memory, job discovery, targeted interviews and document generation. The central idea is that a useful career assistant should learn a person’s actual experience over time and reuse that evidence in each new application.

Python · FastAPI · Claude tool use · Pydantic · PostgreSQL · pgvector · fastembed/ONNX · Next.js · React · Telegram · Twilio/WhatsApp · Whisper · ElevenLabs · Railway

Long-term memoryCareer stories, provenance, confirmation status and version history
3 interfacesWeb, Telegram and WhatsApp connected to the same profile store
PL + ENConversation language and document language selected independently

The problem: a CV is a compressed view of a much richer career

A person may have dozens of relevant examples from one position, while a CV can only show a few. Starting from the same document for every vacancy loses that context. Rewriting it repeatedly can also introduce contradictions or turn an unsupported suggestion into an apparent fact.

I designed Advocate around a reusable evidence base. A new role changes which examples are relevant and how they are explained. It should not change what the person actually did. My work spans the product workflow, memory model, retrieval, agent tools, API, web interface and messaging integrations.

A guided workflow from experience to application

The product has six stages: establish the current CV and profile, clarify career direction, find and select roles, interview against the selected requirements, review fit and gaps, then generate and refine the document.

Job search is iterative. The user can refine location, seniority and preferred work, bring an existing posting or select from discovered roles. The interview then asks about relevant experience missing from the evidence base. The output includes both the person’s strongest fit and requirements that remain unsupported.

The web interface exposes progress, selected offers, the knowledge base and the current PDF. Feedback becomes another revision of the document and can also add information to memory. A user can discuss their experience in Polish while requesting an English CV, or select the reverse.

Memory is a data model, not just a longer chat history

Advocate keeps a core profile, job preferences, positions and individual career stories. Stories distinguish ongoing responsibilities, specific episodes and individual facts. They can include situation, task, action, result, technologies, metrics and the source of the information.

Newly extracted material enters a reviewable state. Records can be confirmed, rejected, corrected or merged, with versions retained. A story belongs to a specific position; when that relationship is missing, the system flags it for clarification instead of guessing the employer.

Before adding a story, the ingestion workflow searches for similar material within the relevant position. A structured merge decision distinguishes another description of the same event from a related or new event. This keeps memory useful as the person tells the same story from different angles.

PostgreSQL stores the durable profile, story records and working state. The agent reloads profile and preference context on subsequent turns, including after a conversation reset. Short-term conversation history and long-term factual memory have separate roles.

Retrieval connects a requirement with supporting experience

I combine PostgreSQL full-text search with pgvector similarity search using reciprocal-rank fusion. Exact terms help retrieve a named technology or responsibility; semantic retrieval helps when a posting and a career story describe similar work differently.

Multilingual embeddings run locally through fastembed and ONNX. This avoids sending career records to an additional embedding provider. The writing and analysis workflow still uses model services. If vector support is unavailable, keyword retrieval remains available.

The context is layered: the core profile and position headings, confirmed stories for key positions, and stories retrieved against particular job requirements. A matching brief references story IDs before drafting, and the implementation records how stories are used in applications. This makes it possible to inspect which experience supports a proposed bullet point.

One application, several ways to use it

The Next.js web application communicates with a FastAPI backend. Web and Telegram share the guided CV-session workflow, including analysis, interview, generation and review. A Telegram conversation can link directly to the relevant web step when a user needs to inspect requirements, confirm a story or review a PDF.

The WhatsApp adapter uses Twilio to expose the conversational agent, with text, voice messages and document delivery. Account linking connects messaging identities with the existing user profile, allowing the channels to reuse the same underlying memory.

Whisper transcription turns voice notes into inputs for the workflow, and ElevenLabs supports spoken replies. Text remains available when voice processing is unavailable. Transport adapters handle delivery while the core services handle career information and document work.

Explicit boundaries around model output

I use Pydantic contracts for extraction, offer analysis, merge decisions and matching briefs. The structured-output layer validates responses and uses bounded repair attempts when necessary. The agent’s tool loop also has an execution cap so one message cannot trigger an unbounded chain of model calls.

Reading a job posting is treated as an evidence-gathering step. The parser looks for embedded job data and usable posting text. If the content cannot support an analysis, the workflow requests the text instead of filling in requirements from a title or company name.

Confirmed memory, newly proposed information and a generated document remain distinguishable. User review is part of that process, and a source-linked draft is still something to inspect before submitting.

Building the product around the agent

The application includes Google sign-in, account-linked messaging, subscription and usage handling, and persisted document artifacts. Keeping the application state in PostgreSQL lets separate services work from the same records and avoids tying a user’s history to one running process.

I separated transport, workflow state, retrieval and document rendering so that a change to one interface does not require rebuilding the career logic. Session logs and explicit error states make interrupted or failed steps visible. The result is an ongoing product engineering project as well as a practical setting for studying memory, grounded generation and human review.

In development: an Advocate plugin

I am also developing a plugin interface for Advocate. The goal is to make its career-memory and application workflows available inside other assistant environments, while reusing the same underlying records and review process. This extension is work in progress alongside the web and messaging interfaces.