# Versed

Make the connection between experience and opportunity visible.

I built Versed to translate between a candidate’s actual experience and an employer’s requirements. The work spans job ingestion and ranking, a competency catalog, agent orchestration, and a focused interface that makes the supporting evidence visible.

Author: Spencer Hardwick
Role: Founder · End-to-end product and engineering
Status: Development paused
Type: Independent project
Placement: independent

Independent project · development paused. I continue to use components in my own job search.

## Workflow explained

Candidate evidence and employer requirements converge into a structured translation. Inspect this illustrative example to see how the wording stays connected to the experience that supports it.

### Candidate evidence

Illustrative source evidence: I owned a customer agent from product scope through implementation, and it is live in production. The translation starts with this concrete ownership and delivery experience.

### Employer requirements

Illustrative employer requirement: Own AI products end to end. A pinned competency catalog helps connect the employer’s language to the candidate’s evidence.

### Visible translation

Supported wording: End-to-end product and technical ownership of a production agent. The wording expresses the requirement through the supplied evidence, preserving the candidate’s actual contribution.

## A vocabulary problem inside a matching problem

Candidates and employers can describe similar work in different language. I built Versed to make that relationship easier to see while keeping the candidate’s actual experience as the source.

I owned the work end to end: product direction, interface design, data pipelines, agent architecture, implementation, and evaluation. I used coding agents throughout, with shared contracts connecting backend and frontend work.

## Bring the explanation into the experience

The earlier product, Soaria, centered on a job feed: collect listings, rank matches, explain fit, and help tailor a résumé. The translation between experience and requirements was valuable, but it was buried inside scores and rewritten text.

I shifted the product toward making that translation visible. The employer asks for a capability; the candidate has evidence; the interface shows the relationship. That creates something a person can inspect, accept, or question.

The focused entry point became a role, a job description, a résumé, and structured translation artifacts. It gave the product a direct way to demonstrate its value while the broader agent experience supported additional work.

## Ground the language

I developed a competency catalog from job-description language to provide a shared reference for candidate and employer vocabulary. Extraction, embeddings, and clustering supported the catalog-building work. Pinning a catalog version gave an individual translation request a consistent grounding source.

The output needed structure as well as fluent language. Validated translations became typed interface artifacts, with evidence and gaps available for inspection. A supported rewrite should make relevant experience easier to understand while preserving the underlying claim.

## Give models and code distinct jobs

The broader authenticated agent uses a validated plan to select tool work. Tools run concurrently; application code assembles their results into cards; commentary streams afterward. The deterministic assembler makes no model calls.

That separation gives the interface a stable contract. Models interpret and explain, while application code controls the structure of the artifacts the user receives. The focused translation request bypasses general planning because its task is already defined.

## Spend reasoning where it matters

The matching pipeline uses vector similarity to narrow the candidate set before model-based scoring. That choice focuses more expensive reasoning on promising matches. A shared gateway centralizes model calls, usage tracking, and budget checks.

Session ownership is equally concrete. Authenticated turns operate on owned sessions, and anonymous work has an explicit claiming path. These decisions make cost, continuity, and access part of the product architecture.

## What I carry forward

The central product move was making the explanation itself useful. A score compresses a judgment; a visible mapping lets the user understand and challenge it.

Development is paused, and I continue to use components in my own job search. The build brought together data engineering, model orchestration, structured interfaces, and the product judgment needed to turn a broad system into a focused interaction.

## Architecture

### System architecture

The later Versed interface connects a focused translation experience to shared API, identity, model, and data services.

#### Experience and identity

**Next.js interface** — Typed cards and interactions

The later frontend renders structured translation and agent artifacts. The earlier Soaria frontend represents a previous generation of the product.

Technologies: Next.js, React, TypeScript.

**Supabase Auth** — User identity

Supabase authentication supplies user identity. The backend validates authentication and applies session ownership checks.

Technologies: Supabase.

#### Application services

**FastAPI** — Request orchestration

The API handles focused translations, authenticated agent turns, session claiming, feedback, and server-sent events.

Technologies: FastAPI, Python.

**Model gateway** — Shared model calls

A shared LiteLLM layer centralizes model calls, usage tracking, and budget checks. Model choice and reasoning cost are application concerns.

Technologies: LiteLLM.

#### Data and background work

**PostgreSQL + pgvector** — Durable application data

PostgreSQL stores application records and conversation state. Vector search supports candidate selection before more expensive model scoring.

Technologies: PostgreSQL, pgvector.

**Prefect jobs** — Ingestion and matching

Scheduled jobs ingest listings, prepare embeddings, and run ranking work. Background processing and interactive requests share the data layer.

Technologies: Prefect, Python.

#### Connections

- **Next.js interface → Supabase Auth (Authentication):** The interface obtains authenticated user identity through Supabase.
- **Next.js interface → FastAPI (HTTP + SSE):** HTTP requests initiate work and SSE delivers structured events and commentary.
- **Supabase Auth → FastAPI (JWT validation):** The backend validates the supplied identity before authenticated operations.
- **FastAPI → Model gateway (Structured model calls):** Application services use the shared gateway to interpret and generate content.
- **FastAPI → PostgreSQL + pgvector (Queries and writes):** Application services persist records and enforce ownership checks around sessions.
- **Prefect jobs → PostgreSQL + pgvector (Scheduled data preparation):** Background jobs write and query the data used by matching and translation.
- **Prefect jobs → Model gateway (Embedding and scoring calls):** Jobs use model capabilities as part of embedding and ranking pipelines.

#### Prefilter before model scoring

Vector similarity narrows the set of jobs before model-based scoring. This focuses expensive reasoning on a smaller candidate set.

#### Share infrastructure, specialize the interaction

The focused translation flow and broader agent experience reuse services while keeping distinct orchestration paths.

#### Make costs visible in the application

Shared usage tracking and budget checks provide a place to manage model cost without coupling each interface to a provider.

### Translation request

The focused flow turns a résumé and role into inspectable translations without invoking a general planner.

#### Resolve the inputs

**Resolve role and job** — Explicit request

Resolve the target role and job description so the request has a defined frame.

**Pin the catalog** — Versioned grounding

Use a pinned competency catalog to give the request a consistent vocabulary and reference for mappings.

#### Ground and validate

**Analyze the résumé** — Candidate evidence

Analyze the candidate’s experience against the role and catalog. Preserve the evidence supporting each proposed translation.

**Validate translations** — Structured output

Validate the generated output against the expected structure before turning it into interface artifacts.

#### Present and retain

**SSE cards** — Inspectable wording

Emit the completed analysis as structured cards over SSE. Presentation is staged; the cards do not imply live incremental reasoning.

**Feedback and claiming** — Continue the session

Feedback records the user’s response. Session claiming connects eligible anonymous work to an authenticated owner with token and ownership checks.

#### Connections

- **Resolve role and job → Pin the catalog (Request context):** The resolved role frames which catalog-grounded relationships matter.
- **Pin the catalog → Analyze the résumé (Pinned reference):** Analysis uses the same catalog version throughout the request.
- **Analyze the résumé → Validate translations (Proposed mappings):** Generated mappings must satisfy the structured output contract.
- **Validate translations → SSE cards (Validated artifacts):** Validated translations become cards rather than arbitrary model-authored interface markup.
- **SSE cards → Feedback and claiming (User interaction):** The user can respond to the translation and retain eligible work through session claiming.

#### Bypass general planning for a defined job

The capture experience already knows the task. A direct path keeps the interaction focused and avoids spending reasoning on selecting a workflow.

#### Keep evidence attached to wording

A translation is useful when the user can see what supports it and identify a gap. The interface makes those relationships inspectable.

#### Treat session ownership explicitly

An anonymous session and an authenticated account are different states. Claiming is a controlled ownership transition.

#### Illustrative translation contract

```json
{
  "requirement": "Own AI products end to end",
  "evidence": "Scoped and implemented a production agent",
  "translation": "End-to-end product and technical ownership",
  "gap": "No adoption metric supplied"
}
```

Synthetic example showing the relationship between requirement, source evidence, wording, and a gap. These illustrative field names explain the contract shape; they are not a literal API response.

### Authenticated agent turn

Model interpretation and application-owned structure have separate responsibilities, with an explicit order for results and commentary.

#### Plan

**Session context** — Owned conversation

Load the authenticated session context needed to interpret the request. Ownership is checked before operating on the session.

**Validated plan** — Select work

The planner produces a structured plan. Validate that output before dispatching tool work.

#### Execute and assemble

**Concurrent tools** — Bounded execution

Run selected tool work concurrently and collect the results before assembly. Concurrent execution does not mean cards stream as each tool finishes.

**Deterministic assembly** — Typed artifacts

Application code converts tool outputs into typed cards. The assembler makes no model calls.

#### Respond and persist

**Emit cards** — Structured results first

Send the assembled cards as structured events, giving the interface a predictable result shape.

**Stream commentary** — Narrative after results

Model-generated commentary streams after the structured results. The narrative explains artifacts whose structure is already established.

**Persist conversation** — Complete the turn

Persist the conversation state before emitting completion, so the next turn can build on the recorded interaction.

#### Connections

- **Session context → Validated plan (Request and history):** The planner receives the current request in session context.
- **Validated plan → Concurrent tools (Validated tool selections):** Only the validated plan is dispatched.
- **Concurrent tools → Deterministic assembly (Collected results):** Wait for the selected tool results before deterministic assembly.
- **Deterministic assembly → Emit cards (Typed payloads):** The assembler produces the structured artifacts the frontend understands.
- **Emit cards → Stream commentary (Results before narrative):** Cards precede the commentary stream.
- **Stream commentary → Persist conversation (Conversation update):** Persist the completed interaction, then signal that the turn is done.

#### Let code own interface structure

Typed artifacts give the backend and frontend a contract independent of how the model phrases its explanation.

#### Make event ordering part of the experience

Showing results before commentary lets the interface establish what happened while the explanation arrives.

#### Separate reasoning from dispatch

Validating planner output creates an explicit boundary between model intent and application execution.



## Project links

- [Soaria · earlier product site](https://www.soaria.xyz/)

---

[View this case study](https://spencerhardwick.com/work/versed/)
[All work](https://spencerhardwick.com)
