---
suivi: 1615
date: 2026-09-18
sujet: Diagnostic — Aktor annonce une tâche de suivi sans la créer
chantier: aktor / taches_suivi
type: diagnostic
statut: poussé
hash: fa7dd7cd
fichiers:
  - docs/suivi/SUIVI_1615_diagnostic_aktor_tache_fantome.md
---

# SUIVI #1615 — diagnostic Aktor : tâche fantôme

## PROMPT ENVOYÉ

Aktor annonce (ou propose) une tâche de suivi ; rien n’est créé en
base. L’utilisateur croit sa demande enregistrée. Diagnostic lecture
seule : Aktor peut-il agir, prompt, reproduction, module tâches,
trois options. Aucun code applicatif.

## BASE GIT

HEAD local = `origin/main` au moment de la rédaction (après #1614).
Aucun fichier applicatif modifié. MySQL local `127.0.0.1:3306`
refusé (`SQLSTATE[HY000] [2002]`), comme #1610 / #1613. Aucun
échange Aktor de production lu ici. Aucun chiffre inventé.

## FICHES LUES

- **#1575 LEÇON** — un marqueur d’origine n’est pas une politique ;
  l’historique assistant convertit une hallucination en consigne de
  tâche. Impact : la chaîne « propose → carte → clic » était déjà
  établie ; le widget n’affiche pas la carte. Ce lot **confirme**
  ce trou UI et le recadre comme bug de confiance (annonce vs action).
- **#1577 LEÇON** — seuil `matched_tokens` ≥ 2, sources affichées et
  persistées. Impact : **non touché**. La FAQ foncier périmée depuis
  le 16/09 reste un sujet distinct (sources fausses ≠ tâche fantôme).
- **#1425 LEÇON** — widget = mêmes POST `/aktor/conversations` et
  `/messages`. Impact : le backend propose déjà ; le widget **jette**
  le JSON `proposition`.
- **#1424 LEÇON** — brièveté et grounding = prompt
  `AssistantAnthropicService::buildSystemBlocks`. Impact : une
  consigne « ne dis jamais je crée » existe déjà (#944) ; ce n’est
  pas un mécanisme.
- **#1050** — `tool_use` Anthropic = agent **consolidation** backlog,
  lecture seule, plus envoyé depuis #1152. Impact : ne pas confondre
  avec le chat.
- **#1040** — `$this->authorize()` sans trait = 500 silencieux sur
  la fiche cadrage admin, pas sur la création chat. Hors incident.

Aucune fiche n’a changé le plan : diagnostic seul, pas de correctif.

---

## A. AKTOR PEUT-IL AGIR, OU SEULEMENT RÉPONDRE ?

### Verdict (sans détour)

Le **chat Aktor** (page `/aktor` et widget dashboard) **n’a aucun
outil Anthropic, aucun function calling, aucune commande exécutée
par le modèle**. L’appel `POST https://api.anthropic.com/v1/messages`
(`AssistantAnthropicService::callMessagesApi` L495-500) envoie
`model`, `max_tokens`, `system`, `messages`. **Pas de clé `tools`.**

Une boucle `max_iterations` (config `assistant.max_iterations`,
défaut 4) existe encore (L31-33, L112-151). Si `stop_reason ===
tool_use`, elle **casse immédiatement** (`break` L151) sans exécuter
quoi que ce soit. C’est un vestige, pas un moteur d’action.

Donc : le modèle **ne peut pas** créer une `taches_suivi` en parlant.
S’il dit « je crée / je vais créer / c’est enregistré », il **invente
une capacité qu’il n’a pas** (ou il décrit un clic utilisateur qui
n’a pas eu lieu).

Ce n’est **pas** « la création échoue ». C’est « la création n’est
jamais demandée par le texte seul ».

### Ce qui existe, et qui n’est pas un outil modèle

Une **chaîne applicative à deux temps**, hors LLM :

1. Le modèle est **instruit** d’émettre un commentaire HTML invisible
   `<!--HECTARION_PROPOSITION:{...}-->` (`action=propose`).
2. `AssistantPropositionMarkerParser` parse ce JSON.
3. `AssistantController::resoudrePropositionAPersister` écrit
   `assistant_conversations.derniere_proposition` (JSON). **Aucune
   ligne `taches_suivi`.** Preuve test :
   `AssistantTacheSuiviCreationTest::test_store_message_propose_sans_creer_tache`.
4. Sur la **page** `/aktor` seulement, une carte « Valider et créer
   la tâche » POST
   `/aktor/conversations/{id}/valider-proposition`.
5. Alors seulement `AssistantTacheSuiviService::creerDepuisProposition`
   appelle `TacheSuiviService::creer`.

Sans le clic (ou si la carte est invisible), **zéro tâche**.

### Agent consolidation (autre produit)

`AssistantConsolidationTools` définit trois tools Anthropic
(`lister_taches_ouvertes`, `lire_tache`, `chercher_historique`) —
**lecture seule**, backlog DSI. Depuis #1152 / #1153 l’appel live
n’envoie **pas** ces tools (`callAnthropic` L866-871, invariant
L162-164). Cet agent **ne crée pas** de tâche et n’est pas le chat
utilisateur.

### Fichiers du module chat Aktor

| Rôle | Fichier |
|---|---|
| Contrôleur chat | `app/Http/Controllers/AssistantController.php` |
| Appel Anthropic + prompt | `app/Services/Assistant/AssistantAnthropicService.php` |
| Retrieval FAQ | `app/Services/Assistant/AssistantFaqRetrievalService.php` |
| Retrieval journal | `app/Services/Assistant/AssistantJournalRetrievalService.php` |
| Retrieval tâches déjà visibles | `app/Services/Assistant/AssistantTacheSuiviRetrievalService.php` |
| Parse marqueur proposition | `app/Services/Assistant/AssistantPropositionMarkerParser.php` |
| Création après clic | `app/Services/Assistant/AssistantTacheSuiviService.php` |
| Config modèle / quota / seuil | `config/assistant.php` |
| Routes | `routes/web.php` L575-604, préfixe `/aktor` |
| UI page | `resources/js/Pages/Assistant/Index.vue` |
| UI widget | `resources/js/Components/Dashboard/AktorWidget.vue` |
| Quota | `app/Services/Assistant/AssistantQuotaService.php` |

Hors chat (ne pas mélanger) : `AssistantConsolidationAgentService`,
`AssistantPropositionController` (fiche cadrage **après** création),
`AktorConsoliderCommand`.

---

## B. LE PROMPT SYSTÈME

Construit dans `AssistantAnthropicService::buildSystemBlocks`
(L191-343), heredoc `$instructions` **L211-311**, plus blocs FAQ /
journal / tâches / cadrage. Pas en base, pas dans `config/assistant.php`
(sauf le modèle et le seuil retrieval).

### Passages qui laissent entendre qu’Aktor « fait » une tâche

Exact, heredoc actuel :

L213 : « OU formuler une tâche de suivi si un besoin reste à traiter
/ si la connaissance manque. »

L223 : « propose d'escalader (action=propose → tâche de suivi pour
qu'un humain documente/traite) »

L226 : « mieux vaut « je ne sais pas, je fais remonter » qu'une
réponse fausse rassurante. »

L242 : « si l'utilisateur FORCE la création (bouton Proposer / créer,
ou demande explicite) : action=propose reste autorisé — le bouton de
création reste disponible côté UI. »

L205-206 (`$forceLigne`, bouton page) : « FORCE : l'utilisateur
demande une proposition IMMÉDIATE (Proposer / créer une tâche).
Tu DOIS émettre action=propose maintenant »

L245-247 : « Deux issues DISTINCTES (répondre ≠ créer) » puis
« texte reformulé {objet, description} pour tâche de suivi (après
validation utilisateur). »

Le verbe **créer** circule partout (bouton UI, FORCE, « bouton de
création »). Le modèle est entraîné à parler métier « tâche » en
permanence.

### Consigne interdisant d’annoncer une action non réalisée

**Oui, elle existe** — #944, L297 :

« GARDE-FOU ANTI-PROMESSE (#944) : tu ne dis JAMAIS « j'ai créé » /
« je crée » une tâche. Tu PROPOSES. La création n'a lieu qu'après
validation de la carte par l'utilisateur. Dans ton texte, annonce le
NOMBRE que tu proposes (« je te propose N tâches »), jamais une
création effectuée. »

Formulation **prescrite** : « je te propose N tâches ».
Formulations **interdites** (prompt) : « j'ai créé », « je crée ».
Le prompt **ne cite pas** « je vais créer » ni « souhaitez-vous que
je crée » : un futur ou une question restent possibles sans violer
la lettre de L297.

Une consigne dans le prompt **n’exécute rien** (#1424 / #1575).

### FAQ et journal comme source de confusion

Journal (fichiers source, visibilité M365) — extraits qui parlent
d’Aktor **comme s’il remontait** la tâche :

- `assistant-tache-suivi-proposition-2026-07-22` (`public_groupes` =
  GS_DSI) : « L'assistant te propose un texte reformulé […] ; quand
  tu valides, il crée une tâche de suivi ». **Correct** (clic).
- `aktor-assistant-refonte-2026-07-23` (GS_DSI) : « il vous répond,
  vous oriente, et remonte une tâche de suivi à la DSI quand il
  faut. » **Omet le clic.** Piste sérieuse pour un compte GS_DSI.
- `aktor-fiche-cadrage-2026-07-28` (GS_ADMIN_ERP) : « Quand Aktor
  remonte une demande d'évolution en tâche de suivi […] »
- `taches-suivi-ui-mes-taches-2026-07-01` (`tous`) : décrit comment
  **l’utilisateur** crée une tâche dans Mes tâches (objet, échéance,
  personnes, programmes). Piste : Aktor lit « comment créer » et
  parle à la 1re personne.

Les articles FAQ sont en table `aide_articles` (prod). Non lisibles
ici. SQL section C. Le retrieval #1577 n’est pas modifié.

FAQ foncier périmée depuis le 16/09 : **sujet distinct**. Elle fausse
les réponses métier ; elle n’explique pas à elle seule une tâche
non créée. À traiter à part.

---

## C. REPRODUCTION — prod inaccessible ici

`api_logs` **ne contient pas** le texte des messages. Le listener
`LogOutboundHttpCall` (L134-154) stocke statut, tokens, bytes,
`entity_type` / `entity_id` (conversation), **pas** le body. Chercher
« je vais créer » dans `api_logs` est **inutile**.

La preuve est `assistant_messages` (+ `derniere_proposition`,
`assistant_conversation_taches`).

MySQL local refusé. Requêtes prêtes pour OVH (lecture seule).

-- 1. Réponses Aktor qui parlent de créer / proposer une tâche
SELECT
  a.id AS message_assistant_id,
  a.created_at,
  c.id AS conversation_id,
  c.user_id,
  c.derniere_proposition IS NOT NULL AS proposition_en_attente,
  c.tache_suivi_id,
  LEFT(u.contenu, 240) AS question,
  LEFT(a.contenu, 500) AS reponse
FROM assistant_messages a
JOIN assistant_conversations c ON c.id = a.conversation_id
LEFT JOIN assistant_messages u
  ON u.conversation_id = a.conversation_id
 AND u.role = 'user'
 AND u.id = (
   SELECT MAX(u2.id) FROM assistant_messages u2
   WHERE u2.conversation_id = a.conversation_id
     AND u2.role = 'user'
     AND u2.id < a.id
 )
WHERE a.role = 'assistant'
  AND (
    a.contenu LIKE '%je vais créer%'
    OR a.contenu LIKE '%je vais creer%'
    OR a.contenu LIKE '%j''ai créé%'
    OR a.contenu LIKE '%j''ai cree%'
    OR a.contenu LIKE '%je crée%'
    OR a.contenu LIKE '%je cree%'
    OR a.contenu LIKE '%c''est créé%'
    OR a.contenu LIKE '%tâche est créée%'
    OR a.contenu LIKE '%tache est creee%'
    OR a.contenu LIKE '%je te propose%'
    OR a.contenu LIKE '%souhaites-tu que je crée%'
    OR a.contenu LIKE '%veux-tu que je crée%'
    OR a.contenu LIKE '%je m''en occupe%'
  )
ORDER BY a.created_at DESC
LIMIT 80;

-- 2. Volume : proposition en attente vs tâche réellement liée
SELECT
  SUM(derniere_proposition IS NOT NULL) AS conv_proposition_en_attente,
  SUM(tache_suivi_id IS NOT NULL) AS conv_avec_tache_colonne,
  COUNT(*) AS conv_total
FROM assistant_conversations
WHERE deleted_at IS NULL;

-- 3. Conversations avec proposition JSON mais AUCUNE ligne pivot
SELECT c.id, c.user_id, c.updated_at, c.tache_suivi_id
FROM assistant_conversations c
LEFT JOIN assistant_conversation_taches act
  ON act.assistant_conversation_id = c.id
WHERE c.deleted_at IS NULL
  AND c.derniere_proposition IS NOT NULL
  AND act.tache_suivi_id IS NULL
ORDER BY c.updated_at DESC
LIMIT 50;

-- 4. Formulation (compter les motifs, 30 derniers jours)
SELECT
  SUM(contenu LIKE '%je te propose%') AS nb_je_te_propose,
  SUM(contenu LIKE '%je vais créer%' OR contenu LIKE '%je vais creer%') AS nb_je_vais_creer,
  SUM(contenu LIKE '%j''ai créé%' OR contenu LIKE '%j''ai cree%') AS nb_jai_cree,
  SUM(contenu LIKE '%je crée%' OR contenu LIKE '%je cree%') AS nb_je_cree,
  SUM(contenu LIKE '%souhaites-tu que je%' OR contenu LIKE '%veux-tu que je%') AS nb_question_offre
FROM assistant_messages
WHERE role = 'assistant'
  AND created_at >= (UTC_TIMESTAMP() - INTERVAL 30 DAY);

-- 5. FAQ : articles qui expliquent la création de tâches / Aktor
SELECT a.id, a.titre, a.slug, a.is_published, a.updated_at,
       LEFT(a.contenu, 200) AS debut
FROM aide_articles a
WHERE a.contenu LIKE '%tâche de suivi%'
   OR a.contenu LIKE '%tache de suivi%'
   OR a.contenu LIKE '%Aktor%'
   OR a.titre LIKE '%Aktor%'
   OR a.titre LIKE '%tâche de suivi%'
ORDER BY a.updated_at DESC;

### Formulation — ce que le code prescrit vs ce qu’on ne peut pas mesurer

| Source | Formulation |
|---|---|
| Prompt L297 | « je te propose N tâches » — prescrit |
| Prompt L297 | « j'ai créé » / « je crée » — interdits |
| Carte `/aktor` | « Proposition de tâche de suivi » + bouton « Valider et créer la tâche » (`Index.vue` L127-140, L1008-1015) |
| Bouton forcer | « Proposer une tâche » (L1135) |
| Widget | aucun de ces libellés ; texte Aktor seul |

Sans SQL n°4, **on ne tranche pas** si le modèle dit plutôt « je vais
créer », « je te propose » ou « souhaitez-vous que je crée ». La
nuance change le correctif (prompt vs UI vs les deux).

---

## D. MODULE TÂCHES DE SUIVI

Création **humaine** : `POST /taches-suivi` →
`TacheSuiviController::store` → `StoreTacheSuiviRequest` →
`TacheSuiviService::creer`. `authorize()` du FormRequest = utilisateur
connecté. Champ **obligatoire** : `objet` (max 300). Optionnels :
description, échéance, url, importance, validation_mode, personnes,
programmes, dossier_foncier_id, etiquettes, projet_id.

Création **Aktor après clic** : même `TacheSuiviService::creer`.
Champs remplis par `AssistantTacheSuiviService` :
objet + description (obligatoires pour passer) ; importance (défaut
`normal`) ; date_echeance (null si absente / passée) ; url =
`page_concernee` ; personnes = tous les `users` avec JSON
`GS_DSI` (filet : demandeur) ; programmes si `programme_id` du
marqueur ou URL `/programmes/{id}` ; créateur SQL = **l’utilisateur
qui clique**, pas un compte Aktor.

Manquant pour un outil modèle « create tout seul » : **rien
d’indispensable** côté schéma (échéance et programme déjà optionnels ;
DSI auto-assigné). Ce qui manque est le **contrat UX** : confirmation
explicite, droits (aujourd’hui n’importe quel `GS_GROUPE` avec
`assistant.lecture` peut valider **sa** conversation), anti-doublon,
et ne pas écrire une hallucination dans `description` (#1575).

Faisabilité technique d’un outil create : **oui**. Décision : **hors
lot**, à ne pas prendre ici.

---

## E. TROIS OPTIONS (exposer, pas choisir)

### Option 1 — Aktor n’annonce plus d’action ; il explique comment faire

Renforcer le prompt (L297 élargi à « je vais créer », « je m’en
occupe », « c’est noté je crée ») + phrase type avec lien
`/mes-taches` et `/aktor`. Widget : une ligne « je ne crée pas la
tâche moi-même ».

Coût : **faible** (prompt + éventuellement 1 phrase widget). Un lot.
Risque : le modèle **récidive** (#1424 : consigne ≠ mécanisme). Les
besoins non résolus ne sont plus tracés si l’utilisateur ne sait pas
où cliquer.
Exige : ne pas toucher au retrieval #1577.

### Option 2 — Aktor crée vraiment (outil + confirmation + droits)

Function calling Anthropic côté chat, ou POST interne après un
marqueur `action=create` **uniquement** si l’utilisateur a confirmé
dans le fil. Réutiliser `creerDepuisProposition`.

Coût : **élevé** (schéma tool, boucle tool_use réelle, droits,
anti-doublon, traces, tests, widget **et** page, cadrage #1027).
Plusieurs lots. Sujet à part.
Risque : création irréversible ; description = hallucination
persistée (#1575) ; Gate/admin bypass si on copie `hasM365Group`.
Exige : arbitrage Robin, confirmation explicite, pas le cycle 4 h
Sage (hors sujet).

### Option 3 — Aktor propose ; l’UI montre un bouton que l’humain actionne

**C’est déjà le design de `/aktor`.** Le trou est le **widget**
(`AktorWidget.vue`) : il affiche `assistant_message.contenu`, ignore
`data.proposition`, n’appelle jamais `valider-proposition`. Lien
« Ouvrir dans Aktor » existe (L191-196) mais n’explique pas qu’il
faut **valider une carte**.

Coût widget : **moyen bas** (réutiliser la carte `Index.vue`, quota
inchangé). Un lot front (zone commune dashboard — sérialiser).
Risque : carte trop grande dans le widget ; double création si on
valide ici **et** sur `/aktor` (le service verrouille déjà
`derniere_proposition` après succès).
Exige : même contrat « rien en base avant clic ».

Hybride 1+3 : durcir le texte **et** montrer le bouton là où
l’utilisateur parle à Aktor.

---

## RECOMMANDATION (non exécutée)

Traiter l’incident comme un **mensonge d’interface**, pas comme un
outil cassé.

1. Mesurer en prod les SQL C (formulation réelle).
2. Si le motif dominant est « je vais / j’ai créé » : option 1
   (prompt), **sans** ouvrir l’option 2.
3. Si l’usage passe par le **widget** : option 3 (carte ou CTA
   explicite « Valider dans Aktor ») — c’est le trou prouvé par le
   code, indépendamment du SQL.
4. Ne pas livrer un create autonome dans le lot qui répare la
   confiance.

## POINTS À ARBITRER PAR ROBIN

1. L’incident rapporté vient-il du **widget dashboard**, de la page
   `/aktor`, ou des deux ?
2. SQL n°4 : quelle formulation réelle (propose / je vais créer /
   j’ai créé) ?
3. Option 1 seule, option 3 widget, ou les deux ?
4. Option 2 (create réel) : **plus tard ou jamais** ?
5. FAQ foncier périmée : lot **séparé**, pas fusionné ici.

---

## DÉPLOIEMENT

Documentation seule. OVH : `git pull origin main`. Pas de migrate,
pas de `journal:sync`, pas de npm, pas de `route:clear`.

## LEÇON

Le chat Aktor n’a pas d’outils. Il a un **marqueur + un clic**.
Annoncer la création sans le clic est un mensonge, plus grave qu’un
500. Le prompt interdit déjà « je crée » et prescrit « je te
propose » : insuffisant. Le widget consomme la même API que `/aktor`
et **n’affiche pas** la carte de validation — trou UI, pas trou SDK.
`api_logs` ne contient pas les phrases d’Aktor ; `assistant_messages`
si. Ne pas ouvrir un tool create pour réparer un mensonge.
