Cycle de vie et identité des sessions
Vue d’ensemble
Une session de démon est une conversation logique épinglée à un sessionId ACP. Le bridge maintient une SessionEntry par session (voir 03-acp-bridge.md) qui couple la connexion enfant ACP avec la comptabilité côté HTTP : files FIFO des prompts, files FIFO des changements de modèle, bus d’événements, permissions en attente, clients attachés, heartbeats, état de restauration, tombstones des trames terminales.
Un client de démon est identifié par X-Qwen-Client-Id — une chaîne opaque validée par le démon que l’appelant HTTP appose sur ses requêtes. Le bridge suit quels clients sont attachés à quelles sessions, et utilise l’ID client d’origine pour piloter la politique de permission designated, les pistes d’audit et l’attribution des événements.
Ce document explique chaque transition du cycle de vie d’une session (create / attach / load / resume / close / die / evict) et chaque surface d’identité exposée par le démon.
Responsabilités
- Créer, attacher, restaurer et nettoyer les sessions.
- Valider
X-Qwen-Client-Idet rejeter les IDs malformés. - Suivre plusieurs clients attachés par session (
clientIds: Map<string, count>,attachCount). - Apposer
originatorClientIdsur les événements sortants. - Exécuter les heartbeats pour que les tableaux de bord sachent quels clients sont encore connectés.
- Exposer les métadonnées de session (
displayName) que les opérateurs définissent viaPATCH /session/:id/metadata. - Piloter l’émission des trames terminales (
session_died,session_closed,client_evicted,stream_error).
Architecture
| Préoccupation | Source | Notes |
|---|---|---|
SessionEntry | packages/acp-bridge/src/bridge.ts | Structure par session ; voir 03-acp-bridge.md pour la liste complète des champs. |
BridgeSession (public) | packages/acp-bridge/src/bridgeTypes.ts | { sessionId, workspaceCwd, attached, clientId?, createdAt? } renvoyé aux gestionnaires HTTP. |
BridgeSessionState | packages/acp-bridge/src/bridgeTypes.ts | LoadSessionResponse | ResumeSessionResponse mis en cache sur l’entrée en tant que restoreState. |
DaemonSession (SDK) | packages/sdk-typescript/src/daemon/types.ts | { sessionId, workspaceCwd, attached, clientId?, createdAt? }. |
| Validation du Client-id | packages/acp-bridge/src/bridge.ts (autour de spawnOrAttach) | Motif [A-Za-z0-9._:-]{1,128} ; InvalidClientIdError si malformé. |
| Nettoyeur de déconnexion de session | packages/cli/src/serve/server.ts | Suit les déconnexions du propriétaire du spawn avec attachCount + spawnOwnerWantedKill. |
Machine à états
Attach vs spawn
Sous sessionScope: 'single' (par défaut), la defaultEntry du bridge est partagée par chaque client qui se connecte. Un POST /session qui arrive alors que defaultEntry existe déjà renvoie attached: true sans créer (spawn) un nouvel enfant ACP. Le bridge incrémente de manière synchrone attachCount et enregistre le X-Qwen-Client-Id de l’appelant dans clientIds.
Sous sessionScope: 'thread', chaque thread peut créer une session distincte. L’appelant respecte toujours maxSessions.
Identité
X-Qwen-Client-Id est facultatif mais fortement recommandé. Le démon n’en génère pas pour le compte de l’appelant — les clients choisissent le leur et le réutilisent d’une requête à l’autre afin que le démon puisse attribuer les votes, auditer les événements et détecter les reconnexions.
Chaque contrôleur indépendant doit utiliser un ID distinct et stable. Web Shell conserve le préfixe historique webui_ pour la compatibilité. Un hôte et un Web Shell embarqué ne doivent partager un ID que lorsqu’ils agissent intentionnellement comme un seul contrôleur logique ; une fois partagé, les logs du démon ne peuvent pas distinguer lequel des deux a initié une requête.
Règles de validation :
- Jeu de caractères :
[A-Za-z0-9._:-]. - Longueur : 1 à 128.
- En dehors de cet ensemble :
InvalidClientIdError(400).
Le démon appose originatorClientId sur les événements SSE sortants lorsque :
- La requête qui a déclenché l’événement contenait
X-Qwen-Client-Id, ET - L’ID est actuellement enregistré dans l’ensemble
clientIdsde la session, ET - La session a un
activePromptOriginatorClientIddéfini (lessessionUpdateetpermission_requestinline héritent de l’origine du prompt actif).
Les appelants anonymes (sans X-Qwen-Client-Id) fonctionnent parfaitement avec la politique first-responder ; designated rejette leurs votes avec permission_forbidden{ reason: 'designated_mismatch' } ; consensus rejette avec la même raison forbidden car le votant n’est pas dans le snapshot votersAtIssue au moment de l’émission ; local-only est la seule politique qui accepte les votants anonymes en boucle locale (loopback).
Workflow
Créer ou attacher
Load / resume
POST /session/:id/load — restaure une session persistée et renvoie la fenêtre de snapshot de relecture bornée actuelle (les notifications session/load ou la relecture en mode réponse sont amorcées avant le retour de la réponse).
POST /session/:id/resume — restaure sans relecture (connection.unstable_resumeSession, exposé sous la capacité stable session_resume du démon ; unstable_session_resume reste un alias obsolète).
Les deux :
- Utilisent un ensemble
pendingRestoreIdspar session sur le canal afin que les appels de restauration simultanés fusionnent (RestoreInProgressError). - Mettent en cache
restoreStatesur l’entrée afin qu’un client qui s’attache tardivement reçoive la même charge utile que le restaurateur d’origine.
Pour une session worktree persistée Part 4A, la restauration est une extension de ce cycle de vie soumise à une porte d’intégrité. Le sidecar identifie explicitement la racine de workspace demandée, le checkout doit être canoniquement contenu sous le répertoire .qwen/worktrees/ correspondant, et son marqueur doit être un fichier régulier à lien unique contenant l’ID de session restauré exact. Le démon ne relocalise un enfant restauré inactif qu’après ces vérifications ; un enfant actif est accepté uniquement lorsque son cwd rapporté est déjà égal au worktree, et un enfant actif dont le cwd rapporté est absent ou ailleurs échoue en mode fermé (fail closed) au lieu d’être relocalisé sous son prompt — sauf pour une restauration à froid qui n’a pas pu différer son prompt de restauration (suppressWorktreeContextRestore désactivé, donc le bridge a déclenché la question re-posée au lieu de la mettre en attente) : cette forme conserve le résultat pré-4B, renvoyant les métadonnées worktree canoniques sans worktreeState, non relocalisé, la session survivant. Les réponses relocalisées et acceptées renvoient les métadonnées worktree canoniques avec worktreeState: "persisted-v1". Un sidecar portant supersededBy n’est jamais restauré : la route renvoie 409 worktree_session_superseded avec l’ID de session de remplacement, une classification décidée à partir de ce lien seul avant toute lecture de marqueur, ainsi les appelants redirigent et réparent leur comptabilité uniquement une fois qu’un load de cet ID a réussi — un transfert interrompu pré-commit nomme un remplacement qui n’est pas le propriétaire du marqueur, ne peut pas être lui-même restauré, et est nettoyé par le reset retenté ; un remplacement restauré dont le lien supersedes correspond à l’ancien sidecar tandis que le marqueur n’a jamais bougé — ou est absent — renvoie 409 worktree_reset_interrupted, dont la réparation consiste à retenter le reset contre la session remplacée ; et un marqueur manquant sans cette paire de liens correspondants renvoie 409 worktree_marker_missing, dont la réparation consiste à réinitialiser la tâche plutôt qu’à retenter la restauration, car aucun chemin de restauration ne recrée un marqueur. La classification interrompue est vérifiée en premier. Un état Part 4A invalide détache un attachement existant ou tue une restauration à froid avec requireZeroAttaches ; un sidecar manquant ne produit également aucune attestation. Chaque fois que la source de restauration effective appartient au Channel, la route supprime le nettoyage best-effort de l’agent ACP pour un état sidecar Part 4A ou inclassable, afin que l’échec de validation préserve la preuve de checkout incertaine. Les métadonnées source persistées sont prioritaires ; lorsqu’elles sont absentes, la requête load/resume fournit la source effective. Un sidecar legacy structurellement valide sans workspaceCwd conserve à la place la restauration existante par l’agent en best-effort : il doit identifier soit la racine de workspace demandée soit le niveau supérieur de son dépôt Git, est vérifié par containment sans attestation de marqueur, peut être nettoyé par l’agent, et peut renvoyer worktree sans worktreeState. En dehors de ce cas explicite de compatibilité legacy, seules les sessions dont la source de restauration effective n’appartient pas au Channel conservent le nettoyage best-effort existant avant la validation de route.
Le transfert de propriété de worktree (POST /session/:id/worktree-reset, annoncé par session_worktree_reset_v1) étend ce cycle de vie pour les resets de tâches Channel : le démon crée un remplacement frais à portée thread dans le workspace racine, le relocalise dans le checkout vérifié, lie la paire de sidecars (supersededBy sur l’ancienne session d’abord, puis supersedes sur le remplacement), bascule le marqueur vers le remplacement sous un verrou de route par checkout et une barrière d’admission qui clôture l’admission de prompt plus les sept autres writers qui démarrent le travail dans le checkout ou déplacent le cwd de session (rewind, changement de cwd, branche, fork, shell, contrôle d’objectif, action de tâche workflow), tandis que les chemins de release et stop restent non clôturés par conception, puis coupe les inscriptions de client de la session remplacée et son association worktree en mémoire. La coupure indique si la session remplacée est réellement partie : un survivant dont l’enfant détient encore du travail en arrière-plan maintient la barrière armée, est journalisé, et est signalé à l’appelant comme supersededSessionLive: true au lieu d’être masqué par la réponse de succès. Un writer clôturé admis sur la session remplacée en cours de transfert est refusé avec 409 worktree_reset_active ; la taxonomie complète des échecs, y compris quelles fenêtres de crash un retour en arrière annule et lesquelles échouent en mode fermé pour réparation par l’opérateur, est documentée avec la route dans qwen-serve-protocol.md.
Heartbeat
POST /session/:id/heartbeat met à jour sessionLastSeenAt indépendamment du clientId. Si la requête contient un X-Qwen-Client-Id enregistré, clientLastSeenAt.set(clientId, Date.now()) est également mis à jour. L’éviction par client n’est pas implémentée dans la v1 ; la révocation est prévue pour la F-series Wave 5. Aujourd’hui, les heartbeats fournissent de l’observabilité pour les tableaux de bord et pour la future politique de révocation dans la PR 24.
Métadonnées
PATCH /session/:id/metadata accepte {displayName?}. Validation :
- Longueur max :
MAX_DISPLAY_NAME_LENGTH = 256. - Ne doit pas contenir de caractères de contrôle (
hasControlCharacterrejette les points de code ≤ 0x1f ou == 0x7f). InvalidSessionMetadataError(400) en cas de violation.
Une mise à jour réussie diffuse session_metadata_updated à chaque abonné.
Terminaison
| Trame terminale | Déclencheur |
|---|---|
session_closed | DELETE /session/:id (client_close) ou fermeture programmatique. |
session_died | channel.exited se déclenche pour n’importe quelle raison (crash, kill de l’enfant). Contient exitCode? + signalCode? lorsque le chemin de sortie de l’OS a été utilisé. |
client_evicted | Dépassement de la file d’attente par abonné sur l’EventBus (voir 10-event-bus.md). PAS une terminaison au niveau de la session — seul cet abonné est fermé. |
stream_error | SubscriberLimitExceededError ou autre échec de flux au niveau de la route. |
Les permissions en attente sont résolues en tant que {kind:'cancelled', reason:'session_closed'} via mediator.forgetSession(sessionId) à chaque chemin de terminaison.
Garde du nettoyeur de déconnexion
Lorsque la réponse HTTP du client propriétaire du spawn ne peut pas être écrite (TCP reset en plein handshake), la route appelle killSession({ requireZeroAttaches: true }). Si un autre client s’est déjà attaché (attachCount > 0), la garde court-circuite et la session continue de vivre. Définir spawnOwnerWantedKill = true mémorise l’intention afin qu’un detachClient() ultérieur qui ramène attachCount à 0 termine le nettoyage différé. Sans cela, un propriétaire de spawn qui se déconnecte rapidement détruirait une session saine à chaque reconnexion.
État et cycle de vie
Champs de SessionEntry critiques pour le cycle de vie :
| Champ | Type | Signification |
|---|---|---|
clientIds | Map<string, number> | IDs clients enregistrés → compte de références d’enregistrement. |
attachCount | number | Nombre de fois que spawnOrAttach a renvoyé attached: true pour cette entrée. |
activePromptOriginatorClientId | string? | Origine du prompt en cours d’exécution. |
restoreState | BridgeSessionState? | Réponse load/resume mise en cache pour que les clients qui s’attachent tardivement voient des charges utiles cohérentes. |
spawnOwnerWantedKill | boolean | Tombstone de nettoyage différé (voir le nettoyeur de déconnexion ci-dessus). |
sessionLastSeenAt | number? | Heartbeat le plus récent parmi tous les clients (epoch ms). |
clientLastSeenAt | Map<string, number> | Heartbeat par client. |
pendingPermissionIds | Set<string> | requestIds ACP actuellement en attente — utilisé lors de l’annulation/fermeture pour les résoudre comme annulées. |
Dépendances
- Couche ACP :
connection.newSession,connection.unstable_resumeSession,connection.loadSession. 03-acp-bridge.mdpour l’architecture globale du bridge.04-permission-mediation.mdpour comprendre comment l’origine + l’identité pilotent les décisions de politique.10-event-bus.mdpour la livraison des trames terminales.
Points de terminaison de session supplémentaires
Ces points de terminaison étendent la surface de cycle de vie de base :
Prompt non bloquant (balise de capacité non_blocking_prompt)
POST /session/:id/prompt renvoie désormais HTTP 202 avec
{ promptId, lastEventId } au lieu de bloquer jusqu’à ce que le prompt soit terminé. Le
résultat réel arrive sur SSE sous la forme de turn_complete / turn_error, et le
champ promptId fait le lien entre ces événements et la réponse 202.
DaemonSessionClient.prompt() utilise automatiquement le chemin non bloquant lorsqu’il
dispose d’un abonnement aux événements actif et fait correspondre de manière transparente le résultat du
flux SSE.
Récapitulatif de session (balise de capacité session_recap)
POST /session/:id/recap demande au modèle rapide un résumé en une ligne de type « où en étais-je ». Il renvoie { sessionId, recap: string | null } ; null signifie que
l’historique était trop court ou que le modèle a temporairement échoué. Ce point de terminaison fonctionne en
best-effort.
Session BTW / Side Question (balise de capacité session_btw)
POST /session/:id/btw pose une question ponctuelle dans le contexte de la session
sans interrompre le flux de conversation principal. Il utilise runForkedAgent sur le
chemin du cache pour un appel LLM à tour unique et sans outil, et renvoie
{ sessionId, answer: string | null }. L’implémentation applique
BTW_MAX_INPUT_LENGTH, des protections contre les fuites inter-sessions, et la gestion des timeouts.
Exécution de commandes Shell
POST /session/:id/shell exécute une commande shell directement sur l’hôte du démon,
sans passer par le LLM. Il diffuse la sortie sur le bus SSE de la session via
les événements user_shell_command / user_shell_result et injecte la commande ainsi que
son résultat dans l’historique de conversation du LLM. La réponse est
{ exitCode, output, aborted }. Pour une session live de workspace secondaire, la
route REST singulière résout le propriétaire de session et s’exécute sur le bridge de ce runtime,
donc la commande démarre dans le cwd du workspace propriétaire. La route ne
fournit pas de sandbox de chemin. Les clients ACP qualifiés par workspace peuvent continuer à utiliser
_qwen/session/shell sur la connexion du workspace propriétaire.
Session Rewind
GET /session/:id/rewind/snapshots et POST /session/:id/rewind résolvent le
runtime live du workspace propriétaire. Les sessions persistées doivent être chargées ou reprises
avant le rewind. Le rewind tronque l’historique de conversation et restaure optionnellement les
fichiers suivis par edit et write_file ; il ne défait pas les commandes shell, Git,
les scripts ou les modifications manuelles. La restauration de fichiers est best-effort, donc une réponse peut
signaler rewound: false et filesFailed[] après que l’historique de conversation a
déjà avancé. Les appels de rewind du SDK utilisent toujours le REST conscient du propriétaire, y compris lorsque le
client utilise par ailleurs le transport ACP, car la mutation doit conserver une authentification
REST stricte.
Détachement de session
POST /session/:id/detach détache explicitement un client d’une session en
décrémentant attachCount ; cela ne ferme pas la session en soi. Si aucun autre
attachement ou abonné ne reste, la session est nettoyée. L’endpoint renvoie 204.
Suppression de sessions par lot
POST /sessions/delete accepte { sessionIds: string[] } (jusqu’à 100 identifiants),
ferme les sessions du bridge et supprime les fichiers de transcription actifs ou archivés. Si des fichiers JSONL actifs et archivés existent pour le même identifiant, la suppression définitive supprime les deux
afin que les opérateurs puissent résoudre le conflit. Il nettoie les sidecars de worktree
actifs et archivés, mais laisse intacts les instantanés de l’historique des fichiers, les transcriptions des sous-agents et les sidecars d’exécution. Il utilise Promise.allSettled pour la résilience et renvoie
{ removed, notFound, errors }.
Archivage de session
POST /sessions/archive déplace les fichiers JSONL des sessions inactives de chats/ vers
chats/archive/. Si la session cible est active, le démon entre d’abord dans
une barrière d’archivage par session et effectue une fermeture stricte qui exige que l’enfant ACP
flush ChatRecordingService ; l’archivage laisse le JSONL en place si la fermeture ou
le flush échoue.
POST /sessions/unarchive replace les fichiers JSONL archivés dans chats/. Il s’agit
uniquement d’une transition d’état de stockage ; les clients doivent appeler session/load ou
session/resume ensuite. Les sessions archivées renvoient 409 session_archived pour
load/resume, et les mutations entrant en concurrence avec une transition d’archivage renvoient
409 session_archiving.
Les fichiers de transcription réguliers vides, endommagés et orphelins restent éligibles pour ces opérations de cycle de vie même lorsqu’ils ne peuvent pas être chargés en tant que conversations. Les vérifications de sécurité de propriété peuvent intentionnellement échouer en mode fermé et exiger l’intervention de l’opérateur. Un fichier modifié après qu’un writer a scellé sa preuve de transfert certifiée échoue avec SessionTranscriptChangedError jusqu’à ce que l’opérateur résolve le verrou scellé et les octets modifiés. Un premier enregistrement physique au format JSON qui dépasse la fenêtre bornée de lecture de propriété échoue avec SessionTranscriptIdentityUnavailableError jusqu’à ce que l’enregistrement soit réparé ou réduit ; les enregistrements endommagés surdimensionnés avec un préfixe non-objet restent éligibles. Un enregistrement récupéré analysable doit contenir des champs de propriété sessionId et cwd en chaîne de caractères, et les états d’archive locaux/étrangers mixtes échouent également en mode fermé. Lorsque session_storage_conflict_repair est annoncé, l’archivage et le désarchivage acceptent resolveConflicts: true : l’archivage conserve la copie archivée, tandis que le désarchivage conserve la copie active. Sans cette option, les conflits actif/archive ne déplacent pas, ne suppriment pas et n’écrasent aucune copie persistée et sont retournés dans le tableau errors du lot. L’archivage ferme toujours strictement une session live avant de classifier le conflit, ce qui peut flusher les enregistrements en attente vers la transcription active. Les routes de cycle de vie qualifiées par workspace utilisent désormais cette enveloppe de lot HTTP 200 au lieu de leur précédente réponse HTTP 409 session_conflict.
Utilisation du contexte (balise de capacité session_context_usage)
GET /session/:id/context-usage renvoie l’utilisation structurée de la fenêtre de contexte.
?detail=true inclut une utilisation plus détaillée regroupée par outil, mémoire et skill.
Statistiques de session (balise de capacité session_stats)
GET /session/:id/stats renvoie les statistiques d’utilisation : métriques du modèle
(tokens d’entrée/sortie, lectures/écritures du cache, coût total), nombres d’appels et
latences par outil, nombres de modifications de fichiers, et nombres d’invocations par skill pour la session
active. Le bloc skills reflète les chargements de corps de skill et les slash commands de skill
uniquement dans cette session ; il ne s’agit pas d’un agrégat d’activité inter-sessions.
Tâches de session (balise de capacité session_tasks)
GET /session/:id/tasks renvoie un instantané des tâches en arrière-plan pour les tâches d’agent,
les tâches shell, les tâches de monitor, et leurs états de cycle de vie. Les entrées d’agent générées
par un autre sous-agent contiennent des champs de lignée optionnels (parentAgentId,
parentName, depth) afin que les clients puissent afficher les sous-agents imbriqués sous forme d’arborescence ; voir
l’exemple de payload dans qwen-serve-protocol.md.
La capacité session_monitor_tool_correlation garantit en outre que les entrées de monitor
portent toolUseId, permettant aux clients de corréler un appel d’outil de transcription
avec les détails de sa tâche.
Statut LSP de la session (balise de capacité session_lsp)
GET /session/:id/lsp renvoie le statut LSP par session épuré pour les clients
du démon : activation, nombre total de serveurs, état indisponible/en cours d’initialisation,
et pour chaque serveur : name, status, languages, transport, command, et
error. Un LSP désactivé ou indisponible est représenté sous forme de données de statut HTTP 200,
et non comme une erreur de transport.
Relecture compactée
POST /session/:id/load renvoie désormais une BridgeRestoredSession qui peut inclure
compactedReplay?: BridgeEvent[], liveJournal?: BridgeEvent[], et
lastEventId?: number. Ces champs constituent la fenêtre de relecture en mémoire bornée du démon
pour une session live, et non une API de transcription complète. La limite de fenêtre par défaut est
de 4 MiB par session live (--compacted-replay-max-bytes), et le démarrage rejette
les limites invalides ; le plafond dur est de 256 MiB. compactedReplay est produit par
TurnBoundaryCompactionEngine : aux limites de tour, il replie les blocs de texte /
pensée consécutifs, réduit les séquences d’appels d’outils à leur état final, écarte les
signaux transitoires, et produit des logs de relecture en O(tours) au lieu de logs en O(tokens)
(généralement une réduction de 25 à 30 fois). Lorsque d’anciennes entrées de relecture ont été supprimées
de cette fenêtre d’octets, compactedReplay[0] est un marqueur synthétique sans id
history_truncated avec {reason: 'replay_window_exceeded', truncatedEvents, retainedEvents, maxBytes, truncatedTurns?, fullTranscriptAvailable: boolean}. fullTranscriptAvailable est un flag de capacité
: true signifie que le client peut paginer la transcription persistée complète avec
GET /session/:id/transcript, tandis que false signifie que seule la relecture bornée est
disponible. Les clients doivent l’afficher comme un statut et appliquer la relecture conservée
normalement ; il ne doit pas déclencher une boucle de resync.
Préchauffage du processus enfant ACP
bridge.preheat() reste disponible pour les intégrateurs explicites, mais qwen serve
tente également de préchauffer l’enfant primaire fiable après le démarrage pour
la compatibilité. Un préchauffage échoué n’est pas fatal et la prochaine commande d’exécution ou
Session réessaie ; les secondaires fiables démarrent à la première utilisation. Le Workspace Runtime
possède l’enfant pendant que le travail est actif. Après que tous les baux de Session et de gestion
se sont vidés, un channelIdleTimeoutMs omis ou à zéro nettoie l’enfant immédiatement ;
le préchauffage simple lui-même est préservé pour la première utilisation et n’arme pas ce nettoyeur.
Un délai configuré positif ou un keepalive actif maintient l’enfant réutilisable pour
la fenêtre restante plus longue. La commande publique ensure du Workspace Runtime
ajoute un bail de workspace renouvelable de dix minutes ; chaque appel réussi
réinitialise cette fenêtre, y compris lorsque le canal était déjà actif.
Configuration
BridgeOptions.maxSessions(par défaut 32) — limite maximale.BridgeOptions.sessionScope(par défaut'single'; optionnel'thread').BridgeOptions.initializeTimeoutMs(par défaut 10s) — délai de démarrage de l’enfant ACP (fabrique de canal + handshakeinitialize) et délai d’expiration par défaut des requêtes.BridgeOptions.sessionRestoreTimeoutMs(par défaut 60s) — délai pourloadSession/unstable_resumeSessionACP. Par défaut 60s ; un timeout d’initialize configuré explicitement peut l’augmenter, mais jamais le diminuer.BridgeOptions.channelIdleTimeoutMs(non défini ou0nettoie après que le travail d’exécution s’est vidé, sauf que le préchauffage simple est préservé pour la première utilisation ; une valeur positive ou un keepalive actif retarde le nettoyage, et le délai le plus long l’emporte).- Tags de capacité :
session_create,session_id_override,session_scope_override,session_load,session_resume,unstable_session_resume(alias obsolète),session_list,session_info,session_close,session_metadata,session_set_model,client_identity,client_heartbeat,session_recap,session_generation,session_btw,session_context_usage,session_tasks,session_monitor_tool_correlation,session_stats,session_lsp,session_resources,session_status,non_blocking_prompt.
Génération sans état (balise de capacité session_generation)
POST /session/:id/generate accepte { "prompt": string } et renvoie un
flux SSE limité à la requête avec les événements started, thinking optionnel, delta, done,
ou error. La requête ne lit aucun historique de conversation, n’enregistre aucun tour,
et n’expose aucun outil. L’enfant ACP utilise un modèle rapide configuré valide lorsqu’il
est disponible et utilise sinon le modèle principal de la session.
Mises en garde et limites connues
connection.unstable_resumeSessionpeut encore être instable au niveau de la couche ACP, mais le démon annonce le contrat de route v1 engagé avecsession_resume.unstable_session_resumeest conservé uniquement comme alias de compatibilité obsolète.- La v1 n’a pas d’éviction par client ; seulement une terminaison par session et par abonné. La politique de révocation est F-series Wave 5 / PR 24.
client_evicteds’applique par abonné, et non par session. Un client dont l’abonné SSE a été évincé peut se reconnecter.- Les clients anonymes (sans
X-Qwen-Client-Id) ne peuvent pas voter sous les politiquesdesignatedouconsensus.
Références
packages/acp-bridge/src/bridge.ts(définition de SessionEntry)packages/acp-bridge/src/bridgeTypes.ts(HttpAcpBridge,BridgeSession,BridgeSessionState)packages/sdk-typescript/src/daemon/types.ts(DaemonSession)packages/sdk-typescript/src/daemon/DaemonSessionClient.ts- Référence wire :
../qwen-serve-protocol.md(catalogue de routes).