billeisenhauer.com — AI-Augmented Job Search System
What Is This
A full-stack Rails 8.1 application that turns the job search into an engineered system. Recruiters paste a job description and get a structured fit assessment powered by Claude Sonnet, a tailored resume, a cover letter, and a booking flow — all generated from a single career context service.
The site also exposes a conversational AI interface (/ask) where anyone can ask freeform questions about my background, with answers grounded in imported LinkedIn data, case studies, and voice guidelines.
22 shipped PRDs. 87 merged PRs. 226 commits. Zero to production in ~6 days, then continuous iteration.
Why This Approach
The product is its own demonstration. The site itself is live proof of the claim “I build with AI.” A recruiter evaluating the fit assessment is experiencing the engineering judgment they’re evaluating.
Every piece of the system follows the service-layer discipline described in Sustainable Web Development with Ruby on Rails — business logic in services, thin controllers, rich Result objects, no logic in models beyond validations.
Key engineering decisions:
-
Streaming with a fallback chain. The
/askconversation uses Anthropic’s streaming API with a custom delimiter protocol (---SUGGESTIONS---) to separate prose from follow-up suggestion chips in a single stream. Turbo Streams broadcast token chunks over Action Cable. A Stimulus controller interceptsturbo:before-stream-renderto coordinate with a polling fallback. If streaming fails entirely, the system falls back to batch response. -
Content-addressed deduplication. Fit assessments are deduplicated via SHA-256 digest of normalized JD text (whitespace + case normalization only, intentionally conservative to avoid false-positive collisions). Exact-match repeats get instant redirects — no LLM call fired.
-
Prompt engineering as a first-class engineering concern. All four LLM services share a
BillContextServicethat assembles the system prompt from database-backed sources: career narrative, case study bodies (truncated, Markdown-stripped), position history, recommendations (first name + last initial only). Twelve voice guidelines enforce specific constraints (“never use ‘leveraged’ or ‘synergies’”; “name limits and contradictions in the same paragraph as accomplishments”). These are version-controlled and tested with prompt guard assertions. -
Attribution funnel as an operator insight tool.
AttributionFunnelServicecomputes conversion rates across 8 pipeline stages (assessment → resume → cover letter → code verified → booking) with 7-day/30-day/all-time windows. Includes verdict calibration — whether “strong fit” assessments convert to bookings at higher rates than “weak fit.” This is an LLM quality signal, not just a usage dashboard. -
Defense in depth on public LLM endpoints. Three layers: honeypot field, Cloudflare Turnstile, and Rack::Attack rate limiting. Plus a session message cap on the conversation interface.
-
wicked_pdf over Grover. Grover requires Node.js + Puppeteer + Chromium, none present in the production Docker image. wkhtmltopdf-binary packages the renderer as a gem binary. Caught during deployment — the kind of dependency decision that only surfaces when you deploy to a minimal container.
-
Cloak mode.
SITE_CLOAKED=trueenv var makes non-admin visitors see a 503 holding page while the admin browses normally. No deploy to toggle.
What Would Break
- Anthropic API changes or rate limits would break all four LLM services simultaneously. The shared context service is a single point of coupling.
- The voice guidelines are fragile to prompt injection — a sufficiently adversarial JD could potentially override them.
- The attribution funnel assumes a linear pipeline. If the product adds non-linear paths (e.g., direct booking without assessment), the funnel model needs rework.
What I Learned
- Streaming over Turbo Streams requires careful delimiter handling — you can’t just pipe API chunks to the browser because a delimiter might split across chunks.
- Content-addressed deduplication is simple and effective for LLM cost control, but the normalization function is a design decision with real tradeoffs.
- Voice guidelines in prompts are more effective than tone adjectives. Specific prohibitions (“never say X”) outperform vague directives (“be professional”).
- The Factory Workflow (
/plan-story → /implement-story → /verify → /demo → /pr) let me ship 22 PRDs in roughly two weeks with consistent quality. The discipline is in the loop, not in any single step.
Repo: github.com/plentyofsaas/billeisenhauer-app (private)
Status: Production at billeisenhauer.com. Active development.