Removed arbitrary character ceilings that silently discarded well-formed explanations without improving output quality. Instead:
- Relaxed reasoning char limit in compose.py (removed 500 char trim)
- Separated summary and experience truncation in engine.py (independent 500/900 budgets) so verbose summaries no longer starve role descriptions
- Removed upper bound in tiers.py polish() check (kept 120 char floor)
- Expanded all template banks from ~6 to 9 variants each for better phrasing diversity
- Expanded BEHAVIOR_TAGS from 3-4 to 5 variants per slot
Length constraints are now enforced at generation time (max_tokens) and validation (floor only), allowing grounded, well-structured prose through.
Reasoning now follows the candidate-ranking research: outcomes over
pedigree, per-requirement justification, beyond keywords, bias-free,
concise. Driven by a prompt-engineering sweep (A/B/C/D over cached
candidates): variant C (structured, verb-first, NO few-shot) won at
11/15 grounded vs 2/15 baseline; few-shot leaked its own facts and
crashed grounding to 0/15.
- Feed the candidate's OWN WORDS (summary + career descriptions) to the
LLM so it can surface a real achievement; grounding source widened to
cover that evidence.
- Winning prompt: S1 starts with a past-tense outcome verb, S2 ties to a
stated requirement, S3 covers seniority/availability + <=1 soft gap;
third person, no schools, no dashes, specific not generic.
- Deterministic "The candidate" strip -> verb-first openers; third-person
guard rejects verbatim first-person copies; smart-quote/dash normalize.
- T2 routed to Qwen3.5-2B (LFM2.5 verified useless: 3/15 verbatim copies)
and dropped from prefetch (-220 MB).
…