Track A — Points d'extension
Un rendu kaboum neuf démarre, passe ses tests, et ne fait rien d’utile —
par conception. Le template possède la plomberie de plateforme ; vous
possédez le domaine. Cette frontière est explicite : sept points
d’extension que le template scaffolde une fois et que vous devez remplir.
Les chemins ci-dessous utilisent booking comme domain_module d’un
acteur généré avec project_name=kalendar.
| # | Point d’extension | Chemin dans l’acteur généré |
|---|---|---|
| 1 | Client de domaine | src/booking/client.py |
| 2 | Modèles de domaine | src/booking/models.py |
| 3 | Outils LangChain | src/agent/tools/booking_tools.py |
| 4 | Champs de configuration de domaine | src/config.py, section 4 |
| 5 | Migrations de domaine | migrations/004_*.sql et suivantes |
| 6 | Prompts système | ROLE_PLAYBOOK dans src/agent/agent.py |
| 7 | Serveurs MCP | variable d’environnement MCP_SERVERS_JSON — zéro code |
1. Client de domaine — src/booking/client.py
Section intitulée « 1. Client de domaine — src/booking/client.py »Une classe encapsulant le système externe auquel votre acteur parle (Google Calendar, Gmail, une API interne). Le scaffolding est délibérément minimal :
import structlog
from src.config import KalendarConfig
logger = structlog.get_logger(__name__)
class BookingClient: """Client for booking operations."""
def __init__(self, config: KalendarConfig): self.config = config logger.info("booking_client_initialized")
async def health_check(self) -> bool: """Check if the domain service is reachable.""" ...Initialisez-le dans le bloc de lifespan de src/main.py marqué
# Domain client (non-critical), et gardez le constructeur sans effet de
bord pour que les tests puissent l’instancier sans identifiants.
2. Modèles de domaine — src/booking/models.py
Section intitulée « 2. Modèles de domaine — src/booking/models.py »Des modèles Pydantic simples partagés par votre client et vos outils. Rien de spécifique au framework ici :
from datetime import UTC, datetime
from pydantic import BaseModel, Field
class Booking(BaseModel): """A calendar booking."""
id: str = Field(..., description="Booking identifier") title: str = Field(..., description="Event title") status: str = Field(default="active", description="Booking status") created_at: datetime = Field(default_factory=lambda: datetime.now(UTC)) metadata: dict | None = Field(default_factory=dict)3. Outils LangChain — src/agent/tools/booking_tools.py
Section intitulée « 3. Outils LangChain — src/agent/tools/booking_tools.py »Les outils suivent le motif de classe BaseTool : un schéma d’entrée
Pydantic, un name/description explicite que le modèle lit, et _run
et _arun tous les deux :
from langchain_core.tools import BaseToolfrom pydantic import BaseModel, Field
from src.config import KalendarConfig
class CreateBookingInput(BaseModel): """Input schema for create_booking.""" title: str = Field(description="Event title") start_iso: str = Field(description="Start time, ISO 8601")
class CreateBookingTool(BaseTool): """Create a booking on the connected calendar."""
name: str = "create_booking" description: str = "Create a calendar booking with a title and start time." args_schema: type[BaseModel] = CreateBookingInput config: KalendarConfig = None
def __init__(self, config: KalendarConfig, **kwargs): super().__init__(**kwargs) self.config = config
def _run(self, title: str, start_iso: str) -> str: return self._arun(title, start_iso)
async def _arun(self, title: str, start_iso: str) -> str: # call the domain client here ...Les outils ne s’enregistrent jamais eux-mêmes. L’orchestrateur
(src/agent/orchestrator.py) les construit et alimente un ToolRegistry
— des sources nommées fusionnées avec dédoublonnage par tool.name, la
première inscription gagnant. Le rendu de base enregistre deux sources,
domain puis mcp ; les plugins d’intégration en ajoutent sans toucher
au registre.
4. Champs de configuration de domaine — src/config.py, section 4
Section intitulée « 4. Champs de configuration de domaine — src/config.py, section 4 »La section 4 de la classe Settings est à vous ; les sections 1–3 et 5+
appartiennent au template. Chaque champ de domaine porte des annotations
x-kaoohi-* qui pilotent le formulaire de déploiement du marketplace :
# ========================================================================= # 4. BOOKING (domain-specific parameters) # ========================================================================= calendar_id: str = Field( default="", description="Google Calendar ID to operate on", json_schema_extra={ "x-kaoohi-section": "booking", "x-kaoohi-managed-by": "user", "x-kaoohi-ui": {"widget": "text", "order": 30}, }, )Des conventions encodées dans les commentaires du template lui-même :
x-kaoohi-sectiondoit correspondre à un id dansKAOOHI_SECTIONS(l’id de section par défaut de la section 4 est votredomain_module).widgetetordersont requis dansx-kaoohi-ui;orderdoit être unique dans sa section.- Les secrets vers les services externes sont optionnels avec un défaut
None—calendar_api_key: SecretStr | None = Field(default=None, ...). Des secrets requis cassent la construction des tests, et un défautSecretStr("")fait échouer la validation de l’émetteur de configuration.
L’ensemble complet des annotations et les règles de validation derrière lui sont couverts dans ](fr/track-a/config-and-verify/).
5. Migrations de domaine — migrations/004_*.sql et suivantes
Section intitulée « 5. Migrations de domaine — migrations/004_*.sql et suivantes »Les migrations 001–003 sont celles du template (conversations,
messages, stats, jetons OAuth) — ne les renumérotez ni ne les éditez
jamais. Les vôtres commencent à 004 et suivent le style maison :
CREATE TABLE IF NOT EXISTS, noms d’index préfixés
idx_{project_name}_, et un en-tête de commentaire disant ce que la
migration crée. Le runner suit les fichiers appliqués dans
{project_name}_migrations :
-- ============================================================-- 004: booking slots cache-- ============================================================
CREATE TABLE IF NOT EXISTS kalendar_booking_slots ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), kompanion_id UUID NOT NULL, slot_start TIMESTAMPTZ NOT NULL, slot_end TIMESTAMPTZ NOT NULL);
CREATE INDEX IF NOT EXISTS idx_kalendar_booking_slots_kompanion ON kalendar_booking_slots(kompanion_id, slot_start);Un test embarqué (test_schema_sensitive_columns.py) échoue si une
migration ajoute une colonne sensible sans la garde attendue — gardez-le
vert.
6. Prompts système — ROLE_PLAYBOOK dans src/agent/agent.py
Section intitulée « 6. Prompts système — ROLE_PLAYBOOK dans src/agent/agent.py »La connaissance du domaine de votre acteur vit dans exactement une
constante. Les deux constantes SYSTEM_PROMPT_* au-dessus appartiennent
au template : copier update les réécrit, et une modification là entre
en conflit à chaque version du template. ROLE_PLAYBOOK est ajouté au
prompt que le rôle sélectionne, donc un seul texte couvre les surfaces
équipe et client :
ROLE_PLAYBOOK: str = ( "Refuse bookings outside opening hours (10:00–18:00, Mon–Fri).\n" "Always confirm the customer's phone number before rescheduling.\n" "Answer in French." )Écrivez les refus, des scénarios numérotés, le vocabulaire du domaine et
les règles d’escalade — la frontière que le code applique, dite à voix
haute. Puis épinglez les phrases porteuses dans
tests/test_system_prompt.py (PINNED_PHRASES associe une description à
la sous-chaîne exacte), pour qu’une régression de prompt échoue
bruyamment au lieu de produire silencieusement un modèle qui a arrêté de
refuser quelque chose.
7. Serveurs MCP — MCP_SERVERS_JSON, zéro code
Section intitulée « 7. Serveurs MCP — MCP_SERVERS_JSON, zéro code »Les outils MCP externes se chargent au démarrage sans changement de code.
Définissez MCP_SERVERS_JSON avec un objet JSON indexé par nom de
serveur ; main.py construit un MultiServerMCPClient
(langchain-mcp-adapters) et enregistre les outils chargés comme source
mcp du registre aux côtés de vos outils de domaine :
MCP_SERVERS_JSON='{ "google-calendar": { "transport": "streamable_http", "url": "https://mcp.example/calendar", "headers": {"Authorization": "Bearer ${CAL_MCP_KEY}"} }}'Un serveur mal formé ou injoignable journalise un avertissement et est ignoré — MCP est non critique par conception.
Métadonnées marketplace : [tool.kaoohi] dans pyproject.toml
Section intitulée « Métadonnées marketplace : [tool.kaoohi] dans pyproject.toml »Aux côtés des sept points de code, l’émetteur lit un bloc [tool.kaoohi]
que vos réponses copier kaoohi_* ont semé une fois : slug, nom,
description, catégorie, connecteurs, image et ressources de déploiement,
déclarations de sécurité et de sortie réseau. Vous l’éditez à la main
après génération — il appartient à l’acteur. Avec vos champs Settings il
produit src/kaoohi/config.json, le fichier que le marketplace consomme.
Ce pipeline, et comment le vérifier, c’est la page suivante.
Des conventions à intérioriser
Section intitulée « Des conventions à intérioriser »- Avertir, ne pas crasher. Chaque service non critique — Redis, client d’auth, MCP, client de domaine, mesh — s’initialise dans un try/except et journalise un avertissement en cas d’échec. PostgreSQL est le seul service critique : pas de base, pas de démarrage. Faites de même dans votre propre câblage.
- Un seul rédacteur par fichier. Chaque fichier a un propriétaire :
les fichiers du template sont réécrits par
copier update, pas les fichiers de l’acteur. Les trappes de sortie (ROLE_PLAYBOOK,src/config_actor.pyviainclude_actor_config_mixin) existent pour que votre contenu vive à côté de la structure possédée par le template sans conflit. - Événements structlog, snake_case. Les événements de log sont des
identifiants style
booking_client_initializedavec des champs structurés — suivez le motif dans chaque module du template.
Au-delà des sept canoniques, le template scaffolde des points d’extension
optionnels quand vous les activez : src/{domain}/runtime.py
(disponibilité), jobs.py (scheduler), coordination.py (mesh),
src/api/panels.py (panneau propriétaire), src/config_actor.py
(configuration possédée par l’acteur). Les modes opératoires pour tous
ceux-ci vivent dans le
guide de développement et d’extension
de kaboum.