Concept
nekostronaut.com est un runtime orienté événements. Le point structurant dans le code actuel est que tout n’est pas un agent: le Supervisor route certains événements vers un agent d’exécution, tandis que la persistance des séquences, le scheduling et plusieurs transitions d’état vivent dans des workers dédiés.
Routeur statique qui mappe un événement supporté vers un seul agent d’exécution, puis applique la politique et attache les insights pré-calculés.
Six agents sont instanciés dans le runtime: `lead_finder`, `personalization`, `sequence`, `reply`, `demo` et `closer`.
Le séquencement, la traversée des steps, la gestion de l’engagement, le no-show et le calcul d’insights sont portés par des workers dédiés hors Supervisor.
| Événement | Propriétaire | Comportement actuel |
|---|---|---|
| lead.created | lead_finder | Qualifie et enrichit le lead, puis peut émettre `lead.scored`. |
| lead.scored | personalization | Envoie directement le premier message outbound, puis émet `sequence.started`. |
| sequence.started | SequenceStarter | Crée les lignes de séquence et l’état initial des steps. Cet événement n’est pas routé par le Supervisor. |
| sequence.step_due | sequence | Exécute les relances ou autres actions quand le scheduler déclare un step à échéance. |
| nurture.triggered | sequence | Passe par le même SequenceAgent pour le nurturing post-démo ou post-engagement. |
| inbound.received | reply | Classe le message inbound, répond quand c’est sûr, ou escalade. |
| demo.sent | demo | Envoie l’email compagnon de démo et enregistre l’état de la démo. |
| demo.viewed / meeting.booked / meeting.cancelled | closer | Fait avancer le lead vers le closing, la reproposition de rendez-vous ou le handoff humain. |
Le worker s’abonne toujours à `lead.enriched`, mais le Supervisor ne route pas cet événement vers un agent. En pratique, il n’alimente donc pas la chaîne d’exécution standard.
Si aucun workspace unique n’est résolu, ou si aucun lead ne matche l’expéditeur après résolution du workspace, le webhook est acquitté sans créer de conversation fantôme.
Le webhook inbound email privilégie volontairement la conversation. L’app fait d’abord confiance au thread avant de faire confiance à l’adresse expéditeur, et elle échoue proprement si le routage reste ambigu.
Si Postmark fournit un `In-Reply-To` ou `References` pointant vers un message outbound connu, l’email inbound est rattaché à cette conversation avant tout contrôle sur l’adresse expéditeur.
S’il n’y a pas de thread, l’app résout d’abord le workspace via l’adresse destinataire exacte, puis via le domaine inbound configuré.
Dans le workspace résolu, l’app cherche ensuite un lead par égalité exacte sur l’email expéditeur. Sans match, le webhook est acquitté mais aucune conversation ni exécution agent n’est créée.
Le séquencement outbound est réparti entre agents et workers. Le premier message est porté par un agent, mais le scheduling durable et la progression sont gérés par des workers.
`Personalization` envoie le premier message outbound sur `lead.scored`.
`SequenceStarter` crée `sequences` et `sequence_steps` après ce premier contact.
`SequenceScheduler` sonde les lignes en attente et émet `sequence.step_due` lorsqu’un step atteint `scheduled_at`.
En mode flow, `MessageSentHandler` marque le step complété et `StepCompletionHandler` traverse le graphe. La couverture du mode historique est plus limitée.
Une réponse inbound normale annule les séquences actives; un out-of-office les met en pause.
Le Supervisor est déterministe et auditable, mais ce n’est pas un planner.
Le routage inbound est à échec fermé: un destinataire ambigu ou un expéditeur non reconnu ne crée pas de conversation fantôme.
La chaîne outbound effective est `lead.created -> lead_finder -> lead.scored -> personalization`.
Les playbooks en mode flow sont la forme runtime la plus solide; les steps historiques ordonnés s’exécutent encore.