Agents IA

Pont CLI Claude

L'app spawn le binaire claude en sous-processus et communique avec lui en stream-json bidirectionnel. Zéro appel à l'API Anthropic — la session Claude Max de Yann sert d'identité.

Les agents disponibles

Définis dans appsettings.json sous Elynav.Agents. Chaque agent correspond à un fichier .claude/agents/<name>.md qui contient son system prompt et ses outils autorisés.

Agent Rôle Outils principaux
elynav-orchestrator Chef d'orchestre — plan du jour, arbitrage priorités, rapports consolidés Read, Write, MCP CRM
elynav-sdr Sales Development — sourcing ICP, enrichissement, séquences outbound WebSearch, WebFetch, MCP CRM
elynav-cmo CMO — stratégie messaging, calendrier éditorial, validation Read, Write, MCP CRM
elynav-content Content Producer — articles, posts, newsletters à partir des briefs CMO Read, Write, MCP CRM
elynav-dev-proposer Dev senior reviewer — audite code, propose des fix sous forme de diff (read-only sur prod) Read, Grep, Glob, Write (proposals only)
elynav-dev-executor Dev senior — applique diffs validés (pair-programming, branche dédiée, validation avant push) Tous outils (mode bypassPermissions en local)

Cycle de vie d'un ClaudeCliRunner

sequenceDiagram autonumber participant UI as Page Blazor participant Hub as ConversationHub (SignalR) participant Mgr as ConversationRunnerManager participant Runner as ClaudeCliRunner participant CLI as claude (sub-process) UI->>Hub: SendMessage(convId, "Salut") Hub->>Mgr: GetOrCreateAsync(convId, agent) alt Première utilisation Mgr->>Runner: new ClaudeCliRunner(agent, ctx, opts, mode) Runner->>CLI: spawn (--print --agent X --input-format stream-json ...) Runner-->>Mgr: runner else Réutilisation Mgr-->>Hub: runner existant end Hub->>Runner: SendUserMessageAsync(text) Runner->>CLI: stdin {"type":"user","message":{...}} loop Streaming CLI-->>Runner: stdout {"type":"assistant","content":"..."} Runner-->>Hub: TextDeltaEvent Hub-->>UI: messageDelta(text) end CLI-->>Runner: stdout {"type":"result","subtype":"success"} Runner-->>Hub: AgentTurnEndedEvent

Le runner persiste entre les messages d'une même conversation. Le sous-processus claude tient l'état conversationnel en mémoire jusqu'à StopAsync().

Arguments CLI assemblés

Construits par ClaudeCliRunner.StartAsync() à partir de la AgentDefinition :

claude
  --print
  --agent <name>
  --input-format stream-json
  --output-format stream-json
  --verbose
  [--mcp-config /path/.mcp.json]    # si fichier détecté dans le cwd
  [--permission-mode bypassPermissions]  # si défini sur l'agent

Modes de permission

default

Confirmations interactives. En --print non-interactif, bloque les Write/Bash non allowlistés. Mode utilisé par tous les agents en prod containerisée.

acceptEdits

Auto-accept des Write/Edit. Bash reste sous allow-list. Compromis raisonnable pour des agents qui ne font que de l'édition de fichiers.

bypassPermissions

Aucune vérification. Refusé en root par le CLI — donc inutilisable dans le conteneur Docker actuel (qui tourne en root). Réservé au pair-programming local.

plan

Read-only. L'agent peut proposer des changements mais pas les appliquer. Utile pour audit.

Parsing du flux stream-json

PumpStdoutAsync lit ligne par ligne, parse chaque ligne en JSON, dispatche en AgentEvent typé selon le champ type :

classDiagram class AgentEvent { <> } class TextDeltaEvent { Text string } class ToolUseEvent { ToolName string InputJson string Id string } class ToolResultEvent { ToolUseId string ResultJson string IsError bool } class AgentStartedEvent { SessionId string AgentName string } class AgentTurnEndedEvent { Reason string } class AgentErrorEvent { Message string } AgentEvent <|-- TextDeltaEvent AgentEvent <|-- ToolUseEvent AgentEvent <|-- ToolResultEvent AgentEvent <|-- AgentStartedEvent AgentEvent <|-- AgentTurnEndedEvent AgentEvent <|-- AgentErrorEvent

Les events sont écrits dans un Channel<AgentEvent> unbounded, et lus par les hubs SignalR qui les relaient au client Blazor pour rendu progressif.

MCP servers — accès CRM

Les agents lisent et écrivent dans la base SQLite via les MCP servers Node du dossier Salesforce/mcp-servers/, déclarés dans .mcp.json. L'app .NET n'expose pas de wrapper — elle laisse le CLI parler aux MCP servers en son nom.

Pourquoi ? Cela évite de dupliquer la logique d'accès CRM en C#. Les MCP servers Node sont la source de vérité unique pour search_accounts, create_contact, log_activity, etc.