TX — Signals + Temporal

TX — Signals + Temporal (transversal)

Categoría: Plumería transversal (no contiene lógica de negocio) Estado: Propio sano — mantenido del target V2 documentado. Síntesis de: Claude TX + Codex TX


1. Responsabilidad

TX es la capa transversal que hace que V3 sea durable, observable, idempotente y aprendible. Coordina procesos, persiste señales operacionales y mantiene la historia de cada documento / plan.

Sub-componente Función
TX.Signals Bus inmutable tipado que acumula eventos de las capas y los proyecta a superficies (tags ERP, comments, audit log, alerts internas).
TX.Temporal Durabilidad, retries por dominio de fallo, idempotencia por workflow_id, aislamiento de entornos.

2. NO es su responsabilidad

  • No contiene criterio contable. Un workflow no decide cuenta ni impuesto.
  • No agrega validaciones ni guards. Las validaciones viven en sus capas (M1, I1).
  • No mutates DB directamente. Toda escritura va vía activity que llama a un repository.
  • No es la única forma de ejecutar el pipeline. Tests y evals invocan AccountingV3Pipeline directo, saltándose Temporal.
  • No conoce semántica fiscal ni contable. Pasa estructuras opacas entre activities sin inspeccionarlas.

3. Señales first-class

V3 necesita eventos estructurados para poder aprender y auditar.

Catálogo central — SIGNAL_REGISTRY

Cada signal tiene SignalSpec:

SignalSpec {
    code: str,                          # "DUDA_CUENTA", "EDITED_BY_HUMAN", ...
    severity: ERROR | WARNING | INFO,
    intent: REVIEW | INFORM | BLOCK,
    audience: list[INTERNAL | CLIENT_VIA_ERP_TAG | CLIENT_VIA_ERP_COMMENT | AUDIT_LOG],
    template_message_es: str,           # i18n string
    blocks_execution: bool,
    dedupe_key_template: str?,
}

Emitir un signal con código no registrado falla. Tipado runtime.

Familias de signals

Familia Ejemplos Audience típica
Decisión IA con duda DUDA_CUENTA, DUDA_IMPUTACION, DUDA_DEDUCIBILIDAD tag ERP + comment + audit
Decisión IA con fallback AVISO_CUENTA_GENERICA, AVISO_IMPUTACION_GENERICA tag ERP + audit
Error material ERROR_LINEAS_VS_TOTAL, ERROR_CUENTA_INVALIDA tag ERP + comment + alert
Provider info PROVEEDOR_NUEVO tag ERP + audit
Calcificación REGLA_APLICADA, REGLA_PROPUESTA_PROMOVIDA, REGLA_INVALIDADA audit + alert ops
Reverse-sync EDITED_BY_HUMAN, CRITERION_CHANGED, APPROVED_BY_HUMAN audit + alert ops
Validación post-write READBACK_DRIFT, COMPENSATION_REQUIRED audit + alert
UX PENDING_PLAN_CREATED, BULK_APPROVE_DETECTED audit

Dedupe

Cada signal tiene dedupe_key (template parametrizado por document_id, plan_id, etc.). El bus mantiene seen_dedupe_keys por workflow execution. Mismo key → segundo emit se descarta.

Projection a surfaces

Al final del workflow, signal_projector consume el SignalBag y proyecta:

Surface Cómo
Tags ERP holded.add_tags(document, [signal.code para signal con CLIENT_VIA_ERP_TAG audience])
Comments ERP holded.post_comment(document, render_signals_as_md([...]) con dedupe por hash
Audit log Intelia INSERT en signal_audit_log table
Alerts internas Slack/PostHog para signals ERROR o patrón excede umbral

La projection es idempotente. Re-ejecución no duplica.

4. Workflows principales

4.1 Document Accounting Workflow

Parent workflow por documento:

DocumentAccountingWorkflow {
    children: [
        MotorWorkflow (M1 fases 1-5),
        IntegratorWorkflow (I1: plan gen + validation + executor + final val)
    ],
    signals: emite/recibe del bus,
    output: ExecutionOutcome
}

4.2 Reverse-Sync Monitor Workflow

Independiente, continuous (con continue_as_new periódico para liberar history):

ReverseSyncMonitorWorkflow {
    activities: [
        poll_holded_changes_activity,
        interpret_comments_activity (LLM),
        emit_signals_to_bus_activity
    ]
}

4.3 Calcification Loop Workflow

Scheduled, durable:

CalcificationLoopWorkflow {
    activities: [
        collect_evidence_activity,
        propose_rule_activity,
        shadow_test_activity (compara contra agente N=15),
        wait_for_intelia_ops_approval (signal-based),
        promote_rule_activity
    ]
}

5. Idempotencia por diseño

  • workflow_id determinista por document_id + version.
  • Reuse policy REJECT_DUPLICATE en producción. Dispara el mismo doc dos veces no duplica trabajo.
  • Para reprocessing legítimo: workflow_id = f"doc-{document_id}-v{m1_version}". Nuevo workflow_id permite re-run sin colisión.
  • Activities con dedupe_key propio para cada operación que toque Holded.

6. Estado y versionado

V3 evita overwrites conceptuales. Cada artefacto relevante tiene versión:

  • internal_plan_v<N> por documento.
  • execution_plan_v<N> por documento.
  • motor_metadata con rule_versions, llm_versions, input_hash.
  • signal_audit_log versionado.

No se sobrescribe nada silenciosamente. Re-decisión produce nuevo versión.

7. Retries por dominio de fallo

Dominio Política Razón
LLM call 3 reintentos, backoff exponencial 1s → 10s LLM falla por rate limit o glitch transitorio
Provider API write 5-10 reintentos con backoff Holded a veces 5xx; recovery alto
DB read 10 reintentos rápidos Casi siempre conexión transitoria
DB write con constraint 1 intento, no retry Constraint failure = bug; no se arregla solo

non_retryable=True para input validation errors, schema mismatches contractuales.

8. Aislamiento de entornos

Workers arrancan con dos namespaces:

  • intelia-prod (o intelia-stage): workflows productivos.
  • evals: aislado, para batch evals sin contaminar history productivo.

Tests locales saltan Temporal completamente (pipeline invocable directo).

9. Contratos de actividades

Las actividades son estrechas y propiedad de cada capa:

Actividad Capa propietaria
prepare_facts M1
decide_account / decide_cost_center / decide_fiscal M1
compose_internal_plan M1 (función pura inline, no activity necesariamente)
lookup_plan_store I1
call_plan_generator I1
execute_provider_group I1
capture_readback I1
validate_final I1
poll_holded_changes T4
interpret_comments_llm T4
collect_evidence T3
shadow_test T3
promote_rule T3
signal_projector TX

Las actividades NO llaman servicios cruzados. Cada una pertenece a una capa.

10. Observabilidad

V3 debe responder:

  • ¿Por qué se decidió esta cuenta? (M1 audit log + decision rationale + rule version)
  • ¿Por qué se ejecutó este plan Holded? (I1 plan + ExecutionPlan + agent reasoning_trace)
  • ¿Qué edición hizo el contable y cuándo? (T4 signals)
  • ¿Qué reglas calcificadas aplicaron en este cliente este mes? (T3 audit)
  • ¿Cuántos planes están pendientes ahora mismo? (Pending Plans state)
  • ¿Cuál es el coste LLM por documento por madurez? (Métricas TX)

11. Fronteras

Con M1, I1, T3, T4, S1

TX es plumería pura. NO contiene:

  • Lógica fiscal o contable.
  • Decisión semántica.
  • Razonamiento de integración.
  • Traducción de operaciones.

Si una decisión semántica entra en TX (e.g. "si confidence baja, skip executor"), está en el sitio equivocado. Vive en I1 o S1.

12. Riesgos y mitigaciones

Riesgo Mitigación
Workflows con lógica de dominio Lint custom: workflows no llaman a decide_* o interpret_* directamente. Solo orquestan activities
Signals se quedan in-memory si workflow crashea Signal bag persiste en Temporal state. Recoverable on resume
Latencia adicional por activities granulares Activities con failure domain compartido se agrupan
workflow_id colisión en reprocessing version suffix explícito. Check de "ya en progress" antes de start
Dedupe de comments rota cuando cambia COMPOSER_VERSION dedupe_key incluye composer_version
LLM activities saturan worker pool Worker pool tuneado por tipo. LLM activities con pool dedicado
Reverse-sync monitor consume recursos forever continue_as_new periódico para liberar history
Temporal history se infla con muchos signals Signals INTERNAL persisten fuera del workflow (a DB Intelia). Workflow solo carga referencias

13. Lo que puede salir mal

Patología 1: TX se convierte en sitio "por comodidad" para lógica que no encaja. Cuando una decisión no tiene lugar claro, se mete en workflow porque ahí "tiene state". Temporal absorbe decisiones. Mitigación: lint + code review obligatorio.

Patología 2: signal explosion. Cada capa empieza a emitir signals indiscriminadamente. 200+ tipos al año, contable ve 20 tags por documento. Mitigación: catálogo central con review obligatorio. Lint que prohíbe emit_signal("custom_code", ...) con código fuera del registry.

Patología 3: dedupe de comments rota. Comments duplicados en Holded porque dedupe_key incompleto. Mitigación: hash de signal bag completo, no per-signal. Si bag cambia → repost; si no → skip.

Patología 4: Temporal history excesivo. Cada signal añade evento al history. 1000 signals/workflow → history pesa MB. Mitigación: signals INTERNAL fuera del history.

14. Métricas operacionales

Métrica Target
tx_workflow_latency_p95 <60s path feliz, <120s con LLM Plan Generator
tx_workflow_failure_rate <0.5% (excluyendo non-retryable expected)
tx_signal_projection_success_rate >99%
tx_temporal_history_size_avg <500 events por workflow
tx_reverse_sync_workflow_lag <30s desde edit a signal emitido
tx_dedupe_save_rate Mide signals deduplicados — detecta bugs de over-emission

15. Open questions

  • ¿Cuál es el schema mínimo común de signal para todo V3?
  • Versionado del SIGNAL_REGISTRY: política de aliasing cuando se renombra signal.
  • Granularidad de activities: ¿call_plan_generator es 1 activity o N (una por tool call)? Trade-off granularidad vs latencia.
  • ¿Cada cuánto continue_as_new en monitor workflows? Propuesta: cada 1000 events.
  • ¿Activities asíncronas tras workflow completion? Para projection a alert lento.
  • Aislamiento per-tenant: ¿cada cliente tiene namespace propio o todos en intelia-prod? Trade-off seguridad y operacional.

Diferencias respecto al target V2 documentado

El target V2 ya tiene transversales Signals + Temporal bien diseñadas. V3 las conserva mayoritariamente, con cambios menores:

  1. Adapter explícito Validation → Signals vía tabla declarativa.
  2. Workflows nuevos para T3 y T4 (no existían en target V2).
  3. Signal Registry como contrato versionado (no solo dict).
  4. Persistencia de signals INTERNAL fuera del workflow history para mantener Temporal history compact.

El resto del diseño (parent + 2 children, durabilidad, retries por dominio, idempotencia por workflow_id, aislamiento de entornos, pipeline invocable sin Temporal) se mantiene.


Síntesis. Detalle granular en versiones originales.