Task — engineering-spec@1

"CUO supervisor Phase 4 — 5 special-case workflow handlers: time-critical SLA bypass, per-instance iteration, multi-output fan-out, sequential-approval gating, persona-pair partnership"

doneTASK-CUO-106
module cuo · class product · priority p0 · created 2026-05-18 · shipped 2026-05-18
depends on TASK-CUO-104, TASK-CUO-105 · blocks none

§1 — Description (BCP-14 normative)

The CUO supervisor MUST ship 5 workflow Handler subclasses at modules/cuo/cuo/core/handlers/ dispatched from workflow pattern: frontmatter, with 8 memory audit kinds, and updated workflow YAML in the 9 affected catalog workflows.

  1. MUST validate workflow_pattern against closed enum per DEC-2381 (cardinality 6, default linear).
  1. MUST dispatch in dispatch.py::pick_handler(workflow) per DEC-2387:
  1. MUST implement TimeCriticalHandler per DEC-2382:
  1. MUST implement PerInstanceHandler per DEC-2383:
  1. MUST implement MultiOutputHandler per DEC-2384:
  1. MUST implement SequentialApprovalHandler per DEC-2385:
  1. MUST implement PersonaPairHandler per DEC-2386:
  1. MUST preserve audit-chain integrity: all 8 new memory audit kinds (DEC-2388) routed through cyberos.core.writer.Writer (no direct file writes).
  1. MUST wire handler dispatch into cli.py execute subcommand: when workflow has pattern != linear, log # dispatched to <HandlerClass> before invoking.
  1. MUST version-bump modules/cuo/pyproject.toml from 3.0.0a3 to 3.0.0a4.
  1. MUST NOT mutate skill_chain[] at runtime (forbidden by CUO AGENTS.md §A.12).
  1. MUST NOT bypass HITL halts in sequential_approval (the approval gate IS a HITL pause).
  1. MUST NOT drop the cuo.handler_dispatched memory row for any non-linear pattern execution.

§2 — Why this design

Why dispatch by frontmatter pattern: field (DEC-2387)? Workflow author declares the pattern in YAML; the supervisor reads it and picks the Handler. Keeps the linear/default path unchanged (zero performance regression for 185/194 workflows) and makes the special cases self-documenting.

Why one Handler subclass per pattern (DEC-2381)? Each pattern has distinct invariants — time-critical wants SLA tracking, per-instance wants fan-in summary, multi-output wants fan-out delivery, sequential-approval wants HITL gates, persona-pair wants peer handoff. Lumping them into a generic handler with a giant switch statement loses these invariants in code review.

Why peer lookup by shared_artefact.content_hash (DEC-2386)? Persona-pair handoffs are about shared artefact ownership, not about routing strings. Looking up by content hash ensures both personas see the same artefact even if their workflows name it differently.

Why version 3.0.0a4 not 3.1.0? Phase 4 is alpha-grade like Phase 1–3 — handler implementations are basic; production hardening (retries, timeouts, observability) comes in 3.2.0.


§3 — API contract

Workflow frontmatter additions (per affected workflow):

# Time-critical workflow
pattern: time_critical
sla_minutes: 240
# Per-instance workflow
pattern: per_instance
instance_descriptor:
  source: workflow.inputs.account_list
  fields: [account_id, account_name, account_tier]
# Multi-output workflow
pattern: multi_output
output_recipients:
  - { recipient_id: "vn-mst", format: "filing-xml-mst", delivery_method: "email" }
  - { recipient_id: "vn-mof", format: "filing-pdf-mof", delivery_method: "portal" }
# Sequential-approval workflow (the gated one)
pattern: sequential_approval
gates:
  - { approver_persona: "chief-ethics-officer", approver_workflow: "per-model-card-ethics-sign-off" }
# Persona-pair workflow
pattern: persona_pair
peer_persona: "cco-customer"
peer_workflow: "churn-collaboration"
shared_artefact: "churn-cohort-analysis"
handoff_step: 4

CLI surface:

cyberos-cuo execute <persona>/<workflow>  # auto-detects pattern from frontmatter
  → dispatched to TimeCriticalHandler   (when pattern: time_critical)
  → dispatched to PerInstanceHandler    (when pattern: per_instance)
  → dispatched to MultiOutputHandler    (when pattern: multi_output)
  → dispatched to SequentialApprovalHandler  (when pattern: sequential_approval)
  → dispatched to PersonaPairHandler    (when pattern: persona_pair)

§4 — Acceptance criteria

  1. workflow_pattern enum cardinality 6.
  2. Default pattern (linear) routes through existing execute_chain() unchanged — no perf regression for 185 affected workflows.
  3. TimeCriticalHandler emits sla_breach when duration > limit.
  4. TimeCriticalHandler bypasses any scheduling layer — invokes synchronously.
  5. PerInstanceHandler iterates exactly len(instance_descriptor) times.
  6. PerInstanceHandler fan-in summary outcome=COMPLETED_BATCH when all succeed; outcome=PARTIAL when any fail.
  7. MultiOutputHandler renders final-step output once per recipient.
  8. MultiOutputHandler emits 1 memory row per recipient.
  9. SequentialApprovalHandler halts on approver failure.
  10. SequentialApprovalHandler resumes on approver success.
  11. PersonaPairHandler routes to peer at declared handoff_step.
  12. PersonaPairHandler shared_artefact content_hash matches across peer chains.
  13. All 8 new memory audit kinds emit through cyberos.core.writer.Writer.
  14. CLI execute prints # dispatched to <HandlerClass> for non-linear patterns.
  15. 9 affected workflows updated with correct pattern: frontmatter.
  16. Existing 21/22 tests still pass post-change.
  17. 6 new test files green (one per handler + dispatch).
  18. pyproject.toml version bumped to 3.0.0a4.
  19. CUO docs site §12 Roadmap updated — Phase 4 marked shipped.
  20. No workflow's skill_chain[] mutated at runtime.

§5 — Verification

# modules/cuo/tests/test_applier_paths.py
def test_dispatch_default_is_linear():
    """Workflows without a pattern: field route to LinearHandler (= existing execute_chain)."""
    from cuo.core.handlers.dispatch import pick_handler
    workflow_dict = {"frontmatter": {}, "body": "..."}
    handler = pick_handler(workflow_dict)
    assert handler.__class__.__name__ == "LinearHandler"

def test_dispatch_reads_pattern_frontmatter():
    """Workflows with pattern: time_critical route to TimeCriticalHandler."""
    from cuo.core.handlers.dispatch import pick_handler
    workflow_dict = {"frontmatter": {"pattern": "time_critical", "sla_minutes": 240}}
    handler = pick_handler(workflow_dict)
    assert handler.__class__.__name__ == "TimeCriticalHandler"
    assert handler.sla_minutes == 240


# modules/cuo/tests/test_proposal_applier.py
def test_time_critical_emits_sla_breach_when_slow(tmp_memory):
    """If actual_duration > sla, memory gets a cuo.time_critical_sla_breach row."""
    from cuo.core.handlers.time_critical import TimeCriticalHandler
    handler = TimeCriticalHandler(sla_minutes=1)  # 1 minute SLA
    # Mock chain that takes 90 seconds
    result = handler.execute(slow_chain_fixture, memory_root=tmp_memory)
    breach_rows = [r for r in tmp_memory.audit_rows() if r.extra.get("kind") == "cuo.time_critical_sla_breach"]
    assert len(breach_rows) == 1
    assert breach_rows[0].extra["breach_severity"] > 0.5


# tests/test_per_instance_handler.py
def test_per_instance_iterates_once_per_account():
    """instance_descriptor with 5 accounts → 5 chain invocations + 1 summary."""
    from cuo.core.handlers.per_instance import PerInstanceHandler
    instances = [{"account_id": f"acct-{i}"} for i in range(5)]
    handler = PerInstanceHandler(instance_descriptor=instances)
    result = handler.execute(cso_sales_workflow_fixture)
    assert result.outcome == "COMPLETED_BATCH"
    assert len(result.per_instance) == 5


# tests/test_multi_output_handler.py
def test_multi_output_fanout_to_recipients():
    """3 recipients → final step output rendered 3 times + 3 memory rows."""
    from cuo.core.handlers.multi_output import MultiOutputHandler
    recipients = [
        {"recipient_id": "vn-mst", "format": "xml", "delivery_method": "email"},
        {"recipient_id": "vn-mof", "format": "pdf", "delivery_method": "portal"},
        {"recipient_id": "vn-sbv", "format": "json", "delivery_method": "api"},
    ]
    handler = MultiOutputHandler(output_recipients=recipients)
    result = handler.execute(clo_legal_workflow_fixture, memory_root=tmp_memory)
    fanout_rows = [r for r in tmp_memory.audit_rows() if r.extra.get("kind") == "cuo.multi_output_fanout"]
    assert len(fanout_rows) == 3


# modules/cuo/tests/test_proposal_applier.py
def test_sequential_approval_halts_on_ethics_reject():
    """If ethics-sign-off chain fails, model-card-release does NOT execute."""
    from cuo.core.handlers.sequential_approval import SequentialApprovalHandler
    handler = SequentialApprovalHandler(gates=[{
        "approver_persona": "chief-ethics-officer",
        "approver_workflow": "per-model-card-ethics-sign-off"
    }])
    # Mock approver chain that fails
    result = handler.execute(caio_per_model_card_release_fixture, approver_outcome="FAILED")
    assert result.outcome == "BLOCKED"
    halt_rows = [r for r in tmp_memory.audit_rows() if r.extra.get("kind") == "cuo.sequential_approval_halted"]
    assert len(halt_rows) == 1


# modules/cuo/tests/test_proposal_applier.py
def test_persona_pair_handoff_at_declared_step():
    """At handoff_step, primary pauses + peer invoked + result threaded back."""
    from cuo.core.handlers.persona_pair import PersonaPairHandler
    handler = PersonaPairHandler(
        peer_persona="chief-customer-officer",
        peer_workflow="churn-collaboration",
        shared_artefact="churn-cohort-analysis",
        handoff_step=4,
    )
    result = handler.execute(cro_revenue_churn_fixture)
    handoff_rows = [r for r in tmp_memory.audit_rows() if r.extra.get("kind") == "cuo.persona_pair_handoff"]
    assert len(handoff_rows) >= 1
    # Verify shared artefact content hash matches across both legs
    primary_hash = result.shared_artefact_hash
    peer_hash = result.peer_artefact_hash
    assert primary_hash == peer_hash

§7 — Dependencies

Upstream: TASK-CUO-104 (topological chain walk), TASK-CUO-105 (per-step rollback — sequential_approval halt may trigger rollback of completed steps).

Cross-module: TASK-SKILL-001 (skill registry for peer-workflow lookup), TASK-MEMORY-111 (PII scrubbing for SLA-breach reason field).

Downstream: None — Phase 4 closes the supervisor design. Future work (TASK-CUO-107+) shifts to production hardening (retries, observability, multi-tenant).


§10 — Failure modes

FailureDetectionOutcomeRecovery
Unknown pattern: value in workflow frontmatterdispatch.py rejectsrefuse to execute; emit cuo.handler_dispatch_failedauthor fixes frontmatter
time_critical chain hangs past SLAtimer in TimeCriticalHandlersla_breach row emitted; chain continues (don't kill, deliver late + audit)operator reviews breach in memory
per_instance empty descriptorempty list checkrefuse to execute; outcome=BLOCKEDauthor populates descriptor
multi_output zero recipientsempty list checkrefuse to execute; outcome=BLOCKEDauthor adds recipients
sequential_approval approver chain has no halting stepapprover chain returns COMPLETED without explicit approval audittreat as auto-approved + log warningoperator decides if approval is implicit-OK
persona_pair peer workflow not foundcatalog lookup missoutcome=FAILED; emit cuo.persona_pair_peer_not_foundauthor fixes peer_persona/peer_workflow
persona_pair shared_artefact hash mismatchcontent_hash comparisonoutcome=FAILED; emit cuo.persona_pair_artefact_driftauthor reconciles peer workflows
Handler raises uncaught exceptionsupervisor try/exceptoutcome=FAILED with stack trace in notesbug report + fix
Concurrent execution of same persona_pair from both sidescontent_hash dedupsecond invocation joins first's resultinherent