Turing College Logo

Do what you love

Do what you love

About this project

1. What Gustav does

Gustav is an agentic career assistant that solves what keyword search cannot: it finds the occupation you cannot name. Ordinary job search fails the person who is stuck precisely because they do not know which title to type. Gustav works the other way around. It learns what you are good at and what you want, and maps that onto a knowledge base of more than three thousand occupations. Three promises define it. First, it finds what you cannot name. Second, it is built for total recall, so nothing that genuinely fits is ever silently missed. Third, it still shows you only what is relevant, with perfect precision. You are never buried in noise, and you never lose the hidden option that was right for you.

2. The technical approach

Gustav is engineered to win on two evaluation metrics that normally trade off against each other. In RAGAS terms these are context recall and context precision: push recall up and precision usually falls, tighten precision and you start missing things. Gustav refuses that trade.

Recall is handled by a fully autonomous agentic ReAct loop. Given only its own knowledge of the data, its tools, and the user's request, the agent designs its own retrieval strategy, invents its own recall evaluation (a self-chosen control group of about ten marker occupations), and keeps searching, reformulating, and expanding until its self-measured recall reaches one hundred percent, that is, until every one of its own markers has been recovered. No developer fixes the steps; the agent chooses them.

It does this with a full arsenal of retrieval methods, on three levels.

Occupation level:

  • Fuzzy and keyword lexical search: trigram similarity (pg_trgm) plus case-insensitive matching (db_search)

  • Occupation vector search by meaning: e5 embeddings, 768 dimensions, HNSW index (similar_occupations)

  • A four-layer hybrid fused by Reciprocal Rank Fusion, combining title trigram, German full-text, skill vector, and occupation vector into one ranking (search_occupations)

  • Governed free-form SQL for exact codes, full-text-only, or vector-only lookups (run_read_query)

Skill level:

  • Per-skill vector search, where every skill has its own embedding (search_by_skill)

  • Skill-to-skill nearest neighbors for synonyms and related skills (skill_neighbors)

  • Skillset coverage as an idf-weighted bundle match (search_by_skillset)

  • Keyword search on skills via SQL or exact URI

Skill-cloud level:

  • Shared-cloud match, finding occupations that share a single thematic facet (search_by_skill_cloud)

  • Reading an occupation's thematic skill clusters (cluster_occupation_skills)

  • Characterizing a whole retrieved set at once (characterize_set)

The agent picks which of these to use, and in what order, based on each individual user request. And it learns: a persistent strategy memory records what worked, held at three levels, one per data dimension, one per tool, and one general strategy memory, so future searches start from earned experience rather than from scratch.

Once recall is satisfied, Gustav switches to precision. It classifies every retrieved occupation one by one against the user's own criteria: the personal fit read from the CV, the twelve shared fulfilment dimensions, and the individual criteria the user wrote in chat, each scored from one to five with a plain-language reason. A deterministic reachability signal, with no language model involved, measures how well the user's own skills actually cover each occupation. The list is re-ranked, never filtered, so nothing is silently discarded, and the user accepts or rejects with reasons while Gustav adjusts the weights and re-ranks. Only then does it search real job boards for matching openings.

3. Architecture and security

Gustav runs on a FastAPI and Postgres backend with a pgvector knowledge base, driven by a React frontend, but its defining architectural choice is the agent runtime. To run a capable agent on a Claude subscription rather than on a per-call API key, the ReAct loop executes inside a caged Claude CLI, in a Docker-on-Colima container, reachable only through typed tools behind an MCP door. That same cage is the security boundary, so the cost decision and the hardening turned out to be one and the same artifact.

Security is structural, not bolted on, and the real boundary is the database grant, not a text filter. The agent's free-form SQL runs under a dedicated read-only role that is deny-by-default, an allow-list of nineteen tables and three views, read only, with a one-second timeout and a fresh connection per query. A second, stricter verifier role, scoped to just two views, independently re-checks any query before it is learned. Every user's private data is isolated at the row level by Postgres, fail-closed so that no identity yields zero rows, and the agent's identity always comes from its session token, never from a tool argument, so no tool call can reach another user's data. The container sits on an internal, deny-by-default network with a memory-only home, no host mount, and no shell; outbound traffic is permitted only to Anthropic, through a Squid egress filter. A PII filter guards long-term memory, and an append-only, forgery-resistant strategy memory stops the agent from fabricating its own approvals. The project is also honest about its remaining limits, for example the agent running as root inside the container and the tool door binding broadly, protected by a bearer token and a host allow-list.

Created byAdrian Sprenger
Published atJuly 16, 2026
CourseAI engineering
Looking for
Potential usersCo-founderNew jobFreelance projects