Skip to content

refactor(compile): split agentic-pipeline shape out of standalone_ir.rs; move Agent-job condition contributions into Declarations #987

Description

@jamesadevine

Background

src/compile/standalone_ir.rs is ~2,260 lines and the file name is a lie. Only ~30 lines (build_standalone_pipeline) are actually standalone-specific. The other ~2,000 lines define the canonical agentic pipeline shape (Setup → Agent → Detection → SafeOutputs → Teardown) and are called by all four targets via build_canonical_jobs (a pub(crate) builder extracted in 468359f6).

The most visible symptom is build_agentic_condition (lines ~970–1080), which composes the Agent-job condition from signals owned by multiple extensions. It reaches across abstraction boundaries to:

Reference Real owner
front_matter.is_synthetic_pr() / .pr_filters() / .pipeline_filters() types.rs
step ID "synthPr" + its declared outputs extensions/ado_script.rs
step IDs "prGate" / "pipelineGate" + their SHOULD_RUN output filter_ir.rs

The typed-condition refactor in 660d2487 enforced those refs at compile time (graph validation rejects renames), but the knowledge of "which extensions feed the Agent-job condition and how" still lives in a compile target rather than in the extensions themselves.

The same shape concern applies to:

  • agent_job_variables_hoist (lines ~899–930) — knows about the synthPr step ID and its hoisted outputs.
  • wire_explicit_dependencies (lines ~1170–1210) — knows the canonical Setup → Agent → Detection → SafeOutputs edge list.
  • Every build_*_job (Setup / Agent / Detection / SafeOutputs / Teardown) — defines the canonical 5-job shape.

Why it matters

Every future cross-extension signal (e.g. a new extension that wants to gate the Agent job, or a different default Detection-job shape per target) ends up either:

  1. Carving another Condition::Custom clause into build_agentic_condition, or
  2. Adding a new flag to BuiltPipelineContext / StandaloneCtx and threading it through.

Both options add to the existing fan-in of standalone_ir.rs without giving extensions a place to own their own contributions to canonical-job conditions / variables / dependencies.

Proposed direction

Two related refactors. They could land separately:

1. Rename + split standalone_ir.rs

Extract a new module — src/compile/agentic_pipeline.rs (or canonical.rs) — that owns:

  • BuiltPipelineContext / build_pipeline_context / build_canonical_jobs
  • All per-job builders (build_setup_job, build_agent_job, build_detection_job, build_safe_outputs_job, build_teardown_job)
  • build_agentic_condition, agent_job_variables_hoist, wire_explicit_dependencies
  • Helper step builders (download_compiler_step, prepare_agent_prompt_step, etc.)

standalone_ir.rs shrinks back to its ~30-line shape wrapper. job_ir.rs / stage_ir.rs / onees_ir.rs import from agentic_pipeline directly instead of through a standalone_ir:: re-export.

This is a pure file move + import-path update — zero behaviour change.

2. Move condition contribution into Declarations

Today extensions contribute steps, env, hosts, MCPG entries, etc. — but not conditions on the canonical jobs they implicitly extend. Add a new field:

pub struct Declarations {
    ...
    /// Clauses to AND into the canonical Agent job''s `condition:`.
    /// The compiler folds these into a single `Condition::And` before
    /// emitting. Lets an extension gate the Agent job declaratively
    /// without the agentic-pipeline builder hard-coding knowledge of
    /// each extension''s signals.
    pub agent_conditions: Vec<Condition>,

    // Possibly also:
    // pub detection_conditions: Vec<Condition>,
    // pub safe_outputs_conditions: Vec<Condition>,
}

Then:

  • AdoScriptExtension::declarations() emits:
    • The synth-PR-skip clause (when front_matter.is_synthetic_pr())
    • The "real PR or synth-PR requires gate-passed" clause (when pr_filters are active)
    • The "ResourceTrigger requires pipeline-gate-passed" clause (when pipeline_filters are active)
  • User expression: escape hatches in front matter become a typed Condition::Custom contribution from a small new code path (currently wired in build_agentic_condition).
  • build_agentic_condition reduces to Condition::And(decls.iter().flat_map(|d| d.agent_conditions).collect()).

After this, the agentic-pipeline builder no longer needs to know which extensions exist or what their step IDs are. New extensions can gate the Agent job without touching standalone_ir.rs.

Out of scope (for now)

  • Same treatment for variable-hoist (agent_job_variables_hoist). Today only AdoScriptExtension contributes; the threshold for adding a Declarations::agent_variables field is a second consumer.
  • Custom per-job shapes (e.g. an extension that wants to insert a fifth canonical job). The current 5-job shape is hard-coded by design — the Three-Stage Pipeline Model is the security contract and shouldn''t be extensible.

Acceptance

  • src/compile/standalone_ir.rs is back to ~30 lines (only the standalone-specific shape wrapper).
  • New src/compile/agentic_pipeline.rs (or canonical.rs) owns the 5-job canonical shape.
  • Declarations carries agent_conditions: Vec<Condition> (and the canonical-jobs builder folds them).
  • build_agentic_condition deleted; all condition contributions come from extensions.
  • Zero lock-file drift across all 33 fixtures.
  • All existing tests pass; new tests cover the new Declarations field and the per-extension condition contributions.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions