Orchestrata Ultimate › Plans › pipeline-automation-plan

Pipeline Automation — Detailed Implementation Plan

Source: plans/pipeline-automation-plan.md at 8a2cb42ad · last changed 2026-08-19 · first committed 2026-06-18 · 746 lines.

Prose pending.

Plan text

Pipeline Automation — Detailed Implementation Plan

Date: 2026-06-25 Specs: SPEC-003 (Intent), SPEC-005 (Multi-Agent), SPEC-016 (VizInt), SPEC-032 (Concierge) Standards: SPEC-097 (Testing), SPEC-100 (Logging) Architecture: Layer 3 (domain services) + Layer 4 (orchestration) Session: Deferred task #6 — critical priority


1. Architecture: Layer Placement

New code in Layer 4 (Orchestration) — coordinates Layer 3 agents. May import concrete classes from Layer 3. Never imports from Layer 5.

Layer 4: Orchestration (8 NEW files)
  orchestrator/pipeline_runner.py     PipelineRunner class
  orchestrator/pipeline_stage.py      PipelineStage, StageResult, StageStatus
  orchestrator/pipeline_result.py     PipelineResult, PipelineMetrics
  orchestrator/artifact_store.py      ArtifactStore, PipelineArtifact, ArtifactType
  orchestrator/pipeline_errors.py     PipelineError hierarchy (5 types)
  orchestrator/pipeline_logger.py     PipelineLogger (structlog)
  orchestrator/pipeline_checkpoint.py  PipelineCheckpoint (Redis-backed)
  orchestrator/pipeline_cli.py        CLI entry

Layer 3: Domain Services (7 MODIFIED)
  agents/volks/concierge/volk.py      Add structured logging
  agents/volks/analyst/agent.py       Add structured logging
  agents/volks/pm/volk.py             Add structured logging
  agents/volks/architect/volk.py      Add structured logging
  agents/volks/codedirector/volk.py   Add structured logging
  agents/volks/qa/volk.py             Add structured logging
  agents/volks/compliance/volk.py     Add structured logging

Layer 2: Communication (1 MODIFIED)
  gateway/handlers/orchestrator.py    Route intent → Concierge via CXP

Layer 4: Session (1 MODIFIED)
  session/session.py                  Add log_pipeline_stage(), log_pipeline_error()

Protocol Boundaries

PipelineRunner communicates with agents via CXP packets — no direct method calls:

# CORRECT — through CXP protocol
packet = CXPPacket(from_agent="U:Ox", to_agent="U:Cx", ...)
response = await agent.process(packet)

# INCORRECT — bypassing CXP
result = concierge._handle_intent(raw_text)

Decision: Phase 1 uses direct instantiation (agents are in-process). Phase 1b will switch to Postman/CXP routing when Postman is verified. This technical debt is documented.

Dependency Injection

All new classes receive dependencies via constructor:

class PipelineRunner:
    def __init__(self, artifact_store, session_controller, pipeline_logger, ...):
        self.artifact_store = artifact_store
        self.session_controller = session_controller
        ...

No global singletons. No module-level state. Everything injectable for testability.


2. Data Models

2.1 PipelineStage & StageResult

# File: orchestrator/pipeline_stage.py
# @traces(spec="003", story="US-004", reqs=["REQ-003-US-004-001-003"])

@dataclass
class PipelineStage:
    alias: str                    # "U:Cx", "U:BA", etc.
    agent_class: type             # ConciergeVolk, AnalystAgent, ...
    input_subject: str            # "intent", "analyze_requirements", ...
    output_subject: str           # "analyze_requirements", "scope_stories", ...
    timeout_seconds: int          # Max time for this stage
    required: bool = True         # If False, skip on failure
    ui_conditional: bool = False  # Only run if UI keywords detected
    artifact_types: list[str]     # ["requirements", "prolog_facts", ...]

@dataclass
class StageResult:
    stage_alias: str
    status: StageStatus           # PENDING|IN_PROGRESS|RESOLVED|FAILED|TIMED_OUT|SKIPPED
    output_packet: CXPPacket | None
    artifacts: list
    duration_ms: float
    error: PipelineError | None
    llm_calls: int = 0

class StageStatus(Enum):
    PENDING = "pending"
    IN_PROGRESS = "in_progress"
    RESOLVED = "resolved"
    FAILED = "failed"
    TIMED_OUT = "timed_out"
    SKIPPED = "skipped"
    GOVERNANCE_DENIED = "governance_denied"

2.2 PipelineResult

# File: orchestrator/pipeline_result.py
# @traces(spec="003", story="US-005", reqs=["REQ-003-US-005-002-007"])

@dataclass
class PipelineResult:
    thread_id: str
    intent: str
    stages: dict[str, StageResult]
    artifacts: list
    metrics: PipelineMetrics
    started_at: datetime
    completed_at: datetime | None
    overall_status: StageStatus

    def successful(self) -> bool: ...
    def failed_stages(self) -> list[str]: ...
    def to_dict(self) -> dict: ...

2.3 PipelineMetrics

# File: orchestrator/pipeline_metrics.py
# @traces(spec="003", story="US-009", reqs=["REQ-003-US-009-001-002"])

@dataclass
class PipelineMetrics:
    stages_completed: int = 0
    stages_failed: int = 0
    stages_skipped: int = 0
    total_duration_ms: float = 0.0
    per_stage_duration: dict[str, float] = {}
    llm_calls_total: int = 0
    prolog_checks_total: int = 0
    governance_violations: int = 0
    artifacts_generated: int = 0
    retries_total: int = 0

2.4 PipelineArtifact & ArtifactType

# File: orchestrator/artifact_store.py
# @traces(spec="003", story="US-006", reqs=["REQ-003-US-006-001-006"])

@dataclass
class PipelineArtifact:
    stage_alias: str
    artifact_type: ArtifactType
    content: str
    content_format: ArtifactFormat
    file_path: str | None = None
    timestamp: datetime = _utc_now()
    thread_id: str = ""
    session_id: str = ""

class ArtifactType(Enum):
    REQUIREMENTS = "requirements"
    PROLOG_FACTS = "prolog_facts"
    TASK_SUMMARY = "task_summary"
    USER_STORIES = "user_stories"
    REQUIREMENTS_ANALYSIS = "requirements_analysis"
    PROJECT_META = "project_meta"
    SCOPE_ESTIMATE = "scope_estimate"
    WARNINGS = "warnings"
    ARCHITECTURE_DESIGN = "architecture_design"
    FILE_TREE = "file_tree"
    API_SPEC = "api_spec"
    CLASS_DIAGRAM = "class_diagram"
    SOURCE_CODE = "source_code"
    TEST_CODE = "test_code"
    IMPLEMENTATION_PLAN = "implementation_plan"
    QA_REPORT = "qa_report"
    RTM = "rtm"
    COMPLIANCE_REPORT = "compliance_report"
    ERROR_LOG = "error_log"

class ArtifactFormat(Enum):
    TEXT = "text"
    JSON = "json"
    MERMAID = "mermaid"
    PYTHON = "python"
    PROLOG = "prolog"
    MARKDOWN = "markdown"

2.5 PipelineError Hierarchy

# File: orchestrator/pipeline_errors.py
# @traces(spec="003", story="US-005", reqs=["REQ-003-US-005-010-015"])

class PipelineError(Exception):
    def __init__(self, stage_alias, thread_id, message,
                 original_error=None, partial_artifacts=None, conversation_history=None):
        self.stage_alias = stage_alias
        self.thread_id = thread_id
        self.original_error = original_error
        self.partial_artifacts = partial_artifacts or []
        ...

class StageTimeoutError(PipelineError): ...
class GovernanceRejectionError(PipelineError): ...
class LLMCallFailedError(PipelineError): ...
class AgentReturnedNoneError(PipelineError): ...
class InfrastructureError(PipelineError): ...

3. Phase 1: PipelineRunner (~700 LOC)

File: src/orchestrata_ultimate/orchestrator/pipeline_runner.py

Stage Definitions (Fixed Pipeline)

STAGES = [
    PipelineStage("U:Cx", ConciergeVolk, "intent", "analyze_requirements", 300,
                  artifact_types=["requirements", "prolog_facts", "task_summary"]),
    PipelineStage("U:BA", AnalystAgent, "analyze_requirements", "scope_stories", 300,
                  artifact_types=["user_stories", "requirements_analysis", "project_meta"]),
    PipelineStage("U:PM", PMVolk, "scope_stories", "design_architecture", 120,
                  artifact_types=["scope_estimate", "warnings"]),
    PipelineStage("U:Ar", ArchitectVolk, "design_architecture", "code_implementation", 300,
                  artifact_types=["architecture_design", "file_tree", "api_spec", "class_diagram"]),
    PipelineStage("U:Cd", CodeDirectorVolk, "code_implementation", "review_code", 600,
                  artifact_types=["source_code", "implementation_plan"]),
    PipelineStage("U:Qa", QaVolk, "review_code", "compliance_check", 600,
                  artifact_types=["test_code", "qa_report"]),
    PipelineStage("U:Cp", ComplianceVolk, "compliance_check", None, 120,
                  artifact_types=["rtm", "compliance_report"]),
    PipelineStage("U:Ux", UXVolk, "design_ux", None, 180,
                  required=False, ui_conditional=True,
                  artifact_types=["ux_design"]),
]

Core Method

async def run(self, intent: str, thread_id: str = None,
              has_ui: bool = False) -> PipelineResult:
    """Execute full pipeline: Cx→BA→PM→Ar→Cd→QA→Cp."""
    thread_id = thread_id or f"pipeline_{uuid.uuid4().hex[:8]}"
    started_at = datetime.now(UTC)
    self.pipeline_logger.pipeline_start(thread_id, intent, len(self.STAGES))

    metrics = PipelineMetrics()
    stages_result = {}
    all_artifacts = []
    current_packet = None

    for stage in self.STAGES:
        if stage.ui_conditional and not has_ui:
            stages_result[stage.alias] = StageResult(status=StageStatus.SKIPPED, ...)
            metrics.stages_skipped += 1
            continue

        try:
            result = await self._run_stage(stage, current_packet, thread_id, intent)
            stages_result[stage.alias] = result
            all_artifacts.extend(result.artifacts)
            metrics.stages_completed += 1
            current_packet = result.output_packet
            self.session_controller.log_work(
                desc=f"Pipeline: {stage.alias} completed ({result.duration_ms:.0f}ms)",
                details=[f"Status: {result.status.value}"]
            )
        except PipelineError as e:
            stages_result[stage.alias] = StageResult(status=StageStatus.FAILED, error=e, ...)
            metrics.stages_failed += 1
            if stage.required:
                break

    completed_at = datetime.now(UTC)
    metrics.total_duration_ms = (completed_at - started_at).total_seconds() * 1000
    metrics.artifacts_generated = len(all_artifacts)

    result = PipelineResult(thread_id=thread_id, intent=intent, stages=stages_result,
                            artifacts=all_artifacts, metrics=metrics, ...)
    self.pipeline_logger.pipeline_complete(thread_id, result)
    return result

Artifact Extraction

def _extract_artifacts(self, stage, packet, thread_id) -> list[PipelineArtifact]:
    """Map payload fields to artifact types."""
    mapping = {
        "requirements": packet.payload.get("requirements"),
        "prolog_facts": "\n".join(packet.payload.get("prolog_facts", [])),
        "task_summary": packet.payload.get("task_summary"),
        "user_stories": packet.payload.get("user_stories"),
        "scope_estimate": str(packet.payload.get("estimate", "")),
        "architecture_design": packet.payload.get("architecture"),
        "file_tree": packet.payload.get("file_tree"),
        "api_spec": packet.payload.get("api_spec"),
        "class_diagram": packet.payload.get("class_diagram"),
        "source_code": str(packet.payload.get("files_created", [])),
        "qa_report": packet.payload.get("review_report"),
        "rtm": str(packet.payload.get("rtm", "")),
        "compliance_report": packet.payload.get("compliance_report"),
    }
    artifacts = []
    for at_str in stage.artifact_types:
        content = mapping.get(at_str)
        if content:
            artifact = PipelineArtifact(stage_alias=stage.alias,
                                        artifact_type=ArtifactType(at_str),
                                        content=content, thread_id=thread_id)
            artifacts.append(artifact)
            self.artifact_store.add(artifact)
    return artifacts

Verification Gate

python -c "from orchestrata_ultimate.orchestrator.pipeline_runner import PipelineRunner"
python scripts/architecture_checker.py --strict
grep -c "@traces" src/orchestrata_ultimate/orchestrator/pipeline_runner.py  # ≥ 8
grep -rn 'MagicMock\|Mock\|patch(' src/orchestrata_ultimate/orchestrator/pipeline_runner.py  # 0

4. Phase 2: ArtifactStore (~350 LOC)

File: src/orchestrata_ultimate/orchestrator/artifact_store.py

class ArtifactStore:
    """Thread-safe artifact collection with disk persistence + Neo4j sync."""

    def __init__(self, output_dir: Path = None):
        self._lock = threading.Lock()
        self._artifacts: list[PipelineArtifact] = []
        self._output_dir = output_dir or Path("pipeline_output")

    def add(self, artifact: PipelineArtifact) -> None:
        with self._lock:
            self._artifacts.append(artifact)
        self._write_to_disk(artifact)

    def by_stage(self, stage_alias: str) -> list[PipelineArtifact]:
        with self._lock:
            return [a for a in self._artifacts if a.stage_alias == stage_alias]

    def by_type(self, artifact_type: ArtifactType) -> list[PipelineArtifact]:
        with self._lock:
            return [a for a in self._artifacts if a.artifact_type == artifact_type]

    def all(self) -> list[PipelineArtifact]:
        with self._lock:
            return list(self._artifacts)

    def by_thread(self, thread_id: str) -> list[PipelineArtifact]:
        with self._lock:
            return [a for a in self._artifacts if a.thread_id == thread_id]

    def _write_to_disk(self, artifact: PipelineArtifact) -> None:
        dir_path = self._output_dir / artifact.thread_id / artifact.stage_alias
        dir_path.mkdir(parents=True, exist_ok=True)
        ext = _format_to_extension(artifact.content_format)
        file_path = dir_path / f"{artifact.artifact_type.value}{ext}"
        file_path.write_text(artifact.content, encoding="utf-8")
        artifact.file_path = str(file_path)

    def to_json(self) -> str:
        with self._lock:
            return json.dumps([...], indent=2, default=str)

Expected Artifacts Per Stage

Stage Artifact Type Format
U:Cx Requirements requirements text
U:Cx Prolog Facts prolog_facts prolog
U:Cx Task Summary task_summary text
U:BA User Stories user_stories json
U:BA Requirements Analysis requirements_analysis text
U:BA Project Meta project_meta json
U:PM Scope Estimate scope_estimate json
U:PM Warnings warnings json
U:Ar Architecture Design architecture_design mermaid
U:Ar File Tree file_tree text
U:Ar API Spec api_spec text
U:Ar Class Diagram class_diagram mermaid
U:Cd Source Code source_code python
U:Cd Implementation Plan implementation_plan json
U:Qa Test Code test_code python
U:Qa QA Report qa_report text
U:Cp RTM rtm json
U:Cp Compliance Report compliance_report text

5. Phase 3: Gateway Bridge (~100 LOC modified)

File: src/orchestrata_ultimate/gateway/handlers/orchestrator.py (MODIFY)

Current behavior: handle_intent() parses text → creates Neo4j task → returns. Never dispatches to Concierge.

New behavior:

async def handle_intent(self, connection_id, params):
    text = params.get("text", "")
    intent_id = f"intent_{uuid.uuid4().hex[:8]}"

    # 1. Create task (existing behavior — keep)
    task = await self._create_task(connection_id, text)

    # 2. NEW: Dispatch to Concierge via CXP
    cxp_result = await self._cxp_handler.handle_send(connection_id, {
        "target": "U:Cx",
        "payload": {"text": text, "thread_id": intent_id, "subject": "intent"},
    })

    # 3. NEW: Subscribe UI to pipeline progress
    subscription_id = await self._pubsub.subscribe(
        connection_id, f"pipeline.{intent_id}", {})

    return {
        "intent_id": intent_id, "task": task,
        "pipeline_thread_id": intent_id, "pipeline_started": True,
        "subscription_id": subscription_id,
    }

6. Phase 4: Observability (~250 LOC + 7 agents modified)

PipelineLogger

# File: orchestrator/pipeline_logger.py
# @traces(spec="100", story="US-100-001", reqs=["REQ-100-001-007"])

class PipelineLogger:
    def __init__(self, logger: logging.Logger):
        self._log = logger

    def stage_start(self, stage, thread_id, intent_summary):
        self._log.info("[PIPELINE] Stage starting", extra={
            "pipeline_stage": stage, "thread_id": thread_id,
            "intent_summary": intent_summary[:200],
            "event": "pipeline.stage.start"})

    def stage_complete(self, stage, thread_id, duration_ms, artifact_count):
        self._log.info("[PIPELINE] Stage complete", extra={
            "pipeline_stage": stage, "thread_id": thread_id,
            "duration_ms": duration_ms, "artifact_count": artifact_count,
            "event": "pipeline.stage.complete"})

    def stage_failed(self, stage, thread_id, error):
        self._log.error("[PIPELINE] Stage failed", extra={
            "pipeline_stage": stage, "thread_id": thread_id,
            "error_type": type(error).__name__,
            "error_message": str(error)[:500],
            "event": "pipeline.stage.failed"})

    def pipeline_start(self, thread_id, intent, stage_count): ...
    def pipeline_complete(self, thread_id, result): ...

Agent Logging Pattern

Add to each agent's process():

logger.info("[PIPELINE] %s received packet", self.alias, extra={
    "stage": self.alias, "thread_id": packet.thread_id,
    "subject": packet.subject, "event": "pipeline.agent.received"})

# ... existing process logic ...

logger.info("[PIPELINE] %s completed — escalating", self.alias, extra={
    "stage": self.alias, "thread_id": packet.thread_id,
    "status": self.status.value, "event": "pipeline.agent.completed"})

Gateway PubSub for UI Updates

# After each stage completes:
if self._pubsub:
    await self._pubsub.publish(f"pipeline.{thread_id}", {
        "event": "stage.complete", "stage": stage.alias,
        "status": "resolved", "duration_ms": duration_ms,
        "artifact_count": len(artifacts),
        "next_stage": self._next_stage_alias(stage),
    })

7. Phase 5: Error Handling & Recovery (~400 LOC)

Retry Strategy

MAX_RETRIES = 3
RETRY_BACKOFF = [5, 10, 20]  # seconds

for attempt in range(MAX_RETRIES + 1):
    try:
        output_packet = await asyncio.wait_for(agent.process(packet), stage.timeout_seconds)
        break
    except asyncio.TimeoutError:
        if attempt < MAX_RETRIES:
            await asyncio.sleep(RETRY_BACKOFF[attempt])
        else:
            raise StageTimeoutError(stage_alias=stage.alias, ...)
    except Exception as e:
        if attempt < MAX_RETRIES and _is_retryable(e):
            await asyncio.sleep(RETRY_BACKOFF[attempt])
        else:
            raise

Checkpointing

# File: orchestrator/pipeline_checkpoint.py

@dataclass
class PipelineCheckpoint:
    thread_id: str
    last_completed_stage: str
    completed_stages: list[str]
    artifacts_so_far: list
    conversation_history: list[str]
    timestamp: datetime

    def save(self, redis_client) -> None:
        key = f"pipeline:checkpoint:{self.thread_id}"
        redis_client.setex(key, 86400, json.dumps(self.to_dict()))

    @staticmethod
    def load(redis_client, thread_id: str) -> "PipelineCheckpoint | None":
        data = redis_client.get(f"pipeline:checkpoint:{thread_id}")
        return PipelineCheckpoint.from_dict(json.loads(data)) if data else None

Error Handling Per Type

Error Type Handling Recovery
LLMCallFailedError Retry 3x with 5s/10s/20s backoff Escalate to human if all fail
GovernanceRejectionError Log violation + Prolog reason Stop pipeline, surface to AuditTrail
AgentReturnedNoneError Log warning, capture partial output Skip if non-required; stop if required
StageTimeoutError Retry 3x Capture partial, mark TIMED_OUT
InfrastructureError Retry 5x with 1s backoff Fail pipeline

8. Phase 6: Session Integration (~100 LOC modified)

File: src/orchestrata_ultimate/session/session.py (MODIFY)

def log_pipeline_stage(self, stage_alias, status, artifact_count, duration_ms):
    self.log_work(
        desc=f"Pipeline: {stage_alias} — {status} "
             f"({artifact_count} artifacts, {duration_ms:.0f}ms)",
        details=[f"Stage: {stage_alias}", f"Status: {status}",
                 f"Artifacts: {artifact_count}", f"Duration: {duration_ms:.0f}ms"])

def log_pipeline_error(self, stage_alias, error_type, error_message):
    self.log_work(
        desc=f"Pipeline: {stage_alias} — FAILED ({error_type})",
        details=[f"Error: {error_message}"])

9. Phase 7: E2E Testing (~600 LOC)

File: tests/orchestrator/test_pipeline_e2e.py

Test Standards Compliance

All tests follow SPEC-097 + AGENTS.md:

Test Cases

Test Description Est. Duration
test_full_pipeline_simple_intent 7 stages complete, 5+ artifacts ~5 min
test_pipeline_with_ui_triggers_ux UI keywords trigger UX stage ~5 min
test_pipeline_stage_timeout Timeout produces StageTimeoutError ~2 min
test_pipeline_checkpoint_and_resume Save/load/resume from checkpoint ~5 min
test_pipeline_empty_intent Empty intent rejected at stage 0 ~1 min
test_session_controller_logs_all_stages 7+ work log entries ~5 min
@pytest.mark.asyncio
async def test_full_pipeline_simple_intent(runner, test_id):
    intent = "Create a Python CLI tool that reads CSV and outputs JSON"
    result = await runner.run(intent, thread_id=test_id)

    assert result.overall_status == StageStatus.RESOLVED
    assert result.metrics.stages_completed == 7
    assert result.metrics.stages_failed == 0
    assert result.metrics.artifacts_generated >= 5
    assert os.path.isdir(f"/tmp/pipeline_test_{test_id}/{test_id}")

Verification Gate

uv run pytest tests/orchestrator/test_pipeline_e2e.py -x -v --timeout=600
# Purity checks
grep -rn 'MagicMock\|Mock\|Fake\|Stub\|Dummy\|skip' tests/orchestrator/test_pipeline_e2e.py  # Expect: 0
python scripts/test_isolation_audit.py tests/orchestrator/
uv run pytest tests/orchestrator/ -n auto -x --timeout=120

10. Phase 8: CLI Command (~200 LOC)

File: src/orchestrata_ultimate/session/cli.py (MODIFY)

session pipeline run "Create a Redis task queue with REST API"
session pipeline run "Build an async event bus" --has-ui
session pipeline status <thread_id>
session pipeline resume <thread_id>
session pipeline list

11. Spec Traceability Matrix

Requirements Referenced Per Spec

Spec Requirements Count
SPEC-003 (Intent) US-001, US-004, US-005, US-006, US-009 27
SPEC-005 (Multi-Agent) US-002, US-009 8
SPEC-016 (VizInt) US-001, US-003, US-008 24
SPEC-032 (Concierge) US-001, US-004, US-006 8
SPEC-006 (Session) US-006 4
SPEC-100 (Logging) US-100-001 7
Total 78

@traces Decorator Pattern

Every public class, method, and function must have:

@traces(
    spec="003",          # Spec ID (number only)
    story="US-004",      # User story
    reqs=[               # Requirement IDs
        "REQ-003-US-004-001",
        "REQ-003-US-004-002",
    ],
)

RTM Update

After implementation, regenerate the requirements traceability matrix:

python scripts/generate_comprehensive_audit.py \
    --specs specs/ \
    --source src/orchestrata_ultimate/orchestrator/

12. Implementation Order

Phase 1: PipelineRunner (4 new files)      ← Must be first
Phase 2: ArtifactStore (1 new file)        ← Needed by Phase 1
Phase 5: Error Handling (2 new files)      ← Recovery depends on Phase 1
Phase 4: Observability (1 new + 7 mod)     ← Logging during Phase 1
Phase 6: Session Integration (1 mod)       ← Wraps Phase 1
Phase 3: Gateway Bridge (1 mod)            ← Connects UI to Phase 1
Phase 8: CLI Command (1 mod)               ← User-facing entry
Phase 7: E2E Testing (1 new + 6 support)   ← Validates all phases

Effort Estimate

Phase New Modified Est. LOC Est. Hours
1: Runner 3 0 ~700 6
2: Artifacts 1 0 ~350 3
3: Gateway 0 1 ~100 2
4: Observe 1 7 ~250 3
5: Errors 2 1 ~400 4
6: Session 0 1 ~100 1
7: Tests 1 0 ~600 5
8: CLI 0 1 ~200 2
Total 8 11 ~2,700 ~26

Success Criteria

  1. session pipeline run "intent" → 7 stages execute automatically
  2. Every stage appears in CXPFeed gadget in real-time
  3. 17 artifact types collected to pipeline_output/<thread_id>/
  4. Errors surface with full context (stage, type, partial output)
  5. Failed pipelines resume from Redis checkpoint
  6. Session work log shows one entry per completed stage
  7. E2E test passes: intent → 7 stages → 5+ artifacts → session entries
  8. Architecture checker: 0 violations
  9. Test purity: 0 mocks, 0 stubs, 0 fakes, 0 skips
  10. All 78 SPEC requirements traced via @traces decorators

References: SPEC-003, SPEC-005, SPEC-016, SPEC-032, SPEC-006, SPEC-097, SPEC-100, AGENTS.md § Test Standards, architecture/layer-dependency-rules.md ENDOFPLAN