| name | profile-schema |
| description | Single source of truth dello SCHEMA del candidate_profile.yml — il formato canonico che TUTTO il team produce e consuma. Modello a 3 livelli: core congelato + blocchi standard + blocchi custom liberi. Definisce i 6 `kind` di blocco che il web sa rendere e la regola di governance (nessun agente inventa il formato). Ogni write del profilo va validato con `jht profile validate`. Riferita da profile-yaml, onboarding-flow, parse-cv, cv-structure. |
| allowed-tools | Bash(jht profile validate *), Bash(python3 *) |
profile-schema — il formato canonico del profilo
Tutti scrivono e leggono lo stesso profilo: l'Assistente lo costruisce, il web lo mostra,
Analista/Scorer/Scrittore lo consumano. Se ognuno usa chiavi diverse, il web non sa
renderizzare e il push perde dati. Questa skill è l'unico formato concordato. La
definizione macchina-verificabile vive in:
shared/config/profile-schema.ts (tipi Zod, lato web)
shared/skills/validate_profile.py (gate runtime, lato agenti/CLI)
🎚️ Modello a 3 livelli
L1 CORE campi congelati e mandatori → tipizzati, interrogabili (matching, /map)
L2 STANDARD slot raccomandati (about, goals, preferences, strengths) → blocchi
L3 CUSTOM carta bianca: blocchi su misura per QUESTA persona → blocchi
L2 e L3 sono entrambi blocchi nello stesso array blocks:. La differenza è solo che
gli L2 usano key raccomandate (sotto); gli L3 hanno key libera che scegli tu.
🧊 L1 — core (sempre presente, mai inventare le chiavi)
name: <str>
target_role: <str>
location: <str>
experience_years: <int>
has_degree: <true|false>
seniority_target: <str>
email: <str>
timezone: <str>
nationality: <str>
birth_year: <int>
industry: <str>
skills:
primary: [<str>, …]
secondary: [<str>, …]
languages:
- language: <str>
level: <str>
experience:
- company: <str>
role: <str>
period: "Sep 2021 - Feb 2023"
summary: |- …
education:
- institution: <str>
degree: <str>
year: <str>
work_authorization:
- region: eu
status: "EU citizen — free movement"
location_preferences: [<città>, …]
contacts:
email: … phone: … linkedin: … github: … website: … address: …
🧩🎨 L2/L3 — blocks (il cuore della flessibilità)
blocks:
- key: <slug stabile>
kind: <uno dei 6 sotto>
title: <titolo mostrato>
ord: <int opzionale>
content: <forma dipende dal kind>
I 6 kind (sono TUTTI — non inventarne altri)
| kind | content | quando |
|---|
narrative | stringa markdown (1ª persona) | testi raccontati: about, goals, aspirazioni |
key_points | [{heading, text}] | preferenze, punti di forza (sezionati, NON un blob) |
tag_list | [<str>] | competenze extra, interessi, città, ruoli target |
key_value | [{label, value}] | dati a coppie: "consulting fit", dettagli settore |
timeline | [{title, subtitle, period, detail}] | sequenze datate ulteriori |
distribution | [{label, value}] | quando un donut/grafico aiuta (es. mix città) |
narrative è il fallback universale: se un dato non entra negli altri 5, usalo lì.
Esempi reali
blocks:
- key: about
kind: narrative
title: Chi sono
content: |-
Sono un analista di credit risk con due anni in private equity…
- key: strengths
kind: key_points
title: Punti di forza
content:
- heading: Analisi del credito
text: Due diligence forense, rating, revisione portafogli.
- heading: Comunicazione
text: Presento deep dive di settore e case study.
- key: consulting_fit
kind: key_value
title: Affinità consulting
content:
- label: Interesse
value: Alto, su temi finance/transaction/strategy
- label: Punto debole
value: Voti non sempre adatti ai track più selettivi
- key: beyond_work
kind: tag_list
title: Oltre il lavoro
content: [Skateboard downhill (campione HU), Canottaggio, Teatro]
🤝 Governance — NON inventare il formato
Ogni profilo è diverso e va mostrato al meglio: hai carta bianca sul contenuto dei
blocchi L3. Ma il formato è congelato. Regole:
- Mai inventare un
kind fuori dai 6. Se un dato non entra, usa narrative.
- Mai inventare chiavi L1 o rinominarle (es.
languages[].name ❌ → language ✅).
- Se ti serve davvero un nuovo
kind o un nuovo campo core, proponilo al Capitano —
l'estensione dello schema passa da qui (e da profile-schema.ts + validate_profile.py),
mai da una convenzione locale del singolo agente.
✅ Validazione obbligatoria dopo OGNI write
Sostituisce il vecchio yaml.safe_load "è YAML valido" con "è conforme allo schema":
jht profile validate
python3 /app/shared/skills/validate_profile.py "$JHT_HOME/profile/candidate_profile.yml"
VALID_PROFILE → prosegui. INVALID_PROFILE → leggi gli ERROR:, correggi, rivalida.
I WARN: (es. chiave legacy) non bloccano ma vanno sistemati quando tocchi quella sezione.
Non parlare all'utente finché non è VALID_PROFILE (un profilo rotto svuota la UI).
See also
profile-yaml — meccanica di scrittura/validazione del file (usa QUESTO schema)
profile-summaries — i testi narrativi → diventano blocchi kind: narrative
onboarding-flow — quando aggiornare cosa
shared/config/profile-schema.ts · shared/skills/validate_profile.py