- consolidate API ownership and remove dead vendor code - add Stripe billing, learning paths, and public CV hardening - add migration, recovery, security, audit, and browser gates
5.6 KiB
AI Career Assistant (Phase 5)
Phase 5 (2026-07-18). The per-application AI Workspace: modules, prompt flow, provider abstraction, history model, extension points. Companion to
cv-builder.md,career-profile-model.md, andMASTER_IMPLEMENTATION_GUIDE.md(AI philosophy). Verified against code + a running container.
Principle
AI assists the application workflow — it never replaces the user. Every module is suggestion only: it returns markdown the user reviews and copies. Nothing is applied automatically to the master profile, a CV variant, or the application. Every prompt carries the guardrail "preserve every factual claim — never invent employers, titles, dates, qualifications, or metrics." Job tracking stays the core product; the assistant improves the application it hangs off.
Where it lives
Each job application gains an AI Workspace tab (AiWorkspacePanel) in the job details dialog. It
is the central place for all AI on that application. Pre-existing per-tab AI (candidate-fit, focus-plan,
interview-prep) is untouched — the workspace is the unified, history-backed home for the Phase 5 modules.
Modules
AiWorkspaceService.Modules, all via ISummarizerService.SummarizeSectionAsync:
| Module | Produces |
|---|---|
job-analysis |
Company/role/skills/tech/experience/education/soft-skills/responsibilities/salary/benefits/work-model/visa/language + summary + likely interview topics + confidence |
career-match |
Match % + reasoning, strengths, weaknesses, missing skills, most-relevant experience, suggested improvements |
cover-letter |
A tailored letter in one of 6 tones (professional, friendly, short, detailed, modern, traditional) |
interview |
Company research, likely/behavioural/technical questions, STAR answer outlines, prep checklist |
application-review |
Overall strength (rating), missing info, weak areas, ATS issues, grammar/clarity, formatting |
Tailored CV is not re-implemented here — it is the Phase 4 CV Builder (CvVariant linked to the
job application). The workspace links to it rather than duplicating it.
Prompt flow
job (JobApplication + Company) ─┐
├─► AiWorkspaceService builds { instruction + guardrail, source }
master profile text ────────────┘ │
▼
ISummarizerService.SummarizeSectionAsync ──► ai-service (active provider)
│
▼
AiInteraction (append-only history) ──► markdown suggestion to the UI
source = the job context (BuildJobContext) plus the user's master profile text
(ApplicationUser.ProfileCvText). No profile fields are written; the text is read-only input.
History model
AiInteraction (JobTrackerApi/Models/AiInteraction.cs) is append-only — one row per generation, never
overwritten. This is deliberately distinct from AiWorkspaceNote (a one-row-per-type cache for
candidate-fit/focus-plan). History gives the user restore/reuse (re-surface a past result), compare
(view two side by side), copy, and delete. ResultJson is { text, meta? }; Provider records which
provider produced it. Each row also stores input/output character counts and a conservative estimated
token count (characters ÷ 4); the estimate is provider-neutral because the sidecar currently returns
text rather than provider billing metadata. Tenant-scoped (owner query filter), cascades with the application.
API (AiWorkspaceController, /api/jobapplications/{id}/ai): GET modules (+ current provider),
POST generate, GET history?module=, DELETE history/{id}. GET /api/ai/usage returns current-month
and all-time totals; the workspace displays the monthly calls and estimated tokens.
Provider abstraction
Generation goes through the existing ISummarizerService → ai-service, which routes to the active
provider (AI_PROVIDER: ollama | gemini | groq) — production can offload a weak local GPU to a cloud
provider. Each AiInteraction records the resolved provider for transparency, and GET …/ai/modules
returns the current provider so the UI can show it.
Per-request user-selectable providers (module 8's "users can choose provider") is a plumbing
extension, not yet wired end-to-end: it needs (a) ai-service to accept a per-request provider
override and (b) an API key configured for each selectable provider. Both are deployment/credential
concerns (a live paid key per provider), so the code path is left as a documented extension point
rather than shipped half-configured. The abstraction already isolates the change to one method.
Extension points
- New module: add a key to
AiWorkspaceService.Modules+ a prompt builder + aswitcharm. No new storage, controller, or UI wiring — the panel enumerates modules fromAI_MODULES. - New cover-letter tone: add to
CoverLetterModes+ModeGuidance. - Structured (JSON) results: swap a module's prompt for JSON and parse into
ResultJson.meta; the UI already rendersresult.textas markdown and can readmeta. - User-selectable provider: thread a
providerparam throughISummarizerService→ ai-service; gate on the provider having a configured key (see above).
Security
Suggestion only; never overwrites user content; everything requires the user to copy it in. The
ai-service stays backend-only (ai_internal network, X-Ai-Service-Token) — see current.md §16.
Markdown renders React nodes (no dangerouslySetInnerHTML), so AI output has no HTML-injection path.