Salta ai contenuti

Ciclo di vita dell'iniezione TTSR

Questo documento descrive l’attuale percorso runtime delle Time Traveling Stream Rules (TTSR), dalla scoperta delle regole all’interruzione dello stream, all’iniezione di retry, alle notifiche delle estensioni e alla gestione dello stato della sessione.

Alla creazione della sessione, createAgentSession() carica tutte le regole scoperte e costruisce un TtsrManager:

const ttsrSettings = settings.getGroup("ttsr");
const ttsrManager = new TtsrManager(ttsrSettings);
const rulesResult = await loadCapability<Rule>(ruleCapability.id, { cwd });
for (const rule of rulesResult.items) {
if (rule.ttsrTrigger) ttsrManager.addRule(rule);
}

loadCapability("rules") deduplica per rule.name con semantica first-wins (priorità del provider più alta per prima). I duplicati oscurati vengono rimossi prima della registrazione TTSR.

La registrazione viene saltata quando:

  • rule.ttsrTrigger è assente
  • una regola con lo stesso rule.name era già stata registrata in questo manager
  • la regex non riesce a compilarsi (new RegExp(rule.ttsrTrigger) genera un’eccezione)

I trigger con regex non valide vengono registrati come warning e ignorati; l’avvio della sessione prosegue.

TtsrSettings.enabled viene caricato nel manager ma attualmente non viene verificato nel gating runtime. Se esistono regole, il matching viene comunque eseguito.

Il rilevamento TTSR viene eseguito all’interno di AgentSession.#handleAgentEvent.

Al turn_start, il buffer dello stream viene resettato:

  • ttsrManager.resetBuffer()

Quando arrivano aggiornamenti dall’assistente e le regole esistono:

  • monitora text_delta e toolcall_delta
  • accoda il delta nel buffer del manager
  • chiama check(buffer)

check() itera le regole registrate e restituisce tutte le regole corrispondenti che superano la politica di ripetizione (#canTrigger).

3. Decisione di trigger e percorso di abort immediato

Sezione intitolata “3. Decisione di trigger e percorso di abort immediato”

Quando una o più regole corrispondono:

  1. markInjected(matches) registra i nomi delle regole nello stato di iniezione del manager.
  2. le regole corrispondenti vengono accodate in #pendingTtsrInjections.
  3. #ttsrAbortPending = true.
  4. agent.abort() viene chiamato immediatamente.
  5. l’evento ttsr_triggered viene emesso in modo asincrono (fire-and-forget).
  6. il lavoro di retry viene pianificato tramite setTimeout(..., 50).

L’abort non è bloccato sui callback delle estensioni.

4. Pianificazione del retry, modalità contesto e iniezione del promemoria

Sezione intitolata “4. Pianificazione del retry, modalità contesto e iniezione del promemoria”

Dopo il timeout di 50ms:

  1. #ttsrAbortPending = false
  2. legge ttsrManager.getSettings().contextMode
  3. se contextMode === "discard", scarta l’output parziale dell’assistente con agent.popMessage()
  4. costruisce il contenuto dell’iniezione dalle regole in attesa usando il template ttsr-interrupt.md
  5. aggiunge un messaggio utente sintetico contenente un blocco <system-interrupt ...> per ogni regola
  6. chiama agent.continue() per riprovare la generazione

Il payload del template è:

<system-interrupt reason="rule_violation" rule="{{name}}" path="{{path}}">
...
{{content}}
</system-interrupt>

Le iniezioni in attesa vengono svuotate dopo la generazione del contenuto.

Comportamento di contextMode sull’output parziale

Sezione intitolata “Comportamento di contextMode sull’output parziale”
  • discard: il messaggio parziale/abortito dell’assistente viene rimosso prima del retry.
  • keep: l’output parziale dell’assistente rimane nello stato della conversazione; il promemoria viene aggiunto dopo di esso.

TtsrManager tiene traccia di #messageCount e di lastInjectedAt per ogni regola.

Una regola può essere attivata solo una volta dopo che ha un record di iniezione.

Una regola può essere riattivata solo quando:

  • messageCount - lastInjectedAt >= repeatGap

messageCount viene incrementato al turn_end, quindi il gap viene misurato in turni completati, non in chunk dello stream.

6. Emissione degli eventi e superfici di estensione/hook

Sezione intitolata “6. Emissione degli eventi e superfici di estensione/hook”

AgentSessionEvent include:

{ type: "ttsr_triggered"; rules: Rule[] }

#emitSessionEvent() instrada l’evento verso:

  • listener delle estensioni (ExtensionRunner.emit({ type: "ttsr_triggered", rules }))
  • sottoscrittori locali della sessione
  • l’API delle estensioni espone on("ttsr_triggered", ...)
  • l’API degli hook espone on("ttsr_triggered", ...)
  • i custom tool ricevono onSession({ reason: "ttsr_triggered", rules })

Differenza di rendering nella modalità interattiva

Sezione intitolata “Differenza di rendering nella modalità interattiva”

La modalità interattiva utilizza session.isTtsrAbortPending per sopprimere la visualizzazione del motivo di arresto dell’assistente abortito come errore visibile durante l’interruzione TTSR, e renderizza un TtsrNotificationComponent quando l’evento arriva.

7. Persistenza e stato di ripristino (implementazione attuale)

Sezione intitolata “7. Persistenza e stato di ripristino (implementazione attuale)”

SessionManager ha pieno supporto dello schema per la persistenza delle regole iniettate:

  • tipo di entry: ttsr_injection
  • API di aggiunta: appendTtsrInjection(ruleNames)
  • API di query: getInjectedTtsrRules()
  • la ricostruzione del contesto include SessionContext.injectedTtsrRules

TtsrManager supporta anche il ripristino tramite restoreInjected(ruleNames).

Nel percorso runtime attuale:

  • AgentSession non aggiunge entry ttsr_injection quando il TTSR si attiva.
  • createAgentSession() non ripristina existingSession.injectedTtsrRules nel ttsrManager.

Effetto netto: la soppressione delle regole iniettate viene applicata in memoria per il processo in esecuzione, ma attualmente non viene persistita/ripristinata attraverso il ricaricamento/ripristino della sessione tramite questo percorso.

8. Limiti di race condition e garanzie di ordinamento

Sezione intitolata “8. Limiti di race condition e garanzie di ordinamento”
  • l’abort è sincrono dal punto di vista del gestore TTSR (agent.abort() viene chiamato immediatamente)
  • il retry è differito tramite timer (50ms)
  • la notifica alle estensioni è asincrona e intenzionalmente non attesa prima della pianificazione di abort/retry

check() restituisce tutte le regole idonee attualmente corrispondenti. Vengono iniettate come batch nel successivo messaggio di retry.

Durante la finestra del timer, lo stato può cambiare (interruzione dell’utente, azioni di modalità, eventi aggiuntivi). La chiamata di retry è best-effort: agent.continue().catch(() => {}) sopprime gli errori successivi.

  • Regex ttsr_trigger non valida: saltata con warning; le altre regole continuano.
  • Nomi di regole duplicati a livello di capability: i duplicati con priorità inferiore vengono oscurati prima della registrazione.
  • Nomi duplicati a livello di manager: la seconda registrazione viene ignorata.
  • contextMode: "keep": l’output parziale in violazione può rimanere nel contesto prima del retry con promemoria.
  • Il repeat-after-gap dipende dagli incrementi del contatore di turni al turn_end; i chunk a metà turno non avanzano i contatori di gap.