Keep external providers behind server consent, task, and prompt-cost gates while persisting actual provider provenance.
5.4 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 ISummarizerService to the ai-service. Ollama is primary; AI_PROVIDER names
only the optional external fallback candidate. Fallback is sequential and requires administrator
enablement, task approval, live Pro/user consent and the prompt cost/privacy ceiling. Each
AiInteraction records the provider returned by the sidecar, plus bounded model/route metadata in
ResultJson.meta; configuration alone is not treated as proof that a provider executed.
Per-request user-selectable providers remain intentionally unsupported. The server-side privacy policy selects a route, not the browser, and provider credentials remain deployment-only.
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. - Provider policy: add task types to the explicit server-side allowlist only after their payload, accounting and production checks pass; do not add browser provider overrides.
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.