Le Framework en Action
team-search @ MarketPlace SA — le parcours d'une équipe en quatre épisodes : un ADR, un sprint, un incident, et un signal produit.
MarketPlace SA
Une plateforme de réservation en ligne : hôtels, expériences, transports. 80 000 établissements en Europe.
L'organisation dispose d'un Nexus Org qui porte les standards non-négociables : conformité PCI-DSS, accessibilité WCAG AA, SLA de performance baseline. La BU Digital Products ajoute un niveau intermédiaire avec ses standards UX et son design system. Trois niveaux d'héritage au total.
team-search est la two pizza team responsable du moteur de recherche et de la page de résultats. Sept personnes : Sophie (PM), Marcus (Tech Lead), deux ingénieures backend, un ingénieur frontend, une designer UX et une ingénieure data/ML. La team opère son propre Nexus Team, héritier des deux niveaux parents.
La situation
Depuis trois semaines, les ingénieures backend observent des pics de latence en production lors des mises à jour massives du catalogue. Quand un opérateur hôtelier met à jour simultanément plusieurs centaines de fiches, les écritures synchrones vers Elasticsearch saturent le thread pool. La P95 monte à 1,8 secondes pendant ces fenêtres. Marcus lance un spike de deux jours. L'équipe évalue deux options : CQRS avec read model séparé, ou indexation event-driven via Kafka. CQRS est écarté : le coût de maintenance dépasse la capacité actuelle de la team, et deux incidents documentés ont montré la fragilité du pattern. La décision : passer à une indexation asynchrone event-driven pour tous les updates catalogue.
Ce qui entre dans Knowledge
Marcus ouvre un Decision Context. Il documente la situation qui a rendu la décision nécessaire, les deux options évaluées, et les raisons précises du rejet de CQRS.
---
register: knowledge
level: team
owner: tech-lead
status: active
consumption-mode: rag
---
# Decision Context — Event-driven indexation
## Situation
P95 latency spikes to 1.8s during mass catalogue updates.
Cause: synchronous writes to Elasticsearch.
## Options evaluated
### CQRS with separate read model
Rejected: high maintenance cost for a 7-person team.
Reference: two P1s on similar pattern (project X, 2024).
## Decision taken
Async event-driven indexation via Kafka.
## Observed consequences
(to be completed after 30 days in production) Ce qui entre dans Intent
Marcus crée la Decision Directive correspondante. Courte, directement applicable, sans narration.
---
register: intent
level: team
owner: tech-lead
status: active
consumption-mode: system-prompt
---
# Decision Directive — Event-driven indexation
All hotel catalogue updates must transit through
a Kafka event before any write to Elasticsearch.
No synchronous catalogue write to Elasticsearch
is allowed in this bounded context.
Acceptance criteria: see Contracts/search-indexation-sla.md
Decision context: Knowledge/decision-contexts/event-driven-indexation.md La situation
Sophie crée le ticket SEARCH-89 : réduire la latence P95 de la page de résultats à moins de 200ms. Un agent est assigné à l'implémentation du pipeline d'indexation.
Le Context Assembler entre en jeu
Avant que l'agent commence, le Context Assembler constitue le task brief en puisant dans tous les registres applicables et la chaîne d'héritage.
---
register: intent
consumption-mode: context-injection
---
# Spec SEARCH-89
## Objective
Reduce P95 latency of the results page to under 200ms.
## Acceptance criteria
- [ ] P95 measured on the indexation pipeline: ≤ 200ms
- [ ] No synchronous Elasticsearch write (see Decision Directive /directives/event-driven)
- [ ] Load test: 500 req/s for 5 minutes without degradation Le quality gate échoue
L'agent génère le pipeline. Il invoque le skill performance-test.mcp avant de proposer le code. La P95 est à 340ms, au-dessus du seuil de 200ms. Goulot d'étranglement identifié : écriture synchrone par message vers Elasticsearch dans le consumer Kafka. L'agent ne propose pas le code. Il remonte l'observation et propose une correction : passer à un bulk write avec fenêtre de 50ms et batch de 100 documents. Marcus valide. L'agent génère le code corrigé, relance le test, obtient P95 = 138ms. Le gate passe.
Une mise à jour Contracts s'ensuit
Marcus constate que le seuil de 200ms dans Contracts était calibré pour des pipelines synchrones. Il ne tient pas compte de la latence de flush propre aux bulk writers asynchrones. Il met à jour le Contracts de la team pour ajouter un seuil P95 ≤ 80ms pour le bulk writer seul.
La situation
Trois semaines après la mise en production, une alerte Datadog se déclenche un mardi matin à 8h47. Les résultats de recherche affichent des hôtels avec des disponibilités périmées. Le consumer Kafka est en backpressure : 12 000 messages en attente dans la partition. Un opérateur vient de mettre à jour 3 400 fiches simultanément.
L'agent exécute le runbook
L'agent recherche dans le registre Operations par similarité sémantique. Le runbook le plus proche est catalog-indexation-lag.md. Il l'exécute : diagnostique le lag (12 847 messages, dernière écriture ES il y a 4m32s), scale le consumer group de 2 à 6 instances, attend la résorption. Lag résorbé en 8 minutes. Durée totale de l'incident : 23 minutes. L'agent notifie Sophie et Marcus via Slack.
[08:47:12] Alert received: kafka.consumer.lag > 5000 (partition: catalog-updates)
[08:47:13] Searching Operations register: "kafka consumer lag catalogue indexation"
[08:47:13] Matched: operations/runbooks/catalog-indexation-lag.md (similarity: 0.94)
[08:47:14] Invoking MCP skill: catalog_indexation_lag(partition="catalog-updates")
[08:47:15] Diagnosed: lag=12847 messages, last_write=4m32s ago
[08:47:16] Action: scaling consumer group from 2 → 6 instances
[08:47:45] Monitoring lag resorption...
[08:55:22] Lag resolved: 43 messages remaining
[08:55:23] Skill result: status=resolved, duration=487s
[08:55:24] Notified: sophie@marketplace.sa, marcus@marketplace.sa
[08:55:24] Incident duration: 23 minutes Le post-mortem alimente Knowledge et Operations
Marcus organise le post-mortem 48h plus tard. L'agent produit un draft structuré à partir des logs et de l'exécution du runbook. Deux artefacts sont créés ou mis à jour : le runbook dans Operations gagne une nouvelle cause connue (backpressure Elasticsearch), et un nouveau Decision Context est ajouté à Knowledge documentant le pattern de fragilité des bursts opérateurs.
La situation
Deux semaines après l'incident, l'ingénieure data/ML livre une analyse sur deux mois de données d'usage. Les résultats sont clairs : les utilisateurs qui obtiennent des résultats avec une précision de pertinence supérieure à 0,75 convertissent 3,4 fois plus. L'hypothèse initiale dans Intent posait que la vitesse était le premier levier de conversion. L'analyse la contredit.
Intent est mis à jour
Sophie révise l'hypothèse produit dans Intent : la pertinence des résultats est le premier levier de conversion, devant la vitesse d'affichage. Implication sur la roadmap : prioriser l'amélioration de l'algorithme de ranking sur le sprint S+1. Accepter une latence P95 plus haute pour les requêtes avec boosting complexe.
---
register: intent
level: team
owner: product-manager
status: active
supersedes: intent/hypotheses/speed-first.md
---
# Product Hypothesis — Relevance as primary conversion lever
## Previous hypothesis (superseded)
Speed is the primary conversion lever for the results page.
## Revised hypothesis
Relevance precision > 0.75 is the primary conversion lever.
Source: ML analysis 2026-05-20, 2 months of production data, n=48,000 sessions.
Conversion lift: 3.4× for precision > 0.75 vs. baseline.
## Roadmap implication
Sprint S+1: prioritize ranking algorithm improvement.
Accept P95 > 200ms for multi-criteria boosting queries. La tension avec Contracts
Cette révision crée une tension directe avec le Contracts org de performance (P95 < 200ms pour toutes les requêtes). Les requêtes avec boosting multi-critères dépassent structurellement ce seuil : leur P95 observé est de 320ms. La team ne peut pas simplement modifier le Contracts org : elle doit déclarer une exception.
Le workflow d'exception
Marcus prépare le contract team avec exception-to pointant vers org/contracts/performance.md. Le Context Assembler détecte que exception-approved-by est null : il bloque l'inclusion de ce contract dans le system prompt jusqu'à validation. Marcus le soumet au responsable qualité de la BU Digital Products. Trois jours plus tard, le responsable BU valide. Le Context Assembler inclut désormais l'exception dans le contexte des agents de la team-search.
---
register: contracts
level: team
exception-to: org/contracts/performance.md
exception-approved-by: null # ← Context Assembler blocks until validated
---
# → Marcus submits to BU Digital Products quality lead
# → BU lead validates (3 days later)
---
register: contracts
level: team
exception-to: org/contracts/performance.md
exception-approved-by: bu-digital-quality-lead # ← now included
exception-approved-date: 2026-05-28
---
P95 for multi-criteria boosting queries: ≤ 350ms (exception to 200ms org standard) Ce que les quatre épisodes traversent
| Mécanisme | Épisode |
|---|---|
| ADR splitté en Decision Context + Decision Directive | Ep. 1 |
| Knowledge alimenté par la mémoire des décisions | Ep. 1 |
| Context Assembler assemblant le task brief | Ep. 2 |
| Quality gate Contracts déclenché par l'agent | Ep. 2 |
| Auto-correction avant soumission | Ep. 2 |
| Boucle Ship → mise à jour Contracts | Ep. 2 |
| Runbook Operations exécuté par l'agent | Ep. 3 |
| Boucle Sync → enrichissement Knowledge + Operations | Ep. 3 |
| Hypothèse produit révisée dans Intent (Shape) | Ep. 4 |
| Mécanisme d'enforcement : extension vs exception | Ep. 4 |
| Validation cross-niveaux (team → BU) | Ep. 4 |
| Context Assembler bloquant un contract non approuvé | Ep. 4 |