Decidir resume vs fork vs fresh con summary tipado segun validez de contexto y deteccion de tool results stale.
Decidir de forma auditable, no por intuición del modelo, entre tres transiciones de sesión de agente: resume (contexto aún válido, objetivo continuo), fork (explorar ramas paralelas sin interferencia) y fresh con TypedSummary (el mundo cambió y el scratchpad quedó stale). [DOC]
El núcleo de ingeniería es la detección de staleness: identificar cuándo los resultados cacheados (lecturas de archivo, salidas de comando, estados de build) ya no reflejan el estado actual, y comprimir el scratchpad en hechos verificables —no un transcript crudo. [INFERENCIA]
No la uses (anti-scope) para: tareas de dominio sin sesión persistente (p. ej. generar un reporte financiero), un solo turno sin contexto previo, o input vacío. Si no hay SessionContext previo, no hay transición que decidir → no activar. [INFERENCIA]
SessionContext (timestamp de captura, tool_results[] con su source+mtime/hash, invariantes del mundo: HEAD de git, hash del lockfile, esquema de BD) y un Goal (continuo | ramificable). [CODE]Transition (Resume | Fork | Fresh) + un reporte JSON de decisión con: transición elegida, stale[] detectados, razón del disparo, y TypedSummary cuando es fresh. [CODE]bash skills/session-lifecycle-management/scripts/check.sh. [CONFIG]SessionContext: timestamp, tool_results con hash/mtime de origen, e invariantes del mundo (HEAD, lockfile, esquema). [CODE]mtime/hash divergió, márcalo stale. Una sola dependencia stale crítica invalida el resume. [CODE]resume; ramificable sin estado mutable compartido → fork; staleness crítico o mundo cambiado → fresh. [CODE]TypedSummary. Emite objeto tipado, no transcript: goal, decisions[], open_questions[], verified_facts[] (cada hecho con su evidencia), stale_dropped[]. [CODE]fresh, qué quedó stale) y pasa el gate. [CODE]mtime + hash, no solo mtime. mtime solo da falsos positivos (touch sin cambio) y falsos negativos (clock skew, checkout). El hash es la verdad; mtime es el filtro barato que evita hashear todo. Trade-off: doble fuente por algo de costo. [INFERENCIA]TypedSummary sobre transcript crudo. El transcript reintroduce results stale que el modelo tratará como verdad actual y quema tokens. El tipado fuerza a descartar lo stale explícitamente. Trade-off: pierdes matiz conversacional a cambio de hechos verificables. [INFERENCIA]fresh. Sesgo conservador: el costo de un resume sobre contexto corrupto (decisiones sobre datos falsos) supera el de re-sintetizar. [SUPUESTO]resume permitido pero el TypedSummary debe dropear ese result, no arrastrarlo. [INFERENCIA]stale siempre o no la almacenes como verified_fact. [SUPUESTO]fresh —la criticidad de la dependencia manda sobre la continuidad del objetivo. [CODE]resume pero un verified_fact falla al reusarse en el turno actual → reclasifica a fresh y re-sintetiza. [INFERENCIA]fork empieza a leer/escribir el workspace de otro → el aislamiento se rompió; detén y re-provisiona scratchpads. [INFERENCIA]TypedSummary contiene un hecho sin source o cuyo source está en stale_dropped → bug de filtrado; corrige antes de emitir. [CODE]# GOOD: decision explicita basada en validez de contexto + staleness tipado
def decide_transition(ctx: SessionContext, goal: Goal) -> Transition:
stale = [tr for tr in ctx.tool_results if is_stale(tr)] # mtime/hash vs origen
if any(tr.critical for tr in stale):
# el mundo cambio: no reusar, sintetizar y reiniciar
return Fresh(summary=typed_summary(ctx, drop=stale))
if goal.is_branchable and not goal.shares_mutable_state:
return Fork(branches=goal.hypotheses, isolated_scratchpad=True)
return Resume(ctx) # contexto valido y objetivo continuo
def typed_summary(ctx: SessionContext, drop: list[ToolResult]) -> TypedSummary:
dropped_sources = {d.source for d in drop}
return TypedSummary(
goal=ctx.goal,
decisions=ctx.decisions,
open_questions=ctx.open_questions,
verified_facts=[f for f in ctx.facts if f.source not in dropped_sources],
stale_dropped=list(dropped_sources),
)# ANTI: resume ciego tras un refactor masivo + transcript crudo como "summary"
def next_session(prev_transcript: str, goal: Goal) -> Session:
# 1) Reusa el contexto sin verificar si los archivos cambiaron (resume tras refactor).
# 2) Pega el transcript completo viejo: ruido, tokens, y tool results stale
# que el modelo tratara como verdad actual.
return Session(context=prev_transcript, goal=goal)Otros anti-patrones: marcar stale solo por mtime sin verificar hash; forks que comparten un workspace; emitir un TypedSummary que conserva results de fuentes droppeadas. [INFERENCIA]
fresh en lugar de resume?Apóyate en
assets/(rúbricaassets/quality-rubric.jsonyassets/checklist.md) para puntuar el reporte antes del done. [CONFIG]
scripts/check.sh sin errores. [CONFIG]resume | fork | fresh y su razón referencia evidencia concreta (qué stale, qué invariante cambió). [CODE]fresh: existe TypedSummary y stale_dropped no está vacío cuando hubo staleness. [CODE]fork: cada rama tiene scratchpad propio declarado. [CODE]verified_fact proviene de una fuente listada en stale_dropped. [CODE]katas-session-resume-forkworkspace-governanceworkflow-forgequality-guardianfdad39c
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.