Pular para o conteúdo

Ciclo de Vida da Injeção TTSR

Este documento abrange o caminho de execução atual do Time Traveling Stream Rules (TTSR), desde a descoberta de regras até a interrupção de stream, injeção de retry, notificações de extensões e gerenciamento de estado de sessão.

Na criação da sessão, createAgentSession() carrega todas as regras descobertas e constrói um 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 por rule.name com semântica de primeira ocorrência vence (prioridade maior do provedor primeiro). Duplicatas sombreadas são removidas antes do registro TTSR.

O registro é ignorado quando:

  • rule.ttsrTrigger está ausente
  • uma regra com o mesmo rule.name já foi registrada neste gerenciador
  • a regex falha ao compilar (new RegExp(rule.ttsrTrigger) lança exceção)

Triggers com regex inválida são registrados como warnings e ignorados; a inicialização da sessão continua.

TtsrSettings.enabled é carregado no gerenciador, mas atualmente não é verificado no controle de execução em tempo de runtime. Se existirem regras, a verificação de correspondência ainda será executada.

A detecção TTSR é executada dentro de AgentSession.#handleAgentEvent.

No turn_start, o buffer de stream é resetado:

  • ttsrManager.resetBuffer()

Quando atualizações do assistente chegam e existem regras:

  • monitora text_delta e toolcall_delta
  • anexa o delta ao buffer do gerenciador
  • chama check(buffer)

check() itera as regras registradas e retorna todas as regras correspondentes que passam pela política de repetição (#canTrigger).

3. Decisão de trigger e caminho de abort imediato

Seção intitulada “3. Decisão de trigger e caminho de abort imediato”

Quando uma ou mais regras correspondem:

  1. markInjected(matches) registra os nomes das regras no estado de injeção do gerenciador.
  2. as regras correspondentes são enfileiradas em #pendingTtsrInjections.
  3. #ttsrAbortPending = true.
  4. agent.abort() é chamado imediatamente.
  5. o evento ttsr_triggered é emitido de forma assíncrona (fire-and-forget).
  6. o trabalho de retry é agendado via setTimeout(..., 50).

O abort não é bloqueado por callbacks de extensões.

4. Agendamento de retry, modo de contexto e injeção de lembrete

Seção intitulada “4. Agendamento de retry, modo de contexto e injeção de lembrete”

Após o timeout de 50ms:

  1. #ttsrAbortPending = false
  2. ttsrManager.getSettings().contextMode
  3. se contextMode === "discard", descarta a saída parcial do assistente com agent.popMessage()
  4. constrói o conteúdo de injeção a partir das regras pendentes usando o template ttsr-interrupt.md
  5. anexa uma mensagem de usuário sintética contendo um bloco <system-interrupt ...> por regra
  6. chama agent.continue() para tentar novamente a geração

O payload do template é:

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

As injeções pendentes são limpas após a geração do conteúdo.

  • discard: a mensagem parcial/abortada do assistente é removida antes do retry.
  • keep: a saída parcial do assistente permanece no estado da conversa; o lembrete é anexado após ela.

5. Política de repetição e lógica de intervalo

Seção intitulada “5. Política de repetição e lógica de intervalo”

TtsrManager rastreia #messageCount e lastInjectedAt por regra.

Uma regra pode ser acionada apenas uma vez após ter um registro de injeção.

Uma regra pode ser reacionada apenas quando:

  • messageCount - lastInjectedAt >= repeatGap

messageCount é incrementado no turn_end, então o intervalo é medido em turnos completos, não em chunks de stream.

6. Emissão de eventos e superfícies de extensão/hook

Seção intitulada “6. Emissão de eventos e superfícies de extensão/hook”

AgentSessionEvent inclui:

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

#emitSessionEvent() encaminha o evento para:

  • listeners de extensão (ExtensionRunner.emit({ type: "ttsr_triggered", rules }))
  • assinantes locais da sessão
  • a API de extensão expõe on("ttsr_triggered", ...)
  • a API de hook expõe on("ttsr_triggered", ...)
  • custom tools recebem onSession({ reason: "ttsr_triggered", rules })

O modo interativo usa session.isTtsrAbortPending para suprimir a exibição do motivo de parada do assistente abortado como uma falha visível durante a interrupção TTSR, e renderiza um TtsrNotificationComponent quando o evento chega.

7. Persistência e estado de retomada (implementação atual)

Seção intitulada “7. Persistência e estado de retomada (implementação atual)”

SessionManager possui suporte completo de schema para persistência de regras injetadas:

  • tipo de entrada: ttsr_injection
  • API de anexação: appendTtsrInjection(ruleNames)
  • API de consulta: getInjectedTtsrRules()
  • a reconstrução de contexto inclui SessionContext.injectedTtsrRules

TtsrManager também suporta restauração via restoreInjected(ruleNames).

No caminho de execução atual:

  • AgentSession não anexa entradas ttsr_injection quando o TTSR é acionado.
  • createAgentSession() não restaura existingSession.injectedTtsrRules de volta no ttsrManager.

Efeito líquido: a supressão de regras injetadas é aplicada em memória para o processo ativo, mas atualmente não é persistida/restaurada entre recarregamento/retomada de sessão por este caminho.

8. Limites de race condition e garantias de ordenação

Seção intitulada “8. Limites de race condition e garantias de ordenação”
  • o abort é síncrono da perspectiva do handler TTSR (agent.abort() é chamado imediatamente)
  • o retry é adiado por timer (50ms)
  • a notificação de extensão é assíncrona e intencionalmente não aguardada antes do agendamento de abort/retry

Múltiplas correspondências na mesma janela de stream

Seção intitulada “Múltiplas correspondências na mesma janela de stream”

check() retorna todas as regras elegíveis que correspondem atualmente. Elas são injetadas como um lote na próxima mensagem de retry.

Durante a janela do timer, o estado pode mudar (interrupção do usuário, ações de modo, eventos adicionais). A chamada de retry é de melhor esforço: agent.continue().catch(() => {}) engole erros subsequentes.

  • Regex ttsr_trigger inválida: ignorada com warning; outras regras continuam.
  • Nomes de regras duplicados na camada de capability: duplicatas de menor prioridade são sombreadas antes do registro.
  • Nomes duplicados na camada do gerenciador: o segundo registro é ignorado.
  • contextMode: "keep": a saída parcial violadora pode permanecer no contexto antes do retry com lembrete.
  • Repeat-after-gap depende dos incrementos de contagem de turnos no turn_end; chunks no meio do turno não avançam os contadores de intervalo.